Skip to content

fix: guarantee tokio runtime release when close rejects#10381

Merged
graphite-app[bot] merged 1 commit into
mainfrom
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects
Jul 22, 2026
Merged

fix: guarantee tokio runtime release when close rejects#10381
graphite-app[bot] merged 1 commit into
mainfrom
07-22-fix_guarantee_tokio_runtime_release_when_close_rejects

Conversation

@shulaoda

@shulaoda shulaoda commented Jul 22, 2026

Copy link
Copy Markdown
Member

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/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.

shulaoda commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add 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.

@shulaoda
shulaoda requested a review from h-a-n-a July 22, 2026 06:44
@graphite-app
graphite-app Bot force-pushed the 07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test branch 2 times, most recently from ab367ba to 872262a Compare July 22, 2026 06:55
@graphite-app
graphite-app Bot force-pushed the 07-22-fix_guarantee_tokio_runtime_release_when_close_rejects branch from 9feff24 to a088d58 Compare July 22, 2026 06:55
@graphite-app
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
graphite-app Bot force-pushed the graphite-base/10381 branch from 872262a to ab000c4 Compare July 22, 2026 07:09
@graphite-app
graphite-app Bot force-pushed the 07-22-fix_guarantee_tokio_runtime_release_when_close_rejects branch from a088d58 to 331b029 Compare July 22, 2026 07:09
@graphite-app
graphite-app Bot changed the base branch from graphite-base/10381 to main July 22, 2026 07:10
@graphite-app
graphite-app Bot force-pushed the 07-22-fix_guarantee_tokio_runtime_release_when_close_rejects branch from 331b029 to abdcd48 Compare July 22, 2026 07:10
@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit d6139c1
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a606e353aebfb000850a43d

shulaoda commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • Jul 22, 7:12 AM UTC: The merge label 'graphite: merge-when-ready' was detected. This PR will be added to the Graphite merge queue once it meets the requirements.
  • Jul 22, 7:14 AM UTC: shulaoda added this pull request to the Graphite merge queue.
  • Jul 22, 7:20 AM UTC: Merged by the Graphite merge queue.

### 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
graphite-app Bot force-pushed the 07-22-fix_guarantee_tokio_runtime_release_when_close_rejects branch from abdcd48 to d6139c1 Compare July 22, 2026 07:16
@graphite-app
graphite-app Bot merged commit d6139c1 into main Jul 22, 2026
32 of 33 checks passed
@graphite-app
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
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.

3 participants