fix(worker): advance planning chains and parents on stale-Running recovery
RecoverStaleRunningAsync bulk-flipped Running rows to Failed via raw ExecuteUpdate, skipping the chain/parent side effects every other terminal transition triggers. After a worker crash mid-run of a planning/improvement child, the chain successor's BlockedByTaskId was never cleared and a WaitingForChildren parent could wedge forever with no event left to re-check it. Now each recovered task runs the same OnChildTerminalAsync side effects (chain advance + parent advance) as FailAsync, best-effort.
This commit is contained in:
@@ -456,6 +456,25 @@ public sealed class TaskStateServiceTests : IDisposable
|
||||
Assert.StartsWith("[stale] ", t.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RecoverStaleRunningAsync_ResolvesBlockedSuccessor_AndAdvancesParent()
|
||||
{
|
||||
var parent = await SeedTaskAsync(TaskStatus.WaitingForChildren, phase: PlanningPhase.Finalized);
|
||||
var c0 = await SeedTaskAsync(TaskStatus.Running, parentId: parent, sortOrder: 0);
|
||||
var c1 = await SeedTaskAsync(TaskStatus.Queued, parentId: parent, sortOrder: 1, blockedBy: c0);
|
||||
|
||||
var count = await _sut.RecoverStaleRunningAsync("worker restart", default);
|
||||
|
||||
Assert.Equal(1, count);
|
||||
Assert.Equal(TaskStatus.Failed, await GetStatusAsync(c0));
|
||||
// c1 was blocked on the crashed task; the chain coordinator resolves it (cancels it,
|
||||
// since the predecessor didn't finish successfully) instead of leaving it Queued with
|
||||
// a dangling BlockedByTaskId that the picker would skip forever.
|
||||
Assert.Equal(TaskStatus.Cancelled, await GetStatusAsync(c1));
|
||||
// Both children are now terminal, so the WaitingForChildren parent advances.
|
||||
Assert.Equal(TaskStatus.WaitingForReview, await GetStatusAsync(parent));
|
||||
}
|
||||
|
||||
// ─── Child terminal → chain advance ───────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user