Phase 6.1 Stream B.4 follow-up - ScheduledRecycleHostedService #101

Merged
dohertj2 merged 1 commits from phase-6-1-stream-b4-hosted-service into v2 2026-04-19 11:44:17 -04:00
Owner

Turns the pure-logic ScheduledRecycleScheduler into a running background feature.

Summary

  • ScheduledRecycleHostedService : BackgroundService. AddScheduler before StartAsync; post-start throws.
  • TickOnceAsync public method extracted as the unit-of-work so tests drive it directly, bypassing BackgroundService + FakeTimeProvider timing semantics.
  • Exception isolation: one scheduler throwing doesn’t take out its neighbours.
  • TickCount + SchedulerCount diagnostics properties.

Test plan

  • 7 new tests: before/after-interval behaviour, multiple-tick accumulation, post-start AddScheduler rejection, throwing-scheduler isolation, SchedulerCount accuracy, empty-list clean tick.
  • Full solution dotnet test: 1193 passing (was 1186, +7).

Production wiring example

builder.Services.AddSingleton<ScheduledRecycleHostedService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<ScheduledRecycleHostedService>());
// Register each Tier C ScheduledRecycleScheduler via host.AddScheduler()

🤖 Generated with Claude Code

Turns the pure-logic ScheduledRecycleScheduler into a running background feature. ## Summary - `ScheduledRecycleHostedService : BackgroundService`. AddScheduler before StartAsync; post-start throws. - `TickOnceAsync` public method extracted as the unit-of-work so tests drive it directly, bypassing BackgroundService + FakeTimeProvider timing semantics. - Exception isolation: one scheduler throwing doesn’t take out its neighbours. - TickCount + SchedulerCount diagnostics properties. ## Test plan - [x] 7 new tests: before/after-interval behaviour, multiple-tick accumulation, post-start AddScheduler rejection, throwing-scheduler isolation, SchedulerCount accuracy, empty-list clean tick. - [x] Full solution `dotnet test`: 1193 passing (was 1186, +7). ## Production wiring example ```csharp builder.Services.AddSingleton<ScheduledRecycleHostedService>(); builder.Services.AddHostedService(sp => sp.GetRequiredService<ScheduledRecycleHostedService>()); // Register each Tier C ScheduledRecycleScheduler via host.AddScheduler() ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dohertj2 added 1 commit 2026-04-19 11:43:57 -04:00
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>
dohertj2 merged commit 0c903ff4e0 into v2 2026-04-19 11:44:17 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#101