コード例 #1
0
        public void EnsureCorrectStatusCode()
        {
            string testMessage       = "Test" + Guid.NewGuid().ToString();
            string activityId        = Guid.NewGuid().ToString();
            int    substatuscode     = 9000;
            string substatus         = substatuscode.ToString();
            double requestCharge     = 42;
            double retryAfter        = 9000;
            string retryAfterLiteral = retryAfter.ToString();
            List <(HttpStatusCode statusCode, CosmosException exception)> exceptionsToStatusCodes = new List <(HttpStatusCode, CosmosException)>()
            {
                (HttpStatusCode.NotFound, CosmosExceptionFactory.CreateNotFoundException(testMessage, new Headers()
                {
                    SubStatusCodeLiteral = substatus, ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
                })),
                (HttpStatusCode.InternalServerError, CosmosExceptionFactory.CreateInternalServerErrorException(testMessage, new Headers()
                {
                    SubStatusCodeLiteral = substatus, ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
                })),
                (HttpStatusCode.BadRequest, CosmosExceptionFactory.CreateBadRequestException(testMessage, new Headers()
                {
                    SubStatusCodeLiteral = substatus, ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
                })),
                (HttpStatusCode.RequestTimeout, CosmosExceptionFactory.CreateRequestTimeoutException(testMessage, new Headers()
                {
                    SubStatusCodeLiteral = substatus, ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
                })),
                ((HttpStatusCode)429, CosmosExceptionFactory.CreateThrottledException(testMessage, new Headers()
                {
                    SubStatusCodeLiteral = substatus, ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
                })),
            };

            foreach ((HttpStatusCode statusCode, CosmosException exception) in exceptionsToStatusCodes)
            {
                this.ValidateExceptionInfo(
                    exception,
                    statusCode,
                    substatus,
                    testMessage,
                    activityId,
                    requestCharge,
                    retryAfter);
            }

            CosmosException cosmosException = CosmosExceptionFactory.CreateNotFoundException(testMessage, new Headers()
            {
                SubStatusCodeLiteral = ((int)SubStatusCodes.ReadSessionNotAvailable).ToString(), ActivityId = activityId, RequestCharge = requestCharge, RetryAfterLiteral = retryAfterLiteral
            });

            this.ValidateExceptionInfo(
                cosmosException,
                HttpStatusCode.NotFound,
                ((int)SubStatusCodes.ReadSessionNotAvailable).ToString(),
                testMessage,
                activityId,
                requestCharge,
                retryAfter);
        }
コード例 #2
0
        public override async Task <ResponseMessage> SendAsync(
            RequestMessage request,
            CancellationToken cancellationToken)
        {
            try
            {
                ResponseMessage response = await this.ProcessMessageAsync(request, cancellationToken);

                Debug.Assert(System.Diagnostics.Trace.CorrelationManager.ActivityId != Guid.Empty, "Trace activity id is missing");

                return(response);
            }
            //catch DocumentClientException and exceptions that inherit it. Other exception types happen before a backend request
            catch (DocumentClientException ex)
            {
                Debug.Assert(System.Diagnostics.Trace.CorrelationManager.ActivityId != Guid.Empty, "Trace activity id is missing");
                return(ex.ToCosmosResponseMessage(request));
            }
            catch (CosmosException ce)
            {
                Debug.Assert(System.Diagnostics.Trace.CorrelationManager.ActivityId != Guid.Empty, "Trace activity id is missing");
                return(ce.ToCosmosResponseMessage(request));
            }
            catch (OperationCanceledException ex)
            {
                // Catch Operation Cancelled Exception and convert to Timeout 408 if the user did not cancel it.
                // Throw the exception if the user cancelled.
                if (cancellationToken.IsCancellationRequested)
                {
                    throw;
                }

                Debug.Assert(System.Diagnostics.Trace.CorrelationManager.ActivityId != Guid.Empty, "Trace activity id is missing");
                CosmosException cosmosException = CosmosExceptionFactory.CreateRequestTimeoutException(
                    message: ex.Data?["Message"].ToString(),
                    headers: new Headers()
                {
                    ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString()
                },
                    innerException: ex,
                    trace: request.Trace);
                return(cosmosException.ToCosmosResponseMessage(request));
            }
            catch (AggregateException ex)
            {
                Debug.Assert(System.Diagnostics.Trace.CorrelationManager.ActivityId != Guid.Empty, "Trace activity id is missing");
                // 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.
                ResponseMessage errorMessage = AggregateExceptionConverter(ex, request);
                if (errorMessage != null)
                {
                    return(errorMessage);
                }

                throw;
            }
        }
コード例 #3
0
        private ShouldRetryResult GetShouldRetryFromException(
            Exception exception)
        {
            if (exception is OperationCanceledException operationCanceledException)
            {
                // throw timeout if the cancellationToken is not canceled (i.e. httpClient timed out)
                string message =
                    $"GatewayStoreClient Request Timeout. Start Time Utc:{this.startDateTimeUtc}; Total Duration:{(DateTime.UtcNow - this.startDateTimeUtc).TotalMilliseconds} Ms; Http Client Timeout:{this.gatewayRequestTimeout.TotalMilliseconds} Ms; Activity id: {Trace.CorrelationManager.ActivityId.ToString()};";
                return(ShouldRetryResult.NoRetry(CosmosExceptionFactory.CreateRequestTimeoutException(
                                                     message,
                                                     innerException: operationCanceledException,
                                                     diagnosticsContext: this.diagnosticsContext)));
            }

            return(ShouldRetryResult.NoRetry());
        }
コード例 #4
0
        public void EnsureCorrectStatusCode()
        {
            string testMessage = "Test" + Guid.NewGuid().ToString();

            List <(HttpStatusCode statusCode, CosmosException exception)> exceptionsToStatusCodes = new List <(HttpStatusCode, CosmosException)>()
            {
                (HttpStatusCode.NotFound, CosmosExceptionFactory.CreateNotFoundException(testMessage, activityId: Guid.NewGuid().ToString())),
                (HttpStatusCode.InternalServerError, CosmosExceptionFactory.CreateInternalServerErrorException(testMessage, activityId: Guid.NewGuid().ToString())),
                (HttpStatusCode.BadRequest, CosmosExceptionFactory.CreateBadRequestException(testMessage, activityId: Guid.NewGuid().ToString())),
                (HttpStatusCode.RequestTimeout, CosmosExceptionFactory.CreateRequestTimeoutException(testMessage, activityId: Guid.NewGuid().ToString())),
                ((HttpStatusCode)429, CosmosExceptionFactory.CreateThrottledException(testMessage, activityId: Guid.NewGuid().ToString())),
            };

            foreach ((HttpStatusCode statusCode, CosmosException exception)item in exceptionsToStatusCodes)
            {
                this.ValidateExceptionInfo(item.exception, item.statusCode, testMessage);
            }
        }
コード例 #5
0
        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);
                    }
                }
            }
        }
コード例 #6
0
        public override async Task RunAsync(CancellationToken cancellationToken)
        {
            string lastContinuation = this.options.StartContinuation;

            while (!cancellationToken.IsCancellationRequested)
            {
                TimeSpan delay = this.options.FeedPollDelay;

                try
                {
                    do
                    {
                        Task <ResponseMessage> task = this.resultSetIterator.ReadNextAsync(cancellationToken);

                        using (CancellationTokenSource cts = new CancellationTokenSource())
                        {
                            if (!ReferenceEquals(await Task.WhenAny(task, Task.Delay(this.options.RequestTimeout, cts.Token)), task))
                            {
                                Task catchExceptionFromTask = task.ContinueWith(task => DefaultTrace.TraceInformation(
                                                                                    "Timed out Change Feed request failed with exception: {2}", task.Exception.InnerException),
                                                                                TaskContinuationOptions.OnlyOnFaulted);
                                throw CosmosExceptionFactory.CreateRequestTimeoutException("Change Feed request timed out", new Headers());
                            }
                            else
                            {
                                cts.Cancel();
                            }
                        }

                        ResponseMessage response = await task;

                        if (response.StatusCode != HttpStatusCode.NotModified && !response.IsSuccessStatusCode)
                        {
                            DefaultTrace.TraceWarning("unsuccessful feed read: lease token '{0}' status code {1}. substatuscode {2}", this.options.LeaseToken, response.StatusCode, response.Headers.SubStatusCode);
                            this.HandleFailedRequest(response, lastContinuation);

                            if (response.Headers.RetryAfter.HasValue)
                            {
                                delay = response.Headers.RetryAfter.Value;
                            }

                            // Out of the loop for a retry
                            break;
                        }

                        lastContinuation = response.Headers.ContinuationToken;
                        if (this.resultSetIterator.HasMoreResults)
                        {
                            await this.DispatchChangesAsync(response, cancellationToken).ConfigureAwait(false);
                        }
                    }while (this.resultSetIterator.HasMoreResults && !cancellationToken.IsCancellationRequested);
                }
                catch (OperationCanceledException canceledException)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        throw;
                    }

                    Extensions.TraceException(canceledException);
                    DefaultTrace.TraceWarning("exception: lease token '{0}'", this.options.LeaseToken);

                    // ignore as it is caused by Cosmos DB client when StopAsync is called
                }

                await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
            }
        }
コード例 #7
0
        private async ValueTask RefreshCachedTokenWithRetryHelperAsync(ITrace trace)
        {
            // A different thread is already updating the access token. Count starts off at 1.
            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;
                    }

                    using (ITrace getTokenTrace = trace.StartChild(
                               name: nameof(this.RefreshCachedTokenWithRetryHelperAsync),
                               component: TraceComponent.Authorization,
                               level: Tracing.TraceLevel.Info))
                    {
                        try
                        {
                            await this.ExecuteGetTokenWithRequestTimeoutAsync();

                            return;
                        }
                        catch (RequestFailedException requestFailedException)
                        {
                            lastException = requestFailedException;
                            getTokenTrace.AddDatum(
                                $"RequestFailedException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}",
                                requestFailedException);

                            DefaultTrace.TraceError($"TokenCredential.GetToken() failed with RequestFailedException. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}");

                            // Don't retry on auth failures
                            if (requestFailedException.Status == (int)HttpStatusCode.Unauthorized ||
                                requestFailedException.Status == (int)HttpStatusCode.Forbidden)
                            {
                                this.cachedAccessToken = default;
                                throw;
                            }
                        }
                        catch (OperationCanceledException operationCancelled)
                        {
                            lastException = operationCancelled;
                            getTokenTrace.AddDatum(
                                $"OperationCanceledException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}",
                                operationCancelled);

                            DefaultTrace.TraceError(
                                $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}");

                            throw CosmosExceptionFactory.CreateRequestTimeoutException(
                                      message: ClientResources.FailedToGetAadToken,
                                      headers: new Headers()
                            {
                                SubStatusCode = SubStatusCodes.FailedToGetAadToken,
                            },
                                      innerException: lastException,
                                      trace: getTokenTrace);
                        }
                        catch (Exception exception)
                        {
                            lastException = exception;
                            getTokenTrace.AddDatum(
                                $"Exception at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}",
                                exception);

                            DefaultTrace.TraceError(
                                $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}");
                        }
                    }

                    DefaultTrace.TraceError(
                        $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}");
                }

                throw CosmosExceptionFactory.CreateUnauthorizedException(
                          message: ClientResources.FailedToGetAadToken,
                          headers: new Headers()
                {
                    SubStatusCode = SubStatusCodes.FailedToGetAadToken,
                },
                          innerException: lastException,
                          trace: trace);
            }
            finally
            {
                this.backgroundRefreshLock.Release();
            }
        }
コード例 #8
0
        private async Task <HttpResponseMessage> SendHttpHelperAsync(
            Func <ValueTask <HttpRequestMessage> > createRequestMessageAsync,
            ResourceType resourceType,
            HttpTimeoutPolicy timeoutPolicy,
            ITrace trace,
            CancellationToken cancellationToken)
        {
            DateTime startDateTimeUtc = DateTime.UtcNow;
            IEnumerator <(TimeSpan requestTimeout, TimeSpan delayForNextRequest)> timeoutEnumerator = timeoutPolicy.GetTimeoutEnumerator();

            timeoutEnumerator.MoveNext();
            while (true)
            {
                cancellationToken.ThrowIfCancellationRequested();

                (TimeSpan requestTimeout, TimeSpan delayForNextRequest) = timeoutEnumerator.Current;
                using (HttpRequestMessage requestMessage = await createRequestMessageAsync())
                {
                    // If the default cancellation token is passed then use the timeout policy
                    using CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                    cancellationTokenSource.CancelAfter(requestTimeout);

                    using (ITrace helperTrace = trace.StartChild($"Execute Http With Timeout Policy: {timeoutPolicy.TimeoutPolicyName}", TraceComponent.Transport, Tracing.TraceLevel.Info))
                    {
                        try
                        {
                            return(await this.ExecuteHttpHelperAsync(
                                       requestMessage,
                                       resourceType,
                                       cancellationTokenSource.Token));
                        }
                        catch (Exception e)
                        {
                            // Log the error message
                            trace.AddDatum(
                                "Error",
                                new PointOperationStatisticsTraceDatum(
                                    activityId: System.Diagnostics.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 (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 || !timeoutPolicy.IsSafeToRetry(requestMessage.Method))
                                {
                                    // 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: {System.Diagnostics.Trace.CorrelationManager.ActivityId};";
                                    throw CosmosExceptionFactory.CreateRequestTimeoutException(
                                              message,
                                              headers: new Headers()
                                    {
                                        ActivityId = System.Diagnostics.Trace.CorrelationManager.ActivityId.ToString()
                                    },
                                              innerException: operationCanceledException,
                                              trace: helperTrace);
                                }

                                break;

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

                                break;

                            case HttpRequestException httpRequestException:
                                if (isOutOfRetries || !timeoutPolicy.IsSafeToRetry(requestMessage.Method))
                                {
                                    throw;
                                }

                                break;

                            default:
                                throw;
                            }
                        }
                    }
                }

                if (delayForNextRequest != TimeSpan.Zero)
                {
                    using (ITrace delayTrace = trace.StartChild("Retry Delay", TraceComponent.Transport, Tracing.TraceLevel.Info))
                    {
                        await Task.Delay(delayForNextRequest);
                    }
                }
            }
        }