chore(claude-do): UsageGate: Parallelitaet stufenweise drosseln statt erst bei
## Kontext: Limits sind Fenster, nicht Summen Die Runs laufen ueber das Claude-Abo. Limits greifen pro 5h-Fenster und pro 7 Tage. Nicht die Wochensumme tut weh, sondern dass ein Agent-Burst ein Fenster leerraeumt, in dem Mika selbst interaktiv arbeiten will. ## Messgrundlage (alle Transcripts unter ~/.claude/projects) Agent-Runs sind ueber die ganze Historie nur **18,4 %** des Account-Verbrauch ClaudeDo-Task: 87105f5e-c4f4-4af4-ae60-89cd2e153e3c
This commit is contained in:
@@ -30,6 +30,7 @@ public sealed class QueueService : BackgroundService
|
||||
private readonly object _lock = new();
|
||||
private readonly Dictionary<string, QueueSlotState> _queueSlots = new();
|
||||
private bool _usageGateBlocked;
|
||||
private int? _lastEffectiveSlots;
|
||||
|
||||
public QueueService(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||
@@ -124,7 +125,7 @@ public sealed class QueueService : BackgroundService
|
||||
|
||||
await Task.WhenAny(wakeTask, timerTask);
|
||||
|
||||
var maxParallel = await GetMaxParallelAsync(stoppingToken);
|
||||
var maxParallel = await GetEffectiveMaxParallelAsync(stoppingToken);
|
||||
|
||||
var gateDecision = await _usageGate.EvaluateAsync(stoppingToken);
|
||||
await ReportUsageGateTransitionAsync(gateDecision);
|
||||
@@ -198,19 +199,64 @@ public sealed class QueueService : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> GetMaxParallelAsync(CancellationToken ct)
|
||||
/// <summary>
|
||||
/// Configured parallelism, stepped down by <see cref="UsageThrottle"/> ahead of the hard usage
|
||||
/// gate. A missing snapshot (poll hasn't landed / endpoint unreachable) fails open to the
|
||||
/// configured value — a broken usage poll must never stall the queue.
|
||||
/// </summary>
|
||||
private async Task<int> GetEffectiveMaxParallelAsync(CancellationToken ct)
|
||||
{
|
||||
int configured;
|
||||
int softPct, hardPct, gateFivePct, gateSevenPct;
|
||||
try
|
||||
{
|
||||
using var context = _dbFactory.CreateDbContext();
|
||||
var settings = await new AppSettingsRepository(context).GetAsync(ct);
|
||||
return Math.Max(1, settings.MaxParallelExecutions);
|
||||
configured = Math.Max(1, settings.MaxParallelExecutions);
|
||||
softPct = settings.UsageThrottleSoftPct;
|
||||
hardPct = settings.UsageThrottleHardPct;
|
||||
gateFivePct = settings.UsageGateFiveHourPct;
|
||||
gateSevenPct = settings.UsageGateSevenDayPct;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to read max parallel executions; defaulting to 1");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var snapshot = _usageState.Snapshot;
|
||||
if (snapshot is null || _usageState.LastError is not null)
|
||||
{
|
||||
_lastEffectiveSlots = configured;
|
||||
return configured;
|
||||
}
|
||||
|
||||
var effective = UsageThrottle.EffectiveSlots(
|
||||
configured, snapshot.FiveHour?.Utilization, snapshot.SevenDay?.Utilization,
|
||||
softPct, hardPct, gateFivePct, gateSevenPct);
|
||||
|
||||
ReportThrottleTransition(configured, effective, snapshot);
|
||||
return effective;
|
||||
}
|
||||
|
||||
private void ReportThrottleTransition(int configured, int effective, UsageSnapshot snapshot)
|
||||
{
|
||||
if (_lastEffectiveSlots == effective) return;
|
||||
var previous = _lastEffectiveSlots;
|
||||
_lastEffectiveSlots = effective;
|
||||
|
||||
if (previous is null && effective == configured) return; // baseline, nothing to report
|
||||
|
||||
if (effective < configured)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"QueueService: usage throttle stepped to {Effective}/{Configured} slots (5h={FiveHour}%, 7d={SevenDay}%)",
|
||||
effective, configured, snapshot.FiveHour?.Utilization, snapshot.SevenDay?.Utilization);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("QueueService: usage throttle cleared, back to {Configured} slots", configured);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunInSlotAsync(string taskId, CancellationToken ct)
|
||||
|
||||
Reference in New Issue
Block a user