fix(worker-tests): close TOCTOU race in slot-failure broadcast poll

QueueServiceSlotFailureTests's throwing-slot test broke its poll loop the
instant the DB read observed Status==Failed, but TaskStateService.FailAsync
commits the status flip before calling the broadcaster's TaskUpdated, so the
assertion could race ahead of the broadcast landing in hub.Proxy.Calls
(~1-in-5 failures in isolation). Wait for both signals before breaking.
This commit is contained in:
mika kuns
2026-08-07 10:19:54 +02:00
parent 1f8f3efc72
commit c6d1fff8b1
@@ -132,13 +132,20 @@ public sealed class QueueServiceSlotFailureTests : IDisposable
await service.StartAsync(cts.Token);
waker.Wake();
// FailAsync (TaskStateService.cs:236-249) commits the DB status flip via
// ExecuteUpdateAsync *before* it calls the broadcaster's TaskUpdated — so a poll that
// breaks the instant it observes Status==Failed can race ahead of the broadcast still
// landing in hub.Proxy.Calls. Wait for both signals together so the assertions below
// never sample a genuinely-not-yet-broadcast window as a failure.
TaskEntity? reloaded = null;
var deadline = DateTime.UtcNow.AddSeconds(10);
while (DateTime.UtcNow < deadline)
{
using var verify = _db.CreateContext();
reloaded = await new TaskRepository(verify).GetByIdAsync(taskId);
if (reloaded!.Status == TaskStatus.Failed) break;
var broadcastSeen = hub.Proxy.Calls.Any(
c => c.Method == "TaskUpdated" && (string)c.Args[0]! == taskId);
if (reloaded!.Status == TaskStatus.Failed && broadcastSeen) break;
await Task.Delay(25);
}
cts.Cancel();