Skip to content

test(wasi): add tokio runtime lifecycle regression test - #10379

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

test(wasi): add tokio runtime lifecycle regression test#10379
graphite-app[bot] merged 1 commit into
mainfrom
07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test

Conversation

@shulaoda

@shulaoda shulaoda commented Jul 22, 2026

Copy link
Copy Markdown
Member

Description

Follow-up to #10363, which must land first. Adds regression coverage for the Access tokio runtime failed in spawn crash class fixed there.

This bug class was invisible to every existing CI test. The runtime acquire and release functions are no-ops on native targets, and no CI test actually executed the WASI binding: the WASI workflow builds the wasm binding and runs the stability test and the examples, but those load the native binding that the same job builds for tooling. So an unbalanced runtime release could only ever crash on StackBlitz, never in CI.

The new tests/wasi/runtime-lifecycle.mjs is a plain node script following the test:stability pattern, wired into the WASI workflow right after the build step. It forces the real wasm binding and covers three sequences:

Two details are load-bearing. NAPI_RS_FORCE_WASI is set to error inside the script before importing rolldown, because true silently falls back to the native binding when the wasi files are missing and the test would pass without testing anything. After the import the script also asserts via require.cache that rolldown-binding.wasi.cjs was really loaded.

Verification

Red and green were produced under identical conditions (same machine, same stable toolchain, napi 3.11.0 on both sides, wasm binding forced and asserted). On main (aa73a6848, without #10363) the test exits 1 in the first sequence with the exact issue signature: Access tokio runtime failed in spawn in napi's tokio_runtime.rs, followed by RuntimeError: unreachable. On top of #10363 all three sequences pass.

To run locally: pnpm build-wasi:debug in packages/rolldown (dist only contains the wasi files when built with TARGET=rolldown-wasi), then pnpm test:wasi-runtime in packages/rolldown/tests.

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 marked this pull request as ready for review July 22, 2026 06:29
@shulaoda
shulaoda requested a review from h-a-n-a July 22, 2026 06:42
@graphite-app
graphite-app Bot changed the base branch from 07-20-fix_pair_tokio_runtime_acquire_and_release_on_wasm_to_prevent_premature_shutdown to graphite-base/10379 July 22, 2026 06:46

shulaoda commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • Jul 22, 6:48 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:00 AM UTC: shulaoda added this pull request to the Graphite merge queue.
  • Jul 22, 7:09 AM UTC: Merged by the Graphite merge queue.

@graphite-app
graphite-app Bot force-pushed the 07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test branch from 7e23056 to ab367ba Compare July 22, 2026 06:54
@graphite-app
graphite-app Bot force-pushed the graphite-base/10379 branch from 6535e5d to 77c8ff9 Compare July 22, 2026 06:54
@graphite-app
graphite-app Bot changed the base branch from graphite-base/10379 to main July 22, 2026 06:55
@graphite-app
graphite-app Bot force-pushed the 07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test branch from ab367ba to 872262a Compare July 22, 2026 06:55

@hyfdev hyfdev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The three scenarios cover the failure modes from #8411, #8747, and the shared invariant behind both fixes. The test forces the WASI binding with NAPI_RS_FORCE_WASI=error, verifies it loaded, and CI confirms all three pass on this head.

— Review by @hyfdev, assisted by deepseek-v4-pro

@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs canceled.

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

### Description

Follow-up to #10363, which must land first. Adds regression coverage for the `Access tokio runtime failed in spawn` crash class fixed there.

This bug class was invisible to every existing CI test. The runtime acquire and release functions are no-ops on native targets, and no CI test actually executed the WASI binding: the WASI workflow builds the wasm binding and runs the stability test and the examples, but those load the native binding that the same job builds for tooling. So an unbalanced runtime release could only ever crash on StackBlitz, never in CI.

The new `tests/wasi/runtime-lifecycle.mjs` is a plain node script following the `test:stability` pattern, wired into the WASI workflow right after the build step. It forces the real wasm binding and covers three sequences:

- The #8411 shape: a build that gets closed (Vite bundling `vite.config.ts`), then a `DevEngine` is created and run.
- The #8747 shape: a build that gets closed, then `watch()` runs until `BUNDLE_END`.
- The invariant behind both: closing one consumer while a `DevEngine` is still alive must not tear the runtime down under the engine.

Two details are load-bearing. `NAPI_RS_FORCE_WASI` is set to `error` inside the script before importing rolldown, because `true` silently falls back to the native binding when the wasi files are missing and the test would pass without testing anything. After the import the script also asserts via `require.cache` that `rolldown-binding.wasi.cjs` was really loaded.

### Verification

Red and green were produced under identical conditions (same machine, same stable toolchain, napi 3.11.0 on both sides, wasm binding forced and asserted). On `main` (`aa73a6848`, without #10363) the test exits 1 in the first sequence with the exact issue signature: `Access tokio runtime failed in spawn` in napi's `tokio_runtime.rs`, followed by `RuntimeError: unreachable`. On top of #10363 all three sequences pass.

To run locally: `pnpm build-wasi:debug` in `packages/rolldown` (dist only contains the wasi files when built with `TARGET=rolldown-wasi`), then `pnpm test:wasi-runtime` in `packages/rolldown/tests`.

<!--
- 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-test_wasi_add_tokio_runtime_lifecycle_regression_test branch from 872262a to ab000c4 Compare July 22, 2026 07:05
@graphite-app
graphite-app Bot merged commit ab000c4 into main Jul 22, 2026
32 of 33 checks passed
@graphite-app
graphite-app Bot deleted the 07-22-test_wasi_add_tokio_runtime_lifecycle_regression_test branch July 22, 2026 07:09
Brooooooklyn added a commit that referenced this pull request Jul 22, 2026
#10379 added tests/wasi/runtime-lifecycle.mjs to regression-cover the
"Access tokio runtime failed in spawn" crash class (#8411/#8747), driving
dev() and watch() on the WASI binding to exercise the shared *tokio*
runtime's acquire/release refcount.

This branch is tokio-free: the WASI binding runs the shared CurrentThread
runtime owned by the N-API environment (the JS runtime leases are no-ops),
so that refcount and its crash class cannot exist; and dev()/watch() are
unsupported on the WASI artifact by design. The imported test therefore
failed at the first dev() call with ERR_ROLLDOWN_UNSUPPORTED_RUNTIME_FEATURE.

Adapted to the tokio-free equivalents while keeping the WASI-binding
force-load + assertion that made the lane valuable:
  1. repeated sequential build+close — runtime survives a consumer closing
  2. two builds alive, close one — the other still works (#8411/#8747
     invariant via the WASI-supported API)
  3. dev() rejects synchronously with the unsupported-runtime code
  4. watch() reports unsupported through its ERROR-event lifecycle
Verified each assertion against the code: dev-engine.ts assertRuntimeFeature
('dev'), watch/index.ts + watch-emitter.ts failSetup emitting {code:'ERROR'}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda
@rolldown-guard rolldown-guard Bot mentioned this pull request Jul 29, 2026
shulaoda added a commit that referenced this pull request Jul 29, 2026
## [1.2.1] - 2026-07-29

### 🚀 Features

- dev: support `hotUpdate` hook (#10305) by @h-a-n-a
- dev: expose `disableWatcher` through the dev engine bindings (#10474) by @shulaoda
- plugin: surface output-option callbacks in [PLUGIN_TIMINGS] (#10411) by @IWANABETHATGUY
- `import.meta.ROLLDOWN_FILE_URL_<referenceId>_<urlId>` support to pass context to `resolveFileUrl` hook (#10297) by @sapphi-red
- dev: skip the HMR update when a module's rebuilt output is unchanged (#10333) by @h-a-n-a
- code-splitting: wrap strict execution order modules on demand (#10104) by @hyfdev
- support `import.meta.ROLLDOWN_FILE_URL_*` (#10296) by @sapphi-red
- implement `resolveFileUrl` plugin hook (#10291) by @sapphi-red
- magic-string: support storeName on overwrite/update (#10312) by @IWANABETHATGUY

### 🐛 Bug Fixes

- external: keep `__toESM` when the importing module is tree-shaken (#10516) by @IWANABETHATGUY
- dev: resolve re-exports from externals through a real import (#10489) by @tbvjaos510
- code-splitting: route CJS barrel initialization per consumer (#10488) by @hyfdev
- error: align filename-pattern diagnostics with Rollup's wording (#10501) by @IWANABETHATGUY
- plugin: validate emitted prebuilt-chunk file names (#10399) by @IWANABETHATGUY
- code-splitting: point a two-argument dynamic import at the chunk that holds the module (#10479) by @hyfdev
- common: align is_path_fragment with Rollup's isPathFragment (#10398) by @IWANABETHATGUY
- code-splitting: never name an internal chunk export `then` (#10480) by @hyfdev
- code-splitting: avoid creating call-site-triggered entry facades (#10485) by @hyfdev
- code-splitting: collect top-level eager order reasons in every strict build (#10463) by @hyfdev
- code-splitting: let a cross-chunk dynamic importer carry the order trigger (#10456) by @hyfdev
- code-splitting: avoid the dynamic-entry facade when the same-chunk collapse carries the trigger (#10433) by @hyfdev
- binding: forward resolveId options.kind to callable builtin plugins (#10440) by @martijnwalraven
- dev: keep the export name of `export * as ns from` (#10476) by @tbvjaos510
- code-splitting: only split an entry facade when something else can load its chunk (#10441) by @hyfdev
- code-splitting: fold the runtime chunk back after order lowering (#10414) by @hyfdev
- runtime: don't leave default undefined for cjs that fakes __esModule (#10453) by @IWANABETHATGUY
- import-glob: escape generated string literals (#10438) by @shantanuraj
- preserve-modules: don't assume every chunk mirrors a module (#10442) by @hyfdev
- filter: treat missing hook inputs as non-matches, not panics (#10443) by @IWANABETHATGUY
- code-splitting: keep CommonJS modules wrapped under strict execution order (#10405) by @hyfdev
- code-splitting: align dynamicImports metadata with the rewritten import() specifier (#10430) by @hyfdev
- dev: skip user plugin hooks for ?rolldown-lazy proxy modules (#10426) by @unknownjedi
- treeshake: preserve side-effect-free spread arguments in manual-pure chains (#10432) by @IWANABETHATGUY
- treeshake: preserve eagerly evaluated child effects of manual pure chains (#10427) by @Nic-Polumeyv
- code-splitting: initialize pure definers behind star re-export barrels under strict execution order (#10409) by @hyfdev
- resolveId: reject string `id` filters (must be a RegExp) (#10412) by @IWANABETHATGUY
- code-splitting: collect eager order reasons for on-demand wrapping (#10387) by @hyfdev
- rolldown: apply output.paths to CJS export-star of external modules (#10406) by @IWANABETHATGUY
- dev: sourcemaps of lazily compiled chunks (#10386) by @tbvjaos510
- plugin: validate emitted file names at emitFile time (#10377) by @Nic-Polumeyv
- guarantee tokio runtime release when close rejects (#10381) by @shulaoda
- code-splitting: make strict transitive init registration deterministic and constant-time (#10320) by @hyfdev
- pair tokio runtime acquire and release on wasm to prevent premature shutdown (#10363) by @shulaoda
- don't body-demand modules from simulated facade chunk includes (#10351) by @IWANABETHATGUY
- tracing: invalid RD_LOG filter and RD_LOG_OUTPUT=json no longer panic (#10343) by @Brooooooklyn
- code-splitting: treat class definition-time global reads as order-sensitive (#10322) by @hyfdev
- code-splitting: deconflict force-included runtime helper statements (#10336) by @hyfdev
- binding: forward all missing parallel JS plugin hooks (#10345) by @Brooooooklyn
- code-splitting: re-derive chunk exec order after the runtime sweep (#10334) by @hyfdev
- types: add undefined to ExistingRawSourceMap optional properties (#10355) by @ocavue
- magic-string: validate relocate ranges before rewiring (#10327) by @IWANABETHATGUY
- add EMPTY_IMPORT_META warning for non-node CJS output (#10222) by @sapphi-red
- call `resolveFileUrl` in a deterministic order (#10330) by @sapphi-red
- magic-string: refuse repeated sendMagicString on a consumed instance (#10331) by @IWANABETHATGUY
- magic-string: refuse to use a MagicString after sendMagicString consumed it (#10326) by @IWANABETHATGUY
- magic-string: reject non-contiguous moves before rewiring the chunk list (#10311) by @IWANABETHATGUY
- handle `./foo/bar` glob pattern the same as `foo/bar` (#10313) by @sapphi-red
- magic-string: throw instead of panicking when append/prepend splits an edited chunk (#10301) by @IWANABETHATGUY
- output: skip separator for empty hoisted functions (#10310) by @hyfdev
- magic-string: don't report a change when edits cancel out (#10299) by @IWANABETHATGUY
- sourcemap: map wrapped imported callees (#10288) by @hyfdev
- magic-string: count positional inserts at index 0 on an empty source (#10306) by @IWANABETHATGUY
- magic-string: return UTF-16 code units from length() (#10295) by @IWANABETHATGUY
- output: skip separator for empty import prelude (#10289) by @hyfdev

### 🚜 Refactor

- dev: make it easier to customize the runtime (#10338) by @sapphi-red
- code-splitting: declare CJS carrier namespaces initializer-free (#10513) by @hyfdev
- rollup-tests: avoid relying on `NODE_PATH` env var resolution (#10496) by @sapphi-red
- code-splitting: derive symbol chunk ownership as pass-local link data (#10449) by @hyfdev
- plugin: share asset emission helpers between copy_module and asset_module (#10450) by @Nic-Polumeyv
- plugin: dedupe hook binding boilerplate (#10428) by @Nic-Polumeyv
- plugin: share the oxc parse preamble between import-glob and dynamic-import-vars (#10452) by @Nic-Polumeyv
- implement `GetAstBuilder` on visitor/pass types (#10424) by @overlookmotel
- pass arrays to AST builder methods (#10419) by @overlookmotel
- shorten AST builder code (#10418) by @overlookmotel
- centralize final ESM init metadata (#10376) by @hyfdev
- descriptor-based parallel-plugin detection (getParallelPluginInfo) (#10349) by @Brooooooklyn
- remove AstFactory in favor of new_* construction traits (#10353) by @Boshen
- enable oxc_ast's `disable_old_builder` feature (#10316) by @Boshen

### 📚 Documentation

- development-guide: update config.json link line reference (#10523) by @dogledogle
- clarify slash normalization for id hook filter (#10511) by @sapphi-red
- code-splitting: record the thenable-namespace rule (#10502) by @hyfdev
- clarify that `esmExternalRequirePlugin` must own its externals (#10439) by @TheAlexLichter
- add troubleshooting entry for the @rolldown/binding-... resolution error (#10390) by @Nic-Polumeyv
- add Code Splitting API reference category (#10382) by @vittorioexp

### ⚡ Performance

- use VisitJsMut in JavaScript-only AST passes (#10507) by @Boshen
- cache the resolved glob matcher for Id/ImporterId filters (#10410) by @IWANABETHATGUY
- scope_hoisting: remove `alloc` field from `ScopeHoistingFinalizer` (#10425) by @overlookmotel
- reduce string allocations (#10423) by @overlookmotel
- hmr: remove unnecessary string allocations (#10422) by @overlookmotel
- avoid allocating static strings via custom builder methods (#10421) by @overlookmotel
- pass string literals to AST builder methods (#10420) by @overlookmotel
- use VisitJs in JavaScript-only AST passes (#10396) by @Boshen
- plugin: shrink erased plugin debug vtables (#10370) by @Boshen
- reuse normalized path buffers (#10315) by @hyfdev

### 🧪 Testing

- code-splitting: pin CJS barrel fallback triggers and flag-off equivalence (#10514) by @hyfdev
- plugin: cover chunk fileName, absolute chunk name, and Windows drive names (#10400) by @IWANABETHATGUY
- code-splitting: cover a single-chunk dynamic import of a wrapped module (#10505) by @hyfdev
- file: remove the unused .dynamic_import fixture (#10481) by @hyfdev
- treeshake: reduce manual-pure fixture to a minimal smoke test, move matrix to unit tests (#10436) by @IWANABETHATGUY
- dev: run vite playgrounds under bundled dev (test-serve-bundled) (#10434) by @h-a-n-a
- stop snapshots from silently hiding user code inside the runtime region (#10431) by @hyfdev
- wasi: add tokio runtime lifecycle regression test (#10379) by @shulaoda
- dev: expect preserved hot.data in hmr-whole-chain-dispose (#10380) by @h-a-n-a
- code-splitting: pin flag-off wrapped-esm init emission (#10324) by @hyfdev
- sourcemap: fix Windows-only failures in composition fixtures (#10368) by @shulaoda
- generated-code: mark assert external in symbols_ns2 for deterministic output (#10339) by @hyfdev
- code-splitting: cover entries-aware strict init cycle (#10307) by @hyfdev
- make `just setup-vite` the only entry point that touches `vite/` (#10332) by @shulaoda
- vite-tests: reuse the shared root vite checkout (#10325) by @shulaoda
- test-dev-server: drop the vite submodule, track rolldown-canary (#10319) by @shulaoda
- vite-tests: track the latest rolldown-canary rebased onto vite main (#10318) by @shulaoda
- unify Vite in vite-test and test-dev-server (#10293) by @h-a-n-a

### ⚙️ Miscellaneous Tasks

- scope the binding artifact download to `bindings-*` (#10525) by @shulaoda
- deps: update napi (#10506) by @renovate[bot]
- deps: update rollup submodule for tests to v4.62.3 (#10494) by @rolldown-guard[bot]
- deps: upgrade oxc to 0.142.0 (#10497) by @shulaoda
- deps: update test262 submodule for tests (#10495) by @rolldown-guard[bot]
- deps: update github actions (#10460) by @renovate[bot]
- deps: update rust crates (#10462) by @renovate[bot]
- deps: update npm packages (#10461) by @renovate[bot]
- metric: alert Discord when the metric workflow fails (#10447) by @IWANABETHATGUY
- metric: mint a short-lived GitHub App token instead of a PAT (#10446) by @IWANABETHATGUY
- repo: repair dangling CLAUDE.md symlink (#10451) by @hanayashiki
- publint/package-metadata fixes and small correctness fixes (#10344) by @Brooooooklyn
- deps: update dependency vite-plus to v0.2.6 (#10404) by @renovate[bot]
- add Semgrep scan workflow (#10395) by @Boshen
- enable Windows CI with a PR label (#10375) by @hyfdev
- deps: upgrade oxc to 0.141.0 (#10373) by @camc314
- deps: update napi (#10369) by @renovate[bot]
- deps: update test262 submodule for tests (#10367) by @rolldown-guard[bot]
- deps: update taiki-e/install-action action to v2.83.3 (#10358) by @renovate[bot]
- deps: update rust crates (#10357) by @renovate[bot]
- deps: update npm packages (#10359) by @renovate[bot]
- deps: update rust crate syn to v3 (#10362) by @renovate[bot]
- deps: update rust crate oxc_sourcemap to v8.1.2 (#10356) by @renovate[bot]
- deps: update dependency vite-plus to v0.2.5 (#10329) by @renovate[bot]
- docs: migrate Netlify redirects to Void config (#10282) by @tux-tn
- deps: update dependency rust to v1.97.1 (#10317) by @renovate[bot]
- runtime: remove unused `runtime/index.js` (#10302) by @IWANABETHATGUY

### ❤️ New Contributors

* @dogledogle made their first contribution in [#10523](#10523)
* @tbvjaos510 made their first contribution in [#10489](#10489)
* @martijnwalraven made their first contribution in [#10440](#10440)
* @shantanuraj made their first contribution in [#10438](#10438)
* @Nic-Polumeyv made their first contribution in [#10450](#10450)
* @hanayashiki made their first contribution in [#10451](#10451)
* @unknownjedi made their first contribution in [#10426](#10426)
* @vittorioexp made their first contribution in [#10382](#10382)
* @tux-tn made their first contribution in [#10282](#10282)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
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