Example #1
0
        public override async Task <CollectionRoutingMap> GetRoutingMapAsync(CancellationToken cancellationToken)
        {
            string collectionRid = await this.GetCachedRIDAsync(
                forceRefresh : false,
                trace : NoOpTrace.Singleton,
                cancellationToken);

            PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.Client.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton);

            CollectionRoutingMap collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(
                collectionRid,
                previousValue : null,
                request : null,
                cancellationToken,
                NoOpTrace.Singleton);

            // Not found.
            if (collectionRoutingMap == null)
            {
                collectionRid = await this.GetCachedRIDAsync(
                    forceRefresh : true,
                    trace : NoOpTrace.Singleton,
                    cancellationToken);

                collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(
                    collectionRid,
                    previousValue : null,
                    request : null,
                    cancellationToken,
                    NoOpTrace.Singleton);
            }

            return(collectionRoutingMap);
        }
        private async Task <ShouldRetryResult> ShouldRetryInternalAsync(
            HttpStatusCode?statusCode,
            SubStatusCodes?subStatusCode,
            CancellationToken cancellationToken)
        {
            if (statusCode == HttpStatusCode.Gone)
            {
                if (subStatusCode == SubStatusCodes.PartitionKeyRangeGone ||
                    subStatusCode == SubStatusCodes.CompletingSplit ||
                    subStatusCode == SubStatusCodes.CompletingPartitionMigration)
                {
                    PartitionKeyRangeCache partitionKeyRangeCache = await this.container.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                    string containerRid = await this.container.GetCachedRIDAsync(forceRefresh : false, cancellationToken : cancellationToken);

                    await partitionKeyRangeCache.TryGetOverlappingRangesAsync(containerRid, FeedRangeEpk.FullRange.Range, forceRefresh : true);

                    return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
                }

                if (subStatusCode == SubStatusCodes.NameCacheIsStale)
                {
                    return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
                }
            }

            return(null);
        }
        internal async Task <Tuple <string, ResponseMessage> > ReadNextInternalAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (this.compositeContinuationToken == null)
            {
                PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                this.containerRid = await this.container.GetRIDAsync(cancellationToken);

                this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(this.containerRid, this.continuationToken, pkRangeCache.TryGetOverlappingRangesAsync);
            }

            (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync();

            string partitionKeyRangeId = rangeId;

            this.continuationToken = currentRangeToken.Token;
            ResponseMessage response = await this.NextResultSetDelegateAsync(this.continuationToken, partitionKeyRangeId, this.maxItemCount, this.changeFeedOptions, cancellationToken);

            if (await this.ShouldRetryFailureAsync(response, cancellationToken))
            {
                return(await this.ReadNextInternalAsync(cancellationToken));
            }

            if (response.IsSuccessStatusCode ||
                response.StatusCode == HttpStatusCode.NotModified)
            {
                // Change Feed read uses Etag for continuation
                currentRangeToken.Token = response.Headers.ETag;
            }

            return(new Tuple <string, ResponseMessage>(partitionKeyRangeId, response));
        }
Example #4
0
        public async Task <IReadOnlyList <FeedRange> > GetFeedRangesAsync(
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken = default)
        {
            PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

            string containerRId = await this.GetRIDAsync(cancellationToken);

            IReadOnlyList <PartitionKeyRange> partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                containerRId,
                new Range <string>(
                    PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey,
                    PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey,
                    isMinInclusive : true,
                    isMaxInclusive : false),
                forceRefresh : true);

            List <FeedRange> feedTokens = new List <FeedRange>(partitionKeyRanges.Count);

            foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges)
            {
                feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange()));
            }

            return(feedTokens);
        }
Example #5
0
        public async Task <IReadOnlyList <FeedRange> > GetFeedRangesAsync(
            ITrace trace,
            CancellationToken cancellationToken = default)
        {
            PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(trace);

            string containerRId;

            containerRId = await this.GetCachedRIDAsync(
                forceRefresh : false,
                trace,
                cancellationToken);

            IReadOnlyList <PartitionKeyRange> partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                containerRId,
                ContainerCore.allRanges,
                trace,
                forceRefresh : true);

            if (partitionKeyRanges == null)
            {
                string refreshedContainerRId;
                refreshedContainerRId = await this.GetCachedRIDAsync(
                    forceRefresh : true,
                    trace,
                    cancellationToken);

                if (string.Equals(containerRId, refreshedContainerRId))
                {
                    throw CosmosExceptionFactory.CreateInternalServerErrorException(
                              $"Container rid {containerRId} did not have a partition key range after refresh",
                              headers: new Headers(),
                              trace: trace);
                }

                partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                    containerRId,
                    ContainerCore.allRanges,
                    trace,
                    forceRefresh : true);

                if (partitionKeyRanges == null)
                {
                    throw CosmosExceptionFactory.CreateInternalServerErrorException(
                              $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh",
                              headers: new Headers(),
                              trace: trace);
                }
            }

            List <FeedRange> feedTokens = new List <FeedRange>(partitionKeyRanges.Count);

            foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges)
            {
                feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange()));
            }

            return(feedTokens);
        }
Example #6
0
        internal static async Task ApplySessionTokenAsync(
            DocumentServiceRequest request,
            ConsistencyLevel defaultConsistencyLevel,
            ISessionContainer sessionContainer,
            PartitionKeyRangeCache partitionKeyRangeCache,
            CollectionCache clientCollectionCache,
            IGlobalEndpointManager globalEndpointManager)
        {
            if (request.Headers == null)
            {
                Debug.Fail("DocumentServiceRequest does not have headers.");
                return;
            }

            // Master resource operations don't require session token.
            if (GatewayStoreModel.IsMasterOperation(request.ResourceType, request.OperationType))
            {
                if (!string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.SessionToken]))
                {
                    request.Headers.Remove(HttpConstants.HttpHeaders.SessionToken);
                }

                return;
            }

            if (!string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.SessionToken]))
            {
                return; // User is explicitly controlling the session.
            }

            string requestConsistencyLevel  = request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel];
            bool   isReadOrBatchRequest     = request.IsReadOnlyRequest || request.OperationType == OperationType.Batch;
            bool   requestHasConsistencySet = !string.IsNullOrEmpty(requestConsistencyLevel) && isReadOrBatchRequest; // Only read requests can have their consistency modified

            bool sessionConsistencyApplies =
                (!requestHasConsistencySet && defaultConsistencyLevel == ConsistencyLevel.Session) ||
                (requestHasConsistencySet &&
                 string.Equals(requestConsistencyLevel, GatewayStoreModel.sessionConsistencyAsString, StringComparison.OrdinalIgnoreCase));

            bool isMultiMasterEnabledForRequest = globalEndpointManager.CanUseMultipleWriteLocations(request);

            if (!sessionConsistencyApplies ||
                (!isReadOrBatchRequest &&
                 !isMultiMasterEnabledForRequest))
            {
                return; // Only apply the session token in case of session consistency and the request is read only or read/write on multimaster
            }

            (bool isSuccess, string sessionToken) = await GatewayStoreModel.TryResolveSessionTokenAsync(
                request,
                sessionContainer,
                partitionKeyRangeCache,
                clientCollectionCache);

            if (isSuccess && !string.IsNullOrEmpty(sessionToken))
            {
                request.Headers[HttpConstants.HttpHeaders.SessionToken] = sessionToken;
            }
        }
 public PartitionKeyRangeGoneRetryPolicy(
     CollectionCache collectionCache,
     PartitionKeyRangeCache partitionKeyRangeCache,
     string collectionLink,
     IDocumentClientRetryPolicy nextRetryPolicy)
     : this(collectionCache, partitionKeyRangeCache, collectionLink, nextRetryPolicy, NoOpTrace.Singleton)
 {
 }
Example #8
0
        public override async Task <IReadOnlyList <PartitionKeyRange> > TryGetOverlappingRangesAsync(
            string collectionResourceId,
            Range <string> range,
            bool forceRefresh = false)
        {
            PartitionKeyRangeCache partitionKeyRangeCache = await this.GetRoutingMapProviderAsync();

            return(await partitionKeyRangeCache.TryGetOverlappingRangesAsync(collectionResourceId, range, forceRefresh));
        }
Example #9
0
        internal static async Task ApplySessionTokenAsync(
            DocumentServiceRequest request,
            ConsistencyLevel defaultConsistencyLevel,
            ISessionContainer sessionContainer,
            PartitionKeyRangeCache partitionKeyRangeCache,
            CollectionCache clientCollectionCache)
        {
            if (request.Headers == null)
            {
                Debug.Fail("DocumentServiceRequest does not have headers.");
                return;
            }

            // Master resource operations don't require session token.
            if (GatewayStoreModel.IsMasterOperation(request.ResourceType, request.OperationType))
            {
                if (!string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.SessionToken]))
                {
                    request.Headers.Remove(HttpConstants.HttpHeaders.SessionToken);
                }

                return;
            }

            if (!string.IsNullOrEmpty(request.Headers[HttpConstants.HttpHeaders.SessionToken]))
            {
                return; // User is explicitly controlling the session.
            }

            string requestConsistencyLevel = request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel];

            bool sessionConsistency =
                defaultConsistencyLevel == ConsistencyLevel.Session ||
                (!string.IsNullOrEmpty(requestConsistencyLevel) &&
                 string.Equals(requestConsistencyLevel, ConsistencyLevel.Session.ToString(), StringComparison.OrdinalIgnoreCase));

            if (!sessionConsistency || (!request.IsReadOnlyRequest && request.OperationType != OperationType.Batch))
            {
                return; // Only apply the session token in case of session consistency and the request is read only
            }

            (bool isSuccess, string sessionToken) = await GatewayStoreModel.TryResolveSessionTokenAsync(
                request,
                sessionContainer,
                partitionKeyRangeCache,
                clientCollectionCache);

            if (!isSuccess)
            {
                sessionToken = sessionContainer.ResolveGlobalSessionToken(request);
            }

            if (!string.IsNullOrEmpty(sessionToken))
            {
                request.Headers[HttpConstants.HttpHeaders.SessionToken] = sessionToken;
            }
        }
 public PartitionKeyRangeGoneRetryPolicy(
     CollectionCache collectionCache,
     PartitionKeyRangeCache partitionKeyRangeCache,
     string collectionLink,
     IDocumentClientRetryPolicy nextRetryPolicy)
 {
     this.collectionCache        = collectionCache;
     this.partitionKeyRangeCache = partitionKeyRangeCache;
     this.collectionLink         = collectionLink;
     this.nextRetryPolicy        = nextRetryPolicy;
 }
        public override async Task <DocumentServiceLease> AcquireAsync(DocumentServiceLease lease)
        {
            if (lease == null)
            {
                throw new ArgumentNullException(nameof(lease));
            }

            string oldOwner = lease.Owner;

            // We need to add the range information to any older leases
            // This would not happen with new created leases but we need to be back compat
            if (lease.FeedRange == null)
            {
                if (!this.lazyContainerRid.ValueInitialized)
                {
                    TryCatch <string> tryInitializeContainerRId = await this.lazyContainerRid.GetValueAsync(NoOpTrace.Singleton, default);

                    if (!tryInitializeContainerRId.Succeeded)
                    {
                        throw tryInitializeContainerRId.Exception.InnerException;
                    }

                    this.partitionKeyRangeCache = await this.monitoredContainer.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton);
                }

                PartitionKeyRange partitionKeyRange = await this.partitionKeyRangeCache.TryGetPartitionKeyRangeByIdAsync(
                    this.lazyContainerRid.Result.Result,
                    lease.CurrentLeaseToken,
                    NoOpTrace.Singleton);

                if (partitionKeyRange != null)
                {
                    lease.FeedRange = new FeedRangeEpk(partitionKeyRange.ToRange());
                }
            }

            return(await this.leaseUpdater.UpdateLeaseAsync(
                       lease,
                       lease.Id,
                       this.requestOptionsFactory.GetPartitionKey(lease.Id, lease.PartitionKey),
                       serverLease =>
            {
                if (serverLease.Owner != oldOwner)
                {
                    DefaultTrace.TraceInformation("{0} lease token was taken over by owner '{1}'", lease.CurrentLeaseToken, serverLease.Owner);
                    throw new LeaseLostException(lease);
                }
                serverLease.Owner = this.options.HostName;
                serverLease.Properties = lease.Properties;
                return serverLease;
            }).ConfigureAwait(false));
        }
        /// <summary>
        /// Get the next set of results from the cosmos service
        /// </summary>
        /// <param name="cancellationToken">(Optional) <see cref="CancellationToken"/> representing request cancellation.</param>
        /// <returns>A query response from cosmos service</returns>
        public override async Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (this.compositeContinuationToken == null)
            {
                PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                this.containerRid = await this.container.GetRIDAsync(cancellationToken);

                this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(this.containerRid, this.continuationToken, pkRangeCache.TryGetOverlappingRangesAsync);
            }

            (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync();

            this.partitionKeyRangeId = rangeId;
            this.continuationToken   = currentRangeToken.Token;

            ResponseMessage response = await this.NextResultSetDelegateAsync(this.continuationToken, this.partitionKeyRangeId, this.maxItemCount, this.changeFeedOptions, cancellationToken);

            if (await this.ShouldRetryFailureAsync(response, cancellationToken))
            {
                cancellationToken.ThrowIfCancellationRequested();

                (CompositeContinuationToken currentRangeTokenForRetry, string rangeIdForRetry) = await this.compositeContinuationToken.GetCurrentTokenAsync();

                currentRangeToken        = currentRangeTokenForRetry;
                this.partitionKeyRangeId = rangeIdForRetry;
                this.continuationToken   = currentRangeToken.Token;
                response = await this.NextResultSetDelegateAsync(this.continuationToken, this.partitionKeyRangeId, this.maxItemCount, this.changeFeedOptions, cancellationToken);
            }

            // Change Feed read uses Etag for continuation
            string responseContinuationToken = response.Headers.ETag;
            bool   hasMoreResults            = response.StatusCode != HttpStatusCode.NotModified;

            if (!hasMoreResults)
            {
                currentRangeToken.Token = responseContinuationToken;
                // Current Range is done, push it to the end
                this.compositeContinuationToken.MoveToNextToken();
            }
            else if (response.IsSuccessStatusCode)
            {
                currentRangeToken.Token = responseContinuationToken;
            }

            // Send to the user the composite state for all ranges
            response.Headers.ContinuationToken = this.compositeContinuationToken.ToString();
            return(response);
        }
        private async Task <ShouldRetryResult> ShouldRetryInternalAsync(
            HttpStatusCode?statusCode,
            SubStatusCodes?subStatusCode,
            CancellationToken cancellationToken)
        {
            if (statusCode == HttpStatusCode.Gone)
            {
                this.retriesOn410++;

                if (this.retriesOn410 > MaxRetryOn410)
                {
                    return(ShouldRetryResult.NoRetry());
                }

                if (subStatusCode == SubStatusCodes.PartitionKeyRangeGone ||
                    subStatusCode == SubStatusCodes.CompletingSplit ||
                    subStatusCode == SubStatusCodes.CompletingPartitionMigration)
                {
                    PartitionKeyRangeCache partitionKeyRangeCache = await this.container.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync(NoOpTrace.Singleton);

                    string containerRid = await this.container.GetCachedRIDAsync(
                        forceRefresh : false,
                        NoOpTrace.Singleton,
                        cancellationToken : cancellationToken);

                    await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                        containerRid,
                        FeedRangeEpk.FullRange.Range,
                        NoOpTrace.Singleton,
                        forceRefresh : true);

                    return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
                }

                if (subStatusCode == SubStatusCodes.NameCacheIsStale)
                {
                    return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
                }
            }

            // Batch API can return 413 which means the response is bigger than 4Mb.
            // Operations that exceed the 4Mb limit are returned as 413/3402, while the operations within the 4Mb limit will be 200
            if (statusCode == HttpStatusCode.RequestEntityTooLarge &&
                (int)subStatusCode == SubstatusCodeBatchResponseSizeExceeded)
            {
                return(ShouldRetryResult.RetryAfter(TimeSpan.Zero));
            }

            return(null);
        }
Example #14
0
        internal static async Task <Tuple <bool, string> > TryResolveSessionTokenAsync(
            DocumentServiceRequest request,
            ISessionContainer sessionContainer,
            PartitionKeyRangeCache partitionKeyRangeCache,
            CollectionCache clientCollectionCache)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

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

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

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

            if (request.ResourceType.IsPartitioned())
            {
                (bool isSuccess, PartitionKeyRange partitionKeyRange) = await TryResolvePartitionKeyRangeAsync(
                    request : request,
                    sessionContainer : sessionContainer,
                    partitionKeyRangeCache : partitionKeyRangeCache,
                    clientCollectionCache : clientCollectionCache,
                    refreshCache : false);

                if (isSuccess && sessionContainer is SessionContainer gatewaySessionContainer)
                {
                    request.RequestContext.ResolvedPartitionKeyRange = partitionKeyRange;
                    string localSessionToken = gatewaySessionContainer.ResolvePartitionLocalSessionTokenForGateway(request, partitionKeyRange.Id);
                    if (!string.IsNullOrEmpty(localSessionToken))
                    {
                        return(new Tuple <bool, string>(true, localSessionToken));
                    }
                }
            }

            return(new Tuple <bool, string>(false, null));
        }
Example #15
0
        async Task <IEnumerable <string> > GetPartitionKeyRangesAsync(
            FeedToken feedToken,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (feedToken is FeedTokenEPKRange feedTokenEPKRange)
            {
                PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                string containerRId = await this.GetRIDAsync(cancellationToken);

                IReadOnlyList <Documents.PartitionKeyRange> partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(containerRId, feedTokenEPKRange.CompleteRange, forceRefresh : false);

                return(partitionKeyRanges.Select(partitionKeyRange => partitionKeyRange.Id));
            }

            if (feedToken is FeedTokenPartitionKeyRange feedTokenPartitionKeyRange)
            {
                if (feedTokenPartitionKeyRange.FeedTokenEPKRange != null)
                {
                    return(await this.GetPartitionKeyRangesAsync(feedTokenPartitionKeyRange.FeedTokenEPKRange, cancellationToken));
                }

                return(new List <string>()
                {
                    feedTokenPartitionKeyRange.PartitionKeyRangeId
                });
            }

            if (feedToken is FeedTokenPartitionKey feedTokenPartitionKey)
            {
                CollectionRoutingMap collectionRoutingMap = await this.GetRoutingMapAsync(cancellationToken);

                PartitionKeyDefinition partitionKeyDefinition = await this.GetPartitionKeyDefinitionAsync(cancellationToken);

                PartitionKeyInternal partitionKeyInternal = feedTokenPartitionKey.PartitionKey.InternalKey;
                string effectivePartitionKeyString        = partitionKeyInternal.GetEffectivePartitionKeyString(partitionKeyDefinition);
                string partitionKeyRangeId = collectionRoutingMap.GetRangeByEffectivePartitionKey(effectivePartitionKeyString).Id;
                return(new List <string>()
                {
                    partitionKeyRangeId
                });
            }

            throw new ArgumentException(nameof(feedToken), ClientResources.FeedToken_UnrecognizedFeedToken);
        }
Example #16
0
        internal override Task <CollectionRoutingMap> GetRoutingMapAsync(CancellationToken cancellationToken)
        {
            string collectionRID = null;

            return(this.GetRIDAsync(cancellationToken)
                   .ContinueWith(ridTask =>
            {
                collectionRID = ridTask.Result;
                return this.ClientContext.Client.DocumentClient.GetPartitionKeyRangeCacheAsync();
            })
                   .Unwrap()
                   .ContinueWith(partitionKeyRangeCachetask =>
            {
                PartitionKeyRangeCache partitionKeyRangeCache = partitionKeyRangeCachetask.Result;
                return partitionKeyRangeCache.TryLookupAsync(
                    collectionRID,
                    null,
                    null,
                    cancellationToken);
            })
                   .Unwrap());
        }
Example #17
0
        public async Task GetChangeFeedTokensAsyncReturnsOnePerPartitionKeyRange()
        {
            // Setting mock to have 3 ranges, to generate 3 tokens
            MultiRangeMockDocumentClient documentClient = new MultiRangeMockDocumentClient();

            using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient();
            Mock <CosmosClientContext> mockContext = new Mock <CosmosClientContext>();

            mockContext.Setup(x => x.ClientOptions).Returns(MockCosmosUtil.GetDefaultConfiguration());
            mockContext.Setup(x => x.DocumentClient).Returns(documentClient);
            mockContext.Setup(x => x.SerializerCore).Returns(MockCosmosUtil.Serializer);
            mockContext.Setup(x => x.Client).Returns(client);
            mockContext.Setup(x => x.CreateLink(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())).Returns(UriFactory.CreateDocumentCollectionUri("test", "test").OriginalString);

            DatabaseInternal     db        = new DatabaseInlineCore(mockContext.Object, "test");
            ContainerInternal    container = new ContainerInlineCore(mockContext.Object, db, "test");
            IEnumerable <string> tokens    = await container.GetChangeFeedTokensAsync();

            Assert.AreEqual(3, tokens.Count());

            PartitionKeyRangeCache pkRangeCache = await documentClient.GetPartitionKeyRangeCacheAsync();

            foreach (string token in tokens)
            {
                // Validate that each token represents a StandByFeedContinuationToken with a single Range
                List <CompositeContinuationToken> deserialized = JsonConvert.DeserializeObject <List <CompositeContinuationToken> >(token);
                Assert.AreEqual(1, deserialized.Count);
                CompositeContinuationToken compositeToken = deserialized[0];

                IReadOnlyList <Documents.PartitionKeyRange> rangesForTheToken = await pkRangeCache.TryGetOverlappingRangesAsync("", compositeToken.Range);

                // Token represents one range
                Assert.AreEqual(1, rangesForTheToken.Count);
                Assert.AreEqual(rangesForTheToken[0].MinInclusive, compositeToken.Range.Min);
                Assert.AreEqual(rangesForTheToken[0].MaxExclusive, compositeToken.Range.Max);
            }
        }
        protected override async Task <DocumentFeedResponse <CosmosElement> > ExecuteInternalAsync(CancellationToken token)
        {
            CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

            PartitionKeyRangeCache partitionKeyRangeCache = await this.Client.GetPartitionKeyRangeCacheAsync();

            IDocumentClientRetryPolicy retryPolicyInstance = this.Client.ResetSessionTokenRetryPolicy.GetRequestPolicy();

            retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(retryPolicyInstance);
            if (base.ResourceTypeEnum.IsPartitioned())
            {
                retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(
                    collectionCache,
                    partitionKeyRangeCache,
                    PathsHelper.GetCollectionPath(base.ResourceLink),
                    retryPolicyInstance);
            }

            return(await BackoffRetryUtility <DocumentFeedResponse <CosmosElement> > .ExecuteAsync(
                       async() =>
            {
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                ++this.retries;
                Tuple <DocumentFeedResponse <CosmosElement>, string> responseAndPartitionIdentifier = await this.ExecuteOnceAsync(retryPolicyInstance, token);
                DocumentFeedResponse <CosmosElement> response = responseAndPartitionIdentifier.Item1;
                string partitionIdentifier = responseAndPartitionIdentifier.Item2;
                if (!string.IsNullOrEmpty(response.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics]))
                {
                    this.fetchExecutionRangeAccumulator.EndFetchRange(
                        partitionIdentifier,
                        response.ActivityId,
                        response.Count,
                        this.retries);
                    response = new DocumentFeedResponse <CosmosElement>(
                        response,
                        response.Count,
                        response.Headers,
                        response.UseETagAsContinuation,
                        new Dictionary <string, QueryMetrics>
                    {
                        {
                            partitionIdentifier,
                            QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                response.ResponseHeaders[HttpConstants.HttpHeaders.QueryMetrics],
                                response.ResponseHeaders[HttpConstants.HttpHeaders.IndexUtilization],
                                new ClientSideMetrics(
                                    this.retries,
                                    response.RequestCharge,
                                    this.fetchExecutionRangeAccumulator.GetExecutionRanges()))
                        }
                    },
                        response.RequestStatistics,
                        response.DisallowContinuationTokenMessage,
                        response.ResponseLengthBytes);
                }

                this.retries = -1;
                return response;
            },
                       retryPolicyInstance,
                       token));
        }
Example #19
0
        internal async Task <(string, ResponseMessage)> ReadNextInternalAsync(CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (this.compositeContinuationToken == null)
            {
                PartitionKeyRangeCache pkRangeCache = await this.clientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

                this.containerRid = await this.container.GetRIDAsync(cancellationToken);

                if (this.changeFeedStartFrom is ChangeFeedStartFromContinuation startFromContinuation)
                {
                    this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(
                        this.containerRid,
                        startFromContinuation.Continuation,
                        pkRangeCache.TryGetOverlappingRangesAsync);

                    (CompositeContinuationToken token, string id) = await this.compositeContinuationToken.GetCurrentTokenAsync();

                    if (token.Token != null)
                    {
                        this.changeFeedStartFrom = ChangeFeedStartFrom.ContinuationToken(token.Token);
                    }
                    else
                    {
                        this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning();
                    }
                }
                else
                {
                    this.compositeContinuationToken = await StandByFeedContinuationToken.CreateAsync(
                        this.containerRid,
                        initialStandByFeedContinuationToken : null,
                        pkRangeCache.TryGetOverlappingRangesAsync);
                }
            }

            (CompositeContinuationToken currentRangeToken, string rangeId) = await this.compositeContinuationToken.GetCurrentTokenAsync();

            FeedRange feedRange = new FeedRangePartitionKeyRange(rangeId);

            if (currentRangeToken.Token != null)
            {
                this.changeFeedStartFrom = new ChangeFeedStartFromContinuationAndFeedRange(currentRangeToken.Token, (FeedRangeInternal)feedRange);
            }
            else
            {
                this.changeFeedStartFrom = ChangeFeedStartFrom.Beginning(feedRange);
            }

            ResponseMessage response = await this.NextResultSetDelegateAsync(this.changeFeedOptions, cancellationToken);

            if (await this.ShouldRetryFailureAsync(response, cancellationToken))
            {
                return(await this.ReadNextInternalAsync(cancellationToken));
            }

            if (response.IsSuccessStatusCode ||
                response.StatusCode == HttpStatusCode.NotModified)
            {
                // Change Feed read uses Etag for continuation
                currentRangeToken.Token = response.Headers.ETag;
            }

            return(rangeId, response);
        }
        private static async Task <Tuple <bool, PartitionKeyRange> > TryResolvePartitionKeyRangeAsync(DocumentServiceRequest request,
                                                                                                      ISessionContainer sessionContainer,
                                                                                                      PartitionKeyRangeCache partitionKeyRangeCache,
                                                                                                      ClientCollectionCache clientCollectionCache,
                                                                                                      bool refreshCache)
        {
            if (refreshCache)
            {
                request.ForceMasterRefresh    = true;
                request.ForceNameCacheRefresh = true;
            }

            PartitionKeyRange   partitonKeyRange = null;
            ContainerProperties collection       = await clientCollectionCache.ResolveCollectionAsync(request, CancellationToken.None, NoOpTrace.Singleton);

            string partitionKeyString = request.Headers[HttpConstants.HttpHeaders.PartitionKey];

            if (partitionKeyString != null)
            {
                CollectionRoutingMap collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(collectionRid : collection.ResourceId,
                                                                                                        previousValue : null,
                                                                                                        request : request,
                                                                                                        cancellationToken : CancellationToken.None,
                                                                                                        NoOpTrace.Singleton);

                if (refreshCache && collectionRoutingMap != null)
                {
                    collectionRoutingMap = await partitionKeyRangeCache.TryLookupAsync(collectionRid : collection.ResourceId,
                                                                                       previousValue : collectionRoutingMap,
                                                                                       request : request,
                                                                                       cancellationToken : CancellationToken.None,
                                                                                       NoOpTrace.Singleton);
                }

                partitonKeyRange = AddressResolver.TryResolveServerPartitionByPartitionKey(request: request,
                                                                                           partitionKeyString: partitionKeyString,
                                                                                           collectionCacheUptoDate: false,
                                                                                           collection: collection,
                                                                                           routingMap: collectionRoutingMap);
            }
            else if (request.PartitionKeyRangeIdentity != null)
            {
                PartitionKeyRangeIdentity partitionKeyRangeId = request.PartitionKeyRangeIdentity;
                partitonKeyRange = await partitionKeyRangeCache.TryGetPartitionKeyRangeByIdAsync(collection.ResourceId,
                                                                                                 partitionKeyRangeId.ToString(),
                                                                                                 NoOpTrace.Singleton,
                                                                                                 refreshCache);
            }

            if (partitonKeyRange == null)
            {
                if (refreshCache)
                {
                    return(new Tuple <bool, PartitionKeyRange>(false, null));
                }

                // need to refresh cache. Maybe split happened.
                return(await GatewayStoreModel.TryResolvePartitionKeyRangeAsync(request : request,
                                                                                sessionContainer : sessionContainer,
                                                                                partitionKeyRangeCache : partitionKeyRangeCache,
                                                                                clientCollectionCache : clientCollectionCache,
                                                                                refreshCache : true));
            }

            return(new Tuple <bool, PartitionKeyRange>(true, partitonKeyRange));
        }
 public void SetCaches(PartitionKeyRangeCache partitionKeyRangeCache,
                       ClientCollectionCache clientCollectionCache)
 {
     this.clientCollectionCache  = clientCollectionCache;
     this.partitionKeyRangeCache = partitionKeyRangeCache;
 }
        public async Task <IReadOnlyList <FeedRange> > GetFeedRangesAsync(
            CosmosDiagnosticsContext diagnosticsContext,
            ITrace trace,
            CancellationToken cancellationToken = default)
        {
            PartitionKeyRangeCache partitionKeyRangeCache = await this.ClientContext.DocumentClient.GetPartitionKeyRangeCacheAsync();

            string containerRId;

            using (diagnosticsContext.CreateScope(nameof(GetCachedRIDAsync)))
            {
                containerRId = await this.GetCachedRIDAsync(
                    forceRefresh : false,
                    cancellationToken);
            }

            IReadOnlyList <PartitionKeyRange> partitionKeyRanges;

            using (diagnosticsContext.CreateScope(nameof(partitionKeyRangeCache.TryGetOverlappingRangesAsync)))
            {
                partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                    containerRId,
                    ContainerCore.allRanges,
                    forceRefresh : true);
            }

            if (partitionKeyRanges == null)
            {
                string refreshedContainerRId;
                using (diagnosticsContext.CreateScope("GetRIDAsyncForceRefresh"))
                {
                    refreshedContainerRId = await this.GetCachedRIDAsync(
                        forceRefresh : true,
                        cancellationToken);
                }

                if (string.Equals(containerRId, refreshedContainerRId))
                {
                    throw CosmosExceptionFactory.CreateInternalServerErrorException(
                              $"Container rid {containerRId} did not have a partition key range after refresh",
                              diagnosticsContext: diagnosticsContext);
                }

                using (diagnosticsContext.CreateScope(nameof(partitionKeyRangeCache.TryGetOverlappingRangesAsync)))
                {
                    partitionKeyRanges = await partitionKeyRangeCache.TryGetOverlappingRangesAsync(
                        containerRId,
                        ContainerCore.allRanges,
                        forceRefresh : true);
                }

                if (partitionKeyRanges == null)
                {
                    throw CosmosExceptionFactory.CreateInternalServerErrorException(
                              $"Container rid {containerRId} returned partitionKeyRanges null after Container RID refresh",
                              diagnosticsContext: diagnosticsContext);
                }
            }

            List <FeedRange> feedTokens = new List <FeedRange>(partitionKeyRanges.Count);

            foreach (PartitionKeyRange partitionKeyRange in partitionKeyRanges)
            {
                feedTokens.Add(new FeedRangeEpk(partitionKeyRange.ToRange()));
            }

            return(feedTokens);
        }
Example #23
0
        protected override async Task <FeedResponse <dynamic> > ExecuteInternalAsync(CancellationToken cancellationToken)
        {
            CollectionCache collectionCache = await this.Client.GetCollectionCacheAsync();

            PartitionKeyRangeCache partitionKeyRangeCache = await this.Client.GetPartitionKeyRangeCache();

            IDocumentClientRetryPolicy retryPolicyInstance = this.Client.RetryPolicy.GetRequestPolicy();

            retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(collectionCache, retryPolicyInstance);
            if (base.ResourceTypeEnum.IsPartitioned())
            {
                retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(
                    collectionCache,
                    partitionKeyRangeCache,
                    PathsHelper.GetCollectionPath(base.ResourceLink),
                    retryPolicyInstance);
            }

            return(await BackoffRetryUtility <FeedResponse <dynamic> > .ExecuteAsync(
                       async() =>
            {
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                ++this.retries;
                this.fetchSchedulingMetrics.Start();
                this.fetchExecutionRangeAccumulator.BeginFetchRange();
                FeedResponse <dynamic> response = await this.ExecuteOnceAsync(retryPolicyInstance, cancellationToken);
                this.fetchSchedulingMetrics.Stop();
                this.fetchExecutionRangeAccumulator.EndFetchRange(response.Count, this.retries);

                if (!string.IsNullOrEmpty(response.Headers[HttpConstants.HttpHeaders.QueryMetrics]))
                {
                    this.fetchExecutionRangeAccumulator.EndFetchRange(response.Count, this.retries);
                    response = new FeedResponse <dynamic>(
                        response,
                        response.Count,
                        response.Headers,
                        response.UseETagAsContinuation,
                        new Dictionary <string, QueryMetrics>
                    {
                        {
                            singlePartitionKeyId,
                            QueryMetrics.CreateFromDelimitedStringAndClientSideMetrics(
                                response.Headers[HttpConstants.HttpHeaders.QueryMetrics],
                                new ClientSideMetrics(
                                    this.retries,
                                    response.RequestCharge,
                                    this.fetchExecutionRangeAccumulator.GetExecutionRanges(),
                                    string.IsNullOrEmpty(response.ResponseContinuation) ? new List <Tuple <string, SchedulingTimeSpan> >()
                            {
                                new Tuple <string, SchedulingTimeSpan>(singlePartitionKeyId, this.fetchSchedulingMetrics.Elapsed)
                            } : new List <Tuple <string, SchedulingTimeSpan> >()),
                                Guid.Parse(response.ActivityId))
                        }
                    },
                        response.RequestStatistics,
                        response.DisallowContinuationTokenMessage,
                        response.ResponseLengthBytes);
                }

                this.retries = -1;
                return response;
            },
                       retryPolicyInstance,
                       cancellationToken));
        }
        public override async Task <ResponseMessage> SendAsync(
            RequestMessage request,
            CancellationToken cancellationToken)
        {
            using (ITrace childTrace = request.Trace.StartChild(this.FullHandlerName, TraceComponent.RequestHandler, Tracing.TraceLevel.Info))
            {
                request.Trace = childTrace;

                ResponseMessage response             = null;
                string          originalContinuation = request.Headers.ContinuationToken;
                try
                {
                    RntdbEnumerationDirection rntdbEnumerationDirection = RntdbEnumerationDirection.Forward;
                    if (request.Properties.TryGetValue(HttpConstants.HttpHeaders.EnumerationDirection, out object direction))
                    {
                        rntdbEnumerationDirection = (byte)direction == (byte)RntdbEnumerationDirection.Reverse ? RntdbEnumerationDirection.Reverse : RntdbEnumerationDirection.Forward;
                    }

                    request.Headers.Remove(HttpConstants.HttpHeaders.IsContinuationExpected);
                    request.Headers.Add(HttpConstants.HttpHeaders.IsContinuationExpected, bool.TrueString);

                    if (!request.Properties.TryGetValue(HandlerConstants.StartEpkString, out object startEpk))
                    {
                        startEpk = PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey;
                    }

                    if (!request.Properties.TryGetValue(HandlerConstants.EndEpkString, out object endEpk))
                    {
                        endEpk = PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey;
                    }

                    startEpk ??= PartitionKeyInternal.MinimumInclusiveEffectivePartitionKey;
                    endEpk ??= PartitionKeyInternal.MaximumExclusiveEffectivePartitionKey;

                    List <Range <string> > providedRanges = new List <Range <string> >
                    {
                        new Range <string>(
                            (string)startEpk,
                            (string)endEpk,
                            isMinInclusive: true,
                            isMaxInclusive: false)
                    };

                    DocumentServiceRequest serviceRequest = request.ToDocumentServiceRequest();

                    PartitionKeyRangeCache routingMapProvider = await this.client.DocumentClient.GetPartitionKeyRangeCacheAsync();

                    CollectionCache collectionCache = await this.client.DocumentClient.GetCollectionCacheAsync(NoOpTrace.Singleton);

                    ContainerProperties collectionFromCache =
                        await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None);

                    //direction is not expected to change  between continuations.
                    Range <string> rangeFromContinuationToken =
                        this.partitionRoutingHelper.ExtractPartitionKeyRangeFromContinuationToken(serviceRequest.Headers, out List <CompositeContinuationToken> suppliedTokens);

                    ResolvedRangeInfo resolvedRangeInfo =
                        await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                            providedPartitionKeyRanges : providedRanges,
                            routingMapProvider : routingMapProvider,
                            collectionRid : collectionFromCache.ResourceId,
                            rangeFromContinuationToken : rangeFromContinuationToken,
                            suppliedTokens : suppliedTokens,
                            direction : rntdbEnumerationDirection);

                    if (serviceRequest.IsNameBased && resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null)
                    {
                        serviceRequest.ForceNameCacheRefresh = true;
                        collectionFromCache = await collectionCache.ResolveCollectionAsync(serviceRequest, CancellationToken.None);

                        resolvedRangeInfo = await this.partitionRoutingHelper.TryGetTargetRangeFromContinuationTokenRangeAsync(
                            providedPartitionKeyRanges : providedRanges,
                            routingMapProvider : routingMapProvider,
                            collectionRid : collectionFromCache.ResourceId,
                            rangeFromContinuationToken : rangeFromContinuationToken,
                            suppliedTokens : suppliedTokens,
                            direction : rntdbEnumerationDirection);
                    }

                    if (resolvedRangeInfo.ResolvedRange == null && resolvedRangeInfo.ContinuationTokens == null)
                    {
                        return(((DocumentClientException) new NotFoundException(
                                    $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Was not able to get queryRoutingInfo even after resolve collection async with force name cache refresh to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}")
                                ).ToCosmosResponseMessage(request));
                    }

                    serviceRequest.RouteTo(new PartitionKeyRangeIdentity(collectionFromCache.ResourceId, resolvedRangeInfo.ResolvedRange.Id));

                    response = await base.SendAsync(request, cancellationToken);

                    if (!response.IsSuccessStatusCode)
                    {
                        this.SetOriginalContinuationToken(request, response, originalContinuation);
                    }
                    else
                    {
                        if (!await this.partitionRoutingHelper.TryAddPartitionKeyRangeToContinuationTokenAsync(
                                response.Headers.CosmosMessageHeaders,
                                providedPartitionKeyRanges: providedRanges,
                                routingMapProvider: routingMapProvider,
                                collectionRid: collectionFromCache.ResourceId,
                                resolvedRangeInfo: resolvedRangeInfo,
                                direction: rntdbEnumerationDirection))
                        {
                            return(((DocumentClientException) new NotFoundException(
                                        $"{DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)}: Call to TryAddPartitionKeyRangeToContinuationTokenAsync failed to the following collectionRid: {collectionFromCache.ResourceId} with the supplied tokens: {JsonConvert.SerializeObject(suppliedTokens)}")
                                    ).ToCosmosResponseMessage(request));
                        }
                    }

                    return(response);
                }
                catch (DocumentClientException ex)
                {
                    ResponseMessage errorResponse = ex.ToCosmosResponseMessage(request);
                    this.SetOriginalContinuationToken(request, errorResponse, originalContinuation);
                    return(errorResponse);
                }
                catch (CosmosException ex)
                {
                    ResponseMessage errorResponse = ex.ToCosmosResponseMessage(request);
                    this.SetOriginalContinuationToken(request, errorResponse, originalContinuation);
                    return(errorResponse);
                }
                catch (AggregateException ex)
                {
                    this.SetOriginalContinuationToken(request, response, originalContinuation);

                    // TODO: because the SDK underneath this path uses ContinueWith or task.Result we need to catch AggregateExceptions here
                    // in order to ensure that underlying DocumentClientExceptions get propagated up correctly. Once all ContinueWith and .Result
                    // is removed this catch can be safely removed.
                    AggregateException innerExceptions    = ex.Flatten();
                    Exception          docClientException = innerExceptions.InnerExceptions.FirstOrDefault(innerEx => innerEx is DocumentClientException);
                    if (docClientException != null)
                    {
                        return(((DocumentClientException)docClientException).ToCosmosResponseMessage(request));
                    }

                    throw;
                }
            }
        }