public void SaveExternalCopyFailure(Uri iconUrl) { if (_externalIconCopyResults == null) { throw new InvalidOperationException("Object was not initialized"); } Set(iconUrl, ExternalIconCopyResult.Fail(iconUrl, _failCacheTime)); }
public async Task <Uri> SaveExternalIcon(Uri originalIconUrl, Uri storageUrl, IStorage mainDestinationStorage, IStorage cacheStorage, CancellationToken cancellationToken) { if (_externalIconCopyResults == null) { throw new InvalidOperationException("Object was not initialized"); } var uriSemaphore = GetUriSemaphore(originalIconUrl); // Attempting to copy to the same location from multiple sources at the same time will throw, // so we'll guard the copy attempt with semaphore. // We'll guard the whole operation so we wouln't even try to copy items to cache more than once. if (!await uriSemaphore.WaitAsync(TimeSpan.Zero, cancellationToken)) { _logger.LogInformation("Failed to enter the semaphore for {IconUrl} immediately, starting to wait", originalIconUrl); await uriSemaphore.WaitAsync(cancellationToken); } try { if (_externalIconCopyResults.TryGetValue(originalIconUrl, out var copyResult)) { if (copyResult.IsCopySucceeded) { return(copyResult.StorageUrl); } // if we have failure stored, we'll try to replace it with success, // now that we've seen one. } var cacheStoragePath = GetCachePath(originalIconUrl); var cacheUrl = cacheStorage.ResolveUri(cacheStoragePath); _logger.LogInformation("Going to store {IconUrl} in cache from {StorageUrl} to {CacheUrl}", originalIconUrl.AbsoluteUri, storageUrl.AbsoluteUri, cacheUrl.AbsoluteUri); await mainDestinationStorage.CopyAsync(storageUrl, cacheStorage, cacheUrl, null, cancellationToken); // Technically, we could get away without storing the success in the dictionary, // but then each get attempt from the cache would result in HTTP request to cache // storage that drastically reduces usefulness of the cache (we trade one HTTP request // for another). Set(originalIconUrl, ExternalIconCopyResult.Success(originalIconUrl, cacheUrl)); return(cacheUrl); } finally { uriSemaphore.Release(); } }
private async Task <bool> TryTakeFromCache(Uri iconUrl, ExternalIconCopyResult cachedResult, IStorage iconCacheStorage, IStorage destinationStorage, CatalogCommitItem item, CancellationToken cancellationToken) { var targetStoragePath = GetTargetStorageIconPath(item); if (cachedResult.IsCopySucceeded) { _logger.LogInformation("Seen {IconUrl} before, will copy from {CachedLocation}", iconUrl, cachedResult.StorageUrl); var storageUrl = cachedResult.StorageUrl; var destinationUrl = destinationStorage.ResolveUri(targetStoragePath); if (storageUrl == destinationUrl) { // We came across the package that initially caused the icon to be added to the cache. // Skipping it. return(true); } try { await Retry.IncrementalAsync( async() => await iconCacheStorage.CopyAsync(storageUrl, destinationStorage, destinationUrl, null, cancellationToken), e => { _logger.LogWarning(0, e, "Exception while copying from cache {StorageUrl}", storageUrl); return(true); }, MaxBlobStorageCopyAttempts, initialWaitInterval : TimeSpan.FromSeconds(5), waitIncrement : TimeSpan.FromSeconds(1)); } catch (Exception e) { _logger.LogWarning(0, e, "Copy from cache failed after {NumRetries} attempts. Falling back to copy from external URL. {StorageUrl}", MaxBlobStorageCopyAttempts, storageUrl); _iconCopyResultCache.Clear(iconUrl); return(false); } } else { _logger.LogInformation("Previous copy attempt failed, skipping {IconUrl} for {PackageId} {PackageVersion}", iconUrl, item.PackageIdentity.Id, item.PackageIdentity.Version); await _iconProcessor.DeleteIconAsync(destinationStorage, targetStoragePath, cancellationToken, item.PackageIdentity.Id, item.PackageIdentity.Version.ToNormalizedString()); } return(true); }
private void Set(Uri iconUrl, ExternalIconCopyResult newItem) { _externalIconCopyResults.AddOrUpdate(iconUrl, newItem, (_, v) => v.IsCopySucceeded ? v : newItem); // will only overwrite failure results }