Worker classification¶
Verified by tests
ClaimWorkerTests, ClaimWorkerGateCadenceTests, HeartbeatWorkerAdaptiveCadenceTests, DeadLetterRecoveryWorkerTests, TransportDeadLetterDrainWorkerTests, MaintenanceWorkerTests, IntegrityCheckpointWorkerTests, IntegrityAuditWorkerTests, SubscriptionExpansionWorkerTests, BackupTickCoordinatorTests — library CI run #31657041675 (2026-08-13)
Every Whizbang background worker falls into one of six classes. The class tells operators "what wakes this worker?" and tells contributors "what should I touch when changing this worker's cadence?"
Legend¶
| Class | Meaning |
|---|---|
| A — NOTIFY-driven | Subscribes to IWorkNotificationListener.OnSignal for its primary wake. Polling may still exist as a safety-net. |
| B — Channel-driven | Wakes on a Channel<T> / BatchFlusher<T> write from an in-process producer. No DB polling. |
| C — Transport-driven | Wakes on a broker (RabbitMQ / ASB) message arrival. Not a DB concern. |
| D — Polling, NOTIFY-eligible | Has a Task.Delay / PeriodicTimer loop AND a NOTIFY signal would semantically apply if wired. Highest-value conversion candidates. |
| E — Necessarily timed | Heartbeat / TTL sweep / scheduled maintenance — must be timer-driven by definition. NOTIFY doesn't apply. |
| F — One-shot / lifecycle | Runs once at start / shutdown / migration. Not a polling concern. |
As of v0.681, zero D-class workers remain — every previously-polling worker either converted to A (NOTIFY-driven) or stayed at the only-makes-sense-as-timed E class.
Per-worker table¶
| Worker | Class | Cadence / driver | Notes |
|---|---|---|---|
ClaimWorker |
A | OnSignal + safety-net poll — NotifyHealthyPollingIntervalMilliseconds (default 5 s) when NOTIFY is healthy, tight PollingIntervalMilliseconds (250 ms base, adaptive backoff) otherwise |
Outbox/Inbox/Perspective/OrphanRedistribute via _onSignal; EnableSafetyNetPoll=false gives pure NOTIFY-only wakes |
PerspectiveWorker |
A | WorkSignalCategory.Perspective + NotifyHealthyPollingIntervalMilliseconds safety-net (default 1 s, equal to the poll interval; raise to relax) |
v0.681 slice 7a wired the previously-unused signal |
DeadLetterRecoveryWorker |
A | WorkSignalCategory.DeadLetterReady + ScanIntervalMinutes backstop (10 min) |
v0.681 slice 7c added the AFTER INSERT trigger |
TransportDeadLetterDrainWorker |
E (push planned) | Timer poll — IntervalMinutes (default 10 min) |
v0.681 slice 7d added the ITransport.SubscribeToDeadLetterAsync push contract (default throws NotSupportedException), but neither this worker nor any shipped transport wires it yet — polling is the only active path today |
OutboxPublishWorker |
B | IWorkChannelWriter |
Drained when ClaimWorker dispatches |
InboxHandlerWorker |
B | BatchFlusher<HandlerCommitRequest> |
|
InboxDispatchWorker |
B | IInboxChannelWriter |
|
InboxDrainWorker |
B | IInboxDrainChannel |
|
OutboxDrainWorker |
B | IOutboxDrainChannel |
|
OutboxCompletionFlushWorker |
B | BatchFlusher<Guid> |
|
PerspectiveCompletionFlushWorker |
B | BatchFlusher |
|
FailureFlushWorker |
B | BatchFlusher |
|
LeaseRenewalWorker |
B | BatchFlusher<CategorizedLeaseRenewal> |
|
TransportConsumerWorker |
C | Transport subscription | |
ServiceBusConsumerWorker |
C | ASB receiver loop | |
HeartbeatWorker |
E | 30 s default; adaptive 60 s when alive-lock held (slice 7b) | See instance liveness |
MaintenanceWorker |
E | 10 min default (IntervalMinutes) |
Full-table scan; not event-driven |
RecentlyProcessedEventCacheSweepWorker |
E | 60 s (SweepIntervalSeconds) |
In-memory TTL eviction |
IntegrityCheckpointWorker |
E | CheckpointIntervalSeconds (default 60 s) |
Stream-integrity Phase B origin-side checkpoint publisher; publishes even on empty windows |
IntegrityAuditWorker |
E | AuditIntervalMinutes (default 1440 — daily) |
Stream-integrity Phases A + L deep audit |
BackupTickCoordinator |
E | Idle-aware: ASLEEP until IdleThreshold (30 s), then ticks every PollingInterval (30 s; FastPollingInterval 5 s when NOTIFY broken) |
Runs registered backup ticks only when NOTIFY-driven activity isn't expected |
OrphanInboxJanitor |
F | StartAsync once (startup purge sweep; ExecuteAsync is a no-op) |
|
SubscriptionExpansionWorker |
F | Runs once after the schema gate | Stream-integrity Phase S startup reconciler; baselines/backfills consumed-type expansions |
WhizbangShutdownService |
F | Shutdown only (StartAsync is a no-op) |
|
PerspectiveMigrationWorker |
F | On-demand rebuild |
When to read this page¶
- Adding a new worker → pick the right class and cite the existing examples.
- Reviewing a worker's cadence → confirm the class is honoured (e.g. an A-class worker MUST have a NOTIFY subscription, not just a backstop poll).
- Investigating a "why isn't this worker waking" → start with the column "Cadence / driver" and trace it to the source.