Skip to content

fix(runtime): refuse new widget dev work once closed, and never reject a close - #649

Merged
mrgoonie merged 1 commit into
mainfrom
fix/647-widget-dev-close-followups
Oct 8, 2026
Merged

mrgoonie merged 1 commit into
mainfrom
fix/647-widget-dev-close-followups

Conversation

@mrgoonie

@mrgoonie mrgoonie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #647. Follow-ups from the isolated review of #646 (#639).

What changed

  1. Retry budget assert. The held-snapshot test now checks the total wait of the options actually passed to rm. Node waits retryDelay ms longer on each retry, so the test computes retryDelay * maxRetries * (maxRetries + 1) / 2 and requires at least 1000 ms. The current values give 1500 ms. With maxRetries: 1, retryDelay: 1 the test fails with expected 1 to be greater than or equal to 1000.

  2. Closed flag. close() now sets closed:

    • a later start is refused with 503 WIDGET_DEV_UNAVAILABLE, which already meant "this node is not running sessions"; the EN and VI docs now add "or is closing";
    • a resume() that is still going through the store stops, and the remaining sessions stay live in the store for the next boot.
  3. No unhandled rejection. end() catches and logs an engine that throws on close(), so the other sessions still end and close() never rejects. main.ts also attaches a handler to the close promise when it creates it, because it awaits that promise only after the other shutdown steps.

  4. Close cap with several held snapshots. Once the node is closed, a prune starts no further removal. close waits only for the removal already running, about 1.5 s at most, which fits inside the 2 s cap and the node's 5 s shutdown grace. The skipped snapshots stay on the session's list, and the next prune after an install removes them.

  5. Close during settle. This case cannot happen, and a code comment explains why:

    • the newest build is not yet on the snapshot list when a prune or install runs, because the build is remembered later on the same chain;
    • the answer-poll prune runs only for a live session;
    • with item 4, a prune that runs after close removes nothing.

    A test covers the approval path, where only the engine keeps the newest snapshot: the node closes as the answer arrives, and the newest snapshot is still present.

Tests

The new tests fail against main's widget-dev-sessions.ts:

  • starts no further removal once closing... fails with expected [ …(2) ] to have a length of 1 but got 2
  • ends every session, and resolves, on a close where a watcher fails to let go fails with promise rejected "Error: the watcher would not let go" instead of resolving
  • watches nothing once closed... fails with expected { ok: true, ... } to match object { ok: false, code: 'SESSION_STOPPED' }
  • keeps the newest build's snapshot... is a confirmation test for item 5 and passes on main too, as expected for a case that cannot happen.

The spec mocks startDevEngine from @clarkcant/core as a pass-through that counts closes and can be made to throw.

Overlap

PR #644 (#638) also edits widget-dev-sessions.spec.ts. git merge-tree against its head merges cleanly.

…t a close

- Set a closed flag in close(): a late start is refused with 503
  WIDGET_DEV_UNAVAILABLE and a resume still going through the store stops,
  so nothing watches a folder after close.
- Once closed, a prune starts no further snapshot removal, so close waits
  for the one removal in flight only; leftovers stay listed for the next
  prune. This keeps several held snapshots inside the close bound.
- An engine that throws as it closes is logged, and the other sessions
  still end; main attaches its handler to the close promise as it is made.
- Assert the total retry budget of the removal options actually passed to
  rm, so a tiny budget fails the held-snapshot test.
- Document why a close during settle cannot prune the newest build's
  snapshot, with a test for the approval path that relies on it.

Refs #647
@mrgoonie

mrgoonie commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Review attestation: ready to merge at f6014dd1c04daeec18d8e0a8fff9c1bc63e45acf, reviewed by agent:code-reviewer.

A push to this PR makes this attestation stale; the new head needs its own review.

@mrgoonie
mrgoonie enabled auto-merge (squash) October 8, 2026 04:22
@mrgoonie
mrgoonie merged commit b65b42b into main Oct 8, 2026
22 checks passed
@mrgoonie
mrgoonie deleted the fix/647-widget-dev-close-followups branch October 8, 2026 04:23
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.

widget dev: close() follow-ups (retry-budget assert, closed flag, rejection handler, multi-snapshot cap)

1 participant