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.YieldStreamResults(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 } 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; } if (IsErroredBecauseOfSubscriber) { break; } if (lastProcessedEtagOnServer != null) { if (pulledDocs) { // This is an acknowledge when the server returns documents to the subscriber. if (BeforeAcknowledgment()) { AcknowledgeBatchToServer(lastProcessedEtagOnServer); 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. AcknowledgeBatchToServer(lastProcessedEtagOnServer); 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; } })); }
private Task PullDocuments() { return(Task.Run(async() => { while (true) { anySubscriber.WaitOne(); cts.Token.ThrowIfCancellationRequested(); var pulledDocs = false; Etag lastProcessedEtagOnServer = 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.YieldStreamResults(subscriptionRequest, responseStream, customizedEndResult: reader => { if (Equals("LastProcessedEtag", reader.Value) == false) { return false; } lastProcessedEtagOnServer = Etag.Parse(reader.ReadAsString().ResultUnwrap()); return true; })) { while (await streamedDocs.MoveNextAsync().ConfigureAwait(false)) { if (pulledDocs == false) // first doc in batch { BeforeBatch(); } pulledDocs = true; cts.Token.ThrowIfCancellationRequested(); var jsonDoc = streamedDocs.Current; T instance = null; foreach (var subscriber in subscribers) { try { if (isStronglyTyped) { if (instance == null) { instance = jsonDoc.Deserialize <T>(conventions); } subscriber.OnNext(instance); } else { subscriber.OnNext((T)(object)jsonDoc); } } catch (Exception ex) { logger.WarnException("Subscriber threw an exception", ex); if (options.IgnoreSubscribersErrors == false) { IsErrored = true; try { subscriber.OnError(ex); } catch (Exception) { // can happen if a subscriber doesn't have an onError handler - just ignore it } break; } } } if (IsErrored) { break; } } } } } if (IsErrored) { break; } if (pulledDocs) { using (var acknowledgmentRequest = CreateAcknowledgmentRequest(lastProcessedEtagOnServer)) { try { acknowledgmentRequest.ExecuteRequest(); } catch (Exception) { if (acknowledgmentRequest.ResponseStatusCode != HttpStatusCode.RequestTimeout) // ignore acknowledgment timeouts { throw; } } } AfterBatch(); continue; // try to pull more documents from subscription } while (newDocuments.WaitOne(options.ClientAliveNotificationInterval) == false) { using (var clientAliveRequest = CreateClientAliveRequest()) { clientAliveRequest.ExecuteRequest(); } } } })); }