fix(worker): Stuck-Running-Fenster in Continue- und Stop-Pfad schließen
TaskRunner.ContinueAsync: Claim, SeedAsync und SetupMcpConfigAsync liefen vor dem try/catch (anders als RunAsync) - warf einer davon nach dem Running-Claim, propagierte die Exception ungefangen bis zu OverrideSlotService.RunContinueInSlotAsync, das nur loggt. Task blieb Running. Fix: derselbe Aufbau wie RunAsync (Claim+Seed+MCP-Setup im try, MarkFailed im catch, mcpToken/mcpConfigPath vor dem try auf null). SetupMcpConfigAsync bekommt zusätzlich einen onTokenRegistered-Callback, damit die äußere mcpToken-Variable den Token sofort nach dem Register sieht - sonst hätte ein Fehler zwischen Register und Rückgabe (z.B. File.WriteAllTextAsync) den Token in der TaskRunTokenRegistry geleakt (betrifft denselben Aufruf in RunAsync mit, daher dort ebenfalls verdrahtet - RunAsync-Struktur selbst unverändert). QueueService.RunInSlotAsync: der Stop-Button (CancelTask) cancelt die Slot-CTS direkt ohne TaskStateService-Schreibzugriff. Traf das die Pre-Dispatch-DB-Reads, loggte der OCE-Catch nur und die vom Picker bereits auf Running geclaimte Task blieb dort für immer hängen. Fix: Status im Catch neu lesen und nur wenn er noch Running ist über TaskStateService.CancelAsync auf Cancelled setzen - ein Ursprung, der bereits selbst einen Terminalstatus geschrieben hat (z.B. CancelReview), wird nicht überschrieben. Kommentar korrigiert. TDD: neue Tests in ContinueAsyncExceptionTests (Seed-/MCP-Setup-Fehler nach Claim -> Failed, kein Token-Leak) und QueueServiceSlotFailureTests (Stop während Pre-Dispatch -> Cancelled statt Running; ein bereits terminal geschriebener Status wird nicht gestompt) vorher rot, jetzt grün. Worker.Tests: 1213/1213 grün, Worker baut in Release.
This commit is contained in:
@@ -348,9 +348,32 @@ public sealed class QueueService : BackgroundService
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Cancellation is driven by the cancel path, which already wrote the terminal status.
|
||||
// Marking the task Failed here would be a regression (it would stomp Cancelled).
|
||||
// Most cancellation origins (TaskStateService.CancelAsync, the runner's own
|
||||
// OCE handling) already write a terminal status before the CTS is actually
|
||||
// cancelled, so by the time we get here the task has already left Running.
|
||||
// A plain Stop (QueueService.CancelTask cancels the slot's CTS directly, with no
|
||||
// TaskStateService write) landing during the pre-dispatch reads above is the one
|
||||
// gap nothing else covers — the picker's claim already committed status='running'
|
||||
// and nothing since has moved it off that. Only close that specific gap: write
|
||||
// Cancelled when (and only when) the task is still Running, so an origin that
|
||||
// already wrote its own terminal status is never stomped.
|
||||
_logger.LogInformation("Slot runner cancelled for task {TaskId}", taskId);
|
||||
try
|
||||
{
|
||||
TaskStatus current;
|
||||
using (var context = _dbFactory.CreateDbContext())
|
||||
current = await context.Tasks.AsNoTracking()
|
||||
.Where(t => t.Id == taskId)
|
||||
.Select(t => t.Status)
|
||||
.FirstAsync(CancellationToken.None);
|
||||
|
||||
if (current == TaskStatus.Running)
|
||||
await _state.CancelAsync(taskId, DateTime.UtcNow, CancellationToken.None);
|
||||
}
|
||||
catch (Exception cancelEx)
|
||||
{
|
||||
_logger.LogError(cancelEx, "Could not finalize cancelled task {TaskId} after slot cancellation", taskId);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user