예제 #1
0
        private async Task <PartitionKeyRangeBatchExecutionResult> ExecuteAsync(
            PartitionKeyRangeServerBatchRequest serverRequest,
            CancellationToken cancellationToken)
        {
            CosmosDiagnosticsContext diagnosticsContext = new CosmosDiagnosticsContext();
            CosmosDiagnosticScope    limiterScope       = diagnosticsContext.CreateScope("BatchAsyncContainerExecutor.Limiter");
            SemaphoreSlim            limiter            = this.GetOrAddLimiterForPartitionKeyRange(serverRequest.PartitionKeyRangeId);

            using (await limiter.UsingWaitAsync(cancellationToken))
            {
                limiterScope.Dispose();
                using (Stream serverRequestPayload = serverRequest.TransferBodyStream())
                {
                    Debug.Assert(serverRequestPayload != null, "Server request payload expected to be non-null");
                    ResponseMessage responseMessage = await this.cosmosClientContext.ProcessResourceOperationStreamAsync(
                        this.cosmosContainer.LinkUri,
                        ResourceType.Document,
                        OperationType.Batch,
                        new RequestOptions(),
                        cosmosContainerCore : this.cosmosContainer,
                        partitionKey : null,
                        streamPayload : serverRequestPayload,
                        requestEnricher : requestMessage => BatchAsyncContainerExecutor.AddHeadersToRequestMessage(requestMessage, serverRequest.PartitionKeyRangeId),
                        diagnosticsScope : diagnosticsContext,
                        cancellationToken : cancellationToken).ConfigureAwait(false);

                    using (diagnosticsContext.CreateScope("BatchAsyncContainerExecutor.ToResponse"))
                    {
                        TransactionalBatchResponse serverResponse = await TransactionalBatchResponse.FromResponseMessageAsync(responseMessage, serverRequest, this.cosmosClientContext.SerializerCore).ConfigureAwait(false);

                        return(new PartitionKeyRangeBatchExecutionResult(serverRequest.PartitionKeyRangeId, serverRequest.Operations, serverResponse));
                    }
                }
            }
        }
        /// <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)
        {
            CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(this.changeFeedOptions);

            using (diagnostics.GetOverallScope())
            {
                diagnostics.AddDiagnosticsInternal(new FeedRangeStatistics(this.changeFeedOptions.FeedRange));
                if (!this.lazyContainerRid.ValueInitialized)
                {
                    using (diagnostics.CreateScope("InitializeContainerResourceId"))
                    {
                        TryCatch <string> tryInitializeContainerRId = await this.lazyContainerRid.GetValueAsync(cancellationToken);

                        if (!tryInitializeContainerRId.Succeeded)
                        {
                            if (!(tryInitializeContainerRId.Exception.InnerException is CosmosException cosmosException))
                            {
                                throw new InvalidOperationException("Failed to convert to CosmosException.");
                            }

                            return(cosmosException.ToCosmosResponseMessage(
                                       new RequestMessage(
                                           method: null,
                                           requestUri: null,
                                           diagnosticsContext: diagnostics)));
                        }
                    }

                    if (this.FeedRangeContinuation == null)
                    {
                        using (diagnostics.CreateScope("InitializeContinuation"))
                        {
                            await this.InitializeFeedContinuationAsync(cancellationToken);
                        }
                    }

                    TryCatch validateContainer = this.FeedRangeContinuation.ValidateContainer(this.lazyContainerRid.Result.Result);
                    if (!validateContainer.Succeeded)
                    {
                        return(CosmosExceptionFactory
                               .CreateBadRequestException(
                                   message: validateContainer.Exception.InnerException.Message,
                                   innerException: validateContainer.Exception.InnerException,
                                   diagnosticsContext: diagnostics)
                               .ToCosmosResponseMessage(
                                   new RequestMessage(
                                       method: null,
                                       requestUri: null,
                                       diagnosticsContext: diagnostics)));
                    }
                }

                return(await this.ReadNextInternalAsync(diagnostics, cancellationToken));
            }
        }
예제 #3
0
        internal async Task <InMemoryRawDek> UnwrapAsync(
            DataEncryptionKeyProperties dekProperties,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            EncryptionKeyWrapProvider encryptionKeyWrapProvider = this.ClientContext.ClientOptions.EncryptionKeyWrapProvider;

            if (encryptionKeyWrapProvider == null)
            {
                throw new ArgumentException(ClientResources.EncryptionKeyWrapProviderNotConfigured);
            }

            EncryptionKeyUnwrapResult unwrapResult = null;

            using (diagnosticsContext.CreateScope("UnwrapDataEncryptionKey"))
            {
                unwrapResult = await encryptionKeyWrapProvider.UnwrapKeyAsync(
                    dekProperties.WrappedDataEncryptionKey,
                    dekProperties.EncryptionKeyWrapMetadata,
                    cancellationToken);
            }

            EncryptionAlgorithm encryptionAlgorithm = this.GetEncryptionAlgorithm(unwrapResult.DataEncryptionKey, dekProperties.EncryptionAlgorithmId);

            return(new InMemoryRawDek(unwrapResult.DataEncryptionKey, encryptionAlgorithm, unwrapResult.ClientCacheTimeToLive));
        }
예제 #4
0
        internal override async Task <ContainerProperties> GetCachedContainerPropertiesAsync(
            string containerUri,
            ITrace trace,
            CancellationToken cancellationToken)
        {
            using (ITrace childTrace = trace.StartChild("Get Container Properties", TraceComponent.Transport, Tracing.TraceLevel.Info))
            {
                this.ThrowIfDisposed();
                CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContextCore.Create(requestOptions: null);
                using (diagnosticsContext.GetOverallScope())
                {
                    ClientCollectionCache collectionCache = await this.DocumentClient.GetCollectionCacheAsync();

                    try
                    {
                        using (diagnosticsContext.CreateScope("ContainerCache.ResolveByNameAsync"))
                        {
                            return(await collectionCache.ResolveByNameAsync(
                                       HttpConstants.Versions.CurrentVersion,
                                       containerUri,
                                       forceRefesh : false,
                                       cancellationToken));
                        }
                    }
                    catch (DocumentClientException ex)
                    {
                        throw CosmosExceptionFactory.Create(ex, diagnosticsContext);
                    }
                }
            }
        }
        internal override async Task <ContainerProperties> GetCachedContainerPropertiesAsync(
            string containerUri,
            CancellationToken cancellationToken)
        {
            this.ThrowIfDisposed();
            CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContextCore.Create(requestOptions: null);

            using (diagnosticsContext.GetOverallScope())
            {
                ClientCollectionCache collectionCache = await this.DocumentClient.GetCollectionCacheAsync();

                try
                {
                    using (diagnosticsContext.CreateScope("ContainerCache.ResolveByNameAsync"))
                    {
                        return(await collectionCache.ResolveByNameAsync(
                                   HttpConstants.Versions.CurrentVersion,
                                   containerUri,
                                   cancellationToken));
                    }
                }
                catch (DocumentClientException ex)
                {
                    throw CosmosExceptionFactory.Create(ex, diagnosticsContext);
                }
            }
        }
        internal override async Task <Stream> EncryptItemAsync(
            Stream input,
            EncryptionOptions encryptionOptions,
            DatabaseCore database,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            if (input == null)
            {
                throw new ArgumentException(ClientResources.InvalidRequestWithEncryptionOptions);
            }

            Debug.Assert(encryptionOptions != null);
            Debug.Assert(database != null);
            Debug.Assert(diagnosticsContext != null);

            using (diagnosticsContext.CreateScope("Encrypt"))
            {
                return(await this.EncryptionProcessor.EncryptAsync(
                           input,
                           encryptionOptions,
                           database,
                           this.ClientOptions.EncryptionKeyWrapProvider,
                           diagnosticsContext,
                           cancellationToken));
            }
        }
예제 #7
0
        internal async Task <(byte[], EncryptionKeyWrapMetadata, InMemoryRawDek)> WrapAsync(
            byte[] key,
            CosmosEncryptionAlgorithm encryptionAlgorithmId,
            EncryptionKeyWrapMetadata metadata,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            EncryptionKeyWrapProvider encryptionKeyWrapProvider = this.ClientContext.ClientOptions.EncryptionKeyWrapProvider;

            if (encryptionKeyWrapProvider == null)
            {
                throw new ArgumentException(ClientResources.EncryptionKeyWrapProviderNotConfigured);
            }

            EncryptionKeyWrapResult keyWrapResponse;

            using (diagnosticsContext.CreateScope("WrapDataEncryptionKey"))
            {
                keyWrapResponse = await encryptionKeyWrapProvider.WrapKeyAsync(key, metadata, cancellationToken);
            }

            // Verify
            DataEncryptionKeyProperties tempDekProperties = new DataEncryptionKeyProperties(this.Id, encryptionAlgorithmId, keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata);
            InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken);

            if (!roundTripResponse.RawDek.SequenceEqual(key))
            {
                throw CosmosExceptionFactory.CreateBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip,
                                                                       diagnosticsContext: diagnosticsContext);
            }

            return(keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse);
        }
예제 #8
0
        /// <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 Task <ResponseMessage> ReadNextAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(this.changeFeedOptions);

            using (diagnostics.CreateScope("ChangeFeedReadNextAsync"))
            {
                return(this.ReadNextInternalAsync(diagnostics, cancellationToken));
            }
        }
예제 #9
0
        private async ValueTask RefreshCachedTokenWithRetryHelperAsync(
            CosmosDiagnosticsContext diagnosticsContext)
        {
            // A different thread is already updating the access token
            bool skipRefreshBecause = this.backgroundRefreshLock.CurrentCount == 1;

            await this.backgroundRefreshLock.WaitAsync();

            try
            {
                // Token was already refreshed successfully from another thread.
                if (skipRefreshBecause && this.cachedAccessToken.ExpiresOn > DateTime.UtcNow)
                {
                    return;
                }

                Exception lastException   = null;
                const int totalRetryCount = 3;
                for (int retry = 0; retry < totalRetryCount; retry++)
                {
                    if (this.cancellationToken.IsCancellationRequested)
                    {
                        DefaultTrace.TraceInformation(
                            "Stop RefreshTokenWithIndefiniteRetries because cancellation is requested");

                        break;
                    }

                    try
                    {
                        using (diagnosticsContext.CreateScope(nameof(this.RefreshCachedTokenWithRetryHelperAsync)))
                        {
                            this.cachedAccessToken = await this.tokenCredential.GetTokenAsync(
                                this.tokenRequestContext,
                                this.cancellationToken);

                            if (!this.userDefinedBackgroundTokenCredentialRefreshInterval.HasValue)
                            {
                                double totalSecondUntilExpire = (this.cachedAccessToken.ExpiresOn - DateTimeOffset.UtcNow).TotalSeconds * DefaultBackgroundTokenCredentialRefreshIntervalPercentage;
                                this.systemBackgroundTokenCredentialRefreshInterval = TimeSpan.FromSeconds(totalSecondUntilExpire);
                            }

                            return;
                        }
                    }
                    catch (RequestFailedException requestFailedException)
                    {
                        lastException = requestFailedException;
                        diagnosticsContext.AddDiagnosticsInternal(
                            new PointOperationStatistics(
                                activityId: Trace.CorrelationManager.ActivityId.ToString(),
                                statusCode: (HttpStatusCode)requestFailedException.Status,
                                subStatusCode: SubStatusCodes.Unknown,
                                responseTimeUtc: DateTime.UtcNow,
                                requestCharge: default,
예제 #10
0
        /// <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)
        {
            CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(this.queryRequestOptions);

            using (diagnostics.GetOverallScope())
            {
                if (!this.lazyContainerRid.ValueInitialized)
                {
                    using (diagnostics.CreateScope("InitializeContainerResourceId"))
                    {
                        TryCatch <string> tryInitializeContainerRId = await this.lazyContainerRid.GetValueAsync(cancellationToken);

                        if (!tryInitializeContainerRId.Succeeded)
                        {
                            CosmosException cosmosException = tryInitializeContainerRId.Exception.InnerException as CosmosException;
                            return(cosmosException.ToCosmosResponseMessage(new RequestMessage(method: null, requestUriString: null, diagnosticsContext: diagnostics)));
                        }
                    }

                    using (diagnostics.CreateScope("InitializeContinuation"))
                    {
                        if (this.FeedRangeContinuation != null)
                        {
                            TryCatch validateContainer = this.FeedRangeContinuation.ValidateContainer(this.lazyContainerRid.Result.Result);
                            if (!validateContainer.Succeeded)
                            {
                                return(CosmosExceptionFactory.CreateBadRequestException(
                                           message: validateContainer.Exception.InnerException.Message,
                                           innerException: validateContainer.Exception.InnerException,
                                           diagnosticsContext: diagnostics).ToCosmosResponseMessage(new RequestMessage(method: null, requestUriString: null, diagnosticsContext: diagnostics)));
                            }
                        }
                        else
                        {
                            await this.InitializeFeedContinuationAsync(cancellationToken);
                        }
                    }
                }

                return(await this.ReadNextInternalAsync(diagnostics, cancellationToken));
            }
        }
예제 #11
0
 private async Task <DataEncryptionKeyProperties> ReadResourceAsync(
     CosmosDiagnosticsContext diagnosticsContext,
     CancellationToken cancellationToken)
 {
     using (diagnosticsContext.CreateScope("ReadDataEncryptionKey"))
     {
         return(await this.ReadInternalAsync(
                    requestOptions : null,
                    diagnosticsContext : diagnosticsContext,
                    cancellationToken : cancellationToken));
     }
 }
예제 #12
0
        public static async Task <IDisposable> UsingWaitAsync(
            this SemaphoreSlim semaphoreSlim,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            using (diagnosticsContext?.CreateScope(nameof(UsingWaitAsync)))
            {
                await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);

                return(new UsableSemaphoreWrapper(semaphoreSlim));
            }
        }
        public Task <ResponseMessage> PatchItemStreamAsync(
            CosmosDiagnosticsContext diagnosticsContext,
            string id,
            PartitionKey partitionKey,
            IReadOnlyList <PatchOperation> patchOperations,
            ItemRequestOptions requestOptions   = null,
            CancellationToken cancellationToken = default)
        {
            if (diagnosticsContext == null)
            {
                throw new ArgumentNullException(nameof(diagnosticsContext));
            }

            if (string.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentNullException(nameof(id));
            }

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

            if (patchOperations == null ||
                !patchOperations.Any())
            {
                throw new ArgumentNullException(nameof(patchOperations));
            }

            Stream patchOperationsStream;

            using (diagnosticsContext.CreateScope("PatchOperationsSerialize"))
            {
                patchOperationsStream = this.ClientContext.SerializerCore.ToStream(patchOperations);
            }

            return(this.ClientContext.ProcessResourceOperationStreamAsync(
                       resourceUri: this.GetResourceUri(
                           requestOptions,
                           OperationType.Patch,
                           id),
                       resourceType: ResourceType.Document,
                       operationType: OperationType.Patch,
                       requestOptions: requestOptions,
                       cosmosContainerCore: this,
                       partitionKey: partitionKey,
                       itemId: id,
                       streamPayload: patchOperationsStream,
                       requestEnricher: null,
                       diagnosticsContext: diagnosticsContext,
                       cancellationToken: cancellationToken));
        }
예제 #14
0
        private Task <TResult> RunWithSynchronizationContextAndDiagnosticsHelperAsync <TResult>(
            CosmosDiagnosticsContext diagnosticsContext,
            Func <CosmosDiagnosticsContext, Task <TResult> > task)
        {
            Debug.Assert(SynchronizationContext.Current != null, "This should only be used when a SynchronizationContext is specified");

            // Used on NETFX applications with SynchronizationContext when doing locking calls
            IDisposable synchronizationContextScope = diagnosticsContext.CreateScope("SynchronizationContext");

            return(Task.Run(() =>
            {
                synchronizationContextScope.Dispose();
                return this.RunWithDiagnosticsHelperAsync <TResult>(
                    diagnosticsContext,
                    task);
            }));
        }
        private Task <TResult> RunWithSynchronizationContextAndDiagnosticsHelperAsync <TResult>(
            string operationName,
            RequestOptions requestOptions,
            Func <CosmosDiagnosticsContext, Task <TResult> > task)
        {
            Debug.Assert(SynchronizationContext.Current != null, "This should only be used when a SynchronizationContext is specified");

            CosmosDiagnosticsContext diagnosticsContext = this.CreateDiagnosticContext(
                operationName,
                requestOptions);

            // Used on NETFX applications with SynchronizationContext when doing locking calls
            return(Task.Run(async() =>
            {
                using (diagnosticsContext.GetOverallScope())
                    using (diagnosticsContext.CreateScope("SynchronizationContext"))
                    {
                        return await task(diagnosticsContext);
                    }
            }));
        }
        private Task <TResult> RunWithSynchronizationContextAndDiagnosticsHelperAsync <TResult>(
            CosmosDiagnosticsContext diagnosticsContext,
            Func <CosmosDiagnosticsContext, Task <TResult> > task)
        {
            Debug.Assert(SynchronizationContext.Current != null, "This should only be used when a SynchronizationContext is specified");

            // Used on NETFX applications with SynchronizationContext when doing locking calls
            IDisposable synchronizationContextScope = diagnosticsContext.CreateScope("SynchronizationContext");

            return(Task.Run(() =>
            {
                using (new ActivityScope(Guid.NewGuid()))
                {
                    // The goal of synchronizationContextScope is to log how much latency the Task.Run added to the latency.
                    // Dispose of it here so it only measures the latency added by the Task.Run.
                    synchronizationContextScope.Dispose();
                    return this.RunWithDiagnosticsHelperAsync <TResult>(
                        diagnosticsContext,
                        task);
                }
            }));
        }
예제 #17
0
        internal override async Task <Stream> DecryptItemAsync(
            Stream input,
            DatabaseInternal database,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            if (input == null || this.ClientOptions.Encryptor == null)
            {
                return(input);
            }

            Debug.Assert(database != null);
            Debug.Assert(diagnosticsContext != null);

            using (diagnosticsContext.CreateScope("Decrypt"))
            {
                return(await this.EncryptionProcessor.DecryptAsync(
                           input,
                           this.ClientOptions.Encryptor,
                           diagnosticsContext,
                           cancellationToken));
            }
        }
        private async Task <HttpResponseMessage> SendHttpHelperAsync(
            Func <ValueTask <HttpRequestMessage> > createRequestMessageAsync,
            ResourceType resourceType,
            CosmosDiagnosticsContext diagnosticsContext,
            HttpTimeoutPolicy timeoutPolicy,
            CancellationToken cancellationToken)
        {
            bool     isDefaultCancellationToken = cancellationToken == default;
            DateTime startDateTimeUtc           = DateTime.UtcNow;
            IEnumerator <(TimeSpan requestTimeout, TimeSpan delayForNextRequest)> timeoutEnumerator = timeoutPolicy.TimeoutEnumerator;

            timeoutEnumerator.MoveNext();
            while (true)
            {
                (TimeSpan requestTimeout, TimeSpan delayForNextRequest) = timeoutEnumerator.Current;
                using (HttpRequestMessage requestMessage = await createRequestMessageAsync())
                {
                    // If the default cancellation token is passed then use the timeout policy
                    CancellationTokenSource cancellationTokenSource = null;
                    if (isDefaultCancellationToken)
                    {
                        cancellationTokenSource = new CancellationTokenSource();
                        cancellationTokenSource.CancelAfter(requestTimeout);
                        cancellationToken = cancellationTokenSource.Token;
                    }

                    cancellationToken.ThrowIfCancellationRequested();

                    try
                    {
                        using (diagnosticsContext.CreateScope(nameof(CosmosHttpClientCore.SendHttpHelperAsync)))
                        {
                            return(await this.ExecuteHttpHelperAsync(
                                       requestMessage,
                                       resourceType,
                                       cancellationToken));
                        }
                    }
                    catch (Exception e)
                    {
                        // Log the error message
                        diagnosticsContext.AddDiagnosticsInternal(
                            new PointOperationStatistics(
                                activityId: Trace.CorrelationManager.ActivityId.ToString(),
                                statusCode: HttpStatusCode.ServiceUnavailable,
                                subStatusCode: SubStatusCodes.Unknown,
                                responseTimeUtc: DateTime.UtcNow,
                                requestCharge: 0,
                                errorMessage: e.ToString(),
                                method: requestMessage.Method,
                                requestUri: requestMessage.RequestUri.OriginalString,
                                requestSessionToken: null,
                                responseSessionToken: null));

                        bool isOutOfRetries = (DateTime.UtcNow - startDateTimeUtc) > timeoutPolicy.MaximumRetryTimeLimit || // Maximum of time for all retries
                                              !timeoutEnumerator.MoveNext();                                                // No more retries are configured

                        switch (e)
                        {
                        case OperationCanceledException operationCanceledException:
                            // Throw if the user passed in cancellation was requested
                            if (!isDefaultCancellationToken && cancellationToken.IsCancellationRequested)
                            {
                                throw;
                            }

                            // Convert OperationCanceledException to 408 when the HTTP client throws it. This makes it clear that the
                            // the request timed out and was not user canceled operation.
                            if (isOutOfRetries || requestMessage.Method != HttpMethod.Get)
                            {
                                // throw timeout if the cancellationToken is not canceled (i.e. httpClient timed out)
                                string message =
                                    $"GatewayStoreClient Request Timeout. Start Time UTC:{startDateTimeUtc}; Total Duration:{(DateTime.UtcNow - startDateTimeUtc).TotalMilliseconds} Ms; Request Timeout {requestTimeout.TotalMilliseconds} Ms; Http Client Timeout:{this.httpClient.Timeout.TotalMilliseconds} Ms; Activity id: {Trace.CorrelationManager.ActivityId};";
                                throw CosmosExceptionFactory.CreateRequestTimeoutException(
                                          message,
                                          innerException: operationCanceledException,
                                          diagnosticsContext: diagnosticsContext);
                            }

                            break;

                        case WebException webException:
                            if (isOutOfRetries || (requestMessage.Method != HttpMethod.Get && !WebExceptionUtility.IsWebExceptionRetriable(webException)))
                            {
                                throw;
                            }

                            break;

                        case HttpRequestException httpRequestException:
                            if (isOutOfRetries || requestMessage.Method != HttpMethod.Get)
                            {
                                throw;
                            }

                            break;

                        default:
                            throw;
                        }
                    }
                }

                if (delayForNextRequest != TimeSpan.Zero)
                {
                    using (diagnosticsContext.CreateScope($"HttpRetryDelay; Delay:{delayForNextRequest} seconds; Current request timeout {requestTimeout}; TimeoutPolicy: {timeoutPolicy.TimeoutPolicyName}"))
                    {
                        await Task.Delay(delayForNextRequest);
                    }
                }
            }
        }
        // Extracted partition key might be invalid as CollectionCache might be stale.
        // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy
        // and partition-key is extracted again.
        private async Task <ResponseMessage> ExtractPartitionKeyAndProcessItemStreamAsync <T>(
            PartitionKey?partitionKey,
            string itemId,
            T item,
            OperationType operationType,
            ItemRequestOptions requestOptions,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            if (diagnosticsContext == null)
            {
                throw new ArgumentNullException(nameof(diagnosticsContext));
            }

            Stream itemStream;

            using (diagnosticsContext.CreateScope("ItemSerialize"))
            {
                itemStream = this.ClientContext.SerializerCore.ToStream <T>(item);
            }

            // User specified PK value, no need to extract it
            if (partitionKey.HasValue)
            {
                PartitionKeyDefinition pKeyDefinition = await this.GetPartitionKeyDefinitionAsync();

                if (partitionKey.HasValue && partitionKey.Value != PartitionKey.None && partitionKey.Value.InternalKey.Components.Count != pKeyDefinition.Paths.Count)
                {
                    throw new ArgumentException(RMResources.MissingPartitionKeyValue);
                }

                return(await this.ProcessItemStreamAsync(
                           partitionKey,
                           itemId,
                           itemStream,
                           operationType,
                           requestOptions,
                           diagnosticsContext : diagnosticsContext,
                           cancellationToken : cancellationToken));
            }

            PartitionKeyMismatchRetryPolicy requestRetryPolicy = null;

            while (true)
            {
                using (diagnosticsContext.CreateScope("ExtractPkValue"))
                {
                    partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, cancellationToken);
                }

                ResponseMessage responseMessage = await this.ProcessItemStreamAsync(
                    partitionKey,
                    itemId,
                    itemStream,
                    operationType,
                    requestOptions,
                    diagnosticsContext : diagnosticsContext,
                    cancellationToken : cancellationToken);

                if (responseMessage.IsSuccessStatusCode)
                {
                    return(responseMessage);
                }

                if (requestRetryPolicy == null)
                {
                    requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(await this.ClientContext.DocumentClient.GetCollectionCacheAsync(), null);
                }

                ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken);

                if (!retryResult.ShouldRetry)
                {
                    return(responseMessage);
                }
            }
        }
예제 #20
0
        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);
        }
        public override Task <HttpResponseMessage> SendHttpAsync(
            Func <ValueTask <HttpRequestMessage> > createRequestMessageAsync,
            ResourceType resourceType,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            diagnosticsContext ??= new CosmosDiagnosticsContextCore();
            HttpRequestMessage requestMessage = null;
            Func <Task <HttpResponseMessage> > funcDelegate = async() =>
            {
                using (diagnosticsContext.CreateScope(nameof(CosmosHttpClientCore.SendHttpAsync)))
                {
                    using (requestMessage = await createRequestMessageAsync())
                    {
                        DateTime sendTimeUtc = DateTime.UtcNow;
                        Guid     localGuid   = Guid.NewGuid(); // For correlating HttpRequest and HttpResponse Traces

                        Guid requestedActivityId = Trace.CorrelationManager.ActivityId;
                        this.eventSource.Request(
                            requestedActivityId,
                            localGuid,
                            requestMessage.RequestUri.ToString(),
                            resourceType.ToResourceTypeString(),
                            requestMessage.Headers);

                        // Only read the header initially. The content gets copied into a memory stream later
                        // if we read the content http client will buffer the message and then it will get buffered
                        // again when it is copied to the memory stream.
                        HttpResponseMessage responseMessage = await this.httpClient.SendAsync(
                            requestMessage,
                            HttpCompletionOption.ResponseHeadersRead,
                            cancellationToken);

                        // WebAssembly HttpClient does not set the RequestMessage property on SendAsync
                        if (responseMessage.RequestMessage == null)
                        {
                            responseMessage.RequestMessage = requestMessage;
                        }

                        DateTime receivedTimeUtc  = DateTime.UtcNow;
                        TimeSpan durationTimeSpan = receivedTimeUtc - sendTimeUtc;

                        Guid activityId = Guid.Empty;
                        if (responseMessage.Headers.TryGetValues(
                                HttpConstants.HttpHeaders.ActivityId,
                                out IEnumerable <string> headerValues) && headerValues.Any())
                        {
                            activityId = new Guid(headerValues.First());
                        }

                        this.eventSource.Response(
                            activityId,
                            localGuid,
                            (short)responseMessage.StatusCode,
                            durationTimeSpan.TotalMilliseconds,
                            responseMessage.Headers);

                        return(responseMessage);
                    }
                }
            };

            HttpRequestMessage GetHttpRequestMessage()
            {
                return(requestMessage);
            }

            return(BackoffRetryUtility <HttpResponseMessage> .ExecuteAsync(
                       callbackMethod : funcDelegate,
                       retryPolicy : new TransientHttpClientRetryPolicy(
                           getHttpRequestMessage: GetHttpRequestMessage,
                           gatewayRequestTimeout: this.httpClient.Timeout,
                           diagnosticsContext: diagnosticsContext),
                       cancellationToken : cancellationToken));
        }
        internal async Task <ResponseMessage> ProcessItemStreamAsync(
            PartitionKey?partitionKey,
            string itemId,
            Stream streamPayload,
            OperationType operationType,
            ItemRequestOptions requestOptions,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            if (requestOptions != null && requestOptions.IsEffectivePartitionKeyRouting)
            {
                partitionKey = null;
            }

            ContainerCore.ValidatePartitionKey(partitionKey, requestOptions);
            Uri resourceUri = this.GetResourceUri(requestOptions, operationType, itemId);

            if (diagnosticsContext == null)
            {
                diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);
            }

            using (diagnosticsContext.CreateOverallScope("ProcessItemStream"))
            {
                if (requestOptions != null && requestOptions.EncryptionOptions != null)
                {
                    if (streamPayload == null)
                    {
                        throw new ArgumentException(ClientResources.InvalidRequestWithEncryptionOptions);
                    }

                    using (diagnosticsContext.CreateScope("Encrypt"))
                    {
                        streamPayload = await this.ClientContext.EncryptionProcessor.EncryptAsync(
                            streamPayload,
                            requestOptions.EncryptionOptions,
                            (DatabaseCore)this.Database,
                            this.ClientContext.ClientOptions.EncryptionKeyWrapProvider,
                            diagnosticsContext,
                            cancellationToken);
                    }
                }

                ResponseMessage responseMessage = await this.ClientContext.ProcessResourceOperationStreamAsync(
                    resourceUri : resourceUri,
                    resourceType : ResourceType.Document,
                    operationType : operationType,
                    requestOptions : requestOptions,
                    cosmosContainerCore : this,
                    partitionKey : partitionKey,
                    itemId : itemId,
                    streamPayload : streamPayload,
                    requestEnricher : null,
                    diagnosticsScope : diagnosticsContext,
                    cancellationToken : cancellationToken);

                if (responseMessage.Content != null && this.ClientContext.ClientOptions.EncryptionKeyWrapProvider != null)
                {
                    using (diagnosticsContext.CreateScope("Decrypt"))
                    {
                        responseMessage.Content = await this.ClientContext.EncryptionProcessor.DecryptAsync(
                            responseMessage.Content,
                            (DatabaseCore)this.Database,
                            this.ClientContext.ClientOptions.EncryptionKeyWrapProvider,
                            diagnosticsContext,
                            cancellationToken);
                    }
                }

                return(responseMessage);
            }
        }
        private async Task <HttpResponseMessage> InvokeClientAsync(
            DocumentServiceRequest request,
            ResourceType resourceType,
            Uri physicalAddress,
            CancellationToken cancellationToken)
        {
            Func <Task <HttpResponseMessage> > funcDelegate = async() =>
            {
                using (HttpRequestMessage requestMessage = await this.PrepareRequestMessageAsync(request, physicalAddress))
                {
                    DateTime sendTimeUtc = DateTime.UtcNow;
                    Guid     localGuid   = Guid.NewGuid(); // For correlating HttpRequest and HttpResponse Traces

                    // DEVNOTE: This is temporary until IClientSideRequestStats is modified to support gateway calls.
                    CosmosDiagnosticsContext diagnosticsContext = null;
                    if (request.RequestContext.ClientRequestStatistics is CosmosClientSideRequestStatistics clientSideRequestStatistics)
                    {
                        diagnosticsContext = clientSideRequestStatistics.DiagnosticsContext;
                    }
                    else
                    {
                        // ClientRequestStatistics are not passed in use Empty to avoid null checks.
                        // Caches do not pass in the ClientRequestStatistics
                        diagnosticsContext = EmptyCosmosDiagnosticsContext.Singleton;
                    }

                    Guid requestedActivityId = Trace.CorrelationManager.ActivityId;
                    this.eventSource.Request(
                        requestedActivityId,
                        localGuid,
                        requestMessage.RequestUri.ToString(),
                        resourceType.ToResourceTypeString(),
                        requestMessage.Headers);

                    TimeSpan durationTimeSpan;
                    try
                    {
                        HttpResponseMessage responseMessage;
                        using (diagnosticsContext.CreateScope("GatewayRequestTime"))
                        {
                            responseMessage = await this.httpClient.SendAsync(requestMessage, cancellationToken);
                        }

                        DateTime receivedTimeUtc = DateTime.UtcNow;
                        durationTimeSpan = receivedTimeUtc - sendTimeUtc;

                        IEnumerable <string> headerValues;
                        Guid activityId = Guid.Empty;
                        if (responseMessage.Headers.TryGetValues(HttpConstants.HttpHeaders.ActivityId, out headerValues) &&
                            headerValues.Count() != 0)
                        {
                            activityId = new Guid(headerValues.First());
                        }

                        this.eventSource.Response(
                            activityId,
                            localGuid,
                            (short)responseMessage.StatusCode,
                            durationTimeSpan.TotalMilliseconds,
                            responseMessage.Headers);

                        return(responseMessage);
                    }
                    catch (TaskCanceledException ex)
                    {
                        if (!cancellationToken.IsCancellationRequested)
                        {
                            // throw timeout if the cancellationToken is not canceled (i.e. httpClient timed out)
                            durationTimeSpan = DateTime.UtcNow - sendTimeUtc;
                            string message = $"GatewayStoreClient Request Timeout. Start Time:{sendTimeUtc}; Total Duration:{durationTimeSpan}; Http Client Timeout:{this.httpClient.Timeout}; Activity id: {requestedActivityId}; Inner Message: {ex.Message};";
                            throw new RequestTimeoutException(message, ex, requestMessage.RequestUri);
                        }
                        else
                        {
                            throw;
                        }
                    }
                    catch (OperationCanceledException ex)
                    {
                        if (!cancellationToken.IsCancellationRequested)
                        {
                            // throw timeout if the cancellationToken is not canceled (i.e. httpClient timed out)
                            durationTimeSpan = DateTime.UtcNow - sendTimeUtc;
                            string message = $"GatewayStoreClient Request Timeout. Start Time:{sendTimeUtc}; Total Duration:{durationTimeSpan}; Http Client Timeout:{this.httpClient.Timeout}; Activity id: {requestedActivityId}; Inner Message: {ex.Message};";
                            throw new RequestTimeoutException(message, ex, requestMessage.RequestUri);
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            };

            return(await BackoffRetryUtility <HttpResponseMessage> .ExecuteAsync(funcDelegate, new WebExceptionRetryPolicy(), cancellationToken));
        }
        private Task <HttpResponseMessage> SendHttpAsync(
            Func <ValueTask <HttpRequestMessage> > createRequestMessageAsync,
            HttpCompletionOption httpCompletionOption,
            ResourceType resourceType,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            diagnosticsContext ??= new CosmosDiagnosticsContextCore();
            HttpRequestMessage requestMessage = null;
            Func <Task <HttpResponseMessage> > funcDelegate = async() =>
            {
                using (diagnosticsContext.CreateScope(nameof(CosmosHttpClientCore.SendHttpAsync)))
                {
                    using (requestMessage = await createRequestMessageAsync())
                    {
                        DateTime sendTimeUtc = DateTime.UtcNow;
                        Guid     localGuid   = Guid.NewGuid(); // For correlating HttpRequest and HttpResponse Traces

                        Guid requestedActivityId = Trace.CorrelationManager.ActivityId;
                        this.eventSource.Request(
                            requestedActivityId,
                            localGuid,
                            requestMessage.RequestUri.ToString(),
                            resourceType.ToResourceTypeString(),
                            requestMessage.Headers);

                        HttpResponseMessage responseMessage = await this.httpClient.SendAsync(
                            requestMessage,
                            httpCompletionOption,
                            cancellationToken);

                        DateTime receivedTimeUtc  = DateTime.UtcNow;
                        TimeSpan durationTimeSpan = receivedTimeUtc - sendTimeUtc;

                        Guid activityId = Guid.Empty;
                        if (responseMessage.Headers.TryGetValues(
                                HttpConstants.HttpHeaders.ActivityId,
                                out IEnumerable <string> headerValues) && headerValues.Any())
                        {
                            activityId = new Guid(headerValues.First());
                        }

                        this.eventSource.Response(
                            activityId,
                            localGuid,
                            (short)responseMessage.StatusCode,
                            durationTimeSpan.TotalMilliseconds,
                            responseMessage.Headers);

                        return(responseMessage);
                    }
                }
            };

            HttpRequestMessage GetHttpRequestMessage() => requestMessage;

            return(BackoffRetryUtility <HttpResponseMessage> .ExecuteAsync(
                       callbackMethod : funcDelegate,
                       retryPolicy : new TransientHttpClientRetryPolicy(
                           getHttpRequestMessage: GetHttpRequestMessage,
                           gatewayRequestTimeout: this.httpClient.Timeout,
                           diagnosticsContext: diagnosticsContext),
                       cancellationToken : cancellationToken));
        }
        // Extracted partition key might be invalid as CollectionCache might be stale.
        // Stale collection cache is refreshed through PartitionKeyMismatchRetryPolicy
        // and partition-key is extracted again.
        internal async Task <ResponseMessage> ExtractPartitionKeyAndProcessItemStreamAsync <T>(
            PartitionKey?partitionKey,
            string itemId,
            T item,
            OperationType operationType,
            RequestOptions requestOptions,
            CancellationToken cancellationToken)
        {
            CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);

            using (diagnosticsContext.CreateOverallScope("ItemStream"))
            {
                Stream itemStream;
                using (diagnosticsContext.CreateScope("ItemSerialize"))
                {
                    itemStream = this.ClientContext.SerializerCore.ToStream <T>(item);
                }

                // User specified PK value, no need to extract it
                if (partitionKey.HasValue)
                {
                    return(await this.ProcessItemStreamAsync(
                               partitionKey,
                               itemId,
                               itemStream,
                               operationType,
                               requestOptions,
                               diagnosticsScope : diagnosticsContext,
                               cancellationToken : cancellationToken));
                }

                PartitionKeyMismatchRetryPolicy requestRetryPolicy = null;
                while (true)
                {
                    using (diagnosticsContext.CreateScope("ExtractPkValue"))
                    {
                        partitionKey = await this.GetPartitionKeyValueFromStreamAsync(itemStream, cancellationToken);
                    }

                    ResponseMessage responseMessage = await this.ProcessItemStreamAsync(
                        partitionKey,
                        itemId,
                        itemStream,
                        operationType,
                        requestOptions,
                        diagnosticsScope : diagnosticsContext,
                        cancellationToken : cancellationToken);

                    if (responseMessage.IsSuccessStatusCode)
                    {
                        return(responseMessage);
                    }

                    if (requestRetryPolicy == null)
                    {
                        requestRetryPolicy = new PartitionKeyMismatchRetryPolicy(await this.ClientContext.DocumentClient.GetCollectionCacheAsync(), null);
                    }

                    ShouldRetryResult retryResult = await requestRetryPolicy.ShouldRetryAsync(responseMessage, cancellationToken);

                    if (!retryResult.ShouldRetry)
                    {
                        return(responseMessage);
                    }
                }
            }
        }