node-api: make object property arrays const - #65621
Merged
nodejs-github-bot merged 1 commit intoSep 2, 2026
Merged
Conversation
Signed-off-by: umuoy1 <burningdian@gmail.com>
Collaborator
|
Review requested:
|
Member
|
Thanks! FWIW, this API has already been documented as taking |
legendecas
approved these changes
Aug 28, 2026
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #65621 +/- ##
==========================================
- Coverage 90.07% 90.05% -0.02%
==========================================
Files 751 751
Lines 254921 254921
Branches 48129 48127 -2
==========================================
- Hits 229627 229578 -49
- Misses 16479 16513 +34
- Partials 8815 8830 +15
🚀 New features to boost your workflow:
|
KevinEady
approved these changes
Aug 28, 2026
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Contributor
Author
|
Could someone take a look at why node-test-pull-request/76782 failed? Looks like only the AIX failed. |
Member
|
we'll restart CI when we know it's in a good state, those AIX failures are unrelated to this PR |
This comment has been minimized.
This comment has been minimized.
Collaborator
vmoroz
approved these changes
Sep 1, 2026
Collaborator
|
Landed in 3942bf7 |
aduh95
pushed a commit
that referenced
this pull request
Sep 3, 2026
Signed-off-by: umuoy1 <burningdian@gmail.com> PR-URL: #65621 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
toyobayashi
added a commit
to toyobayashi/emnapi
that referenced
this pull request
Sep 13, 2026
aduh95
pushed a commit
that referenced
this pull request
Sep 15, 2026
Signed-off-by: umuoy1 <burningdian@gmail.com> PR-URL: #65621 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
aduh95
pushed a commit
that referenced
this pull request
Sep 19, 2026
Signed-off-by: umuoy1 <burningdian@gmail.com> PR-URL: #65621 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
aduh95
pushed a commit
that referenced
this pull request
Sep 19, 2026
Signed-off-by: umuoy1 <burningdian@gmail.com> PR-URL: #65621 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
Brooooooklyn
added a commit
to napi-rs/napi-rs
that referenced
this pull request
Oct 5, 2026
…hreads 2.2.0 emnapi 2.0.0-alpha.6 and @emnapi/wasi-threads 2.2.0 were published on 2026-10-05. Compared with 2.0.0-alpha.5 / 2.1.0 they contain: - const property arrays in js_native_api.h (nodejs/node#65621) - NAN V8 compatibility coverage (toyobayashi/emnapi#236) - fix: delay env allocation (toyobayashi/emnapi#237; C changes, and @emnapi/core now hands the env address to @emnapi/runtime as a function) - the wasi-threads background pool preload (toyobayashi/emnapi#239) The threaded loader's pool preload was written against #239, and `napi build` requires emnapi, @emnapi/core and @emnapi/runtime to be the same version, so the root resolutions and the exact pins in examples/wasi-heap-sync and examples/custom-async-runtime move together. The lockfile change is only these four packages (and the two example workspace entries). The caret ranges in cli and wasm-runtime already admit the new versions. Checked locally (macOS arm64, Node 24.21): - cli templates.spec: 150 passed; full cli ava suite: 414 passed, the 11 new.spec failures were template git fetches and new.spec alone passes 20/20 - shared-async-runtime test:wasi 11/11, test:wasi-workerd 11/11, test:wasi-threads-pool 8/8 - wasi-heap-sync stress: 5/5, and 20/20 with bounds checks - shared-async-runtime test:wasi-threads-crash: 2 passed, 1 failed. "dispose() after a crash releases an armed CurrentThread sleep" times out: its child calls dispose() from the unhandledRejection that @emnapi/wasi-threads 2.1.0 raised when a worker failed to load, and 2.2.0 logs that error instead of throwing it, so dispose() never runs. On 2.1.0 the whole file passes 3/3, and on 2.2.0 a dispose() fired by a timer still rejects with the crash and releases the sleep. The test's trigger needs updating. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda
Brooooooklyn
added a commit
to rolldown/rolldown
that referenced
this pull request
Oct 5, 2026
…hreads 2.2.0 emnapi shipped the pool-preload side today: emnapi, @emnapi/core and @emnapi/runtime 2.0.0-alpha.6, and @emnapi/wasi-threads 2.2.0 (@emnapi/core depends on exactly that version). Since alpha.5 the release carries four upstream changes: - const property arrays in js_native_api.h (nodejs/node#65621) - NAN V8 compatibility (toyobayashi/emnapi#236; rolldown does not link it) - delay env allocation (toyobayashi/emnapi#237): C changes, and core hands the env address to the runtime as a function - wasi-threads background pool preload (toyobayashi/emnapi#239) The published JS dist of @emnapi/core and @emnapi/wasi-threads is byte-identical to the vendored tarballs this branch tested with, and @emnapi/runtime differs only in a source map. The C package differs: the vendored emnapi.tgz was the alpha.5 archives relabelled as alpha.6, while the release ships rebuilt lib/**/*.a archives (#237, the const arrays). So both .wasm files were rebuilt; the build scripts now link node_modules/.pnpm/emnapi@2.0.0-alpha.6_*/emnapi/lib/wasm32-wasip1{,-threads}. Change - pnpm-workspace.yaml: back to the shape before the tarballs. Overrides keep only the @napi-rs/cli tarball and an exact `emnapi: 2.0.0-alpha.6`; the catalog pins emnapi, @emnapi/core and @emnapi/runtime at exactly 2.0.0-alpha.6, and @emnapi/wasi-threads 2.2.0 comes from @emnapi/core's exact dependency. The pins stay exact because the .wasm links the C archives and `napi build` refuses an emnapi that reports another version than @emnapi/core. The comment block drops the relabelled archives and their sha256 list. - .napi-validation: deleted emnapi.tgz, emnapi-core.tgz, emnapi-runtime.tgz, emnapi-wasi-threads.tgz. - pnpm-lock.yaml: only emnapi entries move. With no @emnapi/* override, the wasm32-only optional platform packages (sharp, lzma, tar, oxc-resolver, wasm-tools) get their own emnapi 1.x copies back, the same versions as before the tarballs. - check-workerd-packed-consumer.mjs: expects @emnapi/core and @emnapi/runtime 2.0.0-alpha.6 from the registry. - browser-tests staging comments: only the cli tarball is still staged; the code is unchanged and still works with one override. - async-runtime implementation.md: the emnapi side is released; the release floors stay. The committed loaders under packages/rolldown/src are byte-identical after the native -> threaded wasi -> single wasi -> native rebuild; the bundled threaded loaders now take @emnapi/core and runtime alpha.6 and @emnapi/wasi-threads 2.2.0 from the registry copies. Still temporary (until the napi-rs release): the @napi-rs/cli tarball (packed as 3.10.6 from napi-rs e6e50eb4) and the Cargo [patch.crates-io] pins at e6e50eb4. Validation (local, M5 Max, debug wasm) lane result test:wasi-threaded MultiThread / CurrentThread 5 + 2 skipped, each dist files threaded / single ok / ok stability MultiThread / CurrentThread ok / ok wasi-runtime-lifecycle MultiThread / CurrentThread ok / ok stress default 7 PASS stress bounds/no-trap/forced/handoff 20/10/20/20 PASS stress heap-ceiling / no-growth 1 / 2 PASS worker crash latch 12 PASS test:wasi-pool-preload, 20 runs 20/20 (320 PASS, 0 FAIL) load-failure: 2 after import, 4 before builds, 0 by builds, in all 40 cases threadless, stage, binding deps, workerd memory ok single-thread test:wasi 1581 + 90 skipped loader codegen unit test 7/7 check-wasi-binding-packed-consumer (Node 24 + 20.20.2) ok runtime contract, workerd packed consumer ok browser-tests webcontainer-fallback (staged fixture) 1/1 just lint-node; typos, ls-lint, vp fmt --check ok Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda
Brooooooklyn
added a commit
to napi-rs/napi-rs
that referenced
this pull request
Oct 5, 2026
…the loader (#3558) * feat(async-runtime): export the configured pool worker count on threaded WASI Problem: the generated threaded WASI loader starts emnapi's reuse pool empty, so every pool thread a MultiThread runtime spawns on its first async call boots a Worker and loads the wasm first. The loader cannot size the pool itself: the worker count lives in the Rust runtime options, and reading them through `configured_options()` takes the state lock, which a crashed pool thread may still hold. Change: `RuntimeController` keeps a lock-free `AtomicU32` mirror of the pool size, computed by the pure helper `pool_workers_for` (MultiThread -> worker_threads, CurrentThread -> 0). It is stored at the three sites that write `state.options` (`new`, `configure`, `configure_partial_inner`), so a configure that fails validation or is refused as frozen leaves it unchanged. On wasm32-wasip1-threads only (`napi_runtime_wasi_threads`), `napi_wasm_runtime_pool_workers() -> u32` exports it. It lives in the scheduler layer, so `default-features = false` consumers export it too. README: the "adds no wasm-specific API" line now describes the export. Tests: three unit tests next to `partial_configuration_after_backend_started_is_rejected`: helper values (MultiThread 3 -> 3, MultiThread 1 -> clamped 2, CurrentThread -> 0); configure / configure_partial update the mirror and a failed validation does not; a frozen configure leaves it at 0. Verification: `cargo test -p napi-async-runtime` (375 + 1 passed); `cargo check -p napi-async-runtime --tests --target wasm32-wasip1-threads` with and without default features; clippy -D warnings on the lib for native, wasm32-wasip1-threads (both feature sets); `cargo fmt --check`. The export is present in the shared-async-runtime example wasm (`wasm-objdump -x` lists `napi_wasm_runtime_pool_workers`). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * feat(cli): preload and reconcile the threaded WASI worker pool from the loader Problem: the threaded Node WASI loader passes `reuseWorker: true`, so emnapi's pool starts empty (its own preload, `reuseWorker.size > 0`, throws on the synchronous CommonJS load). The first MultiThread async call then boots one Worker per pool thread and loads the wasm into each. Addons such as rolldown patch the generated loader to work around this. Change (threaded Node loader only; browser, threadless and deferred loaders unchanged): - `__reconcileWasiThreadPool()` reads the addon's `napi_wasm_runtime_pool_workers` export and matches emnapi's idle pool to it: missing Workers are allocated through `onCreateWorker` and start loading without being awaited (a rejected load splices the Worker out of the pool, since emnapi 2.1.0 leaves it there); idle Workers above the count are terminated newest first and removed from the pool and from `__wasiWorkers`. Their `onmessage` is reset as a compatibility line for emnapi <= 2.1.0, whose terminated-worker reporter otherwise prints 'received "loaded" command from terminated worker' for a queued 'loaded' (reproduced: 8/8 runs without the reset, 0/40 with it). A synchronous allocate/load throw terminates that Worker and stops. It never throws or waits, and does nothing after a thread crash, during or after disposal, or for an addon without the export. - It runs once after a successful load (after the initialization try/catch, before the export tail), is published as the non-enumerable, read-only `Symbol.for('napi.rs.wasi.reconcileThreadPool')`, and, with `napi.wasm.asyncRuntime`, runs after every successful `configureAsyncRuntime` through a same-name wrapper (matched by name, like the host install; a throwing configure propagates and skips it). - docs/wasi.md: new "Thread pool preload" section, including the crash-latch caveat and the shared uv async-work pool. Tests: templates.spec.ts runs the sliced reconcile and wrapper against a stubbed instance and thread manager (preload 3, missing export / instance / manager, disposed / disposing / crashed, shrink with and without an emnapi that removes the Worker itself, rejected load, synchronous throws, symbol descriptor, wrapper `this` / args / result / name, wiring order), and checks that no other loader contains the export name. New e2e examples/shared-async-runtime test-wasi-threads-pool.mjs (CI step added after the crash test) runs the real threaded artifact: export 0 by default, MultiThread 3 -> 3 Workers and no new one on the first call, shrink, shrink with queued 'loaded' messages, grow, frozen configure. Verification: `yarn workspace @napi-rs/cli test` (422 passed); example test:wasi-threads-pool 6/6, test:wasi-threads-crash 3 pass + 2 skipped, test:wasi 11/11, test:wasi-workerd 11/11; oxlint --deny-warnings and oxfmt --check clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * fix(async-runtime): report zero pool workers once the backend has started Problem: `napi_wasm_runtime_pool_workers` kept reporting the configured MultiThread `worker_threads` after the backend started. Building the backend spawns every pool thread at once (Rayon's `build()` spawns `num_threads` and never grows), and those threads take the preloaded idle Workers. A later loader reconcile then sees an empty pool and preloads a second one: reproduced 3 -> 6 Workers through `Symbol.for('napi.rs.wasi.reconcileThreadPool')`. The post-load reconcile hits the same case when a `module_init` / `module_exports` hook calls a runtime-backed API, which starts the backend before the reconcile runs. `configureAsyncRuntime` is not affected: it throws once the runtime has started, and the wrapper skips the reconcile then. Change: the export now means "pool threads the runtime will still spawn". `RuntimeController` stores 0 into the mirror right after each backend build, under the state lock, at both build sites (`backend_locked` on the first submission, and `start` from `Stopped`). The three configure-time writes stay as they were. After the start the timer thread, uv threads and a restart take their Workers from emnapi's on-demand allocation, so a reconcile after first use only releases idle Workers. The loader is unchanged. Field comment, export doc, README and cli docs/wasi.md describe the new meaning. docs/wasi.md also notes that the reconcile symbol lives on the CommonJS loader object, like `napi.rs.wasi.dispose`: the generated ESM entry re-exports named exports only, so an ESM consumer reaches it through `createRequire(import.meta.url)('<pkg>-wasm32-wasi')` or the local `./<name>.wasi.cjs`. Tests: new unit test `pool_workers_drop_to_zero_once_the_backend_started` (MultiThread 3 -> 3; first backend -> 0; stays 0 after shutdown and a restart from `Stopped`). e2e test-wasi-threads-pool.mjs: the frozen case now expects `poolWorkers: 0` after the first call; new case "a reconcile after the backend started allocates no second pool" (MultiThread 3, one async call, manual reconcile: export 0, created 3, idle 0). On the pre-fix artifact it fails (export 3 after the call; the child then reports created 6, idle 3 after the reconcile). Verification: `cargo test -p napi-async-runtime` (376 passed, 1 ignored; consumed_api 1 passed); `cargo check -p napi-async-runtime --tests --target wasm32-wasip1-threads` with and without default features; clippy -D warnings on the lib for native and wasm32-wasip1-threads (both feature sets); `cargo fmt --check`. Rebuilt threaded example: test:wasi-threads-pool 7/7, test:wasi-threads-crash 3 pass + 2 skipped, test:wasi 11/11; cli templates.spec.ts 147 passed; oxfmt --check and oxlint --deny-warnings clean on the touched files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * test(async-runtime): run the pool-worker start test only where pool threads exist The threadless wasm32-wasip1 test run aborts when a test builds or configures a MultiThread runtime, which that target rejects. Gate the new start test and the configure-mirror test with `napi_runtime_os_threads`, like the other MultiThread tests; both still run natively and on wasm32-wasip1-threads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * fix(cli): keep a shrunk WASI pool Worker tracked until it has exited Problem: when `__reconcileWasiThreadPool` shrinks the idle pool, it calls the thread manager's `terminateWorker`, which only starts Node's asynchronous `worker.terminate()` and drops its promise, and then removed the Worker from `__wasiWorkers` right away. A disposal started before that Worker exited skipped it, so `dispose()` could resolve while the Worker was still exiting. With a configure back to CurrentThread followed by an immediate dispose, the disposer resolved before any of the three terminated Workers had exited in 20/20 runs (order ['disposed'], exited 0). Change: the shrink branch keeps the Worker in `__wasiWorkers` and drops it only when its own `worker.terminate()` settles, which happens at the Worker's exit (a second `terminate()` on an exiting Worker settles at that exit). A disposal that starts first finds the Worker and waits for it the way `__terminateWasiWorkers` waits for every other one. A rejected terminate leaves it tracked, so disposal retries and reports it. A non-thenable result drops it at once, as `__terminateWasiWorkers` does. The `onmessage` reset and the pool splice are unchanged. Tests: - templates.spec.ts: the reconcile harness slices the real `__terminateWasiWorkers`; the pool Worker stub gets a `terminate()` that can settle only at a later exit. New case: after a shrink the Worker is still tracked, a disposal does not resolve before its exit, and the exit alone removes it. - test-wasi-threads-pool.mjs: new `shrink-dispose` case (configure MultiThread 3, wait for loads, configure CurrentThread, dispose with no turn in between): order is exit x3 then disposed, all 3 exited. Verification: - templates.spec.ts: 148 passed; with the template reverted the new case fails (1 failed). - test-wasi-threads-pool.mjs on the artifact built before the fix: 7 pass, 1 fail (order ['disposed']); rebuilt artifact: 8 pass, 0 fail. - shrink-dispose child, 20 runs: before ['disposed'] 20/20, after ['exit','exit','exit','disposed'] 20/20. - test:wasi-threads-crash 3 pass / 2 skipped (manual hang checks) / 0 fail; test:wasi 11 pass / 0 fail. - oxfmt --check and oxlint --deny-warnings clean on the touched files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * fix(cli): track a WASI pool Worker whose load threw until it has exited Problem: when `loadWasmModuleToWorker` throws synchronously during a pool reconcile, the loader terminated the Worker through the thread manager and removed it from `__wasiWorkers` at once, while its asynchronous termination was still under way. A disposal started in that window did not wait for it, the same gap e0c2193 closed for the shrink branch. Change: factor "terminate, then drop from `__wasiWorkers` when the promise settles" into `__untrackWasiWorkerOnExit` and use it in both the shrink branch and the sync-failure branch. Tests: templates.spec.ts case for the sync-throw branch (the Worker stays tracked until its `terminate()` settles; a disposal in between waits for it). Verification: - templates.spec.ts: 149 passed; with the e0c2193 template the new case fails (1 failed). - rebuilt threaded artifact: test-wasi-threads-pool 8/8 pass, test:wasi-threads-crash 3 pass / 2 skipped / 0 fail, test:wasi 11/11. - oxfmt --check and oxlint --deny-warnings clean on the touched files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * fix(cli): drop emnapi's deferred calls into wasm after a thread-crash disposal After a worker crash is latched, the public disposer takes __disposeWasiBindingAfterThreadCrash(), which terminates the pool workers while they may still be working. A pool thread terminated while it holds napi's heap-sync allocator lock (spin + sched_yield, never gives up) leaves that lock set. A callback emnapi queued earlier through the context's features.setImmediate then runs on the main thread: _emnapi_set_immediate -> uv__finish_close -> _emnapi_tsfn_do_finalize -> thread_finalize_cb -> __rust_dealloc -> __wrap_free -> lock_slow, and spins there forever. The loader already says nothing may re-enter wasm after the crash path stops the workers, but emnapi's deferred calls still did. Fix: the threaded Node loader creates its emnapi context with features: { setImmediate: __wasiSetImmediate }. __wasiSetImmediate schedules the callback with the host setImmediate and drops it once __wasiReentryClosed is set. The crash disposal sets that flag after it unrefs the waiting-request port and releases the host timers, right before it terminates the workers. The single-thread loaders are unchanged (their rendered output is byte-identical to before). Residual paths emnapi gives no hook for, documented on __wasiSetImmediate: the FinalizationRegistry callbacks that free external memory on GC, threadsafe-function dispatch of async-send type 1 and _emnapi_next_tick (both Promise.resolve().then), and every deferred call under emnapi 1.x, whose createContext ignores its options. Tests: the context-creation spec now expects the features object for the threaded Node loader and the plain options elsewhere; the two crash disposal ordering specs expect the gate before the termination. A new spec runs the generated crash disposal with a stubbed setImmediate: a callback that fires before the disposal still runs, ones that fire after it (queued before or after the disposal started) are dropped, the gate is closed when the workers are terminated, and the threadless loaders have no gate. Without the gate the spec fails. No e2e case: the example addon has no threadsafe function and nothing that holds the heap lock on a pool thread at crash time, so it would need new Rust. Verification: - templates.spec.ts: 150 passed - build:wasi-threads, test:wasi-threads-pool 8/8, test:wasi-threads-crash 3 pass + 2 manual skips - build:wasi, test:wasi 11/11 - oxfmt --check and oxlint --deny-warnings on the touched files: clean - rolldown harness (investigation): hang 1/1500 at 36 children and 19/480 widened before, 0/1500 and 0/480 with the fix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * test(async-runtime): tolerate a std that never runs worker TLS destructors on wasm32-wasip1-threads rustc 1.99.0 changed how std handles thread_local! destructors on wasm32-wasip1-threads. rust-lang/rust#160868 ("std: Adjust cfgs again for TLS on WASI") rewrote the guard selection in library/std/src/sys/thread_local/mod.rs so the "leak everything" branch now covers all(target_family = "wasm", not(target_env = "p3")). That includes the threaded WASI target, which on 1.98 and earlier used a pthread key whose destructor pthread_join waits for. On 1.99.0 std never runs TLS destructors at thread exit on this target, not even late. multi_thread_shutdown_waits_for_worker_tls_destructors asserts that every worker TLS destructor ran before shutdown() returned, so on 1.99.0 it aborts the whole wasm test binary: assertion `left == right` failed: every worker TLS destructor must have completed before shutdown() returned ... left: 0 right: 2 The CI runners picked up rustc 1.99.0 on 2026-10-02. napi-rs main run 36961186116 and this branch's run 37034095113 both fail the same way; rustc 1.98.1 passes. The runtime is unchanged and needs no change: it does not rely on worker TLS destructors, and the join barrier still joins every worker. Only the test's premise moved. On wasm32-wasip1-threads the test now asks a plain std thread whether its TLS destructor ran before join() returned, and checks the final count only when it did. The rest of the test (probe install, zero count before shutdown, shutdown and join) still runs. The assert turns itself back on once std runs these destructors again. Native targets still assert unconditionally. Local results (wasmtime 35.0.0, as CI): - rustc 1.99.0, wasm32-wasip1-threads, full suite: test result: ok. 280 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 16.93s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - rustc 1.99.0, wasm32-wasip1: test result: ok. 56 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s - native (rustc 1.98.0), the changed test: test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 376 filtered out; finished in 0.02s Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * build(deps): pin the released emnapi 2.0.0-alpha.6 and @emnapi/wasi-threads 2.2.0 emnapi 2.0.0-alpha.6 and @emnapi/wasi-threads 2.2.0 were published on 2026-10-05. Compared with 2.0.0-alpha.5 / 2.1.0 they contain: - const property arrays in js_native_api.h (nodejs/node#65621) - NAN V8 compatibility coverage (toyobayashi/emnapi#236) - fix: delay env allocation (toyobayashi/emnapi#237; C changes, and @emnapi/core now hands the env address to @emnapi/runtime as a function) - the wasi-threads background pool preload (toyobayashi/emnapi#239) The threaded loader's pool preload was written against #239, and `napi build` requires emnapi, @emnapi/core and @emnapi/runtime to be the same version, so the root resolutions and the exact pins in examples/wasi-heap-sync and examples/custom-async-runtime move together. The lockfile change is only these four packages (and the two example workspace entries). The caret ranges in cli and wasm-runtime already admit the new versions. Checked locally (macOS arm64, Node 24.21): - cli templates.spec: 150 passed; full cli ava suite: 414 passed, the 11 new.spec failures were template git fetches and new.spec alone passes 20/20 - shared-async-runtime test:wasi 11/11, test:wasi-workerd 11/11, test:wasi-threads-pool 8/8 - wasi-heap-sync stress: 5/5, and 20/20 with bounds checks - shared-async-runtime test:wasi-threads-crash: 2 passed, 1 failed. "dispose() after a crash releases an armed CurrentThread sleep" times out: its child calls dispose() from the unhandledRejection that @emnapi/wasi-threads 2.1.0 raised when a worker failed to load, and 2.2.0 logs that error instead of throwing it, so dispose() never runs. On 2.1.0 the whole file passes 3/3, and on 2.2.0 a dispose() fired by a timer still rejects with the crash and releases the sleep. The test's trigger needs updating. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * test(shared-async-runtime): detect the pool worker crash through the loader, not an unhandled rejection The "dispose-sleep" case of test-wasi-threads-crash.mjs must call dispose() after the pool worker's failed load has latched the binding as crashed, so that the crash disposal (not a normal disposal the crash later overtakes) releases the armed 60 s CurrentThread sleep. The child used the process 'unhandledRejection' event as that signal: @emnapi/wasi-threads 2.1.0 rethrew the failed load at the thread spawn site, and nothing handled it. 2.2.0 (emnapi#239) prints that failure through PThread.printErr instead and drops the failed Worker (dropFailedLoad). No rejection comes, dispose() never ran, and the sleep kept the child alive past the 30 s timeout. The child now disposes on the failed Worker's 'error' event, reached through Node's public process 'worker' event. The loader's onCreateWorker listener latches the crash on that same event, and wasi-worker.mjs raises the shared crash flag before it posts the error, so dispose() sees the crash when it starts. It is the only signal a host gets on both versions: the loader exposes no crash flag, binding calls keep resolving after the crash (plus100 and sleepThenAdd resolved in a probe), and only dispose() reports it. The unhandledRejection listener stays, only to log: on 2.1.0 the "exit" and "dispose" modes still get the rejection and must not die on it. The test now also asserts that the trigger fired. Results (test:wasi-threads-crash; the dispose-sleep child exits in ~530 ms): - @emnapi/wasi-threads 2.2.0 / core alpha.6: 3/3 passed in 5 of 5 runs, and again after restoring and relinking - @emnapi/wasi-threads 2.1.0 / core alpha.5, artifact relinked: 3/3 passed in 3 of 3 runs - test:wasi-threads-pool: 8/8 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * test(custom-async-runtime): pass the real napi_env when re-registering over emnapi 2.0.0-alpha.6 emnapi 2.0.0-alpha.6 (toyobayashi/emnapi#237, "delay env allocation") turned `EnvNativeBridge.address` into a lazy allocator function and moved the pointer to `Env.address`. The two abandoned-cleanup cases still passed `env.bridge.address` to `napi_register_wasm_v1`, so the wasm i32 parameter received ToNumber(function) = NaN = 0: a null env. napi-rs still refused the registration (the owed phase 2 stayed standing, so `..._finish` joined the backend afterwards), but its `napi_throw_error(env, ...)` hit emnapi's `if (!env) return napi_invalid_arg`, so no exception reached JavaScript and the threaded case read `ABANDONED_PENDING_ERROR NONE`. Read the pointer from `Env.address`, falling back to `bridge.address` for emnapi releases that predate the change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda * test(wasm-runtime): create the fixture's dist directory before writing into it createRuntimeFixture put mkdir(dist) in the same Promise.all as three writes into dist/ (cp emnapi-plugins.cjs, cp emnapi-plugins.js, writeFile fs-proxy.cjs). Nothing ordered the mkdir first, so under slow I/O a write could open dist/<file> before dist existed and fail with ENOENT. CI hit this twice on the v2 case: ASAN run 37041725283 and the aarch64-pc-windows-msvc node@24 job 111639107194 of run 37271378422 (both reruns passed). Await the mkdir before the Promise.all of the writes; the rest of the fixture is unchanged. writeModulePackage already awaits its mkdir. Local check (macOS): yarn test x10 = 10 pass / 0 fail; node --test runtime.test.js x20 = 20 pass / 0 fail. A scratch script running the same 8 ops in the old order 200 times hit ENOENT 52, 37 and 40 times in three runs; the fixed order hit it 0 times. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T34fMrYCf2V13Mg8BKmnda --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.
Make
property_namesandproperty_valuesconst innode_api_create_object_with_properties()to match their read-only semantics.Refs: nodejs/node-addon-api#1735 (comment)