private void AssertValidConnection(IncomingConnectionInfo connectionInfo) { //precaution, should never happen.. if (string.IsNullOrWhiteSpace(connectionInfo.SourceDatabaseId) || Guid.TryParse(connectionInfo.SourceDatabaseId, out Guid sourceDbId) == false) { throw new InvalidOperationException( $"Failed to parse source database Id. What I got is {(string.IsNullOrWhiteSpace(connectionInfo.SourceDatabaseId) ? "<empty string>" : Database.DbId.ToString())}. This is not supposed to happen and is likely a bug."); } if (sourceDbId == Database.DbId) { throw new InvalidOperationException( $"Cannot have replication with source and destination being the same database. They share the same db id ({connectionInfo} - {Database.DbId})"); } if (_server.IsPassive()) { throw new InvalidOperationException( $"Cannot accept the incoming replication connection from {connectionInfo.SourceUrl}, because this node is in passive state."); } if (_incoming.TryRemove(connectionInfo.SourceDatabaseId, out IncomingReplicationHandler value)) { if (_log.IsInfoEnabled) { _log.Info( $"Disconnecting existing connection from {value.FromToString} because we got a new connection from the same source db"); } IncomingReplicationRemoved?.Invoke(value); value.Dispose(); } }
public void AcceptIncomingConnection(TcpConnectionOptions tcpConnectionOptions) { ReplicationLatestEtagRequest getLatestEtagMessage; using (tcpConnectionOptions.ContextPool.AllocateOperationContext(out JsonOperationContext context)) using (var readerObject = context.ParseToMemory( tcpConnectionOptions.Stream, "IncomingReplication/get-last-etag-message read", BlittableJsonDocumentBuilder.UsageMode.None, tcpConnectionOptions.PinnedBuffer)) { getLatestEtagMessage = JsonDeserializationServer.ReplicationLatestEtagRequest(readerObject); if (_log.IsInfoEnabled) { _log.Info( $"GetLastEtag: {getLatestEtagMessage.SourceTag}({getLatestEtagMessage.SourceMachineName}) / {getLatestEtagMessage.SourceDatabaseName} ({getLatestEtagMessage.SourceDatabaseId}) - {getLatestEtagMessage.SourceUrl}"); } } var connectionInfo = IncomingConnectionInfo.FromGetLatestEtag(getLatestEtagMessage); try { AssertValidConnection(connectionInfo); } catch (Exception e) { if (_log.IsInfoEnabled) { _log.Info($"Connection from [{connectionInfo}] is rejected.", e); } var incomingConnectionRejectionInfos = _incomingRejectionStats.GetOrAdd(connectionInfo, _ => new ConcurrentQueue <IncomingConnectionRejectionInfo>()); incomingConnectionRejectionInfos.Enqueue(new IncomingConnectionRejectionInfo { Reason = e.ToString() }); try { tcpConnectionOptions.Dispose(); } catch { // do nothing } throw; } try { using (Database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext documentsOperationContext)) using (Database.ConfigurationStorage.ContextPool.AllocateOperationContext(out TransactionOperationContext configurationContext)) using (var writer = new BlittableJsonTextWriter(documentsOperationContext, tcpConnectionOptions.Stream)) using (documentsOperationContext.OpenReadTransaction()) using (configurationContext.OpenReadTransaction()) { var changeVector = DocumentsStorage.GetDatabaseChangeVector(documentsOperationContext); var lastEtagFromSrc = Database.DocumentsStorage.GetLastReplicateEtagFrom( documentsOperationContext, getLatestEtagMessage.SourceDatabaseId); if (_log.IsInfoEnabled) { _log.Info($"GetLastEtag response, last etag: {lastEtagFromSrc}"); } var response = new DynamicJsonValue { [nameof(ReplicationMessageReply.Type)] = "Ok", [nameof(ReplicationMessageReply.MessageType)] = ReplicationMessageType.Heartbeat, [nameof(ReplicationMessageReply.LastEtagAccepted)] = lastEtagFromSrc, [nameof(ReplicationMessageReply.NodeTag)] = _server.NodeTag, [nameof(ReplicationMessageReply.DatabaseChangeVector)] = changeVector }; documentsOperationContext.Write(writer, response); writer.Flush(); } } catch (Exception) { try { tcpConnectionOptions.Dispose(); } catch (Exception) { // do nothing } throw; } var newIncoming = new IncomingReplicationHandler( tcpConnectionOptions, getLatestEtagMessage, this); newIncoming.Failed += OnIncomingReceiveFailed; newIncoming.DocumentsReceived += OnIncomingReceiveSucceeded; if (_log.IsInfoEnabled) { _log.Info( $"Initialized document replication connection from {connectionInfo.SourceDatabaseName} located at {connectionInfo.SourceUrl}"); } // need to safeguard against two concurrent connection attempts var newConnection = _incoming.GetOrAdd(newIncoming.ConnectionInfo.SourceDatabaseId, newIncoming); if (newConnection == newIncoming) { newIncoming.Start(); IncomingReplicationAdded?.Invoke(newIncoming); ForceTryReconnectAll(); } else { newIncoming.Dispose(); } }
public void AcceptIncomingConnection(TcpConnectionOptions tcpConnectionOptions) { ReplicationLatestEtagRequest getLatestEtagMessage; using (var readerObject = tcpConnectionOptions.MultiDocumentParser.ParseToMemory("IncomingReplication/get-last-etag-message read")) { getLatestEtagMessage = JsonDeserializationServer.ReplicationLatestEtagRequest(readerObject); if (_log.IsInfoEnabled) { _log.Info($"GetLastEtag: {getLatestEtagMessage.SourceMachineName} / {getLatestEtagMessage.SourceDatabaseName} ({getLatestEtagMessage.SourceDatabaseId}) - {getLatestEtagMessage.SourceUrl}"); } } var connectionInfo = IncomingConnectionInfo.FromGetLatestEtag(getLatestEtagMessage); try { AssertValidConnection(connectionInfo); } catch (Exception e) { if (_log.IsInfoEnabled) { _log.Info($"Connection from [{connectionInfo}] is rejected.", e); } var incomingConnectionRejectionInfos = _incomingRejectionStats.GetOrAdd(connectionInfo, _ => new ConcurrentQueue <IncomingConnectionRejectionInfo>()); incomingConnectionRejectionInfos.Enqueue(new IncomingConnectionRejectionInfo { Reason = e.ToString() }); throw; } DocumentsOperationContext documentsOperationContext; TransactionOperationContext configurationContext; using (_database.DocumentsStorage.ContextPool.AllocateOperationContext(out documentsOperationContext)) using (_database.ConfigurationStorage.ContextPool.AllocateOperationContext(out configurationContext)) using (var writer = new BlittableJsonTextWriter(documentsOperationContext, tcpConnectionOptions.Stream)) using (var docTx = documentsOperationContext.OpenReadTransaction()) using (var configTx = configurationContext.OpenReadTransaction()) { var documentsChangeVector = new DynamicJsonArray(); foreach (var changeVectorEntry in _database.DocumentsStorage.GetDatabaseChangeVector(documentsOperationContext)) { documentsChangeVector.Add(new DynamicJsonValue { [nameof(ChangeVectorEntry.DbId)] = changeVectorEntry.DbId.ToString(), [nameof(ChangeVectorEntry.Etag)] = changeVectorEntry.Etag }); } var indexesChangeVector = new DynamicJsonArray(); var changeVectorAsArray = _database.IndexMetadataPersistence.GetIndexesAndTransformersChangeVector(configTx.InnerTransaction); foreach (var changeVectorEntry in changeVectorAsArray) { indexesChangeVector.Add(new DynamicJsonValue { [nameof(ChangeVectorEntry.DbId)] = changeVectorEntry.DbId.ToString(), [nameof(ChangeVectorEntry.Etag)] = changeVectorEntry.Etag }); } var lastEtagFromSrc = _database.DocumentsStorage.GetLastReplicateEtagFrom(documentsOperationContext, getLatestEtagMessage.SourceDatabaseId); if (_log.IsInfoEnabled) { _log.Info($"GetLastEtag response, last etag: {lastEtagFromSrc}"); } documentsOperationContext.Write(writer, new DynamicJsonValue { [nameof(ReplicationMessageReply.Type)] = "Ok", [nameof(ReplicationMessageReply.MessageType)] = ReplicationMessageType.Heartbeat, [nameof(ReplicationMessageReply.LastEtagAccepted)] = lastEtagFromSrc, [nameof(ReplicationMessageReply.LastIndexTransformerEtagAccepted)] = _database.IndexMetadataPersistence.GetLastReplicateEtagFrom(configTx.InnerTransaction, getLatestEtagMessage.SourceDatabaseId), [nameof(ReplicationMessageReply.DocumentsChangeVector)] = documentsChangeVector, [nameof(ReplicationMessageReply.IndexTransformerChangeVector)] = indexesChangeVector }); writer.Flush(); } var newIncoming = new IncomingReplicationHandler( tcpConnectionOptions.MultiDocumentParser, _database, tcpConnectionOptions.TcpClient, tcpConnectionOptions.Stream, getLatestEtagMessage, this); newIncoming.Failed += OnIncomingReceiveFailed; newIncoming.DocumentsReceived += OnIncomingReceiveSucceeded; if (_log.IsInfoEnabled) { _log.Info($"Initialized document replication connection from {connectionInfo.SourceDatabaseName} located at {connectionInfo.SourceUrl}", null); } // need to safeguard against two concurrent connection attempts var newConnection = _incoming.GetOrAdd(newIncoming.ConnectionInfo.SourceDatabaseId, newIncoming); if (newConnection == newIncoming) { newIncoming.Start(); } else { newIncoming.Dispose(); } }