예제 #1
0
        // Read the source's end point to get the index json.
        // An exception will be thrown on failure.
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            const int maxRetries = 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       url,
                                       "service_index",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var result = await ConsumeServiceIndexStreamAsync(httpSourceResult.Stream, utcNow, token);

                            return result;
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
        private async Task <SortedDictionary <NuGetVersion, PackageInfo> > FindPackagesByIdAsync(string id, CancellationToken cancellationToken)
        {
            for (var retry = 0; retry != 3; ++retry)
            {
                var baseUri = _baseUris[retry % _baseUris.Count].OriginalString;
                var uri     = baseUri + id.ToLowerInvariant() + "/index.json";

                try
                {
                    using (var result = await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   $"list_{id}",
                                   CreateCacheContext(retry))
                    {
                        IgnoreNotFounds = true,
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(uri, stream)
                    },
                               Logger,
                               cancellationToken))
                    {
                        if (result.Status == HttpSourceResultStatus.NotFound)
                        {
                            return(new SortedDictionary <NuGetVersion, PackageInfo>());
                        }

                        try
                        {
                            return(ConsumeFlatContainerIndex(result.Stream, id, baseUri));
                        }
                        catch
                        {
                            Logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_FileIsCorrupt, result.CacheFileName));

                            throw;
                        }
                    }
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsync), uri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    Logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToRetrievePackage, uri);

                    Logger.LogError(message + Environment.NewLine + ExceptionUtilities.DisplayMessage(ex));

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
예제 #3
0
        private async Task <RepositorySignatureResource> GetRepositorySignatureResourceAsync(
            SourceRepository source,
            string repoSignUrl,
            ILogger log,
            CancellationToken token)
        {
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            for (var retry = 0; retry < 3; retry++)
            {
                using (var sourecCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourecCacheContext, retry);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       repoSignUrl,
                                       "repository_signature",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repoSignUrl, stream),
                            MaxTries = 1
                        },
                                   async httpSourceResult =>
                        {
                            var json = await httpSourceResult.Stream.AsJObjectAsync();

                            return new RepositorySignatureResource(json, source);
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < 2)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingRepositorySignature, repoSignUrl)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == 2)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repoSignUrl);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
예제 #4
0
        // Read the source's end point to get the index json.
        // An exception will be thrown on failure.
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            for (var retry = 0; retry < 3; retry++)
            {
                var cacheContext = HttpSourceCacheContext.CreateCacheContext(new SourceCacheContext(), retry);

                try
                {
                    using (var sourceResponse = await client.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "service_index",
                                   cacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream)
                    },
                               log,
                               token))
                    {
                        return(ConsumeServiceIndexStream(sourceResponse.Stream, utcNow));
                    }
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    log.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                    log.LogError(message + Environment.NewLine + ExceptionUtilities.DisplayMessage(ex));

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
예제 #5
0
        private async Task <NupkgEntry> OpenNupkgStreamAsyncCore(PackageInfo package, CancellationToken cancellationToken)
        {
            for (var retry = 0; retry != 3; ++retry)
            {
                try
                {
                    using (var data = await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   package.ContentUri,
                                   "nupkg_" + package.Identity.Id + "." + package.Identity.Version,
                                   CreateCacheContext(retry))
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateNupkg(package.ContentUri, stream)
                    },
                               Logger,
                               cancellationToken))
                    {
                        return(new NupkgEntry
                        {
                            TempFileName = data.CacheFileName
                        });
                    }
                }
                catch (TaskCanceledException) when(retry < 2)
                {
                    // Requests can get cancelled if we got the data from elsewhere, no reason to warn.
                    string message = string.Format(CultureInfo.CurrentCulture, Strings.Log_CanceledNupkgDownload, package.ContentUri);

                    Logger.LogMinimal(message);
                }
                catch (Exception ex)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToDownloadPackage, package.ContentUri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);
                    if (retry == 2)
                    {
                        Logger.LogError(message);
                    }
                    else
                    {
                        Logger.LogMinimal(message);
                    }
                }
            }

            return(null);
        }
예제 #6
0
        private async Task <RepositorySignatureResource> GetRepositorySignatureResourceAsync(
            SourceRepository source,
            ServiceIndexEntry serviceEntry,
            ILogger log,
            CancellationToken token)
        {
            var repositorySignaturesResourceUri = serviceEntry.Uri;

            if (repositorySignaturesResourceUri == null || !string.Equals(repositorySignaturesResourceUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
            {
                throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.RepositorySignaturesResourceMustBeHttps, source.PackageSource.Source));
            }

            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client   = httpSourceResource.HttpSource;
            var cacheKey = GenerateCacheKey(serviceEntry);

            const int maxRetries = 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       serviceEntry.Uri.AbsoluteUri,
                                       cacheKey,
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repositorySignaturesResourceUri.AbsoluteUri, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var json = await httpSourceResult.Stream.AsJObjectAsync(token);

                            return new RepositorySignatureResource(json, source);
                        },
                                   log,
                                   token));
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
예제 #7
0
        private async Task <SortedDictionary <NuGetVersion, PackageInfo> > FindPackagesByIdAsync(
            string id,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken cancellationToken)
        {
            // Try each base URI 3 times.
            var maxTries           = 3 * _baseUris.Count;
            var packageIdLowerCase = id.ToLowerInvariant();

            for (var retry = 0; retry < maxTries; ++retry)
            {
                var baseUri = _baseUris[retry % _baseUris.Count].OriginalString;
                var uri     = baseUri + packageIdLowerCase + "/index.json";
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   $"list_{packageIdLowerCase}",
                                   httpSourceCacheContext)
                    {
                        IgnoreNotFounds = true,
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(uri, stream),
                        MaxTries = 1
                    },
                               async httpSourceResult =>
                    {
                        var result = new SortedDictionary <NuGetVersion, PackageInfo>();

                        if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromDisk)
                        {
                            try
                            {
                                result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                            }
                            catch
                            {
                                logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_FileIsCorrupt, httpSourceResult.CacheFile));

                                throw;
                            }
                        }
                        else if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromNetwork)
                        {
                            result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                        }

                        return result;
                    },
                               logger,
                               cancellationToken));
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsync), uri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToRetrievePackage,
                        id,
                        uri);

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
        private async Task <IEnumerable <PackageInfo> > FindPackagesByIdAsyncCore(
            string id,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken cancellationToken)
        {
            for (var retry = 0; retry < 3; ++retry)
            {
                var uri = _baseUri + "FindPackagesById()?id='" + id + "'";
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry);

                try
                {
                    var results = new List <PackageInfo>();
                    var uris    = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

                    uris.Add(uri);
                    var page   = 1;
                    var paging = true;
                    while (paging)
                    {
                        // TODO: Pages for a package ID are cached separately.
                        // So we will get inaccurate data when a page shrinks.
                        // However, (1) In most cases the pages grow rather than shrink;
                        // (2) cache for pages is valid for only 30 min.
                        // So we decide to leave current logic and observe.
                        paging = await _httpSource.GetAsync(
                            new HttpSourceCachedRequest(
                                uri,
                                $"list_{id.ToLowerInvariant()}_page{page}",
                                httpSourceCacheContext)
                        {
                            AcceptHeaderValues =
                            {
                                new MediaTypeWithQualityHeaderValue("application/atom+xml"),
                                new MediaTypeWithQualityHeaderValue("application/xml")
                            },
                            EnsureValidContents = stream => HttpStreamValidation.ValidateXml(uri, stream),
                            MaxTries            = 1
                        },
                            async httpSourceResult =>
                        {
                            if (httpSourceResult.Status == HttpSourceResultStatus.NoContent)
                            {
                                // Team city returns 204 when no versions of the package exist
                                // This should result in an empty list and we should not try to
                                // read the stream as xml.
                                return(false);
                            }

                            var doc = await V2FeedParser.LoadXmlAsync(httpSourceResult.Stream);

                            var result = doc.Root
                                         .Elements(_xnameEntry)
                                         .Select(x => BuildModel(id, x))
                                         .Where(x => x != null);

                            results.AddRange(result);

                            // Find the next url for continuation
                            var nextUri = V2FeedParser.GetNextUrl(doc);

                            // Stop if there's nothing else to GET
                            if (string.IsNullOrEmpty(nextUri))
                            {
                                return(false);
                            }

                            // check for any duplicate url and error out
                            if (!uris.Add(nextUri))
                            {
                                throw new FatalProtocolException(string.Format(
                                                                     CultureInfo.CurrentCulture,
                                                                     Strings.Protocol_duplicateUri,
                                                                     nextUri));
                            }

                            uri = nextUri;
                            page++;

                            return(true);
                        },
                            logger,
                            cancellationToken);
                    }

                    return(results);
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsyncCore), uri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToRetrievePackage,
                        id,
                        uri);

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }
예제 #9
0
        internal async Task <XDocument> LoadXmlAsync(
            string uri,
            string cacheKey,
            bool ignoreNotFounds,
            SourceCacheContext sourceCacheContext,
            ILogger log,
            CancellationToken token)
        {
            if (cacheKey != null && sourceCacheContext != null)
            {
                var httpSourceCacheContext = HttpSourceCacheContext.Create(sourceCacheContext, 0);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   cacheKey,
                                   httpSourceCacheContext)
                    {
                        AcceptHeaderValues =
                        {
                            new MediaTypeWithQualityHeaderValue("application/atom+xml"),
                            new MediaTypeWithQualityHeaderValue("application/xml")
                        },
                        EnsureValidContents = stream => HttpStreamValidation.ValidateXml(uri, stream),
                        MaxTries = 1,
                        IgnoreNotFounds = ignoreNotFounds
                    },
                               async response =>
                    {
                        if (ignoreNotFounds && response.Status == HttpSourceResultStatus.NotFound)
                        {
                            // Treat "404 Not Found" as an empty response.
                            return null;
                        }
                        else if (response.Status == HttpSourceResultStatus.NoContent)
                        {
                            // Always treat "204 No Content" as exactly that.
                            return null;
                        }
                        else
                        {
                            return await LoadXmlAsync(response.Stream, token);
                        }
                    },
                               log,
                               token));
                }
                catch (Exception ex)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToFetchV2FeedHttp,
                        uri,
                        ex.Message);

                    throw new FatalProtocolException(message, ex);
                }
            }
            else
            {
                // return results without httpCache
                return(await _httpSource.ProcessResponseAsync(
                           new HttpSourceRequest(
                               () =>
                {
                    var request = HttpRequestMessageFactory.Create(HttpMethod.Get, uri, log);
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/atom+xml"));
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
                    return request;
                })
                {
                    IsRetry = true
                },
                           async response =>
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        var networkStream = await response.Content.ReadAsStreamAsync();
                        return await LoadXmlAsync(networkStream, token);
                    }
                    else if (ignoreNotFounds && response.StatusCode == HttpStatusCode.NotFound)
                    {
                        // Treat "404 Not Found" as an empty response.
                        return null;
                    }
                    else if (response.StatusCode == HttpStatusCode.NoContent)
                    {
                        // Always treat "204 No Content" as exactly that.
                        return null;
                    }
                    else
                    {
                        throw new FatalProtocolException(string.Format(
                                                             CultureInfo.CurrentCulture,
                                                             Strings.Log_FailedToFetchV2Feed,
                                                             uri,
                                                             (int)response.StatusCode,
                                                             response.ReasonPhrase));
                    }
                },
                           sourceCacheContext,
                           log,
                           token));
            }
        }
예제 #10
0
        private async Task <T> ProcessHttpSourceResultAsync <T>(
            PackageIdentity identity,
            string url,
            Func <HttpSourceResult, Task <T> > processAsync,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken token)
        {
            for (var retry = 0; retry < 3; ++retry)
            {
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, retry);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "nupkg_" + identity.Id.ToLowerInvariant() + "." + identity.Version.ToNormalizedString(),
                                   httpSourceCacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateNupkg(url, stream),
                        IgnoreNotFounds = true,
                        MaxTries = 1
                    },
                               async httpSourceResult => await processAsync(httpSourceResult),
                               logger,
                               token));
                }
                catch (TaskCanceledException) when(retry < 2)
                {
                    // Requests can get cancelled if we got the data from elsewhere, no reason to warn.
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_CanceledNupkgDownload, url);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry < 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry == 2)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogError(message);
                }
            }

            return(await processAsync(null));
        }
예제 #11
0
        private async Task <T> ProcessHttpSourceResultAsync <T>(
            PackageIdentity identity,
            string url,
            Func <HttpSourceResult, Task <T> > processAsync,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken token)
        {
            int maxRetries = _enhancedHttpRetryHelper.EnhancedHttpRetryEnabled ? _enhancedHttpRetryHelper.ExperimentalMaxNetworkTryCount : 3;

            for (var retry = 1; retry <= maxRetries; ++retry)
            {
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, isFirstAttempt: retry == 1);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   url,
                                   "nupkg_" + identity.Id.ToLowerInvariant() + "." + identity.Version.ToNormalizedString(),
                                   httpSourceCacheContext)
                    {
                        EnsureValidContents = stream => HttpStreamValidation.ValidateNupkg(url, stream),
                        IgnoreNotFounds = true,
                        MaxTries = 1,
                        IsRetry = retry > 1,
                        IsLastAttempt = retry == maxRetries
                    },
                               async httpSourceResult => await processAsync(httpSourceResult),
                               logger,
                               token));
                }
                catch (TaskCanceledException) when(retry < maxRetries)
                {
                    // Requests can get cancelled if we got the data from elsewhere, no reason to warn.
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_CanceledNupkgDownload, url);

                    logger.LogMinimal(message);
                }
                catch (Exception ex) when(retry < maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);

                    if (_enhancedHttpRetryHelper.EnhancedHttpRetryEnabled &&
                        ex.InnerException != null &&
                        ex.InnerException is IOException &&
                        ex.InnerException.InnerException != null &&
                        ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                    {
                        // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                        // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                        // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                        logger.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                        await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.ExperimentalRetryDelayMilliseconds));
                    }
                }
                catch (Exception ex) when(retry == maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToDownloadPackage,
                        identity,
                        url)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogError(message);
                }
            }

            return(await processAsync(null));
        }
        /// <summary>
        /// Read the source's end point to get the index json.
        /// Retries are logged to any provided <paramref name="log"/> as LogMinimal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="utcNow"></param>
        /// <param name="log"></param>
        /// <param name="token"></param>
        /// <exception cref="OperationCanceledException">Logged to any provided <paramref name="log"/> as LogMinimal prior to throwing.</exception>
        /// <exception cref="FatalProtocolException">Encapsulates all other exceptions.</exception>
        /// <returns></returns>
        private async Task <ServiceIndexResourceV3> GetServiceIndexResourceV3(
            SourceRepository source,
            DateTime utcNow,
            ILogger log,
            CancellationToken token)
        {
            var url = source.PackageSource.Source;
            var httpSourceResource = await source.GetResourceAsync <HttpSourceResource>(token);

            var client = httpSourceResource.HttpSource;

            int maxRetries = _enhancedHttpRetryHelper.IsEnabled ? _enhancedHttpRetryHelper.RetryCount : 3;

            for (var retry = 1; retry <= maxRetries; retry++)
            {
                using (var sourceCacheContext = new SourceCacheContext())
                {
                    var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1);

                    try
                    {
                        return(await client.GetAsync(
                                   new HttpSourceCachedRequest(
                                       url,
                                       "service_index",
                                       cacheContext)
                        {
                            EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(url, stream),
                            MaxTries = 1,
                            IsRetry = retry > 1,
                            IsLastAttempt = retry == maxRetries
                        },
                                   async httpSourceResult =>
                        {
                            var result = await ConsumeServiceIndexStreamAsync(httpSourceResult.Stream, utcNow, token);

                            return result;
                        },
                                   log,
                                   token));
                    }
                    catch (OperationCanceledException ex)
                    {
                        var message = ExceptionUtilities.DisplayMessage(ex);
                        log.LogMinimal(message);
                        throw;
                    }
                    catch (Exception ex) when(retry < maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingServiceIndex, url)
                                      + Environment.NewLine
                                      + ExceptionUtilities.DisplayMessage(ex);

                        log.LogMinimal(message);

                        if (_enhancedHttpRetryHelper.IsEnabled &&
                            ex.InnerException != null &&
                            ex.InnerException is IOException &&
                            ex.InnerException.InnerException != null &&
                            ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                        {
                            // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                            // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                            // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                            log.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                            await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.DelayInMilliseconds), token);
                        }
                    }
                    catch (Exception ex) when(retry == maxRetries)
                    {
                        var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadServiceIndex, url);

                        throw new FatalProtocolException(message, ex);
                    }
                }
            }

            return(null);
        }
예제 #13
0
        private async Task <SortedDictionary <NuGetVersion, PackageInfo> > FindPackagesByIdAsync(
            string id,
            SourceCacheContext cacheContext,
            ILogger logger,
            CancellationToken cancellationToken)
        {
            // Try each base URI _maxRetries times.
            var maxTries           = _maxRetries * _baseUris.Count;
            var packageIdLowerCase = id.ToLowerInvariant();

            for (var retry = 1; retry <= maxTries; ++retry)
            {
                var baseUri = _baseUris[retry % _baseUris.Count].OriginalString;
                var uri     = baseUri + packageIdLowerCase + "/index.json";
                var httpSourceCacheContext = HttpSourceCacheContext.Create(cacheContext, isFirstAttempt: retry == 1);

                try
                {
                    return(await _httpSource.GetAsync(
                               new HttpSourceCachedRequest(
                                   uri,
                                   $"list_{packageIdLowerCase}",
                                   httpSourceCacheContext)
                    {
                        IgnoreNotFounds = true,
                        EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(uri, stream),
                        MaxTries = 1,
                        IsRetry = retry > 1,
                        IsLastAttempt = retry == maxTries
                    },
                               async httpSourceResult =>
                    {
                        var result = new SortedDictionary <NuGetVersion, PackageInfo>();

                        if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromDisk)
                        {
                            try
                            {
                                result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                            }
                            catch
                            {
                                logger.LogWarning(string.Format(CultureInfo.CurrentCulture, Strings.Log_FileIsCorrupt, httpSourceResult.CacheFile));

                                throw;
                            }
                        }
                        else if (httpSourceResult.Status == HttpSourceResultStatus.OpenedFromNetwork)
                        {
                            result = await ConsumeFlatContainerIndexAsync(httpSourceResult.Stream, id, baseUri, cancellationToken);
                        }

                        return result;
                    },
                               logger,
                               cancellationToken));
                }
                catch (Exception ex) when(retry < _maxRetries)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingFindPackagesById, nameof(FindPackagesByIdAsync), uri)
                                  + Environment.NewLine
                                  + ExceptionUtilities.DisplayMessage(ex);

                    logger.LogMinimal(message);

                    if (_enhancedHttpRetryHelper.IsEnabled &&
                        ex.InnerException != null &&
                        ex.InnerException is IOException &&
                        ex.InnerException.InnerException != null &&
                        ex.InnerException.InnerException is System.Net.Sockets.SocketException)
                    {
                        // An IO Exception with inner SocketException indicates server hangup ("Connection reset by peer").
                        // Azure DevOps feeds sporadically do this due to mandatory connection cycling.
                        // Stalling an extra <ExperimentalRetryDelayMilliseconds> gives Azure more of a chance to recover.
                        logger.LogVerbose("Enhanced retry: Encountered SocketException, delaying between tries to allow recovery");
                        await Task.Delay(TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.DelayInMilliseconds), cancellationToken);
                    }
                }
                catch (Exception ex) when(retry == _maxRetries)
                {
                    var message = string.Format(
                        CultureInfo.CurrentCulture,
                        Strings.Log_FailedToRetrievePackage,
                        id,
                        uri);

                    throw new FatalProtocolException(message, ex);
                }
            }

            return(null);
        }