fix: guarantee tokio runtime release when close rejects#10381
Merged
graphite-app[bot] merged 1 commit intoJul 22, 2026
Merged
Conversation
Member
Author
How to use the Graphite Merge QueueAdd the label graphite: merge-when-ready to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
graphite-app
Bot
force-pushed
the
07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test
branch
2 times, most recently
from
July 22, 2026 06:55
ab367ba to
872262a
Compare
graphite-app
Bot
force-pushed
the
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
branch
from
July 22, 2026 06:55
9feff24 to
a088d58
Compare
graphite-app
Bot
changed the base branch from
07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test
to
graphite-base/10381
July 22, 2026 07:05
graphite-app
Bot
force-pushed
the
graphite-base/10381
branch
from
July 22, 2026 07:09
872262a to
ab000c4
Compare
graphite-app
Bot
force-pushed
the
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
branch
from
July 22, 2026 07:09
a088d58 to
331b029
Compare
graphite-app
Bot
force-pushed
the
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
branch
from
July 22, 2026 07:10
331b029 to
abdcd48
Compare
IWANABETHATGUY
approved these changes
Jul 22, 2026
✅ Deploy Preview for rolldown-rs canceled.
|
h-a-n-a
approved these changes
Jul 22, 2026
Member
Author
Merge activity
|
### Description Follow-up to #10363, addressing [this review comment](#10363 (comment)). #10363 made the wasm tokio runtime refcount strictly paired: acquire on create, release on close. But every release still ran as a plain statement after the awaited close calls. If any of those awaits rejects, the release is skipped and the acquire leaks. The count then stays above zero, the runtime is never torn down, and on wasm the park threads hang forever, which is the #8747 symptom in reverse. This is not a regression, the old code had the same shape, but it leaves the pairing guaranteed only on the success path. This PR wraps each release in `try/finally` so it fires no matter how the close settles: - `RolldownBuild.close()` - `DevEngine.close()` - the idempotent `cleanup()` in `scan()` - `Watcher.close()`, which the comment did not name but has the identical pattern The documented ordering is unchanged. `finally` runs only after the awaited close settles, whether it resolves or rejects, so the release still happens strictly after `BindingBundler.close()` is done spawning onto the runtime. The claim-before-first-await guard against double release is also untouched, and the whole change remains a no-op on native targets, where `startAsyncRuntime` and `shutdownAsyncRuntime` compile to empty functions. One pre-existing gap is deliberately left alone: when an early step rejects (for example `stopWorkers()`), the later close calls in the same method are still skipped. This PR only guarantees the runtime release. Making every cleanup step run independently is a separate concern. <!-- - What is this PR solving? Write a clear and concise description. - Reference the issues it solves (e.g. `fixes #123`). - What other alternatives have you explored? - Are there any parts you think require more attention from reviewers? Also, please make sure you do the following: - Read the Contributing Guidelines at https://rolldown.rs/contribution-guide/. - Check that there isn't already a PR that solves the problem the same way. If you find a duplicate, please help us review it. - Update the corresponding documentation if needed. - Include relevant tests that fail without this PR but pass with it. If the tests are not included, explain why. Thank you for contributing to Rolldown! -->
graphite-app
Bot
force-pushed
the
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
branch
from
July 22, 2026 07:16
abdcd48 to
d6139c1
Compare
graphite-app
Bot
deleted the
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
branch
July 22, 2026 07:20
Brooooooklyn
added a commit
that referenced
this pull request
Jul 22, 2026
Catches the branch up to main so CI can run again (the PR was CONFLICTING against the advanced base) and pulls in the fixes that target this branch's red dev-server tests: #10380 test(dev): expect preserved hot.data in hmr-whole-chain-dispose — Vite's rolldown-canary client now preserves import.meta.hot.data; this branch had the stale pre-#10380 assertion. Taken from main. #10363/#10381 tokio runtime acquire/release pairing + release-on-reject. Conflicts resolved to this branch's side (--ours) — the branch's shared tokio-free runtime supersedes main's tokio-refcount work: - dev-engine.ts / experimental.ts / rolldown-build.ts / watch/watcher.ts: main wraps shutdownAsyncRuntime() in try/finally (#10381). This branch already releases via the RuntimeLease abstraction inside a `finally` (release-on-reject guaranteed) plus AggregateError cleanup — a strict superset, so #10381 is subsumed. Verified main touched only the lease/startAsyncRuntime lines in these files. - crates/rolldown_binding/src/lib.rs + generated binding.d.cts: main adds the ACTIVE_TASK_COUNT tokio refcount to start/shutdown_async_runtime (#10363). This branch removed tokio, so those exports are compatibility no-ops (napi env lifecycle owns teardown; the legacy tokio-WASI path uses the old binding's own code). Verified main's changes here are entirely the refcount + its doc comments. No lockfile/catalog conflicts: main touched no manifests in aa73a68..c07a55c. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Follow-up to #10363, addressing this review comment.
#10363 made the wasm tokio runtime refcount strictly paired: acquire on create, release on close. But every release still ran as a plain statement after the awaited close calls. If any of those awaits rejects, the release is skipped and the acquire leaks. The count then stays above zero, the runtime is never torn down, and on wasm the park threads hang forever, which is the #8747 symptom in reverse. This is not a regression, the old code had the same shape, but it leaves the pairing guaranteed only on the success path.
This PR wraps each release in
try/finallyso it fires no matter how the close settles:RolldownBuild.close()DevEngine.close()cleanup()inscan()Watcher.close(), which the comment did not name but has the identical patternThe documented ordering is unchanged.
finallyruns only after the awaited close settles, whether it resolves or rejects, so the release still happens strictly afterBindingBundler.close()is done spawning onto the runtime. The claim-before-first-await guard against double release is also untouched, and the whole change remains a no-op on native targets, wherestartAsyncRuntimeandshutdownAsyncRuntimecompile to empty functions.One pre-existing gap is deliberately left alone: when an early step rejects (for example
stopWorkers()), the later close calls in the same method are still skipped. This PR only guarantees the runtime release. Making every cleanup step run independently is a separate concern.