Skip to content

Commit 4bd6855

Browse files
committed
fix: fix downloading different URLs to same destination
1 parent 8f60b4d commit 4bd6855

File tree

2 files changed

+42
-15
lines changed

2 files changed

+42
-15
lines changed

Tests.Vpn.Service/DownloaderTest.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,34 @@ public async Task Download(CancellationToken ct)
284284
Assert.That(await File.ReadAllTextAsync(destPath, ct), Is.EqualTo("test"));
285285
}
286286

287+
[Test(Description = "Perform 2 downloads with the same destination")]
288+
[CancelAfter(30_000)]
289+
public async Task DownloadSameDest(CancellationToken ct)
290+
{
291+
using var httpServer = EchoServer();
292+
var url0 = new Uri(httpServer.BaseUrl + "/test0");
293+
var url1 = new Uri(httpServer.BaseUrl + "/test1");
294+
var destPath = Path.Combine(_tempDir, "test");
295+
296+
var manager = new Downloader(NullLogger<Downloader>.Instance);
297+
var startTask0 = manager.StartDownloadAsync(new HttpRequestMessage(HttpMethod.Get, url0), destPath,
298+
NullDownloadValidator.Instance, ct);
299+
var startTask1 = manager.StartDownloadAsync(new HttpRequestMessage(HttpMethod.Get, url1), destPath,
300+
NullDownloadValidator.Instance, ct);
301+
var dlTask0 = await startTask0;
302+
await dlTask0.Task;
303+
Assert.That(dlTask0.TotalBytes, Is.EqualTo(5));
304+
Assert.That(dlTask0.BytesRead, Is.EqualTo(5));
305+
Assert.That(dlTask0.Progress, Is.EqualTo(1));
306+
Assert.That(dlTask0.IsCompleted, Is.True);
307+
var dlTask1 = await startTask1;
308+
await dlTask1.Task;
309+
Assert.That(dlTask1.TotalBytes, Is.EqualTo(5));
310+
Assert.That(dlTask1.BytesRead, Is.EqualTo(5));
311+
Assert.That(dlTask1.Progress, Is.EqualTo(1));
312+
Assert.That(dlTask1.IsCompleted, Is.True);
313+
}
314+
287315
[Test(Description = "Download with custom headers")]
288316
[CancelAfter(30_000)]
289317
public async Task WithHeaders(CancellationToken ct)
@@ -395,9 +423,9 @@ public void CancelledOuter(CancellationToken ct)
395423
var manager = new Downloader(NullLogger<Downloader>.Instance);
396424
// The "outer" Task should fail.
397425
var smallerCt = new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token;
398-
Assert.ThrowsAsync<TaskCanceledException>(
399-
async () => await manager.StartDownloadAsync(new HttpRequestMessage(HttpMethod.Get, url), destPath,
400-
NullDownloadValidator.Instance, smallerCt));
426+
Assert.ThrowsAsync<TaskCanceledException>(async () => await manager.StartDownloadAsync(
427+
new HttpRequestMessage(HttpMethod.Get, url), destPath,
428+
NullDownloadValidator.Instance, smallerCt));
401429
}
402430

403431
[Test(Description = "Timeout on response body")]

Vpn.Service/Downloader.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,9 @@ public async Task<DownloadTask> StartDownloadAsync(HttpRequestMessage req, strin
288288
{
289289
var task = _downloads.GetOrAdd(destinationPath,
290290
_ => new DownloadTask(_logger, req, destinationPath, validator));
291-
await task.EnsureStartedAsync(ct);
291+
// EnsureStarted is a no-op if we didn't create a new DownloadTask.
292+
// So, we will only remove the destination once for each time we start a new task.
293+
task.EnsureStarted(tsk => _downloads.TryRemove(destinationPath, out _), ct);
292294

293295
// If the existing (or new) task is for the same URL, return it.
294296
if (task.Request.RequestUri == req.RequestUri)
@@ -357,21 +359,19 @@ internal DownloadTask(ILogger logger, HttpRequestMessage req, string destination
357359
".download-" + Path.GetRandomFileName());
358360
}
359361

360-
internal async Task<Task> EnsureStartedAsync(CancellationToken ct = default)
362+
internal void EnsureStarted(Action<Task> continuation, CancellationToken ct = default)
361363
{
362-
using var _ = await _semaphore.LockAsync(ct);
364+
using var _ = _semaphore.Lock();
363365
if (Task == null!)
364-
Task = await StartDownloadAsync(ct);
365-
366-
return Task;
366+
Task = Start(ct).ContinueWith(continuation, CancellationToken.None);
367367
}
368368

369369
/// <summary>
370370
/// Starts downloading the file. The request will be performed in this task, but once started, the task will complete
371371
/// and the download will continue in the background. The provided CancellationToken can be used to cancel the
372372
/// download.
373373
/// </summary>
374-
private async Task<Task> StartDownloadAsync(CancellationToken ct = default)
374+
private async Task Start(CancellationToken ct = default)
375375
{
376376
Directory.CreateDirectory(_destinationDirectory);
377377

@@ -398,8 +398,7 @@ private async Task<Task> StartDownloadAsync(CancellationToken ct = default)
398398
throw new Exception("Existing file failed validation after 304 Not Modified", e);
399399
}
400400

401-
Task = Task.CompletedTask;
402-
return Task;
401+
return;
403402
}
404403

405404
if (res.StatusCode != HttpStatusCode.OK)
@@ -432,11 +431,11 @@ private async Task<Task> StartDownloadAsync(CancellationToken ct = default)
432431
throw;
433432
}
434433

435-
Task = DownloadAsync(res, tempFile, ct);
436-
return Task;
434+
await Download(res, tempFile, ct);
435+
return;
437436
}
438437

439-
private async Task DownloadAsync(HttpResponseMessage res, FileStream tempFile, CancellationToken ct)
438+
private async Task Download(HttpResponseMessage res, FileStream tempFile, CancellationToken ct)
440439
{
441440
try
442441
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy