private async Task ProcessSubscriptionAysnc() { if (_logger.IsInfoEnabled) { _logger.Info($"Starting proccessing documents for subscription {SubscriptionId} received from {TcpConnection.TcpClient.Client.RemoteEndPoint}"); } DocumentsOperationContext dbContext; using (DisposeOnDisconnect) using (TcpConnection.DocumentDatabase.DocumentsStorage.ContextPool.AllocateOperationContext(out dbContext)) { long startEtag; SubscriptionCriteria criteria; TcpConnection.DocumentDatabase.SubscriptionStorage.GetCriteriaAndEtag(_options.SubscriptionId, dbContext, out criteria, out startEtag); var replyFromClientTask = TcpConnection.MultiDocumentParser.ParseToMemoryAsync("client reply"); using (RegisterForNotificationOnNewDocuments(criteria)) { var patch = SetupFilterScript(criteria); while (CancellationTokenSource.IsCancellationRequested == false) { dbContext.ResetAndRenew(); bool anyDocumentsSentInCurrentIteration = false; using (dbContext.OpenReadTransaction()) { var documents = TcpConnection.DocumentDatabase.DocumentsStorage.GetDocumentsFrom(dbContext, criteria.Collection, startEtag + 1, 0, _options.MaxDocsPerBatch); _buffer.SetLength(0); var docsToFlush = 0; var sendingCurrentBatchStopwatch = Stopwatch.StartNew(); foreach (var doc in documents) { anyDocumentsSentInCurrentIteration = true; startEtag = doc.Etag; if (DocumentMatchCriteriaScript(patch, dbContext, doc) == false) { // make sure that if we read a lot of irrelevant documents, we send keep alive over the network if (sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000) { await SendHeartBeat(); sendingCurrentBatchStopwatch.Reset(); } continue; } doc.EnsureMetadata(); TcpConnection.Context.Write(_bufferedWriter, new DynamicJsonValue { ["Type"] = "Data", ["Data"] = doc.Data }); docsToFlush++; // perform flush for current batch after 1000ms of running if (sendingCurrentBatchStopwatch.ElapsedMilliseconds > 1000) { if (docsToFlush > 0) { await FlushDocsToClient(docsToFlush); docsToFlush = 0; sendingCurrentBatchStopwatch.Reset(); } else { await SendHeartBeat(); } } } if (anyDocumentsSentInCurrentIteration) { TcpConnection.Context.Write(_bufferedWriter, new DynamicJsonValue { ["Type"] = "EndOfBatch" }); await FlushDocsToClient(docsToFlush, true); } if (anyDocumentsSentInCurrentIteration == false) { if (await WaitForChangedDocuments(replyFromClientTask)) { continue; } } SubscriptionConnectionClientMessage clientReply; while (true) { var result = await Task.WhenAny(replyFromClientTask, Task.Delay(TimeSpan.FromSeconds(5), CancellationTokenSource.Token)); if (result == replyFromClientTask) { using (var reply = await replyFromClientTask) { TcpConnection.RegisterBytesReceived(reply.Size); clientReply = JsonDeserializationServer.SubscriptionConnectionClientMessage(reply); } TcpConnection.ResetAndRenew(); replyFromClientTask = TcpConnection.MultiDocumentParser.ParseToMemoryAsync("client reply"); break; } await SendHeartBeat(); } switch (clientReply.Type) { case SubscriptionConnectionClientMessage.MessageType.Acknowledge: TcpConnection.DocumentDatabase.SubscriptionStorage.AcknowledgeBatchProcessed(_options.SubscriptionId, clientReply.Etag); Stats.LastAckReceivedAt = DateTime.UtcNow; Stats.AckRate.Mark(); await WriteJsonAsync(new DynamicJsonValue { ["Type"] = "Confirm", ["Etag"] = clientReply.Etag }); break; default: throw new ArgumentException("Unknown message type from client " + clientReply.Type); } } } } } }