fix(worker): prevent PlanningMergeOrchestrator double-drain race and orphaned state
This commit is contained in:
@@ -56,11 +56,9 @@ public sealed class PlanningMergeOrchestrator
|
||||
.Where(c => c.Worktree is not null && c.Worktree.State != WorktreeState.Merged)
|
||||
.Select(c => c.Id));
|
||||
|
||||
_states[planningTaskId] = new State
|
||||
{
|
||||
TargetBranch = targetBranch,
|
||||
RemainingSubtaskIds = queue,
|
||||
};
|
||||
var state = new State { TargetBranch = targetBranch, RemainingSubtaskIds = queue };
|
||||
if (!_states.TryAdd(planningTaskId, state))
|
||||
throw new InvalidOperationException($"Merge already in progress for {planningTaskId}.");
|
||||
|
||||
await _broadcaster.PlanningMergeStarted(planningTaskId, targetBranch);
|
||||
await DrainAsync(planningTaskId, ct);
|
||||
@@ -70,38 +68,47 @@ public sealed class PlanningMergeOrchestrator
|
||||
{
|
||||
if (!_states.TryGetValue(planningTaskId, out var state)) return;
|
||||
|
||||
while (state.RemainingSubtaskIds.TryDequeue(out var subtaskId))
|
||||
var keepState = false;
|
||||
try
|
||||
{
|
||||
state.CurrentSubtaskId = subtaskId;
|
||||
var result = await _merge.MergeAsync(
|
||||
subtaskId,
|
||||
state.TargetBranch,
|
||||
removeWorktree: true,
|
||||
commitMessage: "Merge subtask",
|
||||
leaveConflictsInTree: true,
|
||||
ct);
|
||||
|
||||
if (result.Status == TaskMergeService.StatusConflict)
|
||||
while (state.RemainingSubtaskIds.TryDequeue(out var subtaskId))
|
||||
{
|
||||
await _broadcaster.PlanningMergeConflict(planningTaskId, subtaskId, result.ConflictFiles);
|
||||
return;
|
||||
state.CurrentSubtaskId = subtaskId;
|
||||
var result = await _merge.MergeAsync(
|
||||
subtaskId,
|
||||
state.TargetBranch,
|
||||
removeWorktree: true,
|
||||
commitMessage: "Merge subtask",
|
||||
leaveConflictsInTree: true,
|
||||
ct);
|
||||
|
||||
if (result.Status == TaskMergeService.StatusConflict)
|
||||
{
|
||||
await _broadcaster.PlanningMergeConflict(planningTaskId, subtaskId, result.ConflictFiles);
|
||||
keepState = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Status != TaskMergeService.StatusMerged)
|
||||
{
|
||||
await _broadcaster.PlanningMergeConflict(
|
||||
planningTaskId, subtaskId,
|
||||
new[] { result.ErrorMessage ?? "merge blocked" });
|
||||
keepState = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await _broadcaster.PlanningSubtaskMerged(planningTaskId, subtaskId);
|
||||
}
|
||||
|
||||
if (result.Status != TaskMergeService.StatusMerged)
|
||||
{
|
||||
await _broadcaster.PlanningMergeConflict(
|
||||
planningTaskId, subtaskId,
|
||||
new[] { result.ErrorMessage ?? "merge blocked" });
|
||||
return;
|
||||
}
|
||||
|
||||
await _broadcaster.PlanningSubtaskMerged(planningTaskId, subtaskId);
|
||||
state.CurrentSubtaskId = null;
|
||||
await FinalizePlanningDoneAsync(planningTaskId, ct);
|
||||
await _broadcaster.PlanningCompleted(planningTaskId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!keepState) _states.TryRemove(planningTaskId, out _);
|
||||
}
|
||||
|
||||
state.CurrentSubtaskId = null;
|
||||
await FinalizePlanningDoneAsync(planningTaskId, ct);
|
||||
_states.TryRemove(planningTaskId, out _);
|
||||
await _broadcaster.PlanningCompleted(planningTaskId);
|
||||
}
|
||||
|
||||
private async Task FinalizePlanningDoneAsync(string planningTaskId, CancellationToken ct)
|
||||
|
||||
Reference in New Issue
Block a user