コード例 #1
0
        public ChangeVectorEntry[] GetIndexesAndTransformersChangeVector(Transaction tx)
        {
            var globalChangeVectorTree = tx.ReadTree(SchemaNameConstants.GlobalChangeVectorTree);

            Debug.Assert(globalChangeVectorTree != null);
            return(ReplicationUtils.ReadChangeVectorFrom(globalChangeVectorTree));
        }
コード例 #2
0
        private IReadOnlyList <ChangeVectorEntry[]> DeleteConflictsFor(Transaction tx, Slice name)
        {
            var table = tx.OpenTable(ConflictsTableSchema, SchemaNameConstants.ConflictMetadataTable);

            Debug.Assert(table != null);

            var list = new List <ChangeVectorEntry[]>();

            while (true)
            {
                bool deleted = false;
                // deleting a value might cause other ids to change, so we can't just pass the list
                // of ids to be deleted, because they wouldn't remain stable during the deletions
                foreach (var seekResult in table.SeekForwardFrom(ConflictsTableSchema.Indexes[NameAndEtagIndexName], name, true))
                {
                    foreach (var tvr in seekResult.Results)
                    {
                        deleted = true;
                        list.Add(ReplicationUtils.GetChangeVectorEntriesFromTableValueReader(tvr, (int)MetadataFields.ChangeVector));

                        //Ids might change due to delete
                        table.Delete(tvr.Id);
                        break;
                    }
                }

                if (deleted == false)
                {
                    return(list);
                }
            }
        }
コード例 #3
0
        public async Task TestConnection()
        {
            var url = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");
            DynamicJsonValue result;

            try
            {
                var timeout        = TimeoutManager.WaitFor(ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan);
                var connectionInfo = ReplicationUtils.GetTcpInfoAsync(url, null, "Test-Connection", Server.ClusterCertificateHolder.Certificate);
                if (await Task.WhenAny(timeout, connectionInfo) == timeout)
                {
                    throw new TimeoutException($"Waited for {ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan} to receive tcp info from {url} and got no response");
                }
                result = await ConnectToClientNodeAsync(connectionInfo.Result, ServerStore.Engine.TcpConnectionTimeout, LoggingSource.Instance.GetLogger("testing-connection", "testing-connection"));
            }
            catch (Exception e)
            {
                result = new DynamicJsonValue
                {
                    [nameof(NodeConnectionTestResult.Success)] = false,
                    [nameof(NodeConnectionTestResult.Error)]   = $"An exception was thrown while trying to connect to {url} : {e}"
                };
            }

            using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                {
                    context.Write(writer, result);
                }
        }
コード例 #4
0
        public void SetGlobalChangeVector(Transaction tx, ByteStringContext context, Dictionary <Guid, long> changeVector)
        {
            var tree = tx.CreateTree(SchemaNameConstants.GlobalChangeVectorTree);

            Debug.Assert(tree != null);

            ReplicationUtils.WriteChangeVectorTo(context, changeVector, tree);
        }
コード例 #5
0
        private ChangeVectorEntry[] SetIndexTransformerChangeVectorForLocalChange(Transaction tx, TransactionOperationContext context, Slice loweredName, TableValueReader oldValue, long newEtag, ChangeVectorEntry[] vector)
        {
            if (oldValue != null)
            {
                var changeVector = ReplicationUtils.GetChangeVectorEntriesFromTableValueReader(oldValue, (int)MetadataFields.ChangeVector);
                return(ReplicationUtils.UpdateChangeVectorWithNewEtag(_environment.DbId, newEtag, changeVector));
            }

            return(GetMergedConflictChangeVectorsAndDeleteConflicts(tx, context, loweredName, newEtag));
        }
コード例 #6
0
        private ChangeVectorEntry[] GetMergedConflictChangeVectorsAndDeleteConflicts(Transaction tx, TransactionOperationContext context, Slice name, long newEtag, ChangeVectorEntry[] existing = null)
        {
            var conflictChangeVectors = DeleteConflictsFor(tx, name);

            //no conflicts, no need to merge
            if (conflictChangeVectors.Count == 0)
            {
                if (existing != null)
                {
                    return(ReplicationUtils.UpdateChangeVectorWithNewEtag(_environment.DbId, newEtag, existing));
                }

                return(new[]
                {
                    new ChangeVectorEntry
                    {
                        Etag = newEtag,
                        DbId = _environment.DbId
                    }
                });
            }

            // need to merge the conflict change vectors
            var maxEtags = new Dictionary <Guid, long>
            {
                [_environment.DbId] = newEtag
            };

            foreach (var conflictChangeVector in conflictChangeVectors)
            {
                foreach (var entry in conflictChangeVector)
                {
                    long etag;
                    if (maxEtags.TryGetValue(entry.DbId, out etag) == false ||
                        etag < entry.Etag)
                    {
                        maxEtags[entry.DbId] = entry.Etag;
                    }
                }
            }

            var changeVector = new ChangeVectorEntry[maxEtags.Count];

            var index = 0;

            foreach (var maxEtag in maxEtags)
            {
                changeVector[index].DbId = maxEtag.Key;
                changeVector[index].Etag = maxEtag.Value;
                index++;
            }
            return(changeVector);
        }
コード例 #7
0
        public Task Establish(X509Certificate2 certificate)
        {
            if (certificate != null)
            {
                var tcpConnection = ReplicationUtils.GetTcpInfo(_nodeUrl, null, $"{nameof(ProxyWebSocketConnection)} to {_nodeUrl}", certificate, _cts.Token);

                var expectedCert = new X509Certificate2(Convert.FromBase64String(tcpConnection.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet);

                _remoteWebSocket.Options.ClientCertificates.Add(certificate);

                _remoteWebSocket.Options.RemoteCertificateValidationCallback += (sender, actualCert, chain, errors) => expectedCert.Equals(actualCert);
            }

            return(_remoteWebSocket.ConnectAsync(_remoteWebSocketUri, _cts.Token));
        }
コード例 #8
0
ファイル: ReplicationLoader.cs プロジェクト: rstonkus/ravendb
        private TcpConnectionInfo GetConnectionInfo(ReplicationNode node, bool external)
        {
            var shutdownInfo = new ConnectionShutdownInfo
            {
                Node     = node,
                External = external
            };

            _outgoingFailureInfo.TryAdd(node, shutdownInfo);
            try
            {
                if (node is ExternalReplication exNode)
                {
                    using (var requestExecutor = RequestExecutor.Create(exNode.ConnectionString.TopologyDiscoveryUrls, exNode.ConnectionString.Database,
                                                                        _server.Server.Certificate.Certificate,
                                                                        DocumentConventions.Default))
                        using (_server.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                        {
                            var database = exNode.ConnectionString.Database;
                            var cmd      = new GetTcpInfoCommand("extrenal-replication", database);
                            requestExecutor.Execute(cmd, ctx);
                            node.Database = database;
                            node.Url      = requestExecutor.Url;
                            return(cmd.Result);
                        }
                }
                if (node is InternalReplication internalNode)
                {
                    using (var cts = new CancellationTokenSource(_server.Engine.TcpConnectionTimeout))
                    {
                        return(ReplicationUtils.GetTcpInfo(internalNode.Url, internalNode.Database, "Replication", _server.Server.Certificate.Certificate, cts.Token));
                    }
                }
                throw new InvalidOperationException(
                          $"Unexpected replication node type, Expected to be '{typeof(ExternalReplication)}' or '{typeof(InternalReplication)}', but got '{node.GetType()}'");
            }
            catch (Exception e)
            {
                // will try to fetch it again later
                if (_log.IsInfoEnabled)
                {
                    _log.Info($"Failed to fetch tcp connection information for the destination '{node.FromString()}' , the connection will be retried later.", e);
                }

                _reconnectQueue.TryAdd(shutdownInfo);
            }
            return(null);
        }
コード例 #9
0
ファイル: NodeDebugHandler.cs プロジェクト: haludi/ravendb
        private async Task <PingResult> PingOnce(string url)
        {
            var sp     = Stopwatch.StartNew();
            var result = new PingResult
            {
                Url = url
            };

            try
            {
                using (var cts = new CancellationTokenSource(ServerStore.Engine.TcpConnectionTimeout))
                {
                    var info = await ReplicationUtils.GetTcpInfoAsync(url, null, "PingTest", ServerStore.Engine.ClusterCertificate, cts.Token);

                    result.TcpInfoTime = sp.ElapsedMilliseconds;
                    using (var tcpClient = await TcpUtils.ConnectAsync(info.Url, ServerStore.Engine.TcpConnectionTimeout).ConfigureAwait(false))
                        using (var stream = await TcpUtils
                                            .WrapStreamWithSslAsync(tcpClient, info, ServerStore.Engine.ClusterCertificate, ServerStore.Engine.TcpConnectionTimeout).ConfigureAwait(false))
                            using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                            {
                                var msg = new DynamicJsonValue
                                {
                                    [nameof(TcpConnectionHeaderMessage.DatabaseName)]     = null,
                                    [nameof(TcpConnectionHeaderMessage.Operation)]        = TcpConnectionHeaderMessage.OperationTypes.Ping,
                                    [nameof(TcpConnectionHeaderMessage.OperationVersion)] = -1
                                };

                                using (var writer = new BlittableJsonTextWriter(context, stream))
                                    using (var msgJson = context.ReadObject(msg, "message"))
                                    {
                                        result.SendTime = sp.ElapsedMilliseconds;
                                        context.Write(writer, msgJson);
                                    }
                                using (var response = context.ReadForMemory(stream, "cluster-ConnectToPeer-header-response"))
                                {
                                    JsonDeserializationServer.TcpConnectionHeaderResponse(response);
                                    result.RecieveTime = sp.ElapsedMilliseconds;
                                }
                            }
                }
            }
            catch (Exception e)
            {
                result.Error = e.ToString();
            }
            return(result);
        }
コード例 #10
0
        private IndexConflictEntry TableValueToConflict(TableValueReader tvr,
                                                        JsonOperationContext context)
        {
            var data = new IndexConflictEntry();

            int size;

            data.Name = new LazyStringValue(null, tvr.Read((int)ConflictFields.Name, out size), size, context).ToString();

            data.ChangeVector = ReplicationUtils.GetChangeVectorEntriesFromTableValueReader(tvr, (int)ConflictFields.ChangeVector);
            data.Type         = (IndexEntryType)(*tvr.Read((int)ConflictFields.Type, out size));
            data.Etag         = Bits.SwapBytes(*(long *)tvr.Read((int)ConflictFields.Etag, out size));

            var ptr = tvr.Read((int)ConflictFields.Definition, out size);

            data.Definition = new BlittableJsonReaderObject(ptr, size, context);

            return(data);
        }
コード例 #11
0
        private IndexEntryMetadata TableValueToMetadata(TableValueReader tvr,
                                                        JsonOperationContext context,
                                                        bool returnNullIfTombstone)
        {
            var metadata = new IndexEntryMetadata();

            int size;

            metadata.Id = Bits.SwapBytes(*(int *)tvr.Read((int)MetadataFields.Id, out size));
            if (returnNullIfTombstone && metadata.Id == -1)
            {
                return(null);
            }

            metadata.Name = new LazyStringValue(null, tvr.Read((int)MetadataFields.Name, out size), size, context).ToString();

            metadata.ChangeVector = ReplicationUtils.GetChangeVectorEntriesFromTableValueReader(tvr, (int)MetadataFields.ChangeVector);
            metadata.Type         = (IndexEntryType)(*tvr.Read((int)MetadataFields.Type, out size));
            metadata.Etag         = Bits.SwapBytes(*(long *)tvr.Read((int)MetadataFields.Etag, out size));
            metadata.IsConflicted = *(bool *)tvr.Read((int)MetadataFields.IsConflicted, out size);

            return(metadata);
        }
コード例 #12
0
        private void MergeEntryVectorWithGlobal(Transaction tx,
                                                ByteStringContext context,
                                                ChangeVectorEntry[] changeVectorForWrite)
        {
            var globalChangeVectorTree = tx.ReadTree(SchemaNameConstants.GlobalChangeVectorTree);

            Debug.Assert(globalChangeVectorTree != null);

            var globalChangeVector = ReplicationUtils.ReadChangeVectorFrom(globalChangeVectorTree);

            // merge metadata change vector into global change vector
            // --> if we have any entry in global vector smaller than in metadata vector,
            // update the entry in global vector to a larger one
            foreach (var item in ReplicationUtils.MergeVectors(globalChangeVector, changeVectorForWrite))
            {
                var   dbId          = item.DbId;
                var   etagBigEndian = Bits.SwapBytes(item.Etag);
                Slice key;
                Slice value;
                using (Slice.External(context, (byte *)&dbId, sizeof(Guid), out key))
                    using (Slice.External(context, (byte *)&etagBigEndian, sizeof(long), out value))
                        globalChangeVectorTree.Add(key, value);
            }
        }
コード例 #13
0
        public async Task TestConnection()
        {
            var url = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");

            url = UrlHelper.TryGetLeftPart(url);
            DynamicJsonValue result = null;

            Task <TcpConnectionInfo> connectionInfo = null;

            try
            {
                try
                {
                    var timeout = TimeoutManager.WaitFor(ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan);

                    using (var cts = new CancellationTokenSource(Server.Configuration.Cluster.OperationTimeout.AsTimeSpan))
                    {
                        connectionInfo = ReplicationUtils.GetTcpInfoAsync(url, null, "Test-Connection", Server.Certificate.Certificate,
                                                                          cts.Token);
                    }
                    Task timeoutTask = await Task.WhenAny(timeout, connectionInfo);

                    if (timeoutTask == timeout)
                    {
                        throw new TimeoutException($"Waited for {ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan} to receive tcp info from {url} and got no response");
                    }
                    await connectionInfo;
                }
                catch (Exception e)
                {
                    result = new DynamicJsonValue
                    {
                        [nameof(NodeConnectionTestResult.Success)]     = false,
                        [nameof(NodeConnectionTestResult.HTTPSuccess)] = false,
                        [nameof(NodeConnectionTestResult.Error)]       = $"An exception was thrown while trying to connect to {url} : {e}"
                    };
                    return;
                }

                try
                {
                    result = await ConnectToClientNodeAsync(connectionInfo.Result, ServerStore.Engine.TcpConnectionTimeout, LoggingSource.Instance.GetLogger("testing-connection", "testing-connection"));
                }
                catch (Exception e)
                {
                    result = new DynamicJsonValue
                    {
                        [nameof(NodeConnectionTestResult.Success)]      = false,
                        [nameof(NodeConnectionTestResult.HTTPSuccess)]  = true,
                        [nameof(NodeConnectionTestResult.TcpServerUrl)] = connectionInfo.Result.Url,
                        [nameof(NodeConnectionTestResult.Error)]        = $"Was able to connect to url {url}, but exception was thrown while trying to connect to TCP port {connectionInfo.Result.Url}: {e}"
                    };
                    return;
                }
            }
            finally
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                    using (var writer = new BlittableJsonTextWriter(context, ResponseBodyStream()))
                    {
                        context.Write(writer, result);
                    }
            }
        }
コード例 #14
0
            private void ListenToMaintenanceWorker()
            {
                var needToWait               = false;
                var firstIteration           = true;
                var onErrorDelayTime         = _parent.Config.OnErrorDelayTime.AsTimeSpan;
                var receiveFromWorkerTimeout = _parent.Config.ReceiveFromWorkerTimeout.AsTimeSpan;
                var tcpTimeout               = _parent.Config.TcpConnectionTimeout.AsTimeSpan;

                if (tcpTimeout < receiveFromWorkerTimeout)
                {
                    if (_log.IsInfoEnabled)
                    {
                        _log.Info(
                            $"Warning: TCP timeout is lower than the receive from worker timeout ({tcpTimeout} < {receiveFromWorkerTimeout}), " +
                            "this could affect the cluster observer's decisions.");
                    }
                }

                TcpConnectionInfo tcpConnection = null;

                while (_token.IsCancellationRequested == false)
                {
                    try
                    {
                        if (needToWait)
                        {
                            needToWait = false; // avoid tight loop if there was timeout / error
                            if (firstIteration == false)
                            {
                                _token.WaitHandle.WaitOne(onErrorDelayTime);
                            }
                            firstIteration = false;
                            using (var timeout = new CancellationTokenSource(tcpTimeout))
                                using (var combined = CancellationTokenSource.CreateLinkedTokenSource(_token, timeout.Token))
                                {
                                    tcpConnection = ReplicationUtils.GetTcpInfo(Url, null, "Supervisor", _parent._server.Server.Certificate.Certificate, combined.Token);
                                }
                        }

                        if (tcpConnection == null)
                        {
                            needToWait = true;
                            continue;
                        }

                        var connection = ConnectToClientNode(tcpConnection, _parent._server.Engine.TcpConnectionTimeout);
                        var tcpClient  = connection.TcpClient;
                        var stream     = connection.Stream;
                        using (tcpClient)
                            using (_cts.Token.Register(tcpClient.Dispose))
                                using (_contextPool.AllocateOperationContext(out JsonOperationContext context))
                                    using (var timeoutEvent = new TimeoutEvent(receiveFromWorkerTimeout, $"Timeout event for: {_name}", singleShot: false))
                                    {
                                        timeoutEvent.Start(OnTimeout);
                                        while (_token.IsCancellationRequested == false)
                                        {
                                            BlittableJsonReaderObject rawReport;
                                            try
                                            {
                                                // even if there is a timeout event, we will keep waiting on the same connection until the TCP timeout occurs.
                                                rawReport = context.ReadForMemory(stream, _readStatusUpdateDebugString);
                                                timeoutEvent.Defer(_parent._leaderClusterTag);
                                            }
                                            catch (Exception e)
                                            {
                                                if (_token.IsCancellationRequested)
                                                {
                                                    return;
                                                }

                                                if (_log.IsInfoEnabled)
                                                {
                                                    _log.Info("Exception occurred while reading the report from the connection", e);
                                                }

                                                ReceivedReport = new ClusterNodeStatusReport(new Dictionary <string, DatabaseStatusReport>(),
                                                                                             ClusterNodeStatusReport.ReportStatus.Error,
                                                                                             e,
                                                                                             DateTime.UtcNow,
                                                                                             _lastSuccessfulReceivedReport);

                                                needToWait = true;
                                                break;
                                            }

                                            var report = BuildReport(rawReport);
                                            timeoutEvent.Defer(_parent._leaderClusterTag);

                                            ReceivedReport = _lastSuccessfulReceivedReport = report;
                                        }
                                    }
                    }
                    catch (Exception e)
                    {
                        if (_log.IsInfoEnabled)
                        {
                            _log.Info($"Exception was thrown while collecting info from {ClusterTag}", e);
                        }

                        ReceivedReport = new ClusterNodeStatusReport(new Dictionary <string, DatabaseStatusReport>(),
                                                                     ClusterNodeStatusReport.ReportStatus.Error,
                                                                     e,
                                                                     DateTime.UtcNow,
                                                                     _lastSuccessfulReceivedReport);

                        needToWait = true;
                    }
                }
            }
コード例 #15
0
        private async Task RequestHandler(HttpContext context)
        {
            try
            {
                context.Response.StatusCode = 200;
                var sp     = Stopwatch.StartNew();
                var tenant = await _router.HandlePath(context, context.Request.Method, context.Request.Path.Value);

                sp.Stop();

                if (_logger.IsInfoEnabled)
                {
                    _logger.Info($"{context.Request.Method} {context.Request.Path.Value}?{context.Request.QueryString.Value} - {context.Response.StatusCode} - {sp.ElapsedMilliseconds:#,#;;0} ms");
                }

                if (TrafficWatchManager.HasRegisteredClients)
                {
                    var requestId = Interlocked.Increment(ref _requestId);

                    var twn = new TrafficWatchNotification
                    {
                        TimeStamp           = DateTime.UtcNow,
                        RequestId           = requestId,                       // counted only for traffic watch
                        HttpMethod          = context.Request.Method ?? "N/A", // N/A ?
                        ElapsedMilliseconds = sp.ElapsedMilliseconds,
                        ResponseStatusCode  = context.Response.StatusCode,
                        RequestUri          = context.Request.GetEncodedUrl(),
                        AbsoluteUri         = $@"{context.Request.Scheme}://{context.Request.Host}",
                        TenantName          = tenant ?? "N/A",
                        CustomInfo          = "",   // TODO: Implement
                        InnerRequestsCount  = 0,    // TODO: Implement
                        QueryTimings        = null, // TODO: Implement
                    };

                    TrafficWatchManager.DispatchMessage(twn);
                }
            }
            catch (Exception e)
            {
                if (context.RequestAborted.IsCancellationRequested)
                {
                    return;
                }
                //TODO: special handling for argument exception (400 bad request)
                //TODO: database not found (503)
                //TODO: operaton cancelled (timeout)
                //TODO: Invalid data exception 422


                //TODO: Proper json output, not like this
                var response = context.Response;

                var documentConflictException = e as DocumentConflictException;
                if (response.HasStarted == false)
                {
                    if (documentConflictException != null)
                    {
                        response.StatusCode = 409;
                    }
                    else if (response.StatusCode < 400)
                    {
                        response.StatusCode = 500;
                    }
                }


                JsonOperationContext ctx;
                using (_server.ServerStore.ContextPool.AllocateOperationContext(out ctx))
                {
                    var djv = new DynamicJsonValue
                    {
                        ["Url"]     = $"{context.Request.Path}?{context.Request.QueryString}",
                        ["Type"]    = e.GetType().FullName,
                        ["Message"] = e.Message
                    };

                    string errorString;

                    try
                    {
                        errorString = e.ToAsyncString();
                    }
                    catch (Exception)
                    {
                        errorString = e.ToString();
                    }

                    djv["Error"] = errorString;

                    var indexCompilationException = e as IndexCompilationException;
                    if (indexCompilationException != null)
                    {
                        djv[nameof(IndexCompilationException.IndexDefinitionProperty)] =
                            indexCompilationException.IndexDefinitionProperty;
                        djv[nameof(IndexCompilationException.ProblematicText)] =
                            indexCompilationException.ProblematicText;
                    }

                    if (documentConflictException != null)
                    {
                        djv["ConflictInfo"] = ReplicationUtils.GetJsonForConflicts(
                            documentConflictException.DocId,
                            documentConflictException.Conflicts);
                    }

                    using (var writer = new BlittableJsonTextWriter(ctx, response.Body))
                    {
                        var json = ctx.ReadObject(djv, "exception");
                        writer.WriteObject(json);
                    }
                }
            }
        }
コード例 #16
0
            private void ListenToMaintenanceWorker()
            {
                var firstIteration           = true;
                var onErrorDelayTime         = _parent.Config.OnErrorDelayTime.AsTimeSpan;
                var receiveFromWorkerTimeout = _parent.Config.ReceiveFromWorkerTimeout.AsTimeSpan;
                var tcpTimeout = _parent.Config.TcpConnectionTimeout.AsTimeSpan;

                if (tcpTimeout < receiveFromWorkerTimeout)
                {
                    if (_log.IsInfoEnabled)
                    {
                        _log.Info(
                            $"Warning: TCP timeout is lower than the receive from worker timeout ({tcpTimeout} < {receiveFromWorkerTimeout}), " +
                            "this could affect the cluster observer's decisions.");
                    }
                }

                while (_token.IsCancellationRequested == false)
                {
                    try
                    {
                        if (firstIteration == false)
                        {
                            // avoid tight loop if there was timeout / error
                            _token.WaitHandle.WaitOne(onErrorDelayTime);
                            if (_token.IsCancellationRequested)
                            {
                                return;
                            }
                        }
                        firstIteration = false;

                        TcpConnectionInfo tcpConnection = null;
                        using (var timeout = new CancellationTokenSource(tcpTimeout))
                            using (var combined = CancellationTokenSource.CreateLinkedTokenSource(_token, timeout.Token))
                            {
                                tcpConnection = ReplicationUtils.GetTcpInfo(Url, null, "Supervisor", _parent._server.Server.Certificate.Certificate, combined.Token);
                                if (tcpConnection == null)
                                {
                                    continue;
                                }
                            }

                        var connection = ConnectToClientNode(tcpConnection, _parent._server.Engine.TcpConnectionTimeout);
                        var tcpClient  = connection.TcpClient;
                        var stream     = connection.Stream;
                        using (tcpClient)
                            using (_cts.Token.Register(tcpClient.Dispose))
                                using (_contextPool.AllocateOperationContext(out JsonOperationContext contextForParsing))
                                    using (_contextPool.AllocateOperationContext(out JsonOperationContext contextForBuffer))
                                        using (contextForBuffer.GetMemoryBuffer(out var readBuffer))
                                            using (var timeoutEvent = new TimeoutEvent(receiveFromWorkerTimeout, $"Timeout event for: {_name}", singleShot: false))
                                            {
                                                timeoutEvent.Start(OnTimeout);
                                                var unchangedReports = new List <DatabaseStatusReport>();

                                                while (_token.IsCancellationRequested == false)
                                                {
                                                    contextForParsing.Reset();
                                                    contextForParsing.Renew();
                                                    BlittableJsonReaderObject rawReport;
                                                    try
                                                    {
                                                        // even if there is a timeout event, we will keep waiting on the same connection until the TCP timeout occurs.

                                                        rawReport = contextForParsing.Sync.ParseToMemory(stream, _readStatusUpdateDebugString, BlittableJsonDocumentBuilder.UsageMode.None, readBuffer);
                                                        timeoutEvent.Defer(_parent._leaderClusterTag);
                                                    }
                                                    catch (Exception e)
                                                    {
                                                        if (_token.IsCancellationRequested)
                                                        {
                                                            return;
                                                        }

                                                        if (_log.IsInfoEnabled)
                                                        {
                                                            _log.Info("Exception occurred while reading the report from the connection", e);
                                                        }

                                                        ReceivedReport = new ClusterNodeStatusReport(new ServerReport(), new Dictionary <string, DatabaseStatusReport>(),
                                                                                                     ClusterNodeStatusReport.ReportStatus.Error,
                                                                                                     e,
                                                                                                     DateTime.UtcNow,
                                                                                                     _lastSuccessfulReceivedReport);

                                                        break;
                                                    }

                                                    _parent.ForTestingPurposes?.BeforeReportBuildAction(this);

                                                    var nodeReport = BuildReport(rawReport, connection.SupportedFeatures);
                                                    timeoutEvent.Defer(_parent._leaderClusterTag);


                                                    UpdateNodeReportIfNeeded(nodeReport, unchangedReports);
                                                    unchangedReports.Clear();

                                                    ReceivedReport = _lastSuccessfulReceivedReport = nodeReport;
                                                    _parent.ForTestingPurposes?.AfterSettingReportAction(this);
                                                }
                                            }
                    }
                    catch (Exception e)
                    {
                        if (_token.IsCancellationRequested)
                        {
                            return;
                        }

                        if (_log.IsInfoEnabled)
                        {
                            _log.Info($"Exception was thrown while collecting info from {ClusterTag}", e);
                        }

                        ReceivedReport = new ClusterNodeStatusReport(new ServerReport(), new Dictionary <string, DatabaseStatusReport>(),
                                                                     ClusterNodeStatusReport.ReportStatus.Error,
                                                                     e,
                                                                     DateTime.UtcNow,
                                                                     _lastSuccessfulReceivedReport);
                    }
                }
            }
コード例 #17
0
            private async Task ListenToMaintenanceWorker()
            {
                bool needToWait               = false;
                var  onErrorDelayTime         = _parent.Config.OnErrorDelayTime.AsTimeSpan;
                var  receiveFromWorkerTimeout = _parent.Config.ReceiveFromWorkerTimeout.AsTimeSpan;

                TcpConnectionInfo tcpConnection = null;

                try
                {
                    tcpConnection = await ReplicationUtils.GetTcpInfoAsync(Url, null, "Supervisor",
                                                                           _parent._server.RavenServer.ClusterCertificateHolder?.Certificate);
                }
                catch (Exception e)
                {
                    if (_log.IsInfoEnabled)
                    {
                        _log.Info($"ClusterMaintenanceSupervisor() => Failed to add to cluster node key = {ClusterTag}", e);
                    }
                }
                while (_token.IsCancellationRequested == false)
                {
                    var internalTaskCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_token);
                    try
                    {
                        if (needToWait)
                        {
                            needToWait = false; // avoid tight loop if there was timeout / error
                            await TimeoutManager.WaitFor(onErrorDelayTime, _token);

                            tcpConnection = await ReplicationUtils.GetTcpInfoAsync(Url, null, "Supervisor",
                                                                                   _parent._server.RavenServer.ClusterCertificateHolder.Certificate);
                        }

                        if (tcpConnection == null)
                        {
                            needToWait = true;
                            continue;
                        }
                        var(tcpClient, connection) = await ConnectToClientNodeAsync(tcpConnection, _parent._server.Engine.TcpConnectionTimeout);

                        using (tcpClient)
                            using (_cts.Token.Register(tcpClient.Dispose))
                                using (connection)
                                {
                                    while (_token.IsCancellationRequested == false)
                                    {
                                        using (_contextPool.AllocateOperationContext(out JsonOperationContext context))
                                        {
                                            var readResponseTask = context.ReadForMemoryAsync(connection, _readStatusUpdateDebugString, internalTaskCancellationToken.Token);
                                            var timeout          = TimeoutManager.WaitFor(receiveFromWorkerTimeout, _token);

                                            if (await Task.WhenAny(readResponseTask.AsTask(), timeout) == timeout)
                                            {
                                                if (_log.IsInfoEnabled)
                                                {
                                                    _log.Info($"Timeout occurred while collecting info from {ClusterTag}");
                                                }
                                                ReceivedReport = new ClusterNodeStatusReport(new Dictionary <string, DatabaseStatusReport>(),
                                                                                             ClusterNodeStatusReport.ReportStatus.Timeout,
                                                                                             null,
                                                                                             DateTime.UtcNow,
                                                                                             _lastSuccessfulReceivedReport);
                                                needToWait = true;
                                                internalTaskCancellationToken.Cancel();
                                                break;
                                            }

                                            using (var statusUpdateJson = await readResponseTask)
                                            {
                                                var report = new Dictionary <string, DatabaseStatusReport>();
                                                foreach (var property in statusUpdateJson.GetPropertyNames())
                                                {
                                                    var value = (BlittableJsonReaderObject)statusUpdateJson[property];
                                                    report.Add(property, JsonDeserializationServer.DatabaseStatusReport(value));
                                                }

                                                ReceivedReport = new ClusterNodeStatusReport(
                                                    report,
                                                    ClusterNodeStatusReport.ReportStatus.Ok,
                                                    null,
                                                    DateTime.UtcNow,
                                                    _lastSuccessfulReceivedReport);
                                                _lastSuccessfulReceivedReport = ReceivedReport;
                                            }
                                        }
                                    }
                                }
                    }
                    catch (Exception e)
                    {
                        if (_log.IsInfoEnabled)
                        {
                            _log.Info($"Exception was thrown while collecting info from {ClusterTag}", e);
                        }
                        ReceivedReport = new ClusterNodeStatusReport(new Dictionary <string, DatabaseStatusReport>(),
                                                                     ClusterNodeStatusReport.ReportStatus.Error,
                                                                     e,
                                                                     DateTime.UtcNow,
                                                                     _lastSuccessfulReceivedReport);
                        needToWait = true;
                    }
                    finally
                    {
                        internalTaskCancellationToken.Dispose();
                    }
                }
            }
コード例 #18
0
        public HttpResponseMessage ReplicationInfoGet()
        {
            var replicationStatistics = ReplicationUtils.GetReplicationInformation(Database);

            return(GetMessageWithObject(replicationStatistics));
        }
コード例 #19
0
ファイル: NodeDebugHandler.cs プロジェクト: ikvm/ravendb
        private async Task <PingResult> PingOnce(string url)
        {
            var sp     = Stopwatch.StartNew();
            var result = new PingResult
            {
                Url = url
            };

            using (var cts = new CancellationTokenSource(ServerStore.Engine.TcpConnectionTimeout))
            {
                var info = await ReplicationUtils.GetTcpInfoAsync(url, null, "PingTest", ServerStore.Engine.ClusterCertificate, cts.Token);

                result.TcpInfoTime = sp.ElapsedMilliseconds;

                using (ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                {
                    var log = new List <string>();

                    using (await TcpUtils.ConnectSecuredTcpSocket(info, ServerStore.Engine.ClusterCertificate, Server.CipherSuitesPolicy,
                                                                  TcpConnectionHeaderMessage.OperationTypes.Ping, NegotiationCallback, context, ServerStore.Engine.TcpConnectionTimeout, log, cts.Token))
                    {
                    }

                    result.Log = log;

                    async Task <TcpConnectionHeaderMessage.SupportedFeatures> NegotiationCallback(string curUrl, TcpConnectionInfo tcpInfo, Stream stream,
                                                                                                  JsonOperationContext ctx, List <string> logs = null)
                    {
                        try
                        {
                            var msg = new DynamicJsonValue
                            {
                                [nameof(TcpConnectionHeaderMessage.DatabaseName)]     = null,
                                [nameof(TcpConnectionHeaderMessage.Operation)]        = TcpConnectionHeaderMessage.OperationTypes.Ping,
                                [nameof(TcpConnectionHeaderMessage.OperationVersion)] = -1,
                                [nameof(TcpConnectionHeaderMessage.ServerId)]         = tcpInfo.ServerId
                            };

                            await using (var writer = new AsyncBlittableJsonTextWriter(ctx, stream))
                                using (var msgJson = ctx.ReadObject(msg, "message"))
                                {
                                    result.SendTime = sp.ElapsedMilliseconds;
                                    logs?.Add($"message sent to url {curUrl} at {result.SendTime} ms.");
                                    ctx.Write(writer, msgJson);
                                }

                            using (var rawResponse = await ctx.ReadForMemoryAsync(stream, "cluster-ConnectToPeer-header-response"))
                            {
                                TcpConnectionHeaderResponse response = JsonDeserializationServer.TcpConnectionHeaderResponse(rawResponse);
                                result.ReceiveTime = sp.ElapsedMilliseconds;
                                logs?.Add($"response received from url {curUrl} at {result.ReceiveTime} ms.");

                                switch (response.Status)
                                {
                                case TcpConnectionStatus.Ok:
                                    result.Error = null;
                                    logs?.Add($"Successfully negotiated with {url}.");
                                    break;

                                case TcpConnectionStatus.AuthorizationFailed:
                                    result.Error = $"Connection to {url} failed because of authorization failure: {response.Message}";
                                    logs?.Add(result.Error);
                                    throw new AuthorizationException(result.Error);

                                case TcpConnectionStatus.TcpVersionMismatch:
                                    result.Error = $"Connection to {url} failed because of mismatching tcp version: {response.Message}";
                                    logs?.Add(result.Error);
                                    throw new AuthorizationException(result.Error);

                                case TcpConnectionStatus.InvalidNetworkTopology:
                                    result.Error = $"Connection to {url} failed because of {nameof(TcpConnectionStatus.InvalidNetworkTopology)} error: {response.Message}";
                                    logs?.Add(result.Error);
                                    throw new InvalidNetworkTopologyException(result.Error);
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            result.Error = e.ToString();
                            logs?.Add($"Error occurred while attempting to negotiate with the server. {e.Message}");
                            throw;
                        }

                        return(null);
                    }
                }
            }
            return(result);
        }
コード例 #20
0
        public static void CreateInfoPackageForDatabase(ZipArchive package, DocumentDatabase database, RequestManager requestManager, string zipEntryPrefix = null)
        {
            zipEntryPrefix = zipEntryPrefix ?? string.Empty;

            var databaseName = database.Name;

            if (string.IsNullOrWhiteSpace(databaseName))
            {
                databaseName = Constants.SystemDatabase;
            }

            var jsonSerializer = JsonExtensions.CreateDefaultJsonSerializer();

            jsonSerializer.Formatting = Formatting.Indented;

            if (database.StartupTasks.OfType <ReplicationTask>().Any())
            {
                var replication = package.CreateEntry(zipEntryPrefix + "replication.json", compressionLevel);

                using (var statsStream = replication.Open())
                    using (var streamWriter = new StreamWriter(statsStream))
                    {
                        jsonSerializer.Serialize(streamWriter, ReplicationUtils.GetReplicationInformation(database));
                        streamWriter.Flush();
                    }
            }

            var sqlReplicationTask = database.StartupTasks.OfType <SqlReplicationTask>().FirstOrDefault();

            if (sqlReplicationTask != null)
            {
                var replication = package.CreateEntry(zipEntryPrefix + "sql_replication.json", compressionLevel);

                using (var statsStream = replication.Open())
                    using (var streamWriter = new StreamWriter(statsStream))
                    {
                        jsonSerializer.Serialize(streamWriter, sqlReplicationTask.Statistics);
                        streamWriter.Flush();
                    }
            }

            var stats = package.CreateEntry(zipEntryPrefix + "stats.json", compressionLevel);

            using (var statsStream = stats.Open())
                using (var streamWriter = new StreamWriter(statsStream))
                {
                    jsonSerializer.Serialize(streamWriter, database.Statistics);
                    streamWriter.Flush();
                }

            var metrics = package.CreateEntry(zipEntryPrefix + "metrics.json", compressionLevel);

            using (var metricsStream = metrics.Open())
                using (var streamWriter = new StreamWriter(metricsStream))
                {
                    jsonSerializer.Serialize(streamWriter, database.CreateMetrics());
                    streamWriter.Flush();
                }

            var logs = package.CreateEntry(zipEntryPrefix + "logs.csv", compressionLevel);

            using (var logsStream = logs.Open())
                using (var streamWriter = new StreamWriter(logsStream))
                {
                    var target = LogManager.GetTarget <DatabaseMemoryTarget>();

                    if (target == null)
                    {
                        streamWriter.WriteLine("DatabaseMemoryTarget was not registered in the log manager, logs are not available");
                    }
                    else
                    {
                        var boundedMemoryTarget = target[databaseName];
                        var log = boundedMemoryTarget.GeneralLog;

                        streamWriter.WriteLine("time,logger,level,message,exception");

                        foreach (var logEvent in log)
                        {
                            streamWriter.WriteLine("{0:O},{1},{2},{3},{4}", logEvent.TimeStamp, logEvent.LoggerName, logEvent.Level, logEvent.FormattedMessage, logEvent.Exception);
                        }
                    }

                    streamWriter.Flush();
                }

            var config = package.CreateEntry(zipEntryPrefix + "config.json", compressionLevel);

            using (var configStream = config.Open())
                using (var streamWriter = new StreamWriter(configStream))
                    using (var jsonWriter = new JsonTextWriter(streamWriter)
                    {
                        Formatting = Formatting.Indented
                    })
                    {
                        GetConfigForDebug(database).WriteTo(jsonWriter, new EtagJsonConverter());
                        jsonWriter.Flush();
                    }

            var indexes = package.CreateEntry(zipEntryPrefix + "indexes.json", compressionLevel);

            using (var indexesStream = indexes.Open())
                using (var streamWriter = new StreamWriter(indexesStream))
                {
                    jsonSerializer.Serialize(streamWriter, database.IndexDefinitionStorage.IndexDefinitions.ToDictionary(x => x.Key, x => x.Value));
                    streamWriter.Flush();
                }

            var currentlyIndexing = package.CreateEntry(zipEntryPrefix + "currently-indexing.json", compressionLevel);

            using (var currentlyIndexingStream = currentlyIndexing.Open())
                using (var streamWriter = new StreamWriter(currentlyIndexingStream))
                {
                    jsonSerializer.Serialize(streamWriter, GetCurrentlyIndexingForDebug(database));
                    streamWriter.Flush();
                }

            var queries = package.CreateEntry(zipEntryPrefix + "queries.json", compressionLevel);

            using (var queriesStream = queries.Open())
                using (var streamWriter = new StreamWriter(queriesStream))
                {
                    jsonSerializer.Serialize(streamWriter, database.WorkContext.CurrentlyRunningQueries);
                    streamWriter.Flush();
                }

            var version = package.CreateEntry(zipEntryPrefix + "version.json", compressionLevel);

            using (var versionStream = version.Open())
                using (var streamWriter = new StreamWriter(versionStream))
                {
                    jsonSerializer.Serialize(streamWriter, new
                    {
                        DocumentDatabase.ProductVersion,
                        DocumentDatabase.BuildVersion
                    });
                    streamWriter.Flush();
                }

            var prefetchStatus = package.CreateEntry(zipEntryPrefix + "prefetch-status.json", compressionLevel);

            using (var prefetchStatusStream = prefetchStatus.Open())
                using (var streamWriter = new StreamWriter(prefetchStatusStream))
                {
                    jsonSerializer.Serialize(streamWriter, GetPrefetchingQueueStatusForDebug(database));
                    streamWriter.Flush();
                }

            var requestTracking = package.CreateEntry(zipEntryPrefix + "request-tracking.json", compressionLevel);

            using (var requestTrackingStream = requestTracking.Open())
                using (var streamWriter = new StreamWriter(requestTrackingStream))
                {
                    jsonSerializer.Serialize(streamWriter, GetRequestTrackingForDebug(requestManager, databaseName));
                    streamWriter.Flush();
                }

            var tasks = package.CreateEntry(zipEntryPrefix + "tasks.json", compressionLevel);

            using (var tasksStream = tasks.Open())
                using (var streamWriter = new StreamWriter(tasksStream))
                {
                    jsonSerializer.Serialize(streamWriter, GetTasksForDebug(database));
                    streamWriter.Flush();
                }

            var systemUtilization = package.CreateEntry(zipEntryPrefix + "system-utilization.json", compressionLevel);

            using (var systemUtilizationStream = systemUtilization.Open())
                using (var streamWriter = new StreamWriter(systemUtilizationStream))
                {
                    long   totalPhysicalMemory = -1;
                    long   availableMemory     = -1;
                    object cpuTimes            = null;

                    try
                    {
                        totalPhysicalMemory = MemoryStatistics.TotalPhysicalMemory;
                        availableMemory     = MemoryStatistics.AvailableMemoryInMb;

                        using (var searcher = new ManagementObjectSearcher("select * from Win32_PerfFormattedData_PerfOS_Processor"))
                        {
                            cpuTimes = searcher.Get()
                                       .Cast <ManagementObject>()
                                       .Select(mo => new
                            {
                                Name  = mo["Name"],
                                Usage = string.Format("{0} %", mo["PercentProcessorTime"])
                            }).ToArray();
                        }
                    }
                    catch (Exception e)
                    {
                        cpuTimes = "Could not get CPU times" + Environment.NewLine + e;
                    }

                    jsonSerializer.Serialize(streamWriter, new
                    {
                        TotalPhysicalMemory = string.Format("{0:#,#.##;;0} MB", totalPhysicalMemory),
                        AvailableMemory     = string.Format("{0:#,#.##;;0} MB", availableMemory),
                        CurrentCpuUsage     = cpuTimes
                    });

                    streamWriter.Flush();
                }
        }
コード例 #21
0
        private void ReplicateToDestination()
        {
            AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiate);

            NativeMemory.EnsureRegistered();
            try
            {
                var connectionInfo = ReplicationUtils.GetTcpInfo(Destination.Url, GetNode(), "Replication",
                                                                 _parent._server.RavenServer.ClusterCertificateHolder.Certificate);

                if (_log.IsInfoEnabled)
                {
                    _log.Info($"Will replicate to {Destination.FromString()} via {connectionInfo.Url}");
                }

                using (_parent._server.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                    using (context.OpenReadTransaction())
                    {
                        var record = _parent.LoadDatabaseRecord();
                        if (record == null)
                        {
                            throw new InvalidOperationException($"The database record for {_parent.Database.Name} does not exist?!");
                        }

                        if (record.Encrypted && Destination.Url.StartsWith("https:", StringComparison.OrdinalIgnoreCase) == false)
                        {
                            throw new InvalidOperationException(
                                      $"{record.DatabaseName} is encrypted, and require HTTPS for replication, but had endpoint with url {Destination.Url} to database {Destination.Database}");
                        }
                    }

                var task = TcpUtils.ConnectSocketAsync(connectionInfo, _parent._server.Engine.TcpConnectionTimeout, _log);
                task.Wait(CancellationToken);
                using (_tcpClient = task.Result)
                {
                    var wrapSsl = TcpUtils.WrapStreamWithSslAsync(_tcpClient, connectionInfo, _parent._server.RavenServer.ClusterCertificateHolder.Certificate);

                    wrapSsl.Wait(CancellationToken);

                    using (_stream = wrapSsl.Result)
                        using (_interruptibleRead = new InterruptibleRead(_database.DocumentsStorage.ContextPool, _stream))
                            using (_buffer = JsonOperationContext.ManagedPinnedBuffer.LongLivedInstance())
                            {
                                var documentSender = new ReplicationDocumentSender(_stream, this, _log);

                                WriteHeaderToRemotePeer();
                                //handle initial response to last etag and staff
                                try
                                {
                                    var response = HandleServerResponse(getFullResponse: true);
                                    if (response.ReplyType == ReplicationMessageReply.ReplyType.Error)
                                    {
                                        var exception = new InvalidOperationException(response.Reply.Exception);
                                        if (response.Reply.Exception.Contains(nameof(DatabaseDoesNotExistException)) ||
                                            response.Reply.Exception.Contains(nameof(DatabaseNotRelevantException)))
                                        {
                                            AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiateError, "Database does not exist");
                                            DatabaseDoesNotExistException.ThrowWithMessageAndException(Destination.Database, response.Reply.Message, exception);
                                        }

                                        AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiateError, $"Got error: {response.Reply.Exception}");
                                        throw exception;
                                    }
                                }
                                catch (DatabaseDoesNotExistException e)
                                {
                                    var msg = $"Failed to parse initial server replication response, because there is no database named {_database.Name} " +
                                              "on the other end. ";
                                    if (_external)
                                    {
                                        msg += "In order for the replication to work, a database with the same name needs to be created at the destination";
                                    }

                                    var young = (DateTime.UtcNow - _startedAt).TotalSeconds < 30;
                                    if (young)
                                    {
                                        msg += "This can happen if the other node wasn't yet notified about being assigned this database and should be resolved shortly.";
                                    }
                                    if (_log.IsInfoEnabled)
                                    {
                                        _log.Info(msg, e);
                                    }

                                    AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiateError, msg);

                                    // won't add an alert on young connections
                                    // because it may take a few seconds for the other side to be notified by
                                    // the cluster that it has this db.
                                    if (young == false)
                                    {
                                        AddAlertOnFailureToReachOtherSide(msg, e);
                                    }

                                    throw;
                                }
                                catch (OperationCanceledException e)
                                {
                                    const string msg = "Got operation canceled notification while opening outgoing replication channel. " +
                                                       "Aborting and closing the channel.";
                                    if (_log.IsInfoEnabled)
                                    {
                                        _log.Info(msg, e);
                                    }
                                    AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiateError, msg);
                                    throw;
                                }
                                catch (Exception e)
                                {
                                    const string msg = "Failed to parse initial server response. This is definitely not supposed to happen.";
                                    if (_log.IsInfoEnabled)
                                    {
                                        _log.Info(msg, e);
                                    }

                                    AddReplicationPulse(ReplicationPulseDirection.OutgoingInitiateError, msg);
                                    AddAlertOnFailureToReachOtherSide(msg, e);

                                    throw;
                                }

                                while (_cts.IsCancellationRequested == false)
                                {
                                    while (true)
                                    {
                                        if (_parent.DebugWaitAndRunReplicationOnce != null)
                                        {
                                            _parent.DebugWaitAndRunReplicationOnce.Wait(_cts.Token);
                                            _parent.DebugWaitAndRunReplicationOnce.Reset();
                                        }

                                        var sp    = Stopwatch.StartNew();
                                        var stats = _lastStats = new OutgoingReplicationStatsAggregator(_parent.GetNextReplicationStatsId(), _lastStats);
                                        AddReplicationPerformance(stats);
                                        AddReplicationPulse(ReplicationPulseDirection.OutgoingBegin);

                                        try
                                        {
                                            using (var scope = stats.CreateScope())
                                            {
                                                try
                                                {
                                                    var didWork = documentSender.ExecuteReplicationOnce(scope);
                                                    if (didWork == false)
                                                    {
                                                        break;
                                                    }

                                                    DocumentsSend?.Invoke(this);

                                                    if (sp.ElapsedMilliseconds > 60 * 1000)
                                                    {
                                                        _waitForChanges.Set();
                                                        break;
                                                    }
                                                }
                                                catch (OperationCanceledException)
                                                {
                                                    // cancellation is not an actual error,
                                                    // it is a "notification" that we need to cancel current operation

                                                    const string msg = "Operation was canceled.";
                                                    AddReplicationPulse(ReplicationPulseDirection.OutgoingError, msg);

                                                    throw;
                                                }
                                                catch (Exception e)
                                                {
                                                    AddReplicationPulse(ReplicationPulseDirection.OutgoingError, e.Message);

                                                    scope.AddError(e);
                                                    throw;
                                                }
                                            }
                                        }
                                        finally
                                        {
                                            stats.Complete();
                                            AddReplicationPulse(ReplicationPulseDirection.OutgoingEnd);
                                        }
                                    }

                                    //if this returns false, this means either timeout or canceled token is activated
                                    while (WaitForChanges(_parent.MinimalHeartbeatInterval, _cts.Token) == false)
                                    {
                                        // open tx
                                        // read current change vector compare to last sent
                                        // if okay, send cv
                                        using (_database.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext ctx))
                                            using (var tx = ctx.OpenReadTransaction())
                                            {
                                                var etag = DocumentsStorage.ReadLastEtag(tx.InnerTransaction);
                                                if (etag == _lastSentDocumentEtag)
                                                {
                                                    SendHeartbeat(DocumentsStorage.GetDatabaseChangeVector(ctx));
                                                }
                                                else
                                                {
                                                    // we have updates that we need to send to the other side
                                                    // let's do that..
                                                    // this can happen if we got replication from another node
                                                    // that we need to send to it. Note that we typically
                                                    // will wait for the other node to send the data directly to
                                                    // our destination, but if it doesn't, we'll step in.
                                                    // In this case, we try to limit congestion in the network and
                                                    // only send updates that we have gotten from someone else after
                                                    // a certain time, to let the other side tell us that it already
                                                    // got it. Note that this is merely an optimization to reduce network
                                                    // traffic. It is fine to have the same data come from different sources.
                                                    break;
                                                }
                                            }
                                    }
                                    _waitForChanges.Reset();
                                }
                            }
                }
            }
            catch (OperationCanceledException e)
            {
                if (_log.IsInfoEnabled)
                {
                    _log.Info($"Operation canceled on replication thread ({FromToString}). This is not necessary due to an issue. Stopped the thread.");
                }
                Failed?.Invoke(this, e);
            }
            catch (IOException e)
            {
                if (_log.IsInfoEnabled)
                {
                    if (e.InnerException is SocketException)
                    {
                        _log.Info(
                            $"SocketException was thrown from the connection to remote node ({FromToString}). This might mean that the remote node is done or there is a network issue.",
                            e);
                    }
                    else
                    {
                        _log.Info($"IOException was thrown from the connection to remote node ({FromToString}).", e);
                    }
                }
                Failed?.Invoke(this, e);
            }
            catch (Exception e)
            {
                if (_log.IsInfoEnabled)
                {
                    _log.Info(
                        $"Unexpected exception occurred on replication thread ({FromToString}). Replication stopped (will be retried later).",
                        e);
                }
                Failed?.Invoke(this, e);
            }
        }