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(); } }
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(); } }