private async Task StartPullingDocs() { PullingTask = PullDocuments().ObserveException(); try { await PullingTask.ConfigureAwait(false); } catch (Exception ex) { if (cts.Token.IsCancellationRequested) { return; } PullingTask = null; SubscriptionException subscriptionEx; var ere = ex as ErrorResponseException; if (ere != null && AsyncDocumentSubscriptions.TryGetSubscriptionException(ere, out subscriptionEx)) { if (subscriptionEx is SubscriptionClosedException) { // someone forced us to drop the connection by calling Subscriptions.Release OnCompletedNotification(); IsClosed = true; return; } } RestartPullingTask().ConfigureAwait(false); } if (IsErrored) { OnCompletedNotification(); try { await CloseSubscription().ConfigureAwait(false); } catch (Exception e) { logger.WarnException("Exception happened during an attempt to close subscription after it becomes faulted", e); } } }
private Task PullDocuments() { return(Task.Run(async() => { try { Etag lastProcessedEtagOnClient = null; while (true) { anySubscriber.WaitOne(); cts.Token.ThrowIfCancellationRequested(); var pulledDocs = false; Etag lastProcessedEtagOnServer = null; int processedDocs = 0; var queue = new BlockingCollection <T>(); Task processingTask = null; using (var subscriptionRequest = CreatePullingRequest()) using (var response = await subscriptionRequest.ExecuteRawResponseAsync().ConfigureAwait(false)) { await response.AssertNotFailingResponse().ConfigureAwait(false); using (var responseStream = await response.GetResponseStreamWithHttpDecompression().ConfigureAwait(false)) { cts.Token.ThrowIfCancellationRequested(); using (var streamedDocs = new AsyncServerClient.YieldStreamResultsAsync(subscriptionRequest, responseStream, customizedEndResult: reader => { if (Equals("LastProcessedEtag", reader.Value) == false) { return false; } lastProcessedEtagOnServer = Etag.Parse(AsyncHelpers.RunSync(reader.ReadAsString)); return true; })) { while (await streamedDocs.MoveNextAsync().ConfigureAwait(false)) { if (pulledDocs == false) // first doc in batch { BeforeBatch(); processingTask = Task.Run(() => { T doc; while (queue.TryTake(out doc, Timeout.Infinite)) { cts.Token.ThrowIfCancellationRequested(); foreach (var subscriber in subscribers) { try { subscriber.OnNext(doc); } catch (Exception ex) { logger.WarnException(string.Format("Subscription #{0}. Subscriber threw an exception", id), ex); if (options.IgnoreSubscribersErrors == false) { IsErroredBecauseOfSubscriber = true; LastSubscriberException = ex; try { subscriber.OnError(ex); } catch (Exception) { // can happen if a subscriber doesn't have an onError handler - just ignore it } SubscriptionConnectionInterrupted(ex, false); taskCompletionSource.TrySetException(ex); break; } } } if (IsErroredBecauseOfSubscriber) { break; } processedDocs++; } }); } pulledDocs = true; cts.Token.ThrowIfCancellationRequested(); var jsonDoc = streamedDocs.Current; if (isStronglyTyped) { var instance = jsonDoc.Deserialize <T>(conventions); var docId = jsonDoc[Constants.Metadata].Value <string>("@id"); if (string.IsNullOrEmpty(docId) == false) { generateEntityIdOnTheClient.TrySetIdentity(instance, docId); } queue.Add(instance); } else { queue.Add((T)(object)jsonDoc); } if (IsErroredBecauseOfSubscriber) { break; } } } } } queue.CompleteAdding(); if (processingTask != null) { await processingTask.ConfigureAwait(false); } if (IsErroredBecauseOfSubscriber) { break; } if (lastProcessedEtagOnServer != null) { if (pulledDocs) { // This is an acknowledge when the server returns documents to the subscriber. if (BeforeAcknowledgment()) { try { AcknowledgeBatchToServer(lastProcessedEtagOnServer); } catch (SubscriptionAckTimeoutException ex) { SubscriptionConnectionInterrupted(ex, true); } AfterAcknowledgment(lastProcessedEtagOnServer); } AfterBatch(processedDocs); continue; // try to pull more documents from subscription } else { if (lastProcessedEtagOnClient != lastProcessedEtagOnServer) { // This is a silent acknowledge, this can happen because there was no documents in range // to be accessible in the time available. This condition can happen when documents must match // a set of conditions to be eligible. try { AcknowledgeBatchToServer(lastProcessedEtagOnServer); } catch (SubscriptionAckTimeoutException ex) { SubscriptionConnectionInterrupted(ex, true); } lastProcessedEtagOnClient = lastProcessedEtagOnServer; continue; // try to pull more documents from subscription } } } while (newDocuments.WaitOne(options.ClientAliveNotificationInterval) == false) { using (var clientAliveRequest = CreateClientAliveRequest()) { clientAliveRequest.ExecuteRequest(); } } } } catch (ErrorResponseException e) { SubscriptionException subscriptionException; if (AsyncDocumentSubscriptions.TryGetSubscriptionException(e, out subscriptionException)) { throw subscriptionException; } throw; } })); }
public DocumentSubscriptions(IDocumentStore documentStore) { innerAsync = new AsyncDocumentSubscriptions(documentStore); }