Skip to content

fix(runtime-core): let shutdown close wait for draining leases - #4141

Merged
ScriptedAlchemy merged 2 commits into
masterfrom
fix/4138-close-drains-leases
Oct 16, 2026
Merged

ScriptedAlchemy merged 2 commits into
masterfrom
fix/4138-close-drains-leases

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4138.

Summary

Under load, a gracefully stopped daemon left WAL files behind. store_shutdown_checkpoint_test::graceful_stop_truncates_profile_and_project_store_wals then failed: 3,122,992 bytes of global.db-wal, deterministic in the gate. On a clean origin/master the test failed 8 of 8 runs while a second copy of the test ran beside it. The daemon log shows the cause. At store_close_start, registry::close reported "store runtime still held at shutdown; its WAL is not truncated" for global.db (2 client leases), the project store (2 client leases and 1 graph lease) and sessions.db (3 client leases). The owners had joined, but the tasks holding their leases were still unwinding, and the close scanned once and gave up.

Test plan

  • Load repro. graceful_stop_truncates_profile_and_project_store_wals runs with --runs_per_test=8, in parallel with the same run on origin/master (load-repro script). With the fix it passed 8 of 8 in each of four rounds, while master failed 8, 7, 2 and 2 of 8 in the same rounds.
  • Mutation. With the rescan disabled (every pass is the final pass) and the release-check opt-out still in place, the fix branch fails 16 of 16, the same as master. So the lease drain is what fixes it, not the opt-out.
  • shutdown_close_waits_for_a_lease_released_while_it_runs (runtime-core) releases a client lease 50 ms after close starts and asserts the runtime closes in that same call. The mutation above fails it (closed left 0, right 1). shutdown_close_drains_every_idle_runtime_concurrently still passes, so idle runtimes do not wait behind held ones.
  • bazel test --config=clippy for runtime-core and store-runtime unit_test, dashboard-api, and //crates/tracedecay:daemon_suite (76 of 77 passed). The one failure, capacity_retirement_completes_while_a_maintenance_tick_runs, is a known flake in the gate's flaky log since 2026-10-15. It passes 3 of 3 alone.
  • rustfmt_check_2024 for runtime-core and dashboard-api

Devin Review

@changeset-bot

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bdd758f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +190 to +199
if crate::native_runtime::sleep_until(
std::time::Instant::now() + SHUTDOWN_HOLD_DRAIN_POLL,
)
.await
.is_err()
{
return self
.close_idle_batch(true)
.await
.map(|(batch, _)| closed + batch);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Timer failure masks an incomplete store close

When sleep_until fails with a held store, close_idle_for_shutdown returns success after one final scan. The held store remains mounted without its shutdown checkpoint, and the timer failure disappears.

Learn more

A held runtime needs the polling window to release its lease before it can close. sleep_until returns an error if it cannot admit or join the native timer. Here that failure triggers an immediate final scan, but the scan's successful return overwrites the timer error. If the lease has not released, shutdown succeeds without closing the store or truncating its WAL.

Example: A runtime holds one client lease, and the native timer refuses admission on the first poll. The final scan finds the same lease, closes nothing, and returns Ok(0) instead of reporting the timer failure.

Recommended fix: Preserve the sleep_until error as a typed registry failure; optionally perform the final scan to close newly idle stores, but propagate the timer failure rather than replacing it with the scan result.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@ScriptedAlchemy
ScriptedAlchemy merged commit e4ccb3b into master Oct 16, 2026
10 of 20 checks 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.

flaky: graceful stop leaves WAL when store leases are still held at close

1 participant