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:
1 parent
8fecf7de2d
commit
a4ced664cf
1 file changed
+12
-5
@@ -55,7 +55,8 @@ namespace PrivaPub.Domain.Media
|
|||||||
readonly IMediaService _media;
|
readonly IMediaService _media;
|
||||||
readonly IOptionsMonitor<MediaOptions> _options;
|
readonly IOptionsMonitor<MediaOptions> _options;
|
||||||
readonly Federation.Moderation.IDomainBlocks _domainBlocks;
|
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 ConcurrentDictionary<string, (ProxyOutcome Outcome, DateTime Until)> _refused = new();
|
||||||
readonly SemaphoreSlim _downloads = new(Downloads);
|
readonly SemaphoreSlim _downloads = new(Downloads);
|
||||||
readonly object _keyLock = new();
|
readonly object _keyLock = new();
|
||||||
@@ -153,15 +154,15 @@ namespace PrivaPub.Domain.Media
|
|||||||
_refused.TryRemove(url, out _);
|
_refused.TryRemove(url, out _);
|
||||||
}
|
}
|
||||||
// shared by everyone asking for it now; the download itself isn't cancelled when one of them leaves
|
// 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
|
try
|
||||||
{
|
{
|
||||||
return await download.WaitAsync(token);
|
return await shared.Value.WaitAsync(token);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (download.IsCompleted)
|
if (shared.Value.IsCompleted)
|
||||||
_inFlight.TryRemove(new KeyValuePair<string, Task<(ProxyOutcome, string, string)>>(url, download));
|
_inFlight.TryRemove(new KeyValuePair<string, Lazy<Task<(ProxyOutcome, string, string)>>>(url, shared));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +171,12 @@ namespace PrivaPub.Domain.Media
|
|||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
if (!await _downloads.WaitAsync(TimeSpan.FromSeconds(30)))
|
if (!await _downloads.WaitAsync(TimeSpan.FromSeconds(30)))
|
||||||
return (ProxyOutcome.Failed, default, default);//too busy: the client tries again
|
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 (path, typePath) = CachePaths(url);
|
||||||
var part = $"{path}.{Guid.NewGuid():N}.part";
|
var part = $"{path}.{Guid.NewGuid():N}.part";
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in new issue
Block a user