private async Task ProcessSubscriptionAsync()
        {
            if (_logger.IsInfoEnabled)
            {
                _logger.Info(
                    $"Starting processing documents for subscription {SubscriptionId} received from {TcpConnection.TcpClient.Client.RemoteEndPoint}");
            }

            using (DisposeOnDisconnect)
                using (TcpConnection.DocumentDatabase.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext docsContext))
                    using (RegisterForNotificationOnNewDocuments())
                    {
                        var replyFromClientTask = GetReplyFromClientAsync();

                        string lastChangeVector = null;
                        string subscriptionChangeVectorBeforeCurrentBatch = SubscriptionState.ChangeVectorForNextBatchStartingPoint;
                        var    startEtag = GetStartEtagForSubscription(docsContext, SubscriptionState);
                        var    patch     = SetupFilterScript();
                        var    fetcher   = new SubscriptionDocumentsFetcher(TcpConnection.DocumentDatabase, _options.MaxDocsPerBatch, SubscriptionId, TcpConnection.TcpClient.Client.RemoteEndPoint);
                        while (CancellationTokenSource.IsCancellationRequested == false)
                        {
                            bool anyDocumentsSentInCurrentIteration = false;

                            var sendingCurrentBatchStopwatch = Stopwatch.StartNew();

                            _buffer.SetLength(0);

                            var docsToFlush = 0;

                            using (TcpConnection.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                                using (var writer = new BlittableJsonTextWriter(context, _buffer))
                                {
                                    using (docsContext.OpenReadTransaction())
                                    {
                                        foreach (var result in fetcher.GetDataToSend(docsContext, Collection, Revisions, SubscriptionState, patch, startEtag))
                                        {
                                            startEtag        = result.Doc.Etag;
                                            lastChangeVector = string.IsNullOrEmpty(SubscriptionState.ChangeVectorForNextBatchStartingPoint)
                                    ? result.Doc.ChangeVector
                                    : ChangeVectorUtils.MergeVectors(result.Doc.ChangeVector, SubscriptionState.ChangeVectorForNextBatchStartingPoint);

                                            if (result.Doc.Data == null)
                                            {
                                                if (sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000)
                                                {
                                                    await SendHeartBeat();

                                                    sendingCurrentBatchStopwatch.Restart();
                                                }

                                                continue;
                                            }

                                            anyDocumentsSentInCurrentIteration = true;
                                            writer.WriteStartObject();

                                            writer.WritePropertyName(context.GetLazyStringForFieldWithCaching(TypeSegment));
                                            writer.WriteValue(BlittableJsonToken.String, context.GetLazyStringForFieldWithCaching(DataSegment));
                                            writer.WriteComma();
                                            writer.WritePropertyName(context.GetLazyStringForFieldWithCaching(DataSegment));
                                            result.Doc.EnsureMetadata();

                                            if (result.Exception != null)
                                            {
                                                var metadata = result.Doc.Data[Client.Constants.Documents.Metadata.Key];
                                                writer.WriteValue(BlittableJsonToken.StartObject,
                                                                  docsContext.ReadObject(new DynamicJsonValue
                                                {
                                                    [Client.Constants.Documents.Metadata.Key] = metadata
                                                }, result.Doc.Id)
                                                                  );
                                                writer.WriteComma();
                                                writer.WritePropertyName(context.GetLazyStringForFieldWithCaching(ExceptionSegment));
                                                writer.WriteValue(BlittableJsonToken.String, context.GetLazyStringForFieldWithCaching(result.Exception.ToString()));
                                            }
                                            else
                                            {
                                                writer.WriteDocument(docsContext, result.Doc, metadataOnly: false);
                                            }

                                            writer.WriteEndObject();
                                            docsToFlush++;

                                            // perform flush for current batch after 1000ms of running or 1 MB
                                            if (_buffer.Length > Constants.Size.Megabyte ||
                                                sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000)
                                            {
                                                if (docsToFlush > 0)
                                                {
                                                    await FlushDocsToClient(writer, docsToFlush);

                                                    docsToFlush = 0;
                                                    sendingCurrentBatchStopwatch.Restart();
                                                }
                                                else
                                                {
                                                    await SendHeartBeat();
                                                }
                                            }
                                        }
                                    }

                                    if (anyDocumentsSentInCurrentIteration)
                                    {
                                        context.Write(writer, new DynamicJsonValue
                                        {
                                            [nameof(SubscriptionConnectionServerMessage.Type)] = nameof(SubscriptionConnectionServerMessage.MessageType.EndOfBatch)
                                        });

                                        await FlushDocsToClient(writer, docsToFlush, true);

                                        if (_logger.IsInfoEnabled)
                                        {
                                            _logger.Info(
                                                $"Finished sending a batch with {docsToFlush} documents for subscription {Options.SubscriptionName}");
                                        }
                                    }
                                }

                            if (anyDocumentsSentInCurrentIteration == false)
                            {
                                if (_logger.IsInfoEnabled)
                                {
                                    _logger.Info(
                                        $"Finished sending a batch with {docsToFlush} documents for subscription {Options.SubscriptionName}");
                                }
                                await TcpConnection.DocumentDatabase.SubscriptionStorage.AcknowledgeBatchProcessed(SubscriptionId,
                                                                                                                   Options.SubscriptionName,
                                                                                                                   lastChangeVector,
                                                                                                                   subscriptionChangeVectorBeforeCurrentBatch);

                                subscriptionChangeVectorBeforeCurrentBatch = lastChangeVector;

                                if (sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000)
                                {
                                    await SendHeartBeat();
                                }

                                using (docsContext.OpenReadTransaction())
                                {
                                    long globalEtag = TcpConnection.DocumentDatabase.DocumentsStorage.GetLastDocumentEtag(docsContext, Collection);

                                    if (globalEtag > startEtag)
                                    {
                                        continue;
                                    }
                                }

                                if (await WaitForChangedDocuments(replyFromClientTask))
                                {
                                    continue;
                                }
                            }

                            SubscriptionConnectionClientMessage clientReply;

                            while (true)
                            {
                                var result = await Task.WhenAny(replyFromClientTask,
                                                                TimeoutManager.WaitFor(TimeSpan.FromMilliseconds(5000), CancellationTokenSource.Token)).ConfigureAwait(false);

                                CancellationTokenSource.Token.ThrowIfCancellationRequested();
                                if (result == replyFromClientTask)
                                {
                                    clientReply = await replyFromClientTask;
                                    if (clientReply.Type == SubscriptionConnectionClientMessage.MessageType.DisposedNotification)
                                    {
                                        CancellationTokenSource.Cancel();
                                        break;
                                    }
                                    replyFromClientTask = GetReplyFromClientAsync();
                                    break;
                                }
                                await SendHeartBeat();
                            }

                            CancellationTokenSource.Token.ThrowIfCancellationRequested();

                            switch (clientReply.Type)
                            {
                            case SubscriptionConnectionClientMessage.MessageType.Acknowledge:
                                await TcpConnection.DocumentDatabase.SubscriptionStorage.AcknowledgeBatchProcessed(
                                    SubscriptionId,
                                    Options.SubscriptionName,
                                    lastChangeVector,
                                    subscriptionChangeVectorBeforeCurrentBatch);

                                subscriptionChangeVectorBeforeCurrentBatch = lastChangeVector;
                                Stats.LastAckReceivedAt = DateTime.UtcNow;
                                Stats.AckRate.Mark();
                                await WriteJsonAsync(new DynamicJsonValue
                                {
                                    [nameof(SubscriptionConnectionServerMessage.Type)] = nameof(SubscriptionConnectionServerMessage.MessageType.Confirm)
                                });

                                break;

                            //precaution, should not reach this case...
                            case SubscriptionConnectionClientMessage.MessageType.DisposedNotification:
                                CancellationTokenSource.Cancel();
                                break;

                            default:
                                throw new ArgumentException("Unknown message type from client " +
                                                            clientReply.Type);
                            }
                        }

                        CancellationTokenSource.Token.ThrowIfCancellationRequested();
                    }
        }
Exemple #2
0
        private async Task ProcessSubscriptionAsync()
        {
            if (_logger.IsInfoEnabled)
            {
                _logger.Info(
                    $"Starting processing documents for subscription {SubscriptionId} received from {TcpConnection.TcpClient.Client.RemoteEndPoint}");
            }

            using (DisposeOnDisconnect)
                using (RegisterForNotificationOnNewDocuments())
                {
                    var replyFromClientTask = GetReplyFromClientAsync();

                    string subscriptionChangeVectorBeforeCurrentBatch = SubscriptionState.ChangeVectorForNextBatchStartingPoint;

                    _startEtag = GetStartEtagForSubscription(SubscriptionState);
                    _filterAndProjectionScript = SetupFilterAndProjectionScript();
                    _documentsFetcher          = new SubscriptionDocumentsFetcher(TcpConnection.DocumentDatabase, _options.MaxDocsPerBatch, SubscriptionId, TcpConnection.TcpClient.Client.RemoteEndPoint, Collection, Revisions, SubscriptionState, _filterAndProjectionScript);

                    while (CancellationTokenSource.IsCancellationRequested == false)
                    {
                        _buffer.SetLength(0);

                        using (TcpConnection.DocumentDatabase.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext docsContext))
                        {
                            var sendingCurrentBatchStopwatch = Stopwatch.StartNew();

                            var anyDocumentsSentInCurrentIteration = await TrySendingBatchToClient(docsContext, sendingCurrentBatchStopwatch);

                            if (anyDocumentsSentInCurrentIteration == false)
                            {
                                if (_logger.IsInfoEnabled)
                                {
                                    _logger.Info(
                                        $"Did not find any documents to send for subscription {Options.SubscriptionName}");
                                }
                                await TcpConnection.DocumentDatabase.SubscriptionStorage.AcknowledgeBatchProcessed(SubscriptionId,
                                                                                                                   Options.SubscriptionName,
                                                                                                                   _lastChangeVector,
                                                                                                                   subscriptionChangeVectorBeforeCurrentBatch);

                                subscriptionChangeVectorBeforeCurrentBatch = _lastChangeVector;

                                if (sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000)
                                {
                                    await SendHeartBeat();
                                }

                                using (docsContext.OpenReadTransaction())
                                {
                                    long globalEtag = TcpConnection.DocumentDatabase.DocumentsStorage.GetLastDocumentEtag(docsContext, Collection);

                                    if (globalEtag > _startEtag)
                                    {
                                        continue;
                                    }
                                }

                                if (await WaitForChangedDocuments(replyFromClientTask))
                                {
                                    continue;
                                }
                            }
                        }

                        (replyFromClientTask, subscriptionChangeVectorBeforeCurrentBatch) =
                            await WaitForClientAck(replyFromClientTask, subscriptionChangeVectorBeforeCurrentBatch);
                    }

                    CancellationTokenSource.Token.ThrowIfCancellationRequested();
                }
        }