/// <summary> /// Tries to the move to the next page in the document producer. /// </summary> /// <param name="token">The cancellation token.</param> /// <returns>Whether the operation was successful.</returns> private async Task <bool> MoveNextPageAsync(CancellationToken token) { token.ThrowIfCancellationRequested(); if (this.itemsLeftInCurrentPage != 0) { throw new InvalidOperationException("Tried to move onto the next page before finishing the first page."); } // We need to buffer pages if empty, since that how we know there are no more pages left. await this.BufferMoreIfEmptyAsync(token); if (this.bufferedPages.Count == 0) { return(false); } // Pull a FeedResponse using TryMonad (we could have buffered an exception). TryMonad <DocumentFeedResponse <CosmosElement> > tryMonad = await this.bufferedPages.TakeAsync(token); DocumentFeedResponse <CosmosElement> feedResponse = tryMonad.Match <DocumentFeedResponse <CosmosElement> >( onSuccess: ((page) => { return(page); }), onError: (exceptionDispatchInfo) => { exceptionDispatchInfo.Throw(); return(null); }); // Update the state. this.PreviousContinuationToken = this.currentContinuationToken; this.currentContinuationToken = feedResponse.ResponseContinuation; this.currentPage = feedResponse.GetEnumerator(); this.IsAtBeginningOfPage = true; this.itemsLeftInCurrentPage = feedResponse.Count; // Prime the enumerator, // so that current is pointing to the first document instead of one before. if (this.MoveToFirstDocumentInPage()) { this.IsAtBeginningOfPage = true; return(true); } else { // We got an empty page if (this.currentContinuationToken != null) { return(await this.MoveNextPageAsync(token)); } return(false); } }
/// <summary> /// Tries to the move to the next page in the document producer. /// </summary> /// <param name="token">The cancellation token.</param> /// <returns>Whether the operation was successful.</returns> private async Task <bool> MoveNextPageAsync(CancellationToken token) { token.ThrowIfCancellationRequested(); if (this.bufferedPages.Count == 0) { return(false); } if (this.itemsLeftInCurrentPage != 0) { throw new InvalidOperationException("Tried to move onto the next page before finishing the first page."); } TryMonad <DocumentFeedResponse <CosmosElement> > tryMonad = await this.bufferedPages.TakeAsync(token); DocumentFeedResponse <CosmosElement> feedResponse = tryMonad.Match <DocumentFeedResponse <CosmosElement> >( onSuccess: ((page) => { return(page); }), onError: (exceptionDispatchInfo) => { exceptionDispatchInfo.Throw(); return(null); }); this.previousContinuationToken = this.currentContinuationToken; this.currentContinuationToken = feedResponse.ResponseContinuation; this.currentPage = feedResponse.GetEnumerator(); this.itemsLeftInCurrentPage = feedResponse.Count; if (this.MoveNextDocumentWithinCurrentPage()) { this.isAtBeginningOfPage = true; return(true); } else { return(false); } }
/// <summary> /// Buffers more documents in the producer. /// </summary> /// <param name="token">The cancellation token.</param> /// <returns>A task to await on.</returns> public async Task BufferMoreDocumentsAsync(CancellationToken token) { token.ThrowIfCancellationRequested(); try { await this.fetchSemaphore.WaitAsync(); if (!this.HasMoreBackendResults) { // Just NOP return; } this.fetchSchedulingMetrics.Start(); this.fetchExecutionRangeAccumulator.BeginFetchRange(); int pageSize = (int)Math.Min(this.pageSize, (long)int.MaxValue); using (DocumentServiceRequest request = this.createRequestFunc(this.PartitionKeyRange, this.backendContinuationToken, pageSize)) { IDocumentClientRetryPolicy retryPolicy = this.createRetryPolicyFunc(); // Custom backoff and retry while (true) { int retries = 0; try { DocumentFeedResponse <CosmosElement> feedResponse = await this.executeRequestFunc(request, retryPolicy, token); this.fetchExecutionRangeAccumulator.EndFetchRange( this.PartitionKeyRange.Id, feedResponse.ActivityId, feedResponse.Count, retries); this.fetchSchedulingMetrics.Stop(); this.hasStartedFetching = true; this.backendContinuationToken = feedResponse.ResponseContinuation; this.activityId = Guid.Parse(feedResponse.ActivityId); await this.bufferedPages.AddAsync(TryMonad <DocumentFeedResponse <CosmosElement> > .FromResult(feedResponse)); Interlocked.Add(ref this.bufferedItemCount, feedResponse.Count); QueryMetrics queryMetrics = QueryMetrics.Zero; if (feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics] != null) { queryMetrics = QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics( feedResponse.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics], new ClientSideMetrics( retries, feedResponse.RequestCharge, this.fetchExecutionRangeAccumulator.GetExecutionRanges(), new List <Tuple <string, SchedulingTimeSpan> >())); } if (!this.HasMoreBackendResults) { queryMetrics = QueryMetrics.CreateWithSchedulingMetrics( queryMetrics, new List <Tuple <string, SchedulingTimeSpan> > { new Tuple <string, SchedulingTimeSpan>( this.PartitionKeyRange.Id, this.fetchSchedulingMetrics.Elapsed) }); } this.produceAsyncCompleteCallback( this, feedResponse.Count, feedResponse.RequestCharge, queryMetrics, feedResponse.ResponseLengthBytes, token); break; } catch (Exception exception) { // See if we need to retry or just throw ShouldRetryResult shouldRetryResult = await retryPolicy.ShouldRetryAsync(exception, token); if (!shouldRetryResult.ShouldRetry) { Exception exceptionToBuffer; if (shouldRetryResult.ExceptionToThrow != null) { exceptionToBuffer = shouldRetryResult.ExceptionToThrow; } else { // Propagate original exception. exceptionToBuffer = exception; } // Buffer the exception instead of throwing, since we don't want an unobserved exception. await this.bufferedPages.AddAsync(TryMonad <DocumentFeedResponse <CosmosElement> > .FromException(exceptionToBuffer)); // null out the backend continuation token, // so that people stop trying to buffer more on this producer. this.hasStartedFetching = true; this.backendContinuationToken = null; break; } else { await Task.Delay(shouldRetryResult.BackoffTime); retries++; } } } } } finally { this.fetchSchedulingMetrics.Stop(); this.fetchSemaphore.Release(); } }
public void SetUp() { sut = new TryMonad(); }