// 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(); }
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); } }
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); } }
// protected methods protected abstract void Invalidate(string reasonInvalidated, bool clearConnectionPool, TopologyVersion responseTopologyDescription);
public void Invalidate(string reasonInvalidated, TopologyVersion responseTopologyDescription) { Invalidate(reasonInvalidated, clearConnectionPool: true, responseTopologyDescription); }
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); } }