Skip to content

Stopping the high water agent cancels its poll wait (JasperFx/wolverine#5318) - #1100

Merged
jeremydmiller merged 3 commits into
mainfrom
wolverine-5318-high-water-stop
Nov 2, 2026
Merged

jeremydmiller merged 3 commits into
mainfrom
wolverine-5318-high-water-stop

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Refs JasperFx/wolverine#5318

JasperFxAsyncDaemon.StopAllAsync calls HighWaterAgent.StopAsync, which awaits the poll loop. The loop's wait (IDaemonWakeup.WaitAsync with FastPollingTime or SlowPollingTime) was only tied to the daemon's own token, which a stop does not cancel, so every stop waited out the rest of the current tick: about 250 ms after activity, up to 1 s after a quiet spell. Wolverine's PauseThenCatchUpOnMartenDaemonActivity pays that on every tracked call.

Change

  • Each poll loop gets its own CancellationTokenSource, linked to the daemon token and created in launchAsync, so every start (and every watchdog restart) has a fresh one and stop-then-start keeps working.
  • The loop's waits use that token. StopAsync cancels it before awaiting the loop; the cancelled wait is the normal exit path (waitAsync already swallows it, and the loop then sees IsRunning == false and leaves).
  • A restart cancels and disposes the retired loop's source, so an abandoned loop parked in a wait exits immediately instead of on its next tick. The generation guard is unchanged.
  • Detect calls still use the daemon token; only the waits changed.

I looked at the other daemon loops. Subscription agents are fed by the high-water mark rather than polling, and the coordinator loops in ProjectionCoordinatorBase already wait on their stopping token, so nothing else has this shape.

Tests

New HighWaterAgent_stop_cancels_poll_wait_Tests: FastPollingTime / SlowPollingTime = 5 s, stop must return in under 1 s, then start again, check the new loop polls, and stop again in under 1 s.

  • Without the fix: fails, stop took 4.9 s.
  • With the fix: passes (488 ms total).
  • EventTests --filter-namespace "EventTests.Daemon*": 499/499 on net9.0 and 499/499 on net10.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U

…ne#5318)

HighWaterAgent.StopAsync awaited the poll loop, but the loop only noticed the
stop after its current FastPollingTime / SlowPollingTime wait ran out, so
every daemon pause (StopAllAsync) cost up to a full polling interval. Each
loop now gets its own CancellationTokenSource linked to the daemon token;
StopAsync cancels it before awaiting the loop, a restart cancels the retired
loop's source, and every launch creates a fresh one so stop-then-start works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCggXgxnG3EBXoR9rrq37U
jeremydmiller and others added 2 commits November 1, 2026 19:32
@jeremydmiller
jeremydmiller merged commit 1d13691 into main Nov 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant