예제 #1
0
        // public methods
        public override void Invalidate(string reasonInvalidated, bool clearConnectionPool, TopologyVersion topologyVersion)
        {
            if (clearConnectionPool)
            {
                ConnectionPool.Clear();
            }
            var newDescription = _baseDescription.With(
                $"InvalidatedBecause:{reasonInvalidated}",
                lastUpdateTimestamp: DateTime.UtcNow,
                topologyVersion: topologyVersion);

            SetDescription(newDescription);
            // TODO: make the heartbeat request conditional so we adhere to this part of the spec
            // > Network error when reading or writing: ... Clients MUST NOT request an immediate check of the server;
            // > since application sockets are used frequently, a network error likely means the server has just become
            // > unavailable, so an immediate refresh is likely to get a network error, too.
            RequestHeartbeat();
        }
예제 #2
0
        private void Heartbeat(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 = InitializeConnection(cancellationToken);
                        lock (_lock)
                        {
                            if (_state.Value == State.Disposed)
                            {
                                try { initializedConnection.Dispose(); } catch { }
                                throw new OperationCanceledException("The ServerMonitor has been disposed.");
                            }
                            _connection          = initializedConnection;
                            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 = GetHelloResult(connection, helloProtocol, cancellationToken);
                    }
                }
                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)
                {
                    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: WireVersion.ToServerVersion(heartbeatHelloResult.MaxWireVersion),
                        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);
            }
        }
예제 #3
0
        private async Task HeartbeatAsync(CancellationToken cancellationToken)
        {
            CommandWireProtocol <BsonDocument> isMasterProtocol = null;

            bool processAnother = true;

            while (processAnother && !cancellationToken.IsCancellationRequested)
            {
                IsMasterResult heartbeatIsMasterResult = 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;
                            heartbeatIsMasterResult   = _connection.Description.IsMasterResult;
                        }
                    }
                    else
                    {
                        isMasterProtocol        = isMasterProtocol ?? InitializeIsMasterProtocol(connection);
                        heartbeatIsMasterResult = await GetIsMasterResultAsync(connection, isMasterProtocol, cancellationToken).ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException) when(cancellationToken.IsCancellationRequested)
                {
                    return;
                }
                catch (Exception ex)
                {
                    IConnection toDispose = null;

                    lock (_lock)
                    {
                        isMasterProtocol = null;

                        heartbeatException = ex;
                        _roundTripTimeMonitor.Reset();

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

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

                ServerDescription newDescription;
                if (heartbeatIsMasterResult != 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: heartbeatIsMasterResult.Me,
                        electionId: heartbeatIsMasterResult.ElectionId,
                        lastWriteTimestamp: heartbeatIsMasterResult.LastWriteTimestamp,
                        logicalSessionTimeout: heartbeatIsMasterResult.LogicalSessionTimeout,
                        maxBatchCount: heartbeatIsMasterResult.MaxBatchCount,
                        maxDocumentSize: heartbeatIsMasterResult.MaxDocumentSize,
                        maxMessageSize: heartbeatIsMasterResult.MaxMessageSize,
                        replicaSetConfig: heartbeatIsMasterResult.GetReplicaSetConfig(),
                        state: ServerState.Connected,
                        tags: heartbeatIsMasterResult.Tags,
                        topologyVersion: heartbeatIsMasterResult.TopologyVersion,
                        type: heartbeatIsMasterResult.ServerType,
                        version: _handshakeBuildInfoResult.ServerVersion,
                        wireVersionRange: new Range <int>(heartbeatIsMasterResult.MinWireVersion, heartbeatIsMasterResult.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 && heartbeatIsMasterResult != null && heartbeatIsMasterResult.TopologyVersion != null) ||
                    // connectionIsStreaming
                    (isMasterProtocol != null && isMasterProtocol.MoreToCome) ||
                    // transitionedWithNetworkError
                    (IsNetworkError(heartbeatException) && previousDescription.Type != ServerType.Unknown);
            }

            bool IsNetworkError(Exception ex)
            {
                return(ex is MongoConnectionException mongoConnectionException && mongoConnectionException.IsNetworkException);
            }
        }
예제 #4
0
        // protected methods

        protected abstract void Invalidate(string reasonInvalidated, bool clearConnectionPool, TopologyVersion responseTopologyDescription);
예제 #5
0
 public void Invalidate(string reasonInvalidated, TopologyVersion responseTopologyDescription)
 {
     Invalidate(reasonInvalidated, clearConnectionPool: true, responseTopologyDescription);
 }
예제 #6
0
        private bool ShouldInvalidateServer(
            IConnection connection,
            Exception exception,
            ServerDescription description,
            out TopologyVersion invalidatingResponseTopologyVersion)
        {
            if (exception is MongoConnectionException mongoConnectionException &&
                mongoConnectionException.ContainsSocketTimeoutException)
            {
                invalidatingResponseTopologyVersion = null;
                return(false);
            }

            if (__invalidatingExceptions.Contains(exception.GetType()))
            {
                invalidatingResponseTopologyVersion = null;
                return(true);
            }

            var commandException = exception as MongoCommandException;

            if (commandException != null)
            {
                var code    = (ServerErrorCode)commandException.Code;
                var message = commandException.ErrorMessage;

                if (IsStateChangeError(code, message))
                {
                    return(!IsStaleStateChangeError(commandException.Result, out invalidatingResponseTopologyVersion));
                }

                if (commandException.GetType() == typeof(MongoWriteConcernException))
                {
                    var writeConcernException = (MongoWriteConcernException)commandException;
                    var writeConcernResult    = writeConcernException.WriteConcernResult;
                    var response          = writeConcernResult.Response;
                    var writeConcernError = response["writeConcernError"].AsBsonDocument;
                    if (writeConcernError != null)
                    {
                        code    = (ServerErrorCode)writeConcernError.GetValue("code", -1).ToInt32();
                        message = writeConcernError.GetValue("errmsg", null)?.AsString;

                        if (IsStateChangeError(code, message))
                        {
                            return(!IsStaleStateChangeError(commandException.Result, out invalidatingResponseTopologyVersion));
                        }
                    }
                }
            }

            invalidatingResponseTopologyVersion = null;
            return(false);

            bool IsStaleStateChangeError(BsonDocument response, out TopologyVersion nonStaleResponseTopologyVersion)
            {
                if (_connectionPool.Generation > connection.Generation)
                {
                    // stale generation number
                    nonStaleResponseTopologyVersion = null;
                    return(true);
                }

                var responseTopologyVersion = TopologyVersion.FromMongoCommandResponse(response);
                // We use FresherThanOrEqualTo instead of FresherThan because a state change should come with a new
                // topology version.
                // We cannot use StalerThan(responseTopologyVersion, description.TopologyVersion) because due to how
                // TopologyVersions comparisons are defined, FresherThanOrEqualTo(x, y) does not imply StalerThan(y, x)
                bool isStale = TopologyVersion.IsFresherThanOrEqualTo(description.TopologyVersion, responseTopologyVersion);

                nonStaleResponseTopologyVersion = isStale ? null : responseTopologyVersion;
                return(isStale);
            }
        }