24
24
using MongoDB . Bson ;
25
25
using MongoDB . Bson . IO ;
26
26
using MongoDB . Bson . Serialization . Serializers ;
27
+ using MongoDB . Bson . TestHelpers ;
27
28
using MongoDB . Bson . TestHelpers . XunitExtensions ;
28
29
using MongoDB . Driver . Core . Bindings ;
29
30
using MongoDB . Driver . Core . Clusters ;
32
33
using MongoDB . Driver . Core . ConnectionPools ;
33
34
using MongoDB . Driver . Core . Connections ;
34
35
using MongoDB . Driver . Core . Events ;
36
+ using MongoDB . Driver . Core . TestHelpers ;
35
37
using MongoDB . Driver . Core . TestHelpers . XunitExtensions ;
36
38
using MongoDB . Driver . Core . WireProtocol ;
37
39
using MongoDB . Driver . Core . WireProtocol . Messages . Encoders ;
@@ -266,6 +268,133 @@ public void GetChannel_should_get_a_connection(
266
268
channel . Should ( ) . NotBeNull ( ) ;
267
269
}
268
270
271
+ [ Theory ]
272
+ [ ParameterAttributeData ]
273
+ public void GetChannel_should_update_topology_and_clear_connection_pool_on_network_error_or_timeout (
274
+ [ Values ( "timedout" , "networkunreachable" ) ] string errorType,
275
+ [ Values ( false , true ) ] bool async)
276
+ {
277
+ var serverId = new ServerId ( _clusterId , _endPoint ) ;
278
+ var connectionId = new ConnectionId ( serverId ) ;
279
+ var innerMostException = CoreExceptionHelper. CreateSocketException( errorType ) ;
280
+
281
+ var openConnectionException = new MongoConnectionException ( connectionId , "Oops" , new IOException ( "Cry" , innerMostException ) ) ;
282
+ var mockConnection = new Mock < IConnectionHandle > ( ) ;
283
+ mockConnection . Setup ( c => c . Open ( It . IsAny < CancellationToken > ( ) ) ) . Throws ( openConnectionException ) ;
284
+ mockConnection . Setup ( c => c . OpenAsync ( It . IsAny < CancellationToken > ( ) ) ) . ThrowsAsync ( openConnectionException ) ;
285
+ var mockConnectionPool = new Mock < IConnectionPool > ( ) ;
286
+ mockConnectionPool . Setup ( p => p . AcquireConnection ( It . IsAny < CancellationToken > ( ) ) ) . Returns ( mockConnection . Object ) ;
287
+ mockConnectionPool . Setup ( p => p . AcquireConnectionAsync ( It . IsAny < CancellationToken > ( ) ) ) . ReturnsAsync ( mockConnection . Object ) ;
288
+ var mockConnectionPoolFactory = new Mock < IConnectionPoolFactory > ( ) ;
289
+ mockConnectionPoolFactory
290
+ . Setup ( f => f . CreateConnectionPool ( It . IsAny < ServerId > ( ) , _endPoint ) )
291
+ . Returns ( mockConnectionPool . Object ) ;
292
+ var mockMonitorServerDescription = new ServerDescription ( serverId , _endPoint ) ;
293
+ var mockServerMonitor = new Mock < IServerMonitor > ( ) ;
294
+ mockServerMonitor . SetupGet ( m => m . Description ) . Returns ( mockMonitorServerDescription ) ;
295
+ mockServerMonitor
296
+ . Setup ( m => m . Invalidate ( It . IsAny < string > ( ) ) )
297
+ . Callback ( ( string reason ) => MockMonitorInvalidate ( reason ) ) ;
298
+ var mockServerMonitorFactory = new Mock < IServerMonitorFactory > ( ) ;
299
+ mockServerMonitorFactory . Setup ( f => f . Create ( It . IsAny < ServerId > ( ) , _endPoint ) ) . Returns ( mockServerMonitor . Object ) ;
300
+
301
+ var subject = new Server ( _clusterId , _clusterClock , _clusterConnectionMode , _settings , _endPoint , mockConnectionPoolFactory . Object , mockServerMonitorFactory . Object , _capturedEvents ) ;
302
+ subject . Initialize ( ) ;
303
+
304
+ IChannelHandle channel = null ;
305
+ Exception exception ;
306
+ if ( async )
307
+ {
308
+ exception = Record . Exception ( ( ) => channel = subject . GetChannelAsync ( CancellationToken . None ) . GetAwaiter ( ) . GetResult ( ) ) ;
309
+ }
310
+ else
311
+ {
312
+ exception = Record . Exception ( ( ) => channel = subject . GetChannel ( CancellationToken . None ) ) ;
313
+ }
314
+
315
+ channel . Should ( ) . BeNull ( ) ;
316
+ exception . Should ( ) . Be ( openConnectionException ) ;
317
+ subject . Description . Type . Should ( ) . Be ( ServerType . Unknown ) ;
318
+ subject . Description . ReasonChanged . Should ( ) . Contain ( "ChannelException during handshake" ) ;
319
+ mockServerMonitor . Verify ( m => m . Invalidate ( It . IsAny < string > ( ) ) , Times . Once ) ;
320
+ mockConnectionPool . Verify ( p => p . Clear ( ) , Times . Once ) ;
321
+
322
+ void MockMonitorInvalidate ( string reason )
323
+ {
324
+ var currentDescription = mockServerMonitor. Object. Description;
325
+ mockServerMonitor. SetupGet( m => m . Description ) . Returns ( currentDescription . With ( reason ) ) ;
326
+ }
327
+ }
328
+
329
+ [ Theory ]
330
+ [ InlineData ( nameof ( MongoConnectionException ) , true ) ]
331
+ [ InlineData ( "MongoConnectionExceptionWithSocketTimeout" , false ) ]
332
+ public void HandleChannelException_should_update_topology_as_expected_on_network_error_or_timeout(
333
+ string errorType , bool shouldUpdateTopology )
334
+ {
335
+ var serverId = new ServerId( _clusterId , _endPoint ) ;
336
+ var connectionId = new ConnectionId( serverId ) ;
337
+ Exception innerMostException;
338
+ switch ( errorType )
339
+ {
340
+ case "MongoConnectionExceptionWithSocketTimeout":
341
+ innerMostException = new SocketException( ( int ) SocketError . TimedOut ) ;
342
+ break ;
343
+ case nameof( MongoConnectionException ) :
344
+ innerMostException = new SocketException( ( int ) SocketError . NetworkUnreachable ) ;
345
+ break ;
346
+ default : throw new ArgumentException( "Unknown error type." ) ;
347
+ }
348
+
349
+ var operationUsingChannelException = new MongoConnectionException( connectionId , "Oops" , new IOException ( "Cry" , innerMostException ) ) ;
350
+ var mockConnection = new Mock< IConnectionHandle > ( ) ;
351
+ var isMasterResult = new IsMasterResult ( new BsonDocument { { "compressors" , new BsonArray ( ) } } ) ;
352
+ // the server version doesn't matter when we're not testing MongoNotPrimaryExceptions, but is needed when
353
+ // Server calls ShouldClearConnectionPoolForException
354
+ var buildInfoResult = new BuildInfoResult ( new BsonDocument { { "version" , "4.4.0" } } ) ;
355
+ mockConnection . SetupGet ( c => c . Description )
356
+ . Returns ( new ConnectionDescription ( new ConnectionId ( serverId , 0 ) , isMasterResult , buildInfoResult ) ) ;
357
+ var mockConnectionPool = new Mock < IConnectionPool > ( ) ;
358
+ mockConnectionPool . Setup ( p => p . AcquireConnection ( It . IsAny < CancellationToken > ( ) ) ) . Returns ( mockConnection . Object ) ;
359
+ mockConnectionPool . Setup ( p => p . AcquireConnectionAsync ( It . IsAny < CancellationToken > ( ) ) ) . ReturnsAsync ( mockConnection . Object ) ;
360
+ var mockConnectionPoolFactory = new Mock < IConnectionPoolFactory > ( ) ;
361
+ mockConnectionPoolFactory
362
+ . Setup ( f => f . CreateConnectionPool ( It . IsAny < ServerId > ( ) , _endPoint ) )
363
+ . Returns ( mockConnectionPool . Object ) ;
364
+ var mockMonitorServerInitialDescription = new ServerDescription ( serverId , _endPoint ) . With ( reasonChanged : "Initial D" , type : ServerType . Standalone ) ;
365
+ var mockServerMonitor = new Mock < IServerMonitor > ( ) ;
366
+ mockServerMonitor . SetupGet ( m => m . Description ) . Returns ( mockMonitorServerInitialDescription ) ;
367
+ mockServerMonitor
368
+ . Setup ( m => m . Invalidate ( It . IsAny < string > ( ) ) )
369
+ . Callback ( ( string reason ) => MockMonitorInvalidate ( reason ) ) ;
370
+ var mockServerMonitorFactory = new Mock < IServerMonitorFactory > ( ) ;
371
+ mockServerMonitorFactory . Setup ( f => f . Create ( It . IsAny < ServerId > ( ) , _endPoint ) ) . Returns ( mockServerMonitor . Object ) ;
372
+ var subject = new Server ( _clusterId , _clusterClock , _clusterConnectionMode , _settings , _endPoint , mockConnectionPoolFactory . Object , mockServerMonitorFactory . Object , _capturedEvents ) ;
373
+ subject . Initialize ( ) ;
374
+
375
+ subject . HandleChannelException ( mockConnection . Object , operationUsingChannelException ) ;
376
+
377
+ if ( shouldUpdateTopology )
378
+ {
379
+ mockServerMonitor . Verify ( m => m . Invalidate ( It . IsAny < string > ( ) ) , Times . Once ) ;
380
+ subject. Description . Type . Should ( ) . Be ( ServerType . Unknown ) ;
381
+ subject. Description . ReasonChanged . Should ( ) . Contain ( "ChannelException" ) ;
382
+ }
383
+ else
384
+ {
385
+ mockServerMonitor . Verify ( m => m . Invalidate ( It . IsAny < string > ( ) ) , Times . Never ) ;
386
+ subject. Description . Should ( ) . Be ( mockMonitorServerInitialDescription ) ;
387
+ }
388
+
389
+ void MockMonitorInvalidate ( string reason )
390
+ {
391
+ var currentDescription = mockServerMonitor. Object. Description;
392
+ mockServerMonitor
393
+ . SetupGet( m => m . Description )
394
+ . Returns ( currentDescription . With ( reason , type : ServerType . Unknown ) ) ;
395
+ }
396
+ }
397
+
269
398
[ Fact ]
270
399
public void Initialize_should_initialize_the_server( )
271
400
{
@@ -407,6 +536,7 @@ internal void IsRecovering_should_return_expected_result_for_message(string mess
407
536
[ InlineData ( nameof ( MongoNotPrimaryException ) , true ) ]
408
537
[ InlineData ( nameof ( SocketException ) , true ) ]
409
538
[ InlineData ( nameof ( TimeoutException ) , false ) ]
539
+ [ InlineData ( "MongoConnectionExceptionWithSocketTimeout" , false ) ]
410
540
[ InlineData ( nameof ( MongoExecutionTimeoutException ) , false ) ]
411
541
internal void ShouldInvalidateServer_should_return_expected_result_for_exceptionType( string exceptionTypeName , bool expectedResult )
412
542
{
@@ -426,6 +556,11 @@ internal void ShouldInvalidateServer_should_return_expected_result_for_exception
426
556
case nameof( MongoNodeIsRecoveringException ) : exception = new MongoNodeIsRecoveringException( connectionId , command , commandResult ) ; break ;
427
557
case nameof( MongoNotPrimaryException ) : exception = new MongoNotPrimaryException( connectionId , command , commandResult ) ; break ;
428
558
case nameof( SocketException ) : exception = new SocketException( ) ; break ;
559
+ case "MongoConnectionExceptionWithSocketTimeout":
560
+ var innermostException = new SocketException( ( int ) SocketError . TimedOut ) ;
561
+ var innerException = new IOException( "Execute Order 66" , innermostException ) ;
562
+ exception = new MongoConnectionException( connectionId , "Yes, Lord Sidious" , innerException ) ;
563
+ break ;
429
564
case nameof( TimeoutException ) : exception = new TimeoutException( ) ; break ;
430
565
case nameof( MongoExecutionTimeoutException ) : exception = new MongoExecutionTimeoutException( connectionId , "message" ) ; break ;
431
566
default : throw new Exception( $ "Invalid exceptionTypeName: { exceptionTypeName } .") ;
@@ -595,6 +730,11 @@ public void Command_should_update_the_session_and_cluster_cluster_times()
595
730
596
731
internal static class ServerReflector
597
732
{
733
+ public static void HandleChannelException( this Server server , IConnection connection , Exception ex )
734
+ {
735
+ Reflector . Invoke ( server , nameof ( HandleChannelException ) , connection , ex ) ;
736
+ }
737
+
598
738
public static bool IsNotMaster ( this Server server , ServerErrorCode code , string message )
599
739
{
600
740
var methodInfo = typeof ( Server ) . GetMethod ( nameof ( IsNotMaster ) , BindingFlags . NonPublic | BindingFlags . Instance ) ;
0 commit comments