feat(worker): Toggle "Continue on session limit reset" für Usage-Limit-Abbrüche
Klassifiziert einen echten Usage-Limit-Abbruch als eigene FailureReason
"usage_limit" (TaskRunner.ClassifyFailureReason: nur bei terminal_reason
"api_error" plus einem Limit-Muster im gerenderten Fehlertext, nicht an
Status==Failed allein). Neuer Toggle AutoContinueOnUsageLimit (app_settings,
Default aus) unter Settings → General → "Usage limit stop":
- UsageLimitAutoContinueCoordinator feuert pro Task genau einmal ContinueTask
über OverrideSlotService, sobald das 5h-Fenster (UsageState.Snapshot.FiveHour
.ResetsAt) tatsächlich zurückgesetzt ist; ein persistenter Marker
(TaskEntity.UsageLimitAutoContinuedAt) verhindert einen zweiten Anlauf bei
einem erneuten Limit-Treffer.
- QueueService schedult zusätzlich einen exakten Wake-Timer auf den
Reset-Zeitpunkt, statt nur auf den 30s-Backstop zu warten.
- Fail-open durchgängig: kein Snapshot/keine Reset-Zeit → kein Timer, kein
Continue, kein Throw. Toggle aus ändert das heutige Verhalten nicht.
Migration AddUsageLimitAutoContinue fügt beide Spalten hinzu; die von
`dotnet ef migrations add` mitgescaffoldete leere UpdateData auf app_settings
(columns/values: []) erzeugte ungültiges SQL ("near WHERE") und wurde entfernt
— TaskNumberMigrationTests deckte das über den vollen Migrate()-Pfad auf.
This commit is contained in:
@@ -92,6 +92,7 @@ public sealed class AppSettingsRepository
|
||||
row.UsageThrottleFiveHourHardPct = Math.Clamp(updated.UsageThrottleFiveHourHardPct, 0, 100);
|
||||
row.UsageThrottleSevenDaySoftPct = Math.Clamp(updated.UsageThrottleSevenDaySoftPct, 0, 100);
|
||||
row.UsageThrottleSevenDayHardPct = Math.Clamp(updated.UsageThrottleSevenDayHardPct, 0, 100);
|
||||
row.AutoContinueOnUsageLimit = updated.AutoContinueOnUsageLimit;
|
||||
|
||||
await _context.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
@@ -214,6 +214,27 @@ public sealed class TaskRepository
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(t => t.RoadblockCount, count), ct);
|
||||
}
|
||||
|
||||
/// Tasks eligible for UsageLimitAutoContinueCoordinator: failed on the usage limit and never
|
||||
/// auto-continued before (that flag is never reset, so a task is only offered this once).
|
||||
public async Task<List<TaskEntity>> GetUsageLimitAutoContinueCandidatesAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await _context.Tasks
|
||||
.Where(t => t.Status == TaskStatus.Failed
|
||||
&& t.FailureReason == "usage_limit"
|
||||
&& t.UsageLimitAutoContinuedAt == null)
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
/// Atomically claims the auto-continue slot for one task. Returns false if it was already
|
||||
/// claimed (e.g. by an earlier tick) — the caller must not fire ContinueTask in that case.
|
||||
public async Task<bool> TryClaimUsageLimitAutoContinueAsync(string taskId, DateTime firedAtUtc, CancellationToken ct = default)
|
||||
{
|
||||
var affected = await _context.Tasks
|
||||
.Where(t => t.Id == taskId && t.UsageLimitAutoContinuedAt == null)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(t => t.UsageLimitAutoContinuedAt, firedAtUtc), ct);
|
||||
return affected > 0;
|
||||
}
|
||||
|
||||
internal async Task<int> FlipAllRunningToFailedAsync(string reason, CancellationToken ct = default)
|
||||
{
|
||||
var resultText = "[stale] " + reason;
|
||||
|
||||
Reference in New Issue
Block a user