Skip to content

Commit e5d9dc1

Browse files
authored
fix: fix Downloader to dispose tempFile and use synchronous IO (#81)
Fixes coder/internal#598 There is a possible race where if the cancellation token is expired, `Download()` never gets called and the tempFile is never disposed of (at least until GC). We also switch to synchronous IO so that a pending overlapped write won't block the deletion. These issues can cause races in our tests when we try to clean up the directory.
1 parent 75cdfd0 commit e5d9dc1

File tree

2 files changed

+23
-20
lines changed

2 files changed

+23
-20
lines changed

Tests.Vpn.Service/DownloaderTest.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -442,23 +442,28 @@ public async Task CancelledWaitingForOther(CancellationToken ct)
442442
[CancelAfter(30_000)]
443443
public async Task CancelledInner(CancellationToken ct)
444444
{
445+
var httpCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
446+
var taskCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
445447
using var httpServer = new TestHttpServer(async ctx =>
446448
{
447449
ctx.Response.StatusCode = 200;
448-
await ctx.Response.OutputStream.WriteAsync("test"u8.ToArray(), ct);
449-
await ctx.Response.OutputStream.FlushAsync(ct);
450-
await Task.Delay(TimeSpan.FromSeconds(5), ct);
450+
await ctx.Response.OutputStream.WriteAsync("test"u8.ToArray(), httpCts.Token);
451+
await ctx.Response.OutputStream.FlushAsync(httpCts.Token);
452+
// wait up to 5 seconds.
453+
await Task.Delay(TimeSpan.FromSeconds(5), httpCts.Token);
451454
});
452455
var url = new Uri(httpServer.BaseUrl + "/test");
453456
var destPath = Path.Combine(_tempDir, "test");
454457

455458
var manager = new Downloader(NullLogger<Downloader>.Instance);
456459
// The "inner" Task should fail.
457-
var smallerCt = new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token;
460+
var taskCt = taskCts.Token;
458461
var dlTask = await manager.StartDownloadAsync(new HttpRequestMessage(HttpMethod.Get, url), destPath,
459-
NullDownloadValidator.Instance, smallerCt);
462+
NullDownloadValidator.Instance, taskCt);
463+
await taskCts.CancelAsync();
460464
var ex = Assert.ThrowsAsync<TaskCanceledException>(async () => await dlTask.Task);
461-
Assert.That(ex.CancellationToken, Is.EqualTo(smallerCt));
465+
Assert.That(ex.CancellationToken, Is.EqualTo(taskCt));
466+
await httpCts.CancelAsync();
462467
}
463468

464469
[Test(Description = "Validation failure")]

Vpn.Service/Downloader.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -453,27 +453,25 @@ private async Task Start(CancellationToken ct = default)
453453
if (res.Content.Headers.ContentLength >= 0)
454454
TotalBytes = (ulong)res.Content.Headers.ContentLength;
455455

456-
FileStream tempFile;
457-
try
458-
{
459-
tempFile = File.Create(TempDestinationPath, BufferSize,
460-
FileOptions.Asynchronous | FileOptions.SequentialScan);
461-
}
462-
catch (Exception e)
463-
{
464-
_logger.LogError(e, "Failed to create temporary file '{TempDestinationPath}'", TempDestinationPath);
465-
throw;
466-
}
467-
468-
await Download(res, tempFile, ct);
456+
await Download(res, ct);
469457
return;
470458
}
471459

472-
private async Task Download(HttpResponseMessage res, FileStream tempFile, CancellationToken ct)
460+
private async Task Download(HttpResponseMessage res, CancellationToken ct)
473461
{
474462
try
475463
{
476464
var sha1 = res.Headers.Contains("ETag") ? SHA1.Create() : null;
465+
FileStream tempFile;
466+
try
467+
{
468+
tempFile = File.Create(TempDestinationPath, BufferSize, FileOptions.SequentialScan);
469+
}
470+
catch (Exception e)
471+
{
472+
_logger.LogError(e, "Failed to create temporary file '{TempDestinationPath}'", TempDestinationPath);
473+
throw;
474+
}
477475
await using (tempFile)
478476
{
479477
var stream = await res.Content.ReadAsStreamAsync(ct);

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