Transfers: stop a transfer queued as the last one finishes from waiting for ever

This commit is contained in:
jaap-jan committed 2026-10-02 14:09:47 +02:00
1 parent 441709f1e9
commit 8bee476039
2 files changed
+73 -2

No files matched your search

@@ -304,6 +304,36 @@ public sealed class FileTransferQueueTests : IDisposable
finished.Failure.ShouldBe("the host is not reachable");
}
/// <remarks>
/// <para>
/// The pump decides the queue is empty under the lock and only finishes its task after letting go, so a
/// transfer queued in that gap used to find a pump that still looked busy, start none, and sit queued for
/// ever. Queuing each transfer the moment the last one reports finishing is what lands in that gap.
/// </para>
/// <para>
/// A race, so it is run many times rather than arranged once; on the code that had it, a few hundred
/// rounds strand a transfer every time. The session factory refuses at once, which keeps each round to
/// the queue's own bookkeeping — the part the race is in.
/// </para>
/// </remarks>
[Fact]
public async Task ATransferQueuedAsTheLastOneFinishes_StillRuns()
{
await using var queue = new FileTransferQueue(
_ => Task.FromException<IRemoteFileStore>(new IOException("refused at once")),
TimeProvider.System);
for (var round = 0; round < 500; round++)
{
var finished = await RunToCompletionAsync(
queue, TransferDirection.Download, Local($"round-{round}"), Remote($"round-{round}"), 1);
finished.State.ShouldBe(TransferState.Failed, $"round {round}");
}
queue.IsBusy.ShouldBeFalse();
}
private FileTransferQueue NewQueue() =>
new(_ => Task.FromResult<IRemoteFileStore>(host), TimeProvider.System);
@@ -371,7 +401,7 @@ public sealed class FileTransferQueueTests : IDisposable
{
start();
return await finished.Task.WaitAsync(TimeSpan.FromSeconds(30), Token);
return await finished.Task.WaitAsync(TimeSpan.FromSeconds(10), Token);
}
finally
{