// 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); }
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); }
// 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); }
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); }
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); }
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); }
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)); } }
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)); }
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); }
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); }