@napi-rs/cli 3.10.5 → 3.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +969 -105
- package/dist/index.cjs +969 -105
- package/dist/index.d.cts +1 -1
- package/dist/index.js +969 -105
- package/docs/wasi.md +270 -0
- package/package.json +2 -2
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +177 -0
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/templates.spec.ts +2618 -9
- package/src/api/templates/load-wasi-template.ts +819 -8
- package/src/api/templates/wasi-worker-template.ts +191 -63
package/docs/wasi.md
CHANGED
|
@@ -539,6 +539,67 @@ pre-created pool, spawning is only a message to an already-running worker,
|
|
|
539
539
|
and if the pool is exhausted the fallback allocates a fresh worker that
|
|
540
540
|
boots once the spawning parent returns to its event loop.
|
|
541
541
|
|
|
542
|
+
## Thread pool preload
|
|
543
|
+
|
|
544
|
+
The threaded Node loader (`<binary>.wasi.cjs`) creates its emnapi worker pool
|
|
545
|
+
empty (`reuseWorker: true`): emnapi's own preload (`reuseWorker.size > 0`)
|
|
546
|
+
cannot run on a synchronous CommonJS load. Without help, every pool thread a
|
|
547
|
+
`MultiThread` runtime spawns on its first async call would first boot a Worker
|
|
548
|
+
and load the wasm into it.
|
|
549
|
+
|
|
550
|
+
An addon built with `napi-async-runtime` exports
|
|
551
|
+
`napi_wasm_runtime_pool_workers() -> u32` on `wasm32-wasip1-threads`: the pool
|
|
552
|
+
threads the runtime will still spawn. That is the configured `MultiThread`
|
|
553
|
+
`worker_threads`, or 0 under `CurrentThread` (also the wasm default before any
|
|
554
|
+
configure), and 0 once the runtime has started (its first async call, or a
|
|
555
|
+
runtime-backed call during module registration): the started runtime already
|
|
556
|
+
spawned all its pool threads and never adds more. So a reconcile after first
|
|
557
|
+
use only releases idle Workers; a thread that starts later (a timer thread, a
|
|
558
|
+
uv thread, a restarted runtime) creates its Worker on demand. It reads an
|
|
559
|
+
atomic and takes no lock, and it comes with the scheduler, so
|
|
560
|
+
`default-features = false` builds have it too. The loader keeps that many
|
|
561
|
+
Workers idle in the pool:
|
|
562
|
+
|
|
563
|
+
- once, right after a successful load (after `#[module_init]` configured the
|
|
564
|
+
runtime);
|
|
565
|
+
- after every successful `configureAsyncRuntime`, when `napi.wasm.asyncRuntime`
|
|
566
|
+
is on. The loader replaces that export with a wrapper (same name, `this`,
|
|
567
|
+
arguments and return value) that calls the original and then reconciles. It
|
|
568
|
+
is matched by name, like the host install above. A configure that throws
|
|
569
|
+
(for example because the runtime already started) propagates and leaves the
|
|
570
|
+
pool alone;
|
|
571
|
+
- whenever you call
|
|
572
|
+
`binding[Symbol.for('napi.rs.wasi.reconcileThreadPool')]()`, published
|
|
573
|
+
non-enumerable and read-only next to the dispose symbol. Use it after
|
|
574
|
+
changing the configuration some other way. Like `napi.rs.wasi.dispose`, the
|
|
575
|
+
symbol lives on the CommonJS loader object. The generated ESM entry
|
|
576
|
+
re-exports named exports only, so an ESM consumer reaches it through
|
|
577
|
+
`createRequire(import.meta.url)('<pkg>-wasm32-wasi')` or the local
|
|
578
|
+
`./<name>.wasi.cjs`.
|
|
579
|
+
|
|
580
|
+
A reconcile creates each missing Worker through `onCreateWorker` (so it is
|
|
581
|
+
tracked, unref'd and gets the crash flags) and starts its load without waiting
|
|
582
|
+
for it; a thread spawn later pops a Worker that is already booting. It
|
|
583
|
+
terminates the idle Workers above the count, newest first, the way a spawn
|
|
584
|
+
takes them. Workers a spawn already took are not in the pool and are left
|
|
585
|
+
alone, so the count only covers idle Workers. A reconcile never throws and
|
|
586
|
+
never waits. It does nothing after a thread crash, once disposal started, or
|
|
587
|
+
for an addon without the export.
|
|
588
|
+
|
|
589
|
+
Two things to know:
|
|
590
|
+
|
|
591
|
+
- A preloaded Worker that fails to load still latches the binding as crashed,
|
|
592
|
+
like any pool Worker: the worker cannot tell a preload from a thread spawn,
|
|
593
|
+
so `dispose()` rejects afterwards. This is by design. The failed Worker is
|
|
594
|
+
taken out of the pool, so a later spawn creates a fresh one.
|
|
595
|
+
- `NAPI_RS_ASYNC_WORK_POOL_SIZE` (or `UV_THREADPOOL_SIZE`, default 4) sizes the
|
|
596
|
+
uv async-work threads, which take their Workers from the same reuse pool. A
|
|
597
|
+
preloaded Worker can therefore end up running a uv thread instead of a
|
|
598
|
+
runtime thread; the runtime thread then creates its own.
|
|
599
|
+
|
|
600
|
+
The browser loaders keep their fixed pre-created pool (see above), and the
|
|
601
|
+
threadless and deferred loaders have no pool.
|
|
602
|
+
|
|
542
603
|
## Detecting the threaded target from Rust
|
|
543
604
|
|
|
544
605
|
rustc gives you nothing to tell the two WASI targets apart. `rustc --print
|
|
@@ -667,3 +728,212 @@ Verified on the patched core: a contended probe under `wasmtime run -S
|
|
|
667
728
|
threads` — `Mutex`, `Condvar`, `RwLock`, `park_until`, `notify_all`, `DashMap`
|
|
668
729
|
— reports `ALL OK` with zero parking stubs left in the module, and rolldown's
|
|
669
730
|
threaded WASI artifact passed its stability lane 3 of 3.
|
|
731
|
+
|
|
732
|
+
## Shared memory growth on `wasm32-wasip1-threads`
|
|
733
|
+
|
|
734
|
+
Every thread of a threaded WASI addon runs on one shared `WebAssembly.Memory`.
|
|
735
|
+
V8 updates that memory's size only on the thread that grew it. Every other
|
|
736
|
+
thread keeps checking `memory.fill`, `memory.copy` and atomics against its old
|
|
737
|
+
size until it handles V8's grow interrupt, and on a host without V8's wasm trap
|
|
738
|
+
handler (`--disable-wasm-trap-handler`, or `--wasm-enforce-bounds-checks`) it
|
|
739
|
+
checks every load and store that way. Such a thread traps with `memory access
|
|
740
|
+
out of bounds` on heap pages another thread just grew. V8 fixed this in
|
|
741
|
+
[v8/v8@3424101](https://github.com/v8/v8/commit/34241014663390c72e08c123faef6fedf395be8e);
|
|
742
|
+
napi-rs works around it until the hosts it supports ship that fix.
|
|
743
|
+
|
|
744
|
+
The workaround is on for every addon built for exactly `wasm32-wasip1-threads`
|
|
745
|
+
with `napi` and `napi_build::setup()`. There is nothing to call or configure:
|
|
746
|
+
|
|
747
|
+
```text
|
|
748
|
+
malloc / free / calloc / realloc / ... (Rust's System, wasi-libc, emnapi's `malloc` export)
|
|
749
|
+
-> napi's __wrap_* (napi-build links with --wrap)
|
|
750
|
+
LOCK (spin; sched_yield every 64 spins; never memory.atomic.wait)
|
|
751
|
+
another thread saw a larger memory? memory.grow(0) refresh this thread
|
|
752
|
+
dlmalloc
|
|
753
|
+
-> __wrap_sbrk: the reserve first, else grow >= 16 MiB,
|
|
754
|
+
refresh and publish the new size
|
|
755
|
+
UNLOCK
|
|
756
|
+
|
|
757
|
+
task poll, block_on poll, blocking closure (napi-async-runtime)
|
|
758
|
+
AsyncTask compute, AsyncRuntimeTask poll, (napi)
|
|
759
|
+
napi's default Tokio runtime and spawn_blocking
|
|
760
|
+
-> another thread saw a larger memory? memory.grow(0) one atomic load + one thread-local load
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
- **The allocator lock.** `napi_build::setup()` links with `--wrap` for the 10
|
|
764
|
+
entries of wasi-libc's dlmalloc and for `sbrk`, so every allocation reaches a
|
|
765
|
+
`__wrap_*` function in `napi`. It takes one lock, refreshes the thread's size
|
|
766
|
+
when another thread has seen a larger memory, and runs the real call. The
|
|
767
|
+
thread that grows the memory publishes the new size before it unlocks, so
|
|
768
|
+
every thread is current before dlmalloc touches a byte for it. The lock also
|
|
769
|
+
covers calloc's zero fill and realloc's copy, and C code that calls `sbrk`
|
|
770
|
+
itself takes it too. It spins like dlmalloc's own lock, so it is safe on a
|
|
771
|
+
browser main thread, where `memory.atomic.wait` traps.
|
|
772
|
+
- **The handoff refresh.** Memory can reach a thread without an allocation
|
|
773
|
+
there: a task that allocated on one thread resumes on another, or a closure
|
|
774
|
+
runs on a pool thread. `napi-async-runtime` and napi's own cross-thread
|
|
775
|
+
entries check the size before they run such work.
|
|
776
|
+
- **The heap break.** dlmalloc first uses the pages between the module's own
|
|
777
|
+
memory and the memory the loader created (`napi.wasm.initialMemory`): they
|
|
778
|
+
exist on every thread from the start, so they never need a refresh, and
|
|
779
|
+
nothing grows until they are used. Past them it only uses pages it grew
|
|
780
|
+
itself, at least 16 MiB at a time, so pages another allocator grew never
|
|
781
|
+
reach dlmalloc. The heap never reaches 2 GiB: an allocation that would pass
|
|
782
|
+
it fails. Node's `node:wasi` (v24 and later) answers `EINVAL` to
|
|
783
|
+
`clock_time_get` and `fd_seek` when a pointer is at or above 2 GiB.
|
|
784
|
+
|
|
785
|
+
The threaded `.wasm` exports `malloc` and `free` as before (`@emnapi/core`
|
|
786
|
+
calls them); they now go through the lock. It also exports the 11 `__wrap_*`
|
|
787
|
+
functions, `napi_wasm_heap_sync_stat`, `napi_wasm_thread_crashed` and
|
|
788
|
+
`napi_wasm_thread_crash_flag_address`, because Rust exports every
|
|
789
|
+
`#[no_mangle]` function of a cdylib. They are not an API.
|
|
790
|
+
The threadless `wasm32-wasip1` artifact has none of this.
|
|
791
|
+
|
|
792
|
+
The addon's `napi-build` must be the same package as `napi`'s own
|
|
793
|
+
build-dependency (a path or git `napi` needs `napi-build` from the same
|
|
794
|
+
checkout). With two copies, the addon's `setup()` does not wrap the allocator,
|
|
795
|
+
and the link fails with an undefined symbol,
|
|
796
|
+
`napi_wasi_heap_sync_needs_napi_build_setup_with_wasi_heap_sync`.
|
|
797
|
+
|
|
798
|
+
### Test counters
|
|
799
|
+
|
|
800
|
+
`napi_wasm_heap_sync_stat(index)` returns one counter as a `u32`. It is for
|
|
801
|
+
tests and diagnosis; `examples/wasi-heap-sync/stress.mjs` and rolldown's
|
|
802
|
+
threaded stress test read it by index, so an index keeps its meaning.
|
|
803
|
+
|
|
804
|
+
| index | value |
|
|
805
|
+
| --------- | ------------------------------------------------------------------------------------------- |
|
|
806
|
+
| 0 | heap growths: `memory.grow(n > 0)` calls by `__wrap_sbrk` |
|
|
807
|
+
| 1 | refreshes after taking the lock (including each thread's first) |
|
|
808
|
+
| 2 | blocks that ended past the thread's refreshed size when dlmalloc returned them; must stay 0 |
|
|
809
|
+
| 3 | the heap break, in pages (rounded up); 0 before the first `sbrk` |
|
|
810
|
+
| 4 | `__heap_end` (where the break starts), in pages; 0 before the first `sbrk` |
|
|
811
|
+
| 5 | refreshes at handoffs (napi-async-runtime and napi's cross-thread entries) |
|
|
812
|
+
| any other | `u32::MAX` |
|
|
813
|
+
|
|
814
|
+
With the loader's default memory a small load reads 0 at index 0: the heap fits
|
|
815
|
+
in the reserve and never grows.
|
|
816
|
+
|
|
817
|
+
### Cost
|
|
818
|
+
|
|
819
|
+
Measured in rolldown on its copy of this code, before napi-rs took it over:
|
|
820
|
+
the same lock and break. napi's copy adds the check that keeps other
|
|
821
|
+
allocators' pages out of the break, and keeps the shared size in a cache line
|
|
822
|
+
of its own. Release wasm, Node 24.21 on arm64, 10 interleaved rounds, medians
|
|
823
|
+
in ms:
|
|
824
|
+
|
|
825
|
+
| load | before the lock | with the lock | cost |
|
|
826
|
+
| --------------------------------- | --------------- | ------------- | ---- |
|
|
827
|
+
| MultiThread, 16 builds | 310.5 | 334.5 | 8% |
|
|
828
|
+
| MultiThread, 16 builds, JS plugin | 1518 | 1589.5 | 5% |
|
|
829
|
+
| MultiThread, parse 16x3 | 218.5 | 241 | 10% |
|
|
830
|
+
| CurrentThread, parse 16x3 | 216.5 | 240.5 | 11% |
|
|
831
|
+
| CurrentThread, transform 16x3 | 623.5 | 640 | 3% |
|
|
832
|
+
|
|
833
|
+
"Before the lock" already grew the heap at least 16 MiB at a time. That part
|
|
834
|
+
of the workaround had made the same loads 1.6-3.2x faster than growing in
|
|
835
|
+
dlmalloc's own small steps (transform: no change), and the lock keeps most of
|
|
836
|
+
that gain. The handoff check alone measured at noise level. Other workloads,
|
|
837
|
+
x64, Linux, Windows and browsers are not measured.
|
|
838
|
+
|
|
839
|
+
### Opting out
|
|
840
|
+
|
|
841
|
+
Pass `--cfg napi_wasi_no_heap_sync` in the target rustflags, for example
|
|
842
|
+
`RUSTFLAGS="--cfg napi_wasi_no_heap_sync" napi build --target
|
|
843
|
+
wasm32-wasip1-threads`. `napi` then leaves out the wrappers and its handoff
|
|
844
|
+
checks, and `napi-build` leaves out the `--wrap` link arguments; both read the
|
|
845
|
+
same cfg, so they cannot come apart. `napi-async-runtime`'s check stays, but it
|
|
846
|
+
never fires: nothing publishes a larger size. `napi_build::setup()` declares
|
|
847
|
+
the cfg, so addon code can test `#[cfg(napi_wasi_no_heap_sync)]` too. Without
|
|
848
|
+
the workaround the trap above comes back on hosts without the V8 fix.
|
|
849
|
+
|
|
850
|
+
### What it does not cover
|
|
851
|
+
|
|
852
|
+
- A `#[global_allocator]` that calls `memory.grow` itself (mimalloc's WASI
|
|
853
|
+
build, talc, lol_alloc, the `dlmalloc` crate) is not locked, and its growth
|
|
854
|
+
is never published. One that ends in libc `malloc`, like std's `System`, is
|
|
855
|
+
locked.
|
|
856
|
+
- Memory that reaches a running thread mid-poll (a channel message, an `Arc`)
|
|
857
|
+
and is touched there before that thread's next allocation or handoff, after
|
|
858
|
+
another thread grew the memory. Below the reserve nothing grows. A
|
|
859
|
+
threadsafe-function call, deferred or async work delivered on the JavaScript
|
|
860
|
+
thread is covered: between taking it off its queue and calling back into the
|
|
861
|
+
module, emnapi runs JavaScript frames (`napi_open_handle_scope`,
|
|
862
|
+
`napi_get_reference_value`, `emnapi_is_node_binding_available`,
|
|
863
|
+
`_emnapi_callback_into_module`), and a JavaScript frame handles V8's grow
|
|
864
|
+
interrupt. What remains there is emnapi's own read of the queue node in C, on
|
|
865
|
+
hosts without the trap handler.
|
|
866
|
+
- Work on threads the addon starts or runs itself: a runtime passed to
|
|
867
|
+
`create_custom_tokio_runtime`, direct `tokio::task::spawn_blocking` calls,
|
|
868
|
+
and a host's own threads outside `napi-async-runtime`'s scheduler.
|
|
869
|
+
- A thread that crashes while it holds the lock leaves the others spinning, as
|
|
870
|
+
a crash inside dlmalloc's own lock always did.
|
|
871
|
+
|
|
872
|
+
## Shutdown polls never wait
|
|
873
|
+
|
|
874
|
+
A loader's `dispose()` runs the environment cleanup in two phases with a poll
|
|
875
|
+
between them: `napi_prepare_wasm_env_cleanup_begin`, then
|
|
876
|
+
`napi_wasm_runtime_work_pending` once per event-loop turn until it answers 0,
|
|
877
|
+
then `napi_prepare_wasm_env_cleanup_finish`. The poll never blocks, and that
|
|
878
|
+
includes locks: when a lock the answer is read under is held by another
|
|
879
|
+
thread, it answers 1 and the loader polls again on its next turn. On
|
|
880
|
+
`wasm32-wasip1-threads` a thread that traps unwinds nothing, so a lock it held
|
|
881
|
+
stays held; a poll that waited for it would park the JavaScript thread in
|
|
882
|
+
`memory.atomic.wait32` for good, before the loader can see the crash. A custom
|
|
883
|
+
`AsyncRuntime` backend must keep the same rule in `shutdown_work_pending`. The
|
|
884
|
+
process-exit teardown has no turns to give and makes the single blocking call,
|
|
885
|
+
`napi_prepare_wasm_env_cleanup`.
|
|
886
|
+
|
|
887
|
+
The two phases themselves may wait. With `napi-async-runtime` on
|
|
888
|
+
`wasm32-wasip1-threads` they wait in 1 ms slices on the JavaScript thread, and
|
|
889
|
+
between slices read the addon's crash flag: one 4-byte word in the shared wasm
|
|
890
|
+
memory. Once it is set, the next slice traps, and the loader reports the crash
|
|
891
|
+
instead of hanging.
|
|
892
|
+
|
|
893
|
+
The generated worker sets that word itself, with `Atomics.store`, so it needs
|
|
894
|
+
no wasm instance:
|
|
895
|
+
|
|
896
|
+
```
|
|
897
|
+
loader thread pool worker
|
|
898
|
+
───────────── ───────────
|
|
899
|
+
instantiate; in beforeInit:
|
|
900
|
+
napi_wasm_thread_crash_flag_address()
|
|
901
|
+
view = Int32Array(memory, address, 1)
|
|
902
|
+
spawn → Worker({ workerData: { load → start → run
|
|
903
|
+
crashFlag, crashReport, ...
|
|
904
|
+
addonCrashFlag: view } }) dies (trap, error, failed load):
|
|
905
|
+
... write crashReport
|
|
906
|
+
cleanup waits in slices ◄────────────── Atomics.store(crashFlag, 1)
|
|
907
|
+
view word is 1 → trap Atomics.store(addonCrashFlag, 1)
|
|
908
|
+
catch → crash rejection emnapi's own error report
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
- The Node worker gets the view in `workerData`. The browser worker gets it by
|
|
912
|
+
`postMessage`: the browser pool is created before the wasm is instantiated,
|
|
913
|
+
so the loader posts it to each pool worker after `beforeInit`, and to a
|
|
914
|
+
worker created later right away.
|
|
915
|
+
- The loader's crash flag always goes up first, so when the trap reaches the
|
|
916
|
+
loader it already sees the crash.
|
|
917
|
+
- A worker whose own setup throws (`@napi-rs/wasm-runtime` cannot be
|
|
918
|
+
resolved, say) raises both flags too, then fails as before.
|
|
919
|
+
- A worker that fails while it loads — after the thread spawn that created it
|
|
920
|
+
already returned — raises the flag the same way. `napi_wasm_thread_crashed`,
|
|
921
|
+
which stores into the same word, is only the fallback for a worker that has
|
|
922
|
+
an instance but no view.
|
|
923
|
+
- An addon built with an older napi has no address export: the loader passes
|
|
924
|
+
no view, and the waits stay unbounded, as before.
|
|
925
|
+
|
|
926
|
+
What still cannot raise the flag: a worker that fails before any of its own
|
|
927
|
+
code runs, or that is killed from outside — the `Worker` cannot start, runs
|
|
928
|
+
out of memory, or is terminated by its resource limits. The loader sees those
|
|
929
|
+
only through the worker's `'error'` or `'exit'` event, which needs a turn of
|
|
930
|
+
its event loop, so a cleanup wait already in progress keeps waiting. The same
|
|
931
|
+
holds before `beforeInit`: a thread spawned while the wasm initializes gets a
|
|
932
|
+
worker with no view.
|
|
933
|
+
|
|
934
|
+
After a crash, the threaded Node loader's `dispose()` only terminates the pool
|
|
935
|
+
workers and rejects, and from then on it drops the calls into wasm that emnapi
|
|
936
|
+
deferred through the context's `setImmediate` (threadsafe-function finalizers,
|
|
937
|
+
handle closes, the finalizer queue). A worker terminated while it held napi's
|
|
938
|
+
heap-sync lock leaves that lock set, so such a call would free memory on the
|
|
939
|
+
JavaScript thread and spin on the lock for good.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@napi-rs/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.7",
|
|
4
4
|
"description": "Cli tools for napi-rs",
|
|
5
5
|
"author": "LongYinan <lynweklm@gmail.com>",
|
|
6
6
|
"homepage": "https://napi.rs/",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"emnapi": "^2.0.0-alpha.4",
|
|
88
88
|
"empathic": "^2.0.1",
|
|
89
89
|
"env-paths": "^4.0.0",
|
|
90
|
-
"oxc-parser": "^0.
|
|
90
|
+
"oxc-parser": "^0.152.0",
|
|
91
91
|
"tsdown": "^0.23.0",
|
|
92
92
|
"tslib": "^2.8.1"
|
|
93
93
|
},
|
|
@@ -1351,6 +1351,36 @@ Generated by [AVA](https://avajs.dev).
|
|
|
1351
1351
|
return __rollbackWasmEnvForWasiInitialization()␊
|
|
1352
1352
|
}␊
|
|
1353
1353
|
␊
|
|
1354
|
+
// A view of the addon's crash flag, one word of the shared wasm memory, for the␊
|
|
1355
|
+
// pool workers: they raise it when their wasm thread dies, and the shutdown␊
|
|
1356
|
+
// waits inside the cleanup calls on this thread then trap instead of waiting on␊
|
|
1357
|
+
// the dead thread for good. Undefined for an addon built with an older napi.␊
|
|
1358
|
+
let __wasiAddonCrashFlag␊
|
|
1359
|
+
␊
|
|
1360
|
+
function __captureWasiAddonCrashFlag(instance) {␊
|
|
1361
|
+
try {␊
|
|
1362
|
+
const getAddress = instance.exports.napi_wasm_thread_crash_flag_address␊
|
|
1363
|
+
if (typeof getAddress !== 'function') {␊
|
|
1364
|
+
return␊
|
|
1365
|
+
}␊
|
|
1366
|
+
const address = getAddress() >>> 0␊
|
|
1367
|
+
const buffer = __sharedMemory.buffer␊
|
|
1368
|
+
if (address === 0 || address % 4 !== 0 || address + 4 > buffer.byteLength) {␊
|
|
1369
|
+
return␊
|
|
1370
|
+
}␊
|
|
1371
|
+
__wasiAddonCrashFlag = new Int32Array(buffer, address, 1)␊
|
|
1372
|
+
} catch {}␊
|
|
1373
|
+
}␊
|
|
1374
|
+
␊
|
|
1375
|
+
function __shareWasiAddonCrashFlag(worker) {␊
|
|
1376
|
+
if (__wasiAddonCrashFlag === undefined) {␊
|
|
1377
|
+
return␊
|
|
1378
|
+
}␊
|
|
1379
|
+
try {␊
|
|
1380
|
+
worker.postMessage({ __napiRsAddonCrashFlag: __wasiAddonCrashFlag })␊
|
|
1381
|
+
} catch {}␊
|
|
1382
|
+
}␊
|
|
1383
|
+
␊
|
|
1354
1384
|
let __wasiModule␊
|
|
1355
1385
|
let __napiModule␊
|
|
1356
1386
|
␊
|
|
@@ -1381,6 +1411,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
1381
1411
|
type: 'module',␊
|
|
1382
1412
|
})␊
|
|
1383
1413
|
__wasiWorkers.add(worker)␊
|
|
1414
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
1384
1415
|
␊
|
|
1385
1416
|
␊
|
|
1386
1417
|
return worker␊
|
|
@@ -1396,6 +1427,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
1396
1427
|
},␊
|
|
1397
1428
|
beforeInit({ instance }) {␊
|
|
1398
1429
|
__napiInstance = instance␊
|
|
1430
|
+
__captureWasiAddonCrashFlag(instance)␊
|
|
1431
|
+
for (const worker of __wasiWorkers) {␊
|
|
1432
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
1433
|
+
}␊
|
|
1399
1434
|
for (const name of Object.keys(instance.exports)) {␊
|
|
1400
1435
|
if (name.startsWith('__napi_register__')) {␊
|
|
1401
1436
|
instance.exports[name]()␊
|
|
@@ -2763,6 +2798,36 @@ Generated by [AVA](https://avajs.dev).
|
|
|
2763
2798
|
return __rollbackWasmEnvForWasiInitialization()␊
|
|
2764
2799
|
}␊
|
|
2765
2800
|
␊
|
|
2801
|
+
// A view of the addon's crash flag, one word of the shared wasm memory, for the␊
|
|
2802
|
+
// pool workers: they raise it when their wasm thread dies, and the shutdown␊
|
|
2803
|
+
// waits inside the cleanup calls on this thread then trap instead of waiting on␊
|
|
2804
|
+
// the dead thread for good. Undefined for an addon built with an older napi.␊
|
|
2805
|
+
let __wasiAddonCrashFlag␊
|
|
2806
|
+
␊
|
|
2807
|
+
function __captureWasiAddonCrashFlag(instance) {␊
|
|
2808
|
+
try {␊
|
|
2809
|
+
const getAddress = instance.exports.napi_wasm_thread_crash_flag_address␊
|
|
2810
|
+
if (typeof getAddress !== 'function') {␊
|
|
2811
|
+
return␊
|
|
2812
|
+
}␊
|
|
2813
|
+
const address = getAddress() >>> 0␊
|
|
2814
|
+
const buffer = __sharedMemory.buffer␊
|
|
2815
|
+
if (address === 0 || address % 4 !== 0 || address + 4 > buffer.byteLength) {␊
|
|
2816
|
+
return␊
|
|
2817
|
+
}␊
|
|
2818
|
+
__wasiAddonCrashFlag = new Int32Array(buffer, address, 1)␊
|
|
2819
|
+
} catch {}␊
|
|
2820
|
+
}␊
|
|
2821
|
+
␊
|
|
2822
|
+
function __shareWasiAddonCrashFlag(worker) {␊
|
|
2823
|
+
if (__wasiAddonCrashFlag === undefined) {␊
|
|
2824
|
+
return␊
|
|
2825
|
+
}␊
|
|
2826
|
+
try {␊
|
|
2827
|
+
worker.postMessage({ __napiRsAddonCrashFlag: __wasiAddonCrashFlag })␊
|
|
2828
|
+
} catch {}␊
|
|
2829
|
+
}␊
|
|
2830
|
+
␊
|
|
2766
2831
|
let __wasiModule␊
|
|
2767
2832
|
let __napiModule␊
|
|
2768
2833
|
␊
|
|
@@ -2793,6 +2858,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
2793
2858
|
type: 'module',␊
|
|
2794
2859
|
})␊
|
|
2795
2860
|
__wasiWorkers.add(worker)␊
|
|
2861
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
2796
2862
|
␊
|
|
2797
2863
|
worker.addEventListener('message', (event) => {␊
|
|
2798
2864
|
if (event.data && typeof event.data === 'object' && event.data.type === 'error') {␊
|
|
@@ -2821,6 +2887,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
2821
2887
|
},␊
|
|
2822
2888
|
beforeInit({ instance }) {␊
|
|
2823
2889
|
__napiInstance = instance␊
|
|
2890
|
+
__captureWasiAddonCrashFlag(instance)␊
|
|
2891
|
+
for (const worker of __wasiWorkers) {␊
|
|
2892
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
2893
|
+
}␊
|
|
2824
2894
|
for (const name of Object.keys(instance.exports)) {␊
|
|
2825
2895
|
if (name.startsWith('__napi_register__')) {␊
|
|
2826
2896
|
instance.exports[name]()␊
|
|
@@ -4194,6 +4264,36 @@ Generated by [AVA](https://avajs.dev).
|
|
|
4194
4264
|
return __rollbackWasmEnvForWasiInitialization()␊
|
|
4195
4265
|
}␊
|
|
4196
4266
|
␊
|
|
4267
|
+
// A view of the addon's crash flag, one word of the shared wasm memory, for the␊
|
|
4268
|
+
// pool workers: they raise it when their wasm thread dies, and the shutdown␊
|
|
4269
|
+
// waits inside the cleanup calls on this thread then trap instead of waiting on␊
|
|
4270
|
+
// the dead thread for good. Undefined for an addon built with an older napi.␊
|
|
4271
|
+
let __wasiAddonCrashFlag␊
|
|
4272
|
+
␊
|
|
4273
|
+
function __captureWasiAddonCrashFlag(instance) {␊
|
|
4274
|
+
try {␊
|
|
4275
|
+
const getAddress = instance.exports.napi_wasm_thread_crash_flag_address␊
|
|
4276
|
+
if (typeof getAddress !== 'function') {␊
|
|
4277
|
+
return␊
|
|
4278
|
+
}␊
|
|
4279
|
+
const address = getAddress() >>> 0␊
|
|
4280
|
+
const buffer = __sharedMemory.buffer␊
|
|
4281
|
+
if (address === 0 || address % 4 !== 0 || address + 4 > buffer.byteLength) {␊
|
|
4282
|
+
return␊
|
|
4283
|
+
}␊
|
|
4284
|
+
__wasiAddonCrashFlag = new Int32Array(buffer, address, 1)␊
|
|
4285
|
+
} catch {}␊
|
|
4286
|
+
}␊
|
|
4287
|
+
␊
|
|
4288
|
+
function __shareWasiAddonCrashFlag(worker) {␊
|
|
4289
|
+
if (__wasiAddonCrashFlag === undefined) {␊
|
|
4290
|
+
return␊
|
|
4291
|
+
}␊
|
|
4292
|
+
try {␊
|
|
4293
|
+
worker.postMessage({ __napiRsAddonCrashFlag: __wasiAddonCrashFlag })␊
|
|
4294
|
+
} catch {}␊
|
|
4295
|
+
}␊
|
|
4296
|
+
␊
|
|
4197
4297
|
let __wasiModule␊
|
|
4198
4298
|
let __napiModule␊
|
|
4199
4299
|
␊
|
|
@@ -4224,6 +4324,7 @@ Generated by [AVA](https://avajs.dev).
|
|
|
4224
4324
|
type: 'module',␊
|
|
4225
4325
|
})␊
|
|
4226
4326
|
__wasiWorkers.add(worker)␊
|
|
4327
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
4227
4328
|
worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))␊
|
|
4228
4329
|
␊
|
|
4229
4330
|
worker.addEventListener('message', (event) => {␊
|
|
@@ -4253,6 +4354,10 @@ Generated by [AVA](https://avajs.dev).
|
|
|
4253
4354
|
},␊
|
|
4254
4355
|
beforeInit({ instance }) {␊
|
|
4255
4356
|
__napiInstance = instance␊
|
|
4357
|
+
__captureWasiAddonCrashFlag(instance)␊
|
|
4358
|
+
for (const worker of __wasiWorkers) {␊
|
|
4359
|
+
__shareWasiAddonCrashFlag(worker)␊
|
|
4360
|
+
}␊
|
|
4256
4361
|
for (const name of Object.keys(instance.exports)) {␊
|
|
4257
4362
|
if (name.startsWith('__napi_register__')) {␊
|
|
4258
4363
|
instance.exports[name]()␊
|
|
@@ -9631,7 +9736,31 @@ Generated by [AVA](https://avajs.dev).
|
|
|
9631
9736
|
},␊
|
|
9632
9737
|
})␊
|
|
9633
9738
|
␊
|
|
9739
|
+
// When this wasm thread dies, raise the addon's crash flag before emnapi reports␊
|
|
9740
|
+
// it. The loader thread may be inside wasm, in a cleanup call that waits on this␊
|
|
9741
|
+
// thread; the flag is what those waits check between short slices, and once it␊
|
|
9742
|
+
// is up they trap instead of waiting for good. It is a word in the shared wasm␊
|
|
9743
|
+
// memory, and the loader posts a view of it (see␊
|
|
9744
|
+
// napi_wasm_thread_crash_flag_address) after it instantiated the wasm, or right␊
|
|
9745
|
+
// after it created this worker. No view, no flag: an addon built with an older␊
|
|
9746
|
+
// napi, or a failure before the view arrived.␊
|
|
9747
|
+
let __addonCrashFlag␊
|
|
9748
|
+
const __beforeReportError = handler.beforeReportError␊
|
|
9749
|
+
handler.beforeReportError = function (...args) {␊
|
|
9750
|
+
if (__addonCrashFlag !== undefined) {␊
|
|
9751
|
+
try {␊
|
|
9752
|
+
Atomics.store(__addonCrashFlag, 0, 1)␊
|
|
9753
|
+
} catch {}␊
|
|
9754
|
+
}␊
|
|
9755
|
+
return __beforeReportError.apply(this, args)␊
|
|
9756
|
+
}␊
|
|
9757
|
+
␊
|
|
9634
9758
|
globalThis.onmessage = function (e) {␊
|
|
9759
|
+
const data = e && e.data␊
|
|
9760
|
+
if (data && data.__napiRsAddonCrashFlag instanceof Int32Array) {␊
|
|
9761
|
+
__addonCrashFlag = data.__napiRsAddonCrashFlag␊
|
|
9762
|
+
return␊
|
|
9763
|
+
}␊
|
|
9635
9764
|
handler.handle(e)␊
|
|
9636
9765
|
}␊
|
|
9637
9766
|
`
|
|
@@ -9687,7 +9816,31 @@ Generated by [AVA](https://avajs.dev).
|
|
|
9687
9816
|
}␊
|
|
9688
9817
|
})␊
|
|
9689
9818
|
␊
|
|
9819
|
+
// When this wasm thread dies, raise the addon's crash flag before emnapi reports␊
|
|
9820
|
+
// it. The loader thread may be inside wasm, in a cleanup call that waits on this␊
|
|
9821
|
+
// thread; the flag is what those waits check between short slices, and once it␊
|
|
9822
|
+
// is up they trap instead of waiting for good. It is a word in the shared wasm␊
|
|
9823
|
+
// memory, and the loader posts a view of it (see␊
|
|
9824
|
+
// napi_wasm_thread_crash_flag_address) after it instantiated the wasm, or right␊
|
|
9825
|
+
// after it created this worker. No view, no flag: an addon built with an older␊
|
|
9826
|
+
// napi, or a failure before the view arrived.␊
|
|
9827
|
+
let __addonCrashFlag␊
|
|
9828
|
+
const __beforeReportError = handler.beforeReportError␊
|
|
9829
|
+
handler.beforeReportError = function (...args) {␊
|
|
9830
|
+
if (__addonCrashFlag !== undefined) {␊
|
|
9831
|
+
try {␊
|
|
9832
|
+
Atomics.store(__addonCrashFlag, 0, 1)␊
|
|
9833
|
+
} catch {}␊
|
|
9834
|
+
}␊
|
|
9835
|
+
return __beforeReportError.apply(this, args)␊
|
|
9836
|
+
}␊
|
|
9837
|
+
␊
|
|
9690
9838
|
globalThis.onmessage = function (e) {␊
|
|
9839
|
+
const data = e && e.data␊
|
|
9840
|
+
if (data && data.__napiRsAddonCrashFlag instanceof Int32Array) {␊
|
|
9841
|
+
__addonCrashFlag = data.__napiRsAddonCrashFlag␊
|
|
9842
|
+
return␊
|
|
9843
|
+
}␊
|
|
9691
9844
|
handler.handle(e)␊
|
|
9692
9845
|
}␊
|
|
9693
9846
|
`
|
|
@@ -9751,7 +9904,31 @@ Generated by [AVA](https://avajs.dev).
|
|
|
9751
9904
|
}␊
|
|
9752
9905
|
})␊
|
|
9753
9906
|
␊
|
|
9907
|
+
// When this wasm thread dies, raise the addon's crash flag before emnapi reports␊
|
|
9908
|
+
// it. The loader thread may be inside wasm, in a cleanup call that waits on this␊
|
|
9909
|
+
// thread; the flag is what those waits check between short slices, and once it␊
|
|
9910
|
+
// is up they trap instead of waiting for good. It is a word in the shared wasm␊
|
|
9911
|
+
// memory, and the loader posts a view of it (see␊
|
|
9912
|
+
// napi_wasm_thread_crash_flag_address) after it instantiated the wasm, or right␊
|
|
9913
|
+
// after it created this worker. No view, no flag: an addon built with an older␊
|
|
9914
|
+
// napi, or a failure before the view arrived.␊
|
|
9915
|
+
let __addonCrashFlag␊
|
|
9916
|
+
const __beforeReportError = handler.beforeReportError␊
|
|
9917
|
+
handler.beforeReportError = function (...args) {␊
|
|
9918
|
+
if (__addonCrashFlag !== undefined) {␊
|
|
9919
|
+
try {␊
|
|
9920
|
+
Atomics.store(__addonCrashFlag, 0, 1)␊
|
|
9921
|
+
} catch {}␊
|
|
9922
|
+
}␊
|
|
9923
|
+
return __beforeReportError.apply(this, args)␊
|
|
9924
|
+
}␊
|
|
9925
|
+
␊
|
|
9754
9926
|
globalThis.onmessage = function (e) {␊
|
|
9927
|
+
const data = e && e.data␊
|
|
9928
|
+
if (data && data.__napiRsAddonCrashFlag instanceof Int32Array) {␊
|
|
9929
|
+
__addonCrashFlag = data.__napiRsAddonCrashFlag␊
|
|
9930
|
+
return␊
|
|
9931
|
+
}␊
|
|
9755
9932
|
handler.handle(e)␊
|
|
9756
9933
|
}␊
|
|
9757
9934
|
`
|
|
Binary file
|