fix(saf): publish _sweepTask only on CAS win so StopAsync drains the real in-flight sweep (plan R2-02 T8)
This commit is contained in:
@@ -821,4 +821,67 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
|
||||
}
|
||||
finally { await service.StopAsync(); }
|
||||
}
|
||||
|
||||
// ── R2 T8: _sweepTask clobber (N3) ──
|
||||
|
||||
[Fact]
|
||||
public async Task TriggerSweep_WhileSweepInFlight_DoesNotClobberTheDrainHandle()
|
||||
{
|
||||
var service = CreateService(retryTimerInterval: TimeSpan.FromHours(1));
|
||||
var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
service.RegisterDeliveryHandler(StoreAndForwardCategory.ExternalSystem, async _ =>
|
||||
{
|
||||
entered.TrySetResult();
|
||||
await release.Task;
|
||||
return true;
|
||||
});
|
||||
await service.StartAsync();
|
||||
try
|
||||
{
|
||||
await service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "t", "{}",
|
||||
attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero);
|
||||
|
||||
service.TriggerSweep(); // real sweep, blocked in the handler
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
service.TriggerSweep(); // redundant kick — pre-fix clobbers _sweepTask
|
||||
|
||||
var handle = service.CurrentSweepTaskForTest;
|
||||
Assert.NotNull(handle);
|
||||
Assert.False(handle!.IsCompleted); // pre-fix: true (a completed no-op replaced the real sweep)
|
||||
}
|
||||
finally
|
||||
{
|
||||
release.TrySetResult();
|
||||
await service.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_WaitsForTheRealInFlightSweep_EvenAfterARedundantTrigger()
|
||||
{
|
||||
var service = CreateService(retryTimerInterval: TimeSpan.FromHours(1));
|
||||
var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
service.RegisterDeliveryHandler(StoreAndForwardCategory.ExternalSystem, async _ =>
|
||||
{
|
||||
entered.TrySetResult();
|
||||
await release.Task;
|
||||
return true;
|
||||
});
|
||||
await service.StartAsync();
|
||||
await service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "t", "{}",
|
||||
attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero);
|
||||
|
||||
service.TriggerSweep();
|
||||
await entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
service.TriggerSweep(); // the clobbering kick
|
||||
|
||||
var stop = service.StopAsync();
|
||||
await Task.Delay(300);
|
||||
Assert.False(stop.IsCompleted); // pre-fix: StopAsync already returned (awaited the no-op)
|
||||
|
||||
release.TrySetResult();
|
||||
await stop.WaitAsync(TimeSpan.FromSeconds(5)); // drains the real sweep promptly once released
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user