diff --git a/PrivaPub/Domain/Media/MediaProxy.cs b/PrivaPub/Domain/Media/MediaProxy.cs index 7d26f19..263c3b5 100644 --- a/PrivaPub/Domain/Media/MediaProxy.cs +++ b/PrivaPub/Domain/Media/MediaProxy.cs @@ -55,7 +55,8 @@ namespace PrivaPub.Domain.Media readonly IMediaService _media; readonly IOptionsMonitor _options; readonly Federation.Moderation.IDomainBlocks _domainBlocks; - readonly ConcurrentDictionary> _inFlight = new(); + // Lazy: GetOrAdd may run its factory more than once when asked at the same time, but only one Lazy is ever evaluated + readonly ConcurrentDictionary>> _inFlight = new(); readonly ConcurrentDictionary _refused = new(); readonly SemaphoreSlim _downloads = new(Downloads); readonly object _keyLock = new(); @@ -153,15 +154,15 @@ namespace PrivaPub.Domain.Media _refused.TryRemove(url, out _); } // shared by everyone asking for it now; the download itself isn't cancelled when one of them leaves - var download = _inFlight.GetOrAdd(url, key => DownloadOnce(key)); + var shared = _inFlight.GetOrAdd(url, key => new Lazy>(() => DownloadOnce(key))); try { - return await download.WaitAsync(token); + return await shared.Value.WaitAsync(token); } finally { - if (download.IsCompleted) - _inFlight.TryRemove(new KeyValuePair>(url, download)); + if (shared.Value.IsCompleted) + _inFlight.TryRemove(new KeyValuePair>>(url, shared)); } } @@ -170,6 +171,12 @@ namespace PrivaPub.Domain.Media await Task.Yield(); if (!await _downloads.WaitAsync(TimeSpan.FromSeconds(30))) return (ProxyOutcome.Failed, default, default);//too busy: the client tries again + // cached by a download that ended just before this one began + if (Cached(url) is { Path: not null } done) + { + _downloads.Release(); + return (ProxyOutcome.Cached, done.Path, done.ContentType); + } var (path, typePath) = CachePaths(url); var part = $"{path}.{Guid.NewGuid():N}.part"; try