예제 #1
0
        public CrossPartitionRangePageAsyncEnumerator(
            IFeedRangeProvider feedRangeProvider,
            CreatePartitionRangePageAsyncEnumerator <TPage, TState> createPartitionRangeEnumerator,
            IComparer <PartitionRangePageAsyncEnumerator <TPage, TState> > comparer,
            int?maxConcurrency,
            CancellationToken cancellationToken,
            CrossPartitionState <TState> state = default)
        {
            if (comparer == null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }

            this.feedRangeProvider = feedRangeProvider ?? throw new ArgumentNullException(nameof(feedRangeProvider));
            this.createPartitionRangeEnumerator = createPartitionRangeEnumerator ?? throw new ArgumentNullException(nameof(createPartitionRangeEnumerator));
            this.cancellationToken = cancellationToken;

            this.lazyEnumerators = new AsyncLazy <PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > >(async(CancellationToken token) =>
            {
                IReadOnlyList <(PartitionKeyRange, TState)> rangeAndStates;
                if (state != default)
                {
                    rangeAndStates = state.Value;
                }
                else
                {
                    // Fan out to all partitions with default state
                    IEnumerable <PartitionKeyRange> ranges = await feedRangeProvider.GetFeedRangesAsync(token);

                    List <(PartitionKeyRange, TState)> rangesAndStatesBuilder = new List <(PartitionKeyRange, TState)>();
                    foreach (PartitionKeyRange range in ranges)
                    {
                        rangesAndStatesBuilder.Add((range, default));
                    }

                    rangeAndStates = rangesAndStatesBuilder;
                }

                List <BufferedPartitionRangePageAsyncEnumerator <TPage, TState> > bufferedEnumerators = rangeAndStates
                                                                                                        .Select(rangeAndState =>
                {
                    PartitionRangePageAsyncEnumerator <TPage, TState> enumerator = createPartitionRangeEnumerator(rangeAndState.Item1, rangeAndState.Item2);
                    BufferedPartitionRangePageAsyncEnumerator <TPage, TState> bufferedEnumerator = new BufferedPartitionRangePageAsyncEnumerator <TPage, TState>(enumerator, cancellationToken);
                    return(bufferedEnumerator);
                })
                                                                                                        .ToList();

                if (maxConcurrency.HasValue)
                {
                    await ParallelPrefetch.PrefetchInParallelAsync(bufferedEnumerators, maxConcurrency.Value, token);
                }

                PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = new PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> >(
                    bufferedEnumerators,
                    comparer);
                return(enumerators);
            });
        }
        public CrossPartitionRangePageAsyncEnumerator(
            IFeedRangeProvider feedRangeProvider,
            CreatePartitionRangePageAsyncEnumerator <TPage, TState> createPartitionRangeEnumerator,
            IComparer <PartitionRangePageAsyncEnumerator <TPage, TState> > comparer,
            CrossPartitionState <TState> state = default)
        {
            this.feedRangeProvider = feedRangeProvider ?? throw new ArgumentNullException(nameof(feedRangeProvider));
            this.createPartitionRangeEnumerator = createPartitionRangeEnumerator ?? throw new ArgumentNullException(nameof(createPartitionRangeEnumerator));

            if (comparer == null)
            {
                throw new ArgumentNullException(nameof(comparer));
            }

            this.lazyEnumerators = new AsyncLazy <PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > >(async(CancellationToken token) =>
            {
                IReadOnlyList <(PartitionKeyRange, TState)> rangeAndStates;
                if (state != default)
                {
                    rangeAndStates = state.Value;
                }
                else
                {
                    // Fan out to all partitions with default state
                    IEnumerable <PartitionKeyRange> ranges = await feedRangeProvider.GetFeedRangesAsync(token);

                    List <(PartitionKeyRange, TState)> rangesAndStatesBuilder = new List <(PartitionKeyRange, TState)>();
                    foreach (PartitionKeyRange range in ranges)
                    {
                        rangesAndStatesBuilder.Add((range, default));
                    }

                    rangeAndStates = rangesAndStatesBuilder;
                }

                PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = new PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> >(comparer);
                foreach ((PartitionKeyRange range, TState rangeState) in rangeAndStates)
                {
                    PartitionRangePageAsyncEnumerator <TPage, TState> enumerator = createPartitionRangeEnumerator(range, rangeState);
                    enumerators.Enqueue(enumerator);
                }

                return(enumerators);
            });
        }
        public CrossPartitionRangePageAsyncEnumerator(
            IFeedRangeProvider feedRangeProvider,
            CreatePartitionRangePageAsyncEnumerator <TPage, TState> createPartitionRangeEnumerator,
            IComparer <PartitionRangePageAsyncEnumerator <TPage, TState> > comparer,
            int?maxConcurrency,
            CancellationToken cancellationToken,
            CrossFeedRangeState <TState> state = default)
        {
            this.feedRangeProvider = feedRangeProvider ?? throw new ArgumentNullException(nameof(feedRangeProvider));
            this.createPartitionRangeEnumerator = createPartitionRangeEnumerator ?? throw new ArgumentNullException(nameof(createPartitionRangeEnumerator));
            this.cancellationToken = cancellationToken;

            this.lazyEnumerators = new AsyncLazy <IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > >(async(ITrace trace, CancellationToken token) =>
            {
                ReadOnlyMemory <FeedRangeState <TState> > rangeAndStates;
                if (state != default)
                {
                    rangeAndStates = state.Value;
                }
                else
                {
                    // Fan out to all partitions with default state
                    List <FeedRangeEpk> ranges = await feedRangeProvider.GetFeedRangesAsync(trace, token);

                    List <FeedRangeState <TState> > rangesAndStatesBuilder = new List <FeedRangeState <TState> >(ranges.Count);
                    foreach (FeedRangeInternal range in ranges)
                    {
                        rangesAndStatesBuilder.Add(new FeedRangeState <TState>(range, default));
                    }

                    rangeAndStates = rangesAndStatesBuilder.ToArray();
                }

                List <BufferedPartitionRangePageAsyncEnumerator <TPage, TState> > bufferedEnumerators = new List <BufferedPartitionRangePageAsyncEnumerator <TPage, TState> >(rangeAndStates.Length);
                for (int i = 0; i < rangeAndStates.Length; i++)
                {
                    FeedRangeState <TState> feedRangeState = rangeAndStates.Span[i];
                    PartitionRangePageAsyncEnumerator <TPage, TState> enumerator = createPartitionRangeEnumerator(feedRangeState);
                    BufferedPartitionRangePageAsyncEnumerator <TPage, TState> bufferedEnumerator = new BufferedPartitionRangePageAsyncEnumerator <TPage, TState>(enumerator, cancellationToken);
                    bufferedEnumerators.Add(bufferedEnumerator);
                }

                if (maxConcurrency.HasValue)
                {
                    await ParallelPrefetch.PrefetchInParallelAsync(bufferedEnumerators, maxConcurrency.Value, trace, token);
                }

                IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > queue;
                if (comparer == null)
                {
                    queue = new QueueWrapper <PartitionRangePageAsyncEnumerator <TPage, TState> >(
                        new Queue <PartitionRangePageAsyncEnumerator <TPage, TState> >(bufferedEnumerators));
                }
                else
                {
                    queue = new PriorityQueueWrapper <PartitionRangePageAsyncEnumerator <TPage, TState> >(
                        new PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> >(
                            bufferedEnumerators,
                            comparer));
                }

                return(queue);
            });
        }
        public async ValueTask <bool> MoveNextAsync(ITrace trace)
        {
            if (trace == null)
            {
                throw new ArgumentNullException(nameof(trace));
            }

            using (ITrace childTrace = trace.StartChild(name: nameof(MoveNextAsync), component: TraceComponent.Pagination, level: TraceLevel.Info))
            {
                IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync(
                    childTrace,
                    cancellationToken : this.cancellationToken);

                if (enumerators.Count == 0)
                {
                    this.Current      = default;
                    this.CurrentRange = default;
                    this.nextState    = default;
                    return(false);
                }

                PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue();
                currentPaginator.SetCancellationToken(this.cancellationToken);
                bool moveNextResult = false;
                try
                {
                    moveNextResult = await currentPaginator.MoveNextAsync(childTrace);
                }
                catch
                {
                    // Re-queue the enumerator to avoid emptying the queue
                    enumerators.Enqueue(currentPaginator);
                    throw;
                }

                if (!moveNextResult)
                {
                    // Current enumerator is empty,
                    // so recursively retry on the next enumerator.
                    return(await this.MoveNextAsync(childTrace));
                }

                if (currentPaginator.Current.Failed)
                {
                    // Check if it's a retryable exception.
                    Exception exception = currentPaginator.Current.Exception;
                    while (exception.InnerException != null)
                    {
                        exception = exception.InnerException;
                    }

                    if (IsSplitException(exception))
                    {
                        // Handle split
                        List <FeedRangeEpk> childRanges = await this.feedRangeProvider.GetChildRangeAsync(
                            currentPaginator.FeedRangeState.FeedRange,
                            childTrace,
                            this.cancellationToken);

                        if (childRanges.Count <= 1)
                        {
                            // We optimistically assumed that the cache is not stale.
                            // In the event that it is (where we only get back one child / the partition that we think got split)
                            // Then we need to refresh the cache
                            await this.feedRangeProvider.RefreshProviderAsync(childTrace, this.cancellationToken);

                            childRanges = await this.feedRangeProvider.GetChildRangeAsync(
                                currentPaginator.FeedRangeState.FeedRange,
                                childTrace,
                                this.cancellationToken);
                        }

                        if (childRanges.Count < 1)
                        {
                            string errorMessage = "SDK invariant violated 4795CC37: Must have at least one EPK range in a cross partition enumerator";
                            throw Resource.CosmosExceptions.CosmosExceptionFactory.CreateInternalServerErrorException(
                                      message: errorMessage,
                                      headers: null,
                                      stackTrace: null,
                                      trace: childTrace,
                                      error: new Microsoft.Azure.Documents.Error {
                                Code = "SDK_invariant_violated_4795CC37", Message = errorMessage
                            });
                        }

                        if (childRanges.Count == 1)
                        {
                            // On a merge, the 410/1002 results in a single parent
                            // We maintain the current enumerator's range and let the RequestInvokerHandler logic kick in
                            enumerators.Enqueue(currentPaginator);
                        }
                        else
                        {
                            // Split
                            foreach (FeedRangeInternal childRange in childRanges)
                            {
                                PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator(
                                    new FeedRangeState <TState>(childRange, currentPaginator.FeedRangeState.State));
                                enumerators.Enqueue(childPaginator);
                            }
                        }

                        // Recursively retry
                        return(await this.MoveNextAsync(childTrace));
                    }

                    // Just enqueue the paginator and the user can decide if they want to retry.
                    enumerators.Enqueue(currentPaginator);

                    this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromException(currentPaginator.Current.Exception);

                    this.CurrentRange = currentPaginator.FeedRangeState.FeedRange;
                    this.nextState    = CrossPartitionRangePageAsyncEnumerator <TPage, TState> .GetNextRange(enumerators);

                    return(true);
                }

                if (currentPaginator.FeedRangeState.State != default)
                {
                    // Don't enqueue the paginator otherwise it's an infinite loop.
                    enumerators.Enqueue(currentPaginator);
                }

                CrossFeedRangeState <TState> crossPartitionState;
                if (enumerators.Count == 0)
                {
                    crossPartitionState = null;
                }
                else
                {
                    FeedRangeState <TState>[] feedRangeAndStates = new FeedRangeState <TState> [enumerators.Count];
                    int i = 0;
                    foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators)
                    {
                        feedRangeAndStates[i++] = enumerator.FeedRangeState;
                    }

                    crossPartitionState = new CrossFeedRangeState <TState>(feedRangeAndStates);
                }

                this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromResult(
                    new CrossFeedRangePage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState));

                this.CurrentRange = currentPaginator.FeedRangeState.FeedRange;
                this.nextState    = CrossPartitionRangePageAsyncEnumerator <TPage, TState> .GetNextRange(enumerators);

                return(true);
            }
        }
예제 #5
0
 public BufferedPartitionRangePageAsyncEnumerator(PartitionRangePageAsyncEnumerator <TPage, TState> enumerator, CancellationToken cancellationToken)
     : base(enumerator.Range, cancellationToken, enumerator.State)
 {
     this.enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator));
 }
 public FullyBufferedPartitionRangeAsyncEnumerator(PartitionRangePageAsyncEnumerator <TPage, TState> enumerator, CancellationToken cancellationToken)
     : base(enumerator.FeedRangeState, cancellationToken)
 {
     this.enumerator    = enumerator ?? throw new ArgumentNullException(nameof(enumerator));
     this.bufferedPages = new List <TPage>();
 }
예제 #7
0
        public async ValueTask <bool> MoveNextAsync()
        {
            this.cancellationToken.ThrowIfCancellationRequested();

            PriorityQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync(cancellationToken : this.cancellationToken);

            if (enumerators.Count == 0)
            {
                return(false);
            }

            PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue();

            if (!await currentPaginator.MoveNextAsync())
            {
                // Current enumerator is empty,
                // so recursively retry on the next enumerator.
                return(await this.MoveNextAsync());
            }

            if (currentPaginator.Current.Failed)
            {
                // Check if it's a retryable exception.
                Exception exception = currentPaginator.Current.Exception;
                while (exception.InnerException != null)
                {
                    exception = exception.InnerException;
                }

                if (IsSplitException(exception))
                {
                    // Handle split
                    IEnumerable <PartitionKeyRange> childRanges = await this.feedRangeProvider.GetChildRangeAsync(
                        currentPaginator.Range,
                        cancellationToken : this.cancellationToken);

                    foreach (PartitionKeyRange childRange in childRanges)
                    {
                        PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator(
                            childRange,
                            currentPaginator.State);
                        enumerators.Enqueue(childPaginator);
                    }

                    // Recursively retry
                    return(await this.MoveNextAsync());
                }

                if (IsMergeException(exception))
                {
                    throw new NotImplementedException();
                }

                // Just enqueue the paginator and the user can decide if they want to retry.
                enumerators.Enqueue(currentPaginator);

                this.Current = TryCatch <CrossPartitionPage <TPage, TState> > .FromException(currentPaginator.Current.Exception);

                return(true);
            }

            if (currentPaginator.State != default)
            {
                // Don't enqueue the paginator otherwise it's an infinite loop.
                enumerators.Enqueue(currentPaginator);
            }

            CrossPartitionState <TState> crossPartitionState;

            if (enumerators.Count == 0)
            {
                crossPartitionState = null;
            }
            else
            {
                List <(PartitionKeyRange, TState)> feedRangeAndStates = new List <(PartitionKeyRange, TState)>(enumerators.Count);
                foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators)
                {
                    feedRangeAndStates.Add((enumerator.Range, enumerator.State));
                }

                crossPartitionState = new CrossPartitionState <TState>(feedRangeAndStates);
            }

            this.Current = TryCatch <CrossPartitionPage <TPage, TState> > .FromResult(
                new CrossPartitionPage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState));

            return(true);
        }
예제 #8
0
        public async ValueTask <bool> MoveNextAsync(ITrace trace)
        {
            if (trace == null)
            {
                throw new ArgumentNullException(nameof(trace));
            }

            this.cancellationToken.ThrowIfCancellationRequested();

            using (ITrace childTrace = trace.StartChild(name: nameof(MoveNextAsync), component: TraceComponent.Pagination, level: TraceLevel.Info))
            {
                IQueue <PartitionRangePageAsyncEnumerator <TPage, TState> > enumerators = await this.lazyEnumerators.GetValueAsync(
                    childTrace,
                    cancellationToken : this.cancellationToken);

                if (enumerators.Count == 0)
                {
                    this.Current      = default;
                    this.CurrentRange = default;
                    return(false);
                }

                PartitionRangePageAsyncEnumerator <TPage, TState> currentPaginator = enumerators.Dequeue();
                if (!await currentPaginator.MoveNextAsync(childTrace))
                {
                    // Current enumerator is empty,
                    // so recursively retry on the next enumerator.
                    return(await this.MoveNextAsync(childTrace));
                }

                if (currentPaginator.Current.Failed)
                {
                    // Check if it's a retryable exception.
                    Exception exception = currentPaginator.Current.Exception;
                    while (exception.InnerException != null)
                    {
                        exception = exception.InnerException;
                    }

                    if (IsSplitException(exception))
                    {
                        // Handle split
                        List <FeedRangeEpk> childRanges = await this.feedRangeProvider.GetChildRangeAsync(
                            currentPaginator.FeedRangeState.FeedRange,
                            childTrace,
                            this.cancellationToken);

                        if (childRanges.Count == 0)
                        {
                            throw new InvalidOperationException("Got back no children");
                        }

                        if (childRanges.Count == 1)
                        {
                            // We optimistically assumed that the cache is not stale.
                            // In the event that it is (where we only get back one child / the partition that we think got split)
                            // Then we need to refresh the cache
                            await this.feedRangeProvider.RefreshProviderAsync(childTrace, this.cancellationToken);

                            childRanges = await this.feedRangeProvider.GetChildRangeAsync(
                                currentPaginator.FeedRangeState.FeedRange,
                                childTrace,
                                this.cancellationToken);
                        }

                        if (childRanges.Count() <= 1)
                        {
                            throw new InvalidOperationException("Expected more than 1 child");
                        }

                        foreach (FeedRangeInternal childRange in childRanges)
                        {
                            PartitionRangePageAsyncEnumerator <TPage, TState> childPaginator = this.createPartitionRangeEnumerator(
                                new FeedRangeState <TState>(childRange, currentPaginator.FeedRangeState.State));
                            enumerators.Enqueue(childPaginator);
                        }

                        // Recursively retry
                        return(await this.MoveNextAsync(childTrace));
                    }

                    // Just enqueue the paginator and the user can decide if they want to retry.
                    enumerators.Enqueue(currentPaginator);

                    this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromException(currentPaginator.Current.Exception);

                    this.CurrentRange = currentPaginator.FeedRangeState.FeedRange;
                    return(true);
                }

                if (currentPaginator.FeedRangeState.State != default)
                {
                    // Don't enqueue the paginator otherwise it's an infinite loop.
                    enumerators.Enqueue(currentPaginator);
                }

                CrossFeedRangeState <TState> crossPartitionState;
                if (enumerators.Count == 0)
                {
                    crossPartitionState = null;
                }
                else
                {
                    FeedRangeState <TState>[] feedRangeAndStates = new FeedRangeState <TState> [enumerators.Count];
                    int i = 0;
                    foreach (PartitionRangePageAsyncEnumerator <TPage, TState> enumerator in enumerators)
                    {
                        feedRangeAndStates[i++] = enumerator.FeedRangeState;
                    }

                    crossPartitionState = new CrossFeedRangeState <TState>(feedRangeAndStates);
                }

                this.Current = TryCatch <CrossFeedRangePage <TPage, TState> > .FromResult(
                    new CrossFeedRangePage <TPage, TState>(currentPaginator.Current.Result, crossPartitionState));

                this.CurrentRange = currentPaginator.FeedRangeState.FeedRange;
                return(true);
            }
        }