The proxy's shared download is started once

ConcurrentDictionary.GetOrAdd may run its factory more than once when asked at the same time, and each run started a
download: five clients asking at once for a new file made three upstream requests in one run of
Clients_asking_at_once_share_one_download. The factory now makes a Lazy, of which only the one kept is ever started,
and a download that begins just after another cached the file uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-07 11:00:04 +02:00
1 parent 8fecf7de2d
commit a4ced664cf
1 file changed
+12 -5
+12 -5
View File
@@ -55,7 +55,8 @@ namespace PrivaPub.Domain.Media
readonly IMediaService _media;
readonly IOptionsMonitor<MediaOptions> _options;
readonly Federation.Moderation.IDomainBlocks _domainBlocks;
readonly ConcurrentDictionary<string, Task<(ProxyOutcome, string, string)>> _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<string, Lazy<Task<(ProxyOutcome, string, string)>>> _inFlight = new();
readonly ConcurrentDictionary<string, (ProxyOutcome Outcome, DateTime Until)> _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<Task<(ProxyOutcome, string, string)>>(() => DownloadOnce(key)));
try
{
return await download.WaitAsync(token);
return await shared.Value.WaitAsync(token);
}
finally
{
if (download.IsCompleted)
_inFlight.TryRemove(new KeyValuePair<string, Task<(ProxyOutcome, string, string)>>(url, download));
if (shared.Value.IsCompleted)
_inFlight.TryRemove(new KeyValuePair<string, Lazy<Task<(ProxyOutcome, string, string)>>>(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