2424using MongoDB . Bson ;
2525using MongoDB . Bson . IO ;
2626using MongoDB . Bson . Serialization . Serializers ;
27+ using MongoDB . Bson . TestHelpers ;
2728using MongoDB . Bson . TestHelpers . XunitExtensions ;
2829using MongoDB . Driver . Core . Bindings ;
2930using MongoDB . Driver . Core . Clusters ;
3233using MongoDB . Driver . Core . ConnectionPools ;
3334using MongoDB . Driver . Core . Connections ;
3435using MongoDB . Driver . Core . Events ;
36+ using MongoDB . Driver . Core . TestHelpers ;
3537using MongoDB . Driver . Core . TestHelpers . XunitExtensions ;
3638using MongoDB . Driver . Core . WireProtocol ;
3739using MongoDB . Driver . Core . WireProtocol . Messages . Encoders ;
@@ -266,6 +268,133 @@ public void GetChannel_should_get_a_connection(
266268 channel . Should ( ) . NotBeNull ( ) ;
267269 }
268270
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+
269398 [ Fact ]
270399 public void Initialize_should_initialize_the_server( )
271400 {
@@ -407,6 +536,7 @@ internal void IsRecovering_should_return_expected_result_for_message(string mess
407536 [ InlineData ( nameof ( MongoNotPrimaryException ) , true ) ]
408537 [ InlineData ( nameof ( SocketException ) , true ) ]
409538 [ InlineData ( nameof ( TimeoutException ) , false ) ]
539+ [ InlineData ( "MongoConnectionExceptionWithSocketTimeout" , false ) ]
410540 [ InlineData ( nameof ( MongoExecutionTimeoutException ) , false ) ]
411541 internal void ShouldInvalidateServer_should_return_expected_result_for_exceptionType( string exceptionTypeName , bool expectedResult )
412542 {
@@ -426,6 +556,11 @@ internal void ShouldInvalidateServer_should_return_expected_result_for_exception
426556 case nameof( MongoNodeIsRecoveringException ) : exception = new MongoNodeIsRecoveringException( connectionId , command , commandResult ) ; break ;
427557 case nameof( MongoNotPrimaryException ) : exception = new MongoNotPrimaryException( connectionId , command , commandResult ) ; break ;
428558 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 ;
429564 case nameof( TimeoutException ) : exception = new TimeoutException( ) ; break ;
430565 case nameof( MongoExecutionTimeoutException ) : exception = new MongoExecutionTimeoutException( connectionId , "message" ) ; break ;
431566 default : throw new Exception( $ "Invalid exceptionTypeName: { exceptionTypeName } .") ;
@@ -595,6 +730,11 @@ public void Command_should_update_the_session_and_cluster_cluster_times()
595730
596731 internal static class ServerReflector
597732 {
733+ public static void HandleChannelException( this Server server , IConnection connection , Exception ex )
734+ {
735+ Reflector . Invoke ( server , nameof ( HandleChannelException ) , connection , ex ) ;
736+ }
737+
598738 public static bool IsNotMaster ( this Server server , ServerErrorCode code , string message )
599739 {
600740 var methodInfo = typeof ( Server ) . GetMethod ( nameof ( IsNotMaster ) , BindingFlags . NonPublic | BindingFlags . Instance ) ;
0 commit comments