コード例 #1
0
        public void PublishDescription(ServerDescription description)
        {
            ServerTuple result;

            if (!_servers.TryGetValue(description.EndPoint, out result))
            {
                throw new InvalidOperationException("Server does not exist.");
            }

            var oldDescription = result.Server.Description;

            if (result.Monitor == null)
            {
                var mockServer = Mock.Get(result.Server);
                mockServer.SetupGet(s => s.Description).Returns(description);
                mockServer.Raise(s => s.DescriptionChanged += null, new ServerDescriptionChangedEventArgs(oldDescription, description));
            }
            else
            {
                if (description.WireVersionRange != null)
                {
                    var maxWireVersion = description.MaxWireVersion;
                    var server         = (Server)result.Server;
                    var helloResult    = new HelloResult(new BsonDocument {
                        { "compressors", new BsonArray() }, { "maxWireVersion", maxWireVersion }
                    });
                    var mockConnection = Mock.Get(server._connectionPool().AcquireConnection(CancellationToken.None));
                    mockConnection.SetupGet(c => c.Description)
                    .Returns(new ConnectionDescription(new ConnectionId(description.ServerId, 0), helloResult));
                }
                var mockMonitor = Mock.Get(result.Monitor);
                mockMonitor.SetupGet(m => m.Description).Returns(description);
                mockMonitor.Raise(m => m.DescriptionChanged += null, new ServerDescriptionChangedEventArgs(oldDescription, description));
            }
        }
コード例 #2
0
        private void ApplyResponse(BsonArray response)
        {
            if (response.Count != 2)
            {
                throw new FormatException($"Invalid response count: {response.Count}.");
            }

            var address       = response[0].AsString;
            var helloDocument = response[1].AsBsonDocument;

            JsonDrivenHelper.EnsureAllFieldsAreValid(helloDocument, "hosts", "isWritablePrimary", OppressiveLanguageConstants.LegacyHelloResponseIsWritablePrimaryFieldName, "helloOk", "maxWireVersion", "minWireVersion", "ok", "primary", "secondary", "setName", "setVersion");

            var endPoint    = EndPointHelper.Parse(address);
            var helloResult = new HelloResult(helloDocument);
            var currentServerDescription = _serverFactory.GetServerDescription(endPoint);
            var newServerDescription     = currentServerDescription.With(
                canonicalEndPoint: helloResult.Me,
                electionId: helloResult.ElectionId,
                replicaSetConfig: helloResult.GetReplicaSetConfig(),
                state: helloResult.Wrapped.GetValue("ok", false).ToBoolean() ? ServerState.Connected : ServerState.Disconnected,
                type: helloResult.ServerType,
                wireVersionRange: new Range <int>(helloResult.MinWireVersion, helloResult.MaxWireVersion));

            var currentClusterDescription = _cluster.Description;

            _serverFactory.PublishDescription(newServerDescription);
            SpinWait.SpinUntil(() => !object.ReferenceEquals(_cluster.Description, currentClusterDescription), 100); // sometimes returns false and that's OK
        }
コード例 #3
0
        // methods
        /// <inheritdoc/>
        public void Authenticate(IConnection connection, ConnectionDescription description, CancellationToken cancellationToken)
        {
            Ensure.IsNotNull(connection, nameof(connection));
            Ensure.IsNotNull(description, nameof(description));

            // If we don't have SaslSupportedMechs as part of the response, that means we didn't piggyback the initial
            // hello or legacy hello request and should query the server (provided that the server >= 4.0), merging results into
            // a new ConnectionDescription
            if (!description.HelloResult.HasSaslSupportedMechs &&
                Feature.ScramSha256Authentication.IsSupported(description.ServerVersion))
            {
                var command           = CustomizeInitialHelloCommand(HelloHelper.CreateCommand(_serverApi));
                var helloProtocol     = HelloHelper.CreateProtocol(command, _serverApi);
                var helloResult       = HelloHelper.GetResult(connection, helloProtocol, cancellationToken);
                var mergedHelloResult = new HelloResult(description.HelloResult.Wrapped.Merge(helloResult.Wrapped));
                description = new ConnectionDescription(
                    description.ConnectionId,
                    mergedHelloResult,
                    description.BuildInfoResult);
            }

            var authenticator = GetOrCreateAuthenticator(connection, description);

            authenticator.Authenticate(connection, description, cancellationToken);
        }
コード例 #4
0
        public void CreateCommand_should_return_expected_result(ExplainVerbosity verbosity, string verbosityString)
        {
            var subject = new ExplainOperation(_databaseNamespace, _explainableOperation, _messageEncoderSettings)
            {
                Verbosity = verbosity
            };

            var expectedResult = new BsonDocument
            {
                { "explain", BsonDocument.Parse("{ find : 'test' }") },
                { "verbosity", verbosityString }
            };

            var endPoint     = new DnsEndPoint("localhost", 27017);
            var serverId     = new ServerId(new ClusterId(), endPoint);
            var connectionId = new ConnectionId(serverId);
            var helloResult  = new HelloResult(new BsonDocument {
                { "ok", 1 }, { "maxMessageSizeBytes", 48000000 }, { "maxWireVersion", WireVersion.Server36 }
            });
            var connectionDescription = new ConnectionDescription(connectionId, helloResult);
            var session = NoCoreSession.Instance;

            var result = subject.CreateExplainCommand(connectionDescription, session);

            result.Should().Be(expectedResult);
        }
コード例 #5
0
        public async Task <string> ShowHelloAsync(HelloResult name)
        {
            var result = ShowHello(name);
            await Task.CompletedTask;

            return(result);
        }
コード例 #6
0
        public string ShowHello(HelloResult hello)
        {
            Console.WriteLine($"from wcf {hello.Name} call SayHello! [{ServiceName}]");
            var result = $"from wcf name:{hello.Name};gender:{hello.Gender};avatar:{hello.Head} [{ServiceName}]";

            return(result);
        }
        // private methods
        private ConnectionDescription CreateConnectionDescription(int maxWireVersion)
        {
            var clusterId    = new ClusterId(1);
            var serverId     = new ServerId(clusterId, new DnsEndPoint("localhost", 27017));
            var connectionId = new ConnectionId(serverId, 1);
            var helloResult  = new HelloResult(new BsonDocument("maxWireVersion", maxWireVersion));

            return(new ConnectionDescription(connectionId, helloResult));
        }
コード例 #8
0
        public HelloResult SayHello(string name)
        {
            Console.WriteLine($"from wcf {name} call SayHello! [{ServiceName}]");
            var result = new HelloResult
            {
                Name   = $"from wcf {name} [{ServiceName}]",
                Gender = "male",
                Head   = "head.png"
            };

            return(result);
        }
コード例 #9
0
        // private methods
        private ConnectionDescription CreateConnectionDescription(SemanticVersion serverVersion)
        {
            var clusterId       = new ClusterId(1);
            var serverId        = new ServerId(clusterId, new DnsEndPoint("localhost", 27017));
            var connectionId    = new ConnectionId(serverId, 1);
            var helloResult     = new HelloResult(new BsonDocument());
            var buildInfoResult = new BuildInfoResult(new BsonDocument
            {
                { "version", serverVersion.ToString() }
            });

            return(new ConnectionDescription(connectionId, helloResult, buildInfoResult));
        }
コード例 #10
0
        private ConnectionDescription CreateConnectionDescriptionSupportingSession(int maxWireVersion = WireVersion.Server36)
        {
            var clusterId     = new ClusterId(1);
            var endPoint      = new DnsEndPoint("localhost", 27017);
            var serverId      = new ServerId(clusterId, endPoint);
            var connectionId  = new ConnectionId(serverId, 1);
            var helloDocument = new BsonDocument
            {
                { "logicalSessionTimeoutMinutes", 30 },
                { "maxWireVersion", maxWireVersion }
            };
            var helloResult = new HelloResult(helloDocument);

            return(new ConnectionDescription(connectionId, helloResult));
        }
コード例 #11
0
        private ConnectionDescription CreateConnectionDescriptionSupportingSession(string version = "3.6.0")
        {
            var clusterId     = new ClusterId(1);
            var endPoint      = new DnsEndPoint("localhost", 27017);
            var serverId      = new ServerId(clusterId, endPoint);
            var connectionId  = new ConnectionId(serverId, 1);
            var helloDocument = new BsonDocument
            {
                { "logicalSessionTimeoutMinutes", 30 }
            };
            var helloResult       = new HelloResult(helloDocument);
            var buildInfoDocument = new BsonDocument
            {
                { "version", version }
            };
            var buildInfoResult = new BuildInfoResult(buildInfoDocument);

            return(new ConnectionDescription(connectionId, helloResult, buildInfoResult));
        }
コード例 #12
0
        private ConnectionDescription CreateConnectionDescription(bool withLogicalSessionTimeout, bool?serviceId = null)
        {
            var clusterId           = new ClusterId(1);
            var endPoint            = new DnsEndPoint("localhost", 27017);
            var serverId            = new ServerId(clusterId, endPoint);
            var connectionId        = new ConnectionId(serverId, 1);
            var helloResultDocument = BsonDocument.Parse($"{{ ok : 1, maxWireVersion : {WireVersion.Server42} }}");

            if (withLogicalSessionTimeout)
            {
                helloResultDocument["logicalSessionTimeoutMinutes"] = 1;
                helloResultDocument["msg"] = "isdbgrid"; // mongos
            }
            if (serviceId.HasValue)
            {
                helloResultDocument["serviceId"] = ObjectId.Empty; // load balancing mode
            }
            var helloResult           = new HelloResult(helloResultDocument);
            var connectionDescription = new ConnectionDescription(connectionId, helloResult);

            return(connectionDescription);
        }
コード例 #13
0
        public void HandleChannelException_should_update_topology_as_expected_on_network_error_or_timeout(
            string errorType, bool shouldUpdateTopology)
        {
            var       serverId     = new ServerId(_clusterId, _endPoint);
            var       connectionId = new ConnectionId(serverId);
            Exception innerMostException;

            switch (errorType)
            {
            case "MongoConnectionExceptionWithSocketTimeout":
                innerMostException = new SocketException((int)SocketError.TimedOut);
                break;

            case nameof(MongoConnectionException):
                innerMostException = new SocketException((int)SocketError.NetworkUnreachable);
                break;

            default: throw new ArgumentException("Unknown error type.");
            }

            var operationUsingChannelException = new MongoConnectionException(connectionId, "Oops", new IOException("Cry", innerMostException));
            var mockConnection = new Mock <IConnectionHandle>();
            var helloResult    = new HelloResult(new BsonDocument {
                { "compressors", new BsonArray() }, { "maxWireVersion", WireVersion.Server44 }
            });

            mockConnection.SetupGet(c => c.Description)
            .Returns(new ConnectionDescription(new ConnectionId(serverId, 0), helloResult));
            var mockConnectionPool = new Mock <IConnectionPool>();

            mockConnectionPool.Setup(p => p.AcquireConnection(It.IsAny <CancellationToken>())).Returns(mockConnection.Object);
            mockConnectionPool.Setup(p => p.AcquireConnectionAsync(It.IsAny <CancellationToken>())).ReturnsAsync(mockConnection.Object);
            var mockConnectionPoolFactory = new Mock <IConnectionPoolFactory>();

            mockConnectionPoolFactory
            .Setup(f => f.CreateConnectionPool(It.IsAny <ServerId>(), _endPoint, It.IsAny <IConnectionExceptionHandler>()))
            .Returns(mockConnectionPool.Object);
            var mockMonitorServerInitialDescription = new ServerDescription(serverId, _endPoint).With(reasonChanged: "Initial D", type: ServerType.Unknown);
            var mockServerMonitor = new Mock <IServerMonitor>();

            mockServerMonitor.SetupGet(m => m.Description).Returns(mockMonitorServerInitialDescription);
            mockServerMonitor.SetupGet(m => m.Lock).Returns(new object());
            var mockServerMonitorFactory = new Mock <IServerMonitorFactory>();

            mockServerMonitorFactory.Setup(f => f.Create(It.IsAny <ServerId>(), _endPoint)).Returns(mockServerMonitor.Object);
            var subject = new DefaultServer(_clusterId, _clusterClock, _clusterConnectionMode, _connectionModeSwitch, _directConnection, _settings, _endPoint, mockConnectionPoolFactory.Object, mockServerMonitorFactory.Object, _capturedEvents, _serverApi);

            subject.Initialize();
            var heartbeatDescription = mockMonitorServerInitialDescription.With(reasonChanged: "Heartbeat", type: ServerType.Standalone);

            mockServerMonitor.Setup(m => m.Description).Returns(heartbeatDescription);
            mockServerMonitor.Raise(
                m => m.DescriptionChanged += null,
                new ServerDescriptionChangedEventArgs(mockMonitorServerInitialDescription, heartbeatDescription));
            subject.Description.Should().Be(heartbeatDescription);

            subject.HandleChannelException(mockConnection.Object, operationUsingChannelException);

            if (shouldUpdateTopology)
            {
                subject.Description.Type.Should().Be(ServerType.Unknown);
                subject.Description.ReasonChanged.Should().Contain("ChannelException");
            }
            else
            {
                subject.Description.Should().Be(heartbeatDescription);
            }
        }
        private void ApplyApplicationError(BsonDocument applicationError)
        {
            var expectedKeys = new[]
            {
                "address",
                "generation", // optional
                "maxWireVersion",
                "when",
                "type",
                "response" // optional
            };

            JsonDrivenHelper.EnsureAllFieldsAreValid(applicationError, expectedKeys);
            var       address            = applicationError["address"].AsString;
            var       endPoint           = EndPointHelper.Parse(address);
            var       server             = (Server)_serverFactory.GetServer(endPoint);
            var       connectionId       = new ConnectionId(server.ServerId);
            var       type               = applicationError["type"].AsString;
            var       maxWireVersion     = applicationError["maxWireVersion"].AsInt32;
            Exception simulatedException = null;

            switch (type)
            {
            case "command":
                var response = applicationError["response"].AsBsonDocument;
                var command  = new BsonDocument("Link", "start!");
                simulatedException = ExceptionMapper.MapNotPrimaryOrNodeIsRecovering(connectionId, command, response, "errmsg");     // can return null
                break;

            case "network":
            {
                var innerException = CoreExceptionHelper.CreateException("IOExceptionWithNetworkUnreachableSocketException");
                simulatedException = new MongoConnectionException(connectionId, "Ignorance, yet knowledge.", innerException);
                break;
            }

            case "timeout":
            {
                var innerException = CoreExceptionHelper.CreateException("IOExceptionWithTimedOutSocketException");
                simulatedException = new MongoConnectionException(connectionId, "Chaos, yet harmony.", innerException);
                break;
            }

            default:
                throw new ArgumentException($"Unsupported value of {type} for type");
            }

            var mockConnection = new Mock <IConnectionHandle>();

            var helloResult = new HelloResult(new BsonDocument {
                { "compressors", new BsonArray() }
            });
            var serverVersion   = WireVersionHelper.MapWireVersionToServerVersion(maxWireVersion);
            var buildInfoResult = new BuildInfoResult(new BsonDocument {
                { "version", serverVersion }
            });

            mockConnection
            .SetupGet(c => c.Description)
            .Returns(new ConnectionDescription(connectionId, helloResult, buildInfoResult));

            int generation = 0;

            if (applicationError.TryGetValue("generation", out var generationBsonValue))
            {
                generation = generationBsonValue.AsInt32;

                if (simulatedException is MongoConnectionException mongoConnectionException)
                {
                    mongoConnectionException.Generation = generation;
                }
            }

            mockConnection.SetupGet(c => c.Generation).Returns(generation);
            mockConnection
            .SetupGet(c => c.Generation)
            .Returns(generation);

            if (simulatedException != null)
            {
                var when = applicationError["when"].AsString;
                switch (when)
                {
                case "beforeHandshakeCompletes":
                    server.HandleBeforeHandshakeCompletesException(simulatedException);
                    break;

                case "afterHandshakeCompletes":
                    server.HandleChannelException(mockConnection.Object, simulatedException);
                    break;

                default:
                    throw new ArgumentException($"Unsupported value of {when} for when.");
                }
            }
        }
コード例 #15
0
        public IEnumerator Connect(
            MonoBehaviour monoBehaviour,
            UnityAction <AsyncResult <bool> > callback
            )
        {
            _webSocket.OnMessage -= this.OnMessageHandler;
            _webSocket.OnError   -= this.OnErrorHandler;

            var       done    = false;
            var       success = false;
            EventArgs args    = null;

            Player[] players         = null;
            var      messageArgsList = new List <MessageEventArgs>();

            void OnOpenHandler(object sender, EventArgs e)
            {
                // 完了フラグ・成功フラグを立てる
                Debug.Log("OnOpenHandler: " + e);
                done    = true;
                success = true;
            }

            void OnErrorHandler(object sender, EventArgs e)
            {
                Debug.Log("OnErrorHandler: " + e);
                // 失敗理由を記録
                args = e;
            }

            void OnCloseHandler(object sender, EventArgs e)
            {
                Debug.Log("OnCloseHandler: " + e);
                // 完了フラグを立てる
                done = true;
            }

            try
            {
                _webSocket.OnOpen  += OnOpenHandler;
                _webSocket.OnError += OnErrorHandler;
                _webSocket.OnClose += OnCloseHandler;
                _webSocket.ConnectAsync();

                for (var i = 0; i < 30 && !done; i++)
                {
#if DISABLE_COROUTINE
                    yield return(null);
#else
                    yield return(new WaitForSeconds(1));
#endif
                }

                if (!success)
                {
                    // 失敗した場合は抜ける
                    yield break;
                }

                success = false;
                done    = false;

                Debug.Log("Hello");
                HelloResult helloResult = null;
                void OnMessageHandler(object sender, EventArgs e)
                {
                    // 認証処理の応答を処理するためのハンドラ
                    Debug.Log("OnMessage: " + e.ToString());
                    if (e is MessageEventArgs data)
                    {
                        var(messageType, payload, sequenceNumber, lifeTimeMilliSeconds) = _messenger.Unpack(data.RawData);
                        var message = _messenger.Parse(messageType, payload);
                        if (message is Error error)
                        {
                            _eventQueue.Enqueue(
                                new OnErrorEvent(
                                    error,
                                    sequenceNumber,
                                    lifeTimeMilliSeconds
                                    )
                                );
                            return;
                        }
                        else if (message is HelloResult)
                        {
                            helloResult = message as HelloResult;
                            success     = true;
                        }
                        else
                        {
                            messageArgsList.Add(data);
                        }
                    }
                    else
                    {
                        args = e;
                    }

                    done = true;
                }

                try
                {
                    _webSocket.OnMessage += OnMessageHandler;

                    Debug.Log("SendAsync");
                    _webSocket.SendAsync(
                        _messenger.Pack(
                            new HelloRequest
                    {
                        AccessToken = _accessToken,
                        MyProfile   = Profile,
                    }
                            ), completed =>
                    {
                        if (completed)
                        {
                            success = true;
                        }

                        done = true;
                    }
                        );

                    for (var i = 0; i < 30 && !done; i++)
                    {
#if DISABLE_COROUTINE
                        yield return(null);
#else
                        yield return(new WaitForSeconds(1));
#endif
                    }

                    if (!success || helloResult == null)
                    {
                        // 失敗した場合は抜ける
                        yield break;
                    }

                    MyConnectionId = helloResult.MyProfile.ConnectionId;
                    players        = helloResult.Players.ToArray();

                    Connected = true;
                }
                finally
                {
                    _webSocket.OnMessage -= OnMessageHandler;
                }
            }
            finally
            {
                _webSocket.OnOpen  -= OnOpenHandler;
                _webSocket.OnError -= OnErrorHandler;
                _webSocket.OnClose -= OnCloseHandler;

                callback.Invoke(new AsyncResult <bool>(
                                    success,
                                    success ? null : new ConnectionException(args)
                                    ));

                if (players != null)
                {
                    foreach (var player in players)
                    {
                        _eventQueue.Enqueue(
                            new OnJoinPlayerEvent(
                                player,
                                0,
                                0
                                )
                            );
                    }
                }

                foreach (var e in messageArgsList)
                {
                    this.OnMessageHandler(null, e);
                }

                _webSocket.OnMessage += this.OnMessageHandler;
                _webSocket.OnError   += this.OnErrorHandler;
            }

            _monoBehaviour = monoBehaviour;
            if (monoBehaviour != null)
            {
                _dispatchCoroutine = monoBehaviour.StartCoroutine(Dispatch());
            }
        }
コード例 #16
0
        private async Task HeartbeatAsync(CancellationToken cancellationToken)
        {
            CommandWireProtocol <BsonDocument> helloProtocol = null;
            bool processAnother = true;

            while (processAnother && !cancellationToken.IsCancellationRequested)
            {
                HelloResult heartbeatHelloResult = null;
                Exception   heartbeatException   = null;
                var         previousDescription  = _currentDescription;

                try
                {
                    IConnection connection;
                    lock (_lock)
                    {
                        connection = _connection;
                    }
                    if (connection == null)
                    {
                        var initializedConnection = await InitializeConnectionAsync(cancellationToken).ConfigureAwait(false);

                        lock (_lock)
                        {
                            if (_state.Value == State.Disposed)
                            {
                                try { initializedConnection.Dispose(); } catch { }
                                throw new OperationCanceledException("The ServerMonitor has been disposed.");
                            }
                            _connection = initializedConnection;
                            _handshakeBuildInfoResult = _connection.Description.BuildInfoResult;
                            heartbeatHelloResult      = _connection.Description.HelloResult;
                        }
                    }
                    else
                    {
                        // If MoreToCome is true, that means we are streaming hello or legacy hello results and must
                        // continue using the existing helloProtocol object.
                        // Otherwise helloProtocol has either not been initialized or we may need to switch between
                        // heartbeat commands based on the last heartbeat response.
                        if (helloProtocol == null || helloProtocol.MoreToCome == false)
                        {
                            helloProtocol = InitializeHelloProtocol(connection, previousDescription?.HelloOk ?? false);
                        }
                        heartbeatHelloResult = await GetHelloResultAsync(connection, helloProtocol, cancellationToken).ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                {
                    return;
                }
                catch (Exception ex)
                {
                    IConnection toDispose = null;

                    lock (_lock)
                    {
                        helloProtocol = null;

                        heartbeatException = ex;
                        _roundTripTimeMonitor.Reset();

                        toDispose   = _connection;
                        _connection = null;
                    }
                    toDispose?.Dispose();
                }

                lock (_lock)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        return;
                    }
                }

                ServerDescription newDescription;
                if (heartbeatHelloResult != null)
                {
                    if (_handshakeBuildInfoResult == null)
                    {
                        // we can be here only if there is a bug in the driver
                        throw new ArgumentNullException("BuildInfo has been lost.");
                    }

                    var averageRoundTripTime        = _roundTripTimeMonitor.Average;
                    var averageRoundTripTimeRounded = TimeSpan.FromMilliseconds(Math.Round(averageRoundTripTime.TotalMilliseconds));

                    newDescription = _baseDescription.With(
                        averageRoundTripTime: averageRoundTripTimeRounded,
                        canonicalEndPoint: heartbeatHelloResult.Me,
                        electionId: heartbeatHelloResult.ElectionId,
                        helloOk: heartbeatHelloResult.HelloOk,
                        lastWriteTimestamp: heartbeatHelloResult.LastWriteTimestamp,
                        logicalSessionTimeout: heartbeatHelloResult.LogicalSessionTimeout,
                        maxBatchCount: heartbeatHelloResult.MaxBatchCount,
                        maxDocumentSize: heartbeatHelloResult.MaxDocumentSize,
                        maxMessageSize: heartbeatHelloResult.MaxMessageSize,
                        replicaSetConfig: heartbeatHelloResult.GetReplicaSetConfig(),
                        state: ServerState.Connected,
                        tags: heartbeatHelloResult.Tags,
                        topologyVersion: heartbeatHelloResult.TopologyVersion,
                        type: heartbeatHelloResult.ServerType,
                        version: _handshakeBuildInfoResult.ServerVersion,
                        wireVersionRange: new Range <int>(heartbeatHelloResult.MinWireVersion, heartbeatHelloResult.MaxWireVersion));
                }
                else
                {
                    newDescription = _baseDescription.With(lastUpdateTimestamp: DateTime.UtcNow);
                }

                if (heartbeatException != null)
                {
                    var topologyVersion = default(Optional <TopologyVersion>);
                    if (heartbeatException is MongoCommandException heartbeatCommandException)
                    {
                        topologyVersion = TopologyVersion.FromMongoCommandException(heartbeatCommandException);
                    }
                    newDescription = newDescription.With(heartbeatException: heartbeatException, topologyVersion: topologyVersion);
                }

                newDescription = newDescription.With(reasonChanged: "Heartbeat", lastHeartbeatTimestamp: DateTime.UtcNow);

                lock (_lock)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    SetDescription(newDescription);
                }

                processAnother =
                    // serverSupportsStreaming
                    (newDescription.Type != ServerType.Unknown && heartbeatHelloResult != null && heartbeatHelloResult.TopologyVersion != null) ||
                    // connectionIsStreaming
                    (helloProtocol != null && helloProtocol.MoreToCome) ||
                    // transitionedWithNetworkError
                    (IsNetworkError(heartbeatException) && previousDescription.Type != ServerType.Unknown);
            }

            bool IsNetworkError(Exception ex)
            {
                return(ex is MongoConnectionException mongoConnectionException && mongoConnectionException.IsNetworkException);
            }
        }