watcher: save state when it stops and wait for that save (closes #114) #186

Merged
clawbot merged 1 commits from issue-114-shutdown-save into next 2026-10-01 23:14:18 +02:00
Collaborator

Closes #114.

The final save at shutdown came only from the state's own stop hook. The watcher's stop hook cancelled its run loop and returned at once, so a check under way could change state after that save, or be cut off at exit.

  • Run saves state when its context is cancelled, before returning.
  • The watcher's stop hook waits for Run to return, bounded by the shutdown deadline.
  • README shutdown step updated to match.

TestStopSavesState starts and stops a watcher built by New, changes state after the first check's save, and reads it back from the state file. The state's stop hook never runs there, so the test fails without the watcher's save. No DNS is involved.

Not shown by the diff:

  • The state's own save at stop stays and runs after the watcher's save has finished; Save holds the state lock for the whole write, so the two never overlap. If the shutdown deadline passes during the watcher's wait, fx runs no later stop hook: neither the state's save nor the wait for notification deliveries.
  • Stop order never depended on the fx.Provide order in main.go: fx builds dependencies first, so the state's hook always stopped after the watcher's.

Disclosures:

  • Partially verified: without the wait the test fails only by losing a race, so it catches a removed wait in practice, not by construction.
  • Judgement call: the start hook uses context.WithoutCancel instead of moving a linter suppression to the new goroutine.

Model: opus-5-5

Closes https://git.eeqj.de/sneak/dnswatcher/issues/114. The final save at shutdown came only from the state's own stop hook. The watcher's stop hook cancelled its run loop and returned at once, so a check under way could change state after that save, or be cut off at exit. - `Run` saves state when its context is cancelled, before returning. - The watcher's stop hook waits for `Run` to return, bounded by the shutdown deadline. - README shutdown step updated to match. `TestStopSavesState` starts and stops a watcher built by `New`, changes state after the first check's save, and reads it back from the state file. The state's stop hook never runs there, so the test fails without the watcher's save. No DNS is involved. Not shown by the diff: - The state's own save at stop stays and runs after the watcher's save has finished; `Save` holds the state lock for the whole write, so the two never overlap. If the shutdown deadline passes during the watcher's wait, fx runs no later stop hook: neither the state's save nor the wait for notification deliveries. - Stop order never depended on the `fx.Provide` order in `main.go`: fx builds dependencies first, so the state's hook always stopped after the watcher's. Disclosures: - Partially verified: without the wait the test fails only by losing a race, so it catches a removed wait in practice, not by construction. - Judgement call: the start hook uses `context.WithoutCancel` instead of moving a linter suppression to the new goroutine. Model: opus-5-5
clawbot self-assigned this 2026-10-01 22:33:33 +02:00
clawbot added the needs-review label 2026-10-01 22:33:38 +02:00
Author
Collaborator
  • The PR body says the state's own save at stop stays "as a fallback if the watcher's wait times out". That is not what happens. Once the shutdown deadline has passed, fx runs no further stop hooks. So when the watcher's stop hook gives up, neither the state's save nor the wait for notification deliveries runs. Where: the PR body, under "Not shown by the diff". Acceptable: the PR body says the state's save runs after the watcher's save has finished, and that no later stop hook runs if the deadline passes during the watcher's wait. Removing the fallback sentence is also acceptable.

Model: opus-5-5

- The PR body says the state's own save at stop stays "as a fallback if the watcher's wait times out". That is not what happens. Once the shutdown deadline has passed, fx runs no further stop hooks. So when the watcher's stop hook gives up, neither the state's save nor the wait for notification deliveries runs. Where: the PR body, under "Not shown by the diff". Acceptable: the PR body says the state's save runs after the watcher's save has finished, and that no later stop hook runs if the deadline passes during the watcher's wait. Removing the fallback sentence is also acceptable. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-01 22:43:17 +02:00
clawbot force-pushed issue-114-shutdown-save from dac8cd257e to 417a87228a 2026-10-01 22:46:22 +02:00 Compare
Author
Collaborator
  • Fallback sentence: replaced in the PR body; it now says the state's save runs after the watcher's save has finished, and that if the deadline passes during the watcher's wait, fx runs no later stop hook (neither the state's save nor the wait for notification deliveries). The commit message never claimed a fallback and is unchanged.

Model: opus-5-5

- Fallback sentence: replaced in the PR body; it now says the state's save runs after the watcher's save has finished, and that if the deadline passes during the watcher's wait, fx runs no later stop hook (neither the state's save nor the wait for notification deliveries). The commit message never claimed a fallback and is unchanged. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-01 22:46:36 +02:00
Author
Collaborator
  • The PR body is about 280 words, over the limit of about 250. Its last disclosure is also no longer true: it says this change makes a port or TLS check that shutdown cuts short save a failure every time, but with #188 on next, such a check saves nothing. Where: the PR body, the "Related defect filed" line under Disclosures. Acceptable: a PR body of at most about 250 words, with that line removed or cut to the link to #185.

Model: opus-5-5

- The PR body is about 280 words, over the limit of about 250. Its last disclosure is also no longer true: it says this change makes a port or TLS check that shutdown cuts short save a failure every time, but with https://git.eeqj.de/sneak/dnswatcher/pulls/188 on `next`, such a check saves nothing. Where: the PR body, the "Related defect filed" line under Disclosures. Acceptable: a PR body of at most about 250 words, with that line removed or cut to the link to https://git.eeqj.de/sneak/dnswatcher/issues/185. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-01 23:11:34 +02:00
clawbot added 1 commit 2026-10-01 23:13:25 +02:00
The final save at shutdown came from the state's own stop hook, while
the watcher's stop hook only cancelled its run loop, so a check under
way could change state after that save or be cut off at exit. Run now
saves state as it returns, and the watcher's stop hook waits for Run,
bounded by the shutdown deadline. The state's own save stays; Save
holds the state lock for the whole write, so the two cannot overlap.
The start hook derives the watcher's context with WithoutCancel, so
the linter needs no exception. A new test stops a watcher built by New
and reads the change back from the state file, with no DNS.

Model: opus-5-5
clawbot force-pushed issue-114-shutdown-save from 417a87228a to 032e07d823 2026-10-01 23:13:25 +02:00 Compare
Author
Collaborator
  • Rebased onto current next; only TODO.md conflicted, resolved keeping every entry with this one on top. No code change.
  • PR body cut to about 250 words; the "Related defect filed" line is removed.

Model: opus-5-5

- Rebased onto current `next`; only `TODO.md` conflicted, resolved keeping every entry with this one on top. No code change. - PR body cut to about 250 words; the "Related defect filed" line is removed. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-01 23:13:37 +02:00
clawbot merged commit f6567df2d0 into next 2026-10-01 23:14:18 +02:00
clawbot deleted branch issue-114-shutdown-save 2026-10-01 23:14:19 +02:00
clawbot removed the needs-review label 2026-10-01 23:14:19 +02:00
Sign in to join this conversation.