Turns the Phase 6.1 Stream B.4 pure-logic ScheduledRecycleScheduler (shipped in PR #79) into a running background feature. A Tier C driver registers its scheduler at startup; the hosted service ticks every TickInterval (default 1 min) and invokes TickAsync on each registered scheduler. Server.Hosting: - ScheduledRecycleHostedService : BackgroundService. AddScheduler(s) must be called before StartAsync — registering post-start throws InvalidOperationException to avoid "some ticks saw my scheduler, some didn't" races. ExecuteAsync loops on Task.Delay(TickInterval, _timeProvider, stoppingToken) + delegates to a public TickOnceAsync method for one tick. - TickOnceAsync extracted as the unit-of-work so tests drive it directly without needing to synchronize with FakeTimeProvider + BackgroundService timing semantics. - Exception isolation: if one scheduler throws, the loop logs + continues to the next scheduler. A flaky supervisor can't take down the tick for every other Tier C driver. - Diagnostics: TickCount + SchedulerCount properties for tests + logs. Tests (7 new ScheduledRecycleHostedServiceTests, all pass): - TickOnce before interval doesn't fire; TickCount still advances. - TickOnce at/after interval fires the underlying scheduler exactly once. - Multiple ticks accumulate count. - AddScheduler after StartAsync throws. - Throwing scheduler doesn't poison its neighbours (logs + continues). - SchedulerCount matches registrations. - Empty scheduler list ticks cleanly (no-op + counter advances). Full solution dotnet test: 1193 passing (was 1186, +7). Pre-existing Client.CLI Subscribe flake unchanged. Production wiring (Program.cs): builder.Services.AddSingleton<ScheduledRecycleHostedService>(); builder.Services.AddHostedService(sp => sp.GetRequiredService<ScheduledRecycleHostedService>()); // During DI configuration, once Tier C drivers + their ScheduledRecycleSchedulers // are resolved, call host.AddScheduler(scheduler) for each. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.5 KiB
5.5 KiB