feat(worker): chain a normal parent's children on WaitingForChildren
A standalone parent (PlanningPhase.None) with existing Idle/Queued children now runs itself first, then sequences its children instead of flat-enqueuing all of them at once: SubmitForChildrenAsync calls PlanningChainCoordinator.SetupChainAsync(enqueue: true) right after the Running -> WaitingForChildren transition, mirroring the planning path but firing immediately instead of waiting for an explicit "Queue plan". Planning parents are left untouched (they queue via QueuePlanAsync), and a child already Running makes SetupChainAsync throw, which is now caught and logged so it can't fail the parent's own transition.
This commit is contained in:
@@ -159,16 +159,40 @@ public sealed class TaskStateService : ITaskStateService
|
||||
|
||||
public async Task<TransitionResult> SubmitForChildrenAsync(string taskId, DateTime finishedAt, string? result, CancellationToken ct)
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var affected = await ctx.Tasks
|
||||
.Where(t => t.Id == taskId && t.Status == TaskStatus.Running)
|
||||
.ExecuteUpdateAsync(s => s
|
||||
.SetProperty(t => t.Status, TaskStatus.WaitingForChildren)
|
||||
.SetProperty(t => t.FinishedAt, finishedAt)
|
||||
.SetProperty(t => t.Result, result), ct);
|
||||
PlanningPhase planningPhase;
|
||||
await using (var ctx = await _dbFactory.CreateDbContextAsync(ct))
|
||||
{
|
||||
var affected = await ctx.Tasks
|
||||
.Where(t => t.Id == taskId && t.Status == TaskStatus.Running)
|
||||
.ExecuteUpdateAsync(s => s
|
||||
.SetProperty(t => t.Status, TaskStatus.WaitingForChildren)
|
||||
.SetProperty(t => t.FinishedAt, finishedAt)
|
||||
.SetProperty(t => t.Result, result), ct);
|
||||
|
||||
if (affected == 0)
|
||||
return new TransitionResult(false, "Task not running; cannot submit for children.");
|
||||
if (affected == 0)
|
||||
return new TransitionResult(false, "Task not running; cannot submit for children.");
|
||||
|
||||
planningPhase = await ctx.Tasks.AsNoTracking()
|
||||
.Where(t => t.Id == taskId)
|
||||
.Select(t => t.PlanningPhase)
|
||||
.FirstAsync(ct);
|
||||
}
|
||||
|
||||
// A normal (non-planning) parent queues its own children as a sequential chain right
|
||||
// here; a planning parent's children wait for the user's explicit "Queue plan" action
|
||||
// (PlanningChainCoordinator.QueuePlanAsync), so leave those untouched. A child that's
|
||||
// already Running makes SetupChainAsync throw — log it rather than fail this transition.
|
||||
if (planningPhase == PlanningPhase.None)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _chain.SetupChainAsync(taskId, enqueue: true, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "SetupChainAsync failed for parent {TaskId}", taskId);
|
||||
}
|
||||
}
|
||||
|
||||
await NotifyAsync(taskId);
|
||||
return new TransitionResult(true, null);
|
||||
|
||||
Reference in New Issue
Block a user