@napi-rs/cli 3.10.4 → 3.10.6
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 +1943 -153
- package/dist/index.cjs +1942 -152
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +1943 -153
- package/docs/build.md +1 -0
- package/docs/wasi.md +351 -8
- package/package.json +2 -2
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +2629 -86
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/templates.spec.ts +2192 -10
- package/src/api/build.ts +38 -1
- package/src/api/templates/load-wasi-template.ts +1412 -30
- package/src/api/templates/wasi-worker-template.ts +191 -63
- package/src/commands/__tests__/build.spec.ts +6 -0
- package/src/def/build.ts +13 -0
- package/src/utils/__tests__/ohos-selfsign.spec.ts +285 -0
- package/src/utils/__tests__/reconciliation.spec.ts +19 -0
- package/src/utils/config.ts +10 -6
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +5 -4
- package/src/utils/ohos-selfsign.ts +646 -0
package/docs/build.md
CHANGED
|
@@ -58,3 +58,4 @@ new NapiCli().build({
|
|
|
58
58
|
| features | --features,-F | string[] | false | | Space-separated list of features to activate |
|
|
59
59
|
| allFeatures | --all-features | boolean | false | | Activate all available features |
|
|
60
60
|
| noDefaultFeatures | --no-default-features | boolean | false | | Do not activate the `default` feature |
|
|
61
|
+
| ohosSign | --ohos-sign | boolean | false | true | Whether to self-sign the built binary for OpenHarmony targets by injecting a `.codesign` fs-verity section, so the `.so` can be loaded on HarmonyOS devices. Only works with `*-unknown-linux-ohos` targets |
|
package/docs/wasi.md
CHANGED
|
@@ -390,12 +390,16 @@ override the default in either direction:
|
|
|
390
390
|
|
|
391
391
|
## Shared async runtime hosts
|
|
392
392
|
|
|
393
|
-
`CurrentThread` is the
|
|
394
|
-
|
|
395
|
-
`
|
|
396
|
-
host
|
|
397
|
-
|
|
398
|
-
|
|
393
|
+
`CurrentThread` is the default async-runtime flavor on every WebAssembly
|
|
394
|
+
target, and the only flavor on threadless `wasm32-wasip1`. On
|
|
395
|
+
`wasm32-wasip1-threads` an addon may select `MultiThread` instead — always an
|
|
396
|
+
explicit host act, never a default (see the crate's "Running MultiThread on
|
|
397
|
+
`wasm32-wasip1-threads`" section).
|
|
398
|
+
|
|
399
|
+
A `CurrentThread` addon built with the `napi-async-runtime` crate makes no
|
|
400
|
+
progress until a JavaScript task host publishes its runnable turns, and its
|
|
401
|
+
timers never fire until a timer host relays them. Set `napi.wasm.asyncRuntime`
|
|
402
|
+
and the generated loaders install both for you:
|
|
399
403
|
|
|
400
404
|
```json
|
|
401
405
|
{
|
|
@@ -407,8 +411,16 @@ both for you:
|
|
|
407
411
|
}
|
|
408
412
|
```
|
|
409
413
|
|
|
410
|
-
This affects WASI output only. Native `.node` bindings
|
|
411
|
-
flavor on real threads and need no JavaScript host.
|
|
414
|
+
This affects WASI output only. Native `.node` bindings default to the
|
|
415
|
+
`MultiThread` flavor on real threads and need no JavaScript host.
|
|
416
|
+
|
|
417
|
+
Keep the flag on even for a `wasm32-wasip1-threads` addon that configures
|
|
418
|
+
`MultiThread`. The loaders cannot see the flavor, so they install both hosts
|
|
419
|
+
unconditionally — and a `MultiThread` runtime never uses either: its futures
|
|
420
|
+
run on the Rayon pool instead of published host turns, and its timers come from
|
|
421
|
+
the executor-owned heap plus a timekeeper thread. The installed task host is an
|
|
422
|
+
unref'd threadsafe function, so it does not hold the event loop open. That is
|
|
423
|
+
what lets one artifact choose its flavor at runtime.
|
|
412
424
|
|
|
413
425
|
With the flag on:
|
|
414
426
|
|
|
@@ -526,3 +538,334 @@ would never boot and the caller would deadlock waiting for it. With a
|
|
|
526
538
|
pre-created pool, spawning is only a message to an already-running worker,
|
|
527
539
|
and if the pool is exhausted the fallback allocates a fresh worker that
|
|
528
540
|
boots once the spawning parent returns to its event loop.
|
|
541
|
+
|
|
542
|
+
## Detecting the threaded target from Rust
|
|
543
|
+
|
|
544
|
+
rustc gives you nothing to tell the two WASI targets apart. `rustc --print
|
|
545
|
+
cfg` emits an _identical_ set for `wasm32-wasip1` and
|
|
546
|
+
`wasm32-wasip1-threads` — same `target_arch`, same `target_os`, same
|
|
547
|
+
`target_env = "p1"` — and `target_feature = "atomics"` is set for neither,
|
|
548
|
+
because the wasm `atomics` feature is still unstable and the stable channel
|
|
549
|
+
keeps unstable target features out of the cfg set
|
|
550
|
+
([rust-lang/rust#77839](https://github.com/rust-lang/rust/issues/77839)).
|
|
551
|
+
Passing `-C target-feature=+atomics` does not change that: rustc warns that
|
|
552
|
+
the feature is unstable and the cfg still does not appear. Only the exact
|
|
553
|
+
cargo `TARGET` answers the question, and only a build script can read it.
|
|
554
|
+
|
|
555
|
+
An addon crate gets the answer for free. `napi_build::setup()` emits
|
|
556
|
+
`cfg(napi_wasi_threads)` when — and only when — the crate is being compiled
|
|
557
|
+
for `wasm32-wasip1-threads`, so the addon can write:
|
|
558
|
+
|
|
559
|
+
```rust
|
|
560
|
+
#[cfg(napi_wasi_threads)]
|
|
561
|
+
const WORKERS: usize = 4;
|
|
562
|
+
#[cfg(not(napi_wasi_threads))]
|
|
563
|
+
const WORKERS: usize = 1;
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
`setup()` also prints the matching `cargo::rustc-check-cfg` line on _every_
|
|
567
|
+
target, so `#[cfg(napi_wasi_threads)]` is a known cfg everywhere and never
|
|
568
|
+
trips the `unexpected_cfgs` lint on the targets where it is not set.
|
|
569
|
+
|
|
570
|
+
A build-script cfg is crate-local: it reaches the crate whose `build.rs`
|
|
571
|
+
printed it and nothing else. Another crate in the same graph that needs the
|
|
572
|
+
distinction therefore needs its own build script — which is why `napi` and
|
|
573
|
+
`napi-async-runtime` each carry one:
|
|
574
|
+
|
|
575
|
+
```rust
|
|
576
|
+
// build.rs
|
|
577
|
+
fn main() {
|
|
578
|
+
println!("cargo::rustc-check-cfg=cfg(my_wasi_threads)");
|
|
579
|
+
if std::env::var("TARGET").as_deref() == Ok("wasm32-wasip1-threads") {
|
|
580
|
+
println!("cargo::rustc-cfg=my_wasi_threads");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Use the older single-colon `cargo:` form if the crate's `rust-version` is
|
|
586
|
+
below 1.77.
|
|
587
|
+
|
|
588
|
+
Absence has to be the conservative branch. The cfg is permission to use
|
|
589
|
+
shared memory and real threads, never a requirement that something be
|
|
590
|
+
configured: a plain `cargo build`, `cargo test`, or rust-analyzer run — no
|
|
591
|
+
`napi build`, no CLI, no `RUSTFLAGS` — must still compile a correct crate on
|
|
592
|
+
the `not(...)` side. Nothing outside the build script can set it, so a
|
|
593
|
+
mistake there is silent.
|
|
594
|
+
|
|
595
|
+
### Third-party locks
|
|
596
|
+
|
|
597
|
+
`parking_lot_core`, the lock core under `parking_lot` and `dashmap`, does not
|
|
598
|
+
follow this rule, and an addon can pull it in without ever naming it. Its
|
|
599
|
+
only threaded-wasm parker is gated on the crate's `nightly` feature _and_
|
|
600
|
+
`target_feature = "atomics"` (0.9.12, `src/thread_parker/mod.rs:69`), so on
|
|
601
|
+
stable the cascade falls through to the wasm stub, whose `prepare_park` is
|
|
602
|
+
`panic!("Parking not supported on this platform")`
|
|
603
|
+
(`src/thread_parker/wasm.rs:26`). The build succeeds and the addon dies at the
|
|
604
|
+
first _contended_ lock, on a target whose `std` has fully working threads.
|
|
605
|
+
|
|
606
|
+
Upstream
|
|
607
|
+
[Amanieu/parking_lot#529](https://github.com/Amanieu/parking_lot/pull/529)
|
|
608
|
+
adds a `std::sync::Mutex` and `Condvar` parker for WASI, but its selection arm
|
|
609
|
+
keys on `target_feature = "atomics"` as well, so it cannot be reached on
|
|
610
|
+
stable either. Until that is resolved there are two options:
|
|
611
|
+
|
|
612
|
+
- Keep `parking_lot` and `dashmap` out of the `wasm32-wasip1-threads`
|
|
613
|
+
dependency graph, or
|
|
614
|
+
- route the whole graph through a `parking_lot_core` that detects the triple
|
|
615
|
+
in its own `build.rs`, exactly as above. napi-rs maintains that fork and
|
|
616
|
+
publishes it as `lock_api-napi` 0.4.15, `parking_lot_core-napi` 0.9.13 and
|
|
617
|
+
`parking_lot-napi` 0.12.6; each keeps the upstream `[lib] name`, so
|
|
618
|
+
`use parking_lot::Mutex` still compiles. A transitive user such as
|
|
619
|
+
`dashmap` is only reachable through `[patch.crates-io]`, and the only
|
|
620
|
+
form cargo accepts is a git pin on the fork's `wasi-threads-parker`
|
|
621
|
+
branch, which still carries the _upstream_ package names. Pin the exact
|
|
622
|
+
revision rather than the branch, so the lock stays reproducible:
|
|
623
|
+
|
|
624
|
+
```toml
|
|
625
|
+
[patch.crates-io.parking_lot_core]
|
|
626
|
+
git = "https://github.com/napi-rs/parking_lot"
|
|
627
|
+
rev = "ac046ba44e72159e90e36b7323b1058ba1d48ad2"
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The published `*-napi` crates cannot be named in such an entry. Cargo
|
|
631
|
+
rejects a crates.io package replacing another crates.io package, because
|
|
632
|
+
a patch must point to a different source:
|
|
633
|
+
|
|
634
|
+
```toml
|
|
635
|
+
[patch.crates-io]
|
|
636
|
+
parking_lot_core = { package = "parking_lot_core-napi", version = "0.9.13" }
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
```text
|
|
640
|
+
error: patch for `parking_lot_core-napi` points to the same source, but
|
|
641
|
+
patches must point to different sources
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Over a path or git source the entry resolves, and is then dropped. Cargo
|
|
645
|
+
does honour `package =` — it selects that package from the replacement
|
|
646
|
+
source — but the selected package keeps its own name, and a dependency on
|
|
647
|
+
`parking_lot_core` is satisfied only by a package named
|
|
648
|
+
`parking_lot_core`. The fork's `master` carries the renamed manifests
|
|
649
|
+
(`name = "parking_lot_core-napi"`), so pointing the rename at it gives:
|
|
650
|
+
|
|
651
|
+
```toml
|
|
652
|
+
[patch.crates-io.parking_lot_core]
|
|
653
|
+
git = "https://github.com/napi-rs/parking_lot"
|
|
654
|
+
rev = "e243c6c43832c151bce2887fcb209b5b8b72ac61" # master
|
|
655
|
+
package = "parking_lot_core-napi"
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
```text
|
|
659
|
+
warning: patch `parking_lot_core-napi v0.9.13 (…?rev=e243c6c4…)` was not
|
|
660
|
+
used in the crate graph
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
The lock then records it under `[[patch.unused]]` and `dashmap` keeps
|
|
664
|
+
building against stock `parking_lot_core` 0.9.12 — the panicking parker.
|
|
665
|
+
|
|
666
|
+
Verified on the patched core: a contended probe under `wasmtime run -S
|
|
667
|
+
threads` — `Mutex`, `Condvar`, `RwLock`, `park_until`, `notify_all`, `DashMap`
|
|
668
|
+
— reports `ALL OK` with zero parking stubs left in the module, and rolldown's
|
|
669
|
+
threaded WASI artifact passed its stability lane 3 of 3.
|
|
670
|
+
|
|
671
|
+
## Shared memory growth on `wasm32-wasip1-threads`
|
|
672
|
+
|
|
673
|
+
Every thread of a threaded WASI addon runs on one shared `WebAssembly.Memory`.
|
|
674
|
+
V8 updates that memory's size only on the thread that grew it. Every other
|
|
675
|
+
thread keeps checking `memory.fill`, `memory.copy` and atomics against its old
|
|
676
|
+
size until it handles V8's grow interrupt, and on a host without V8's wasm trap
|
|
677
|
+
handler (`--disable-wasm-trap-handler`, or `--wasm-enforce-bounds-checks`) it
|
|
678
|
+
checks every load and store that way. Such a thread traps with `memory access
|
|
679
|
+
out of bounds` on heap pages another thread just grew. V8 fixed this in
|
|
680
|
+
[v8/v8@3424101](https://github.com/v8/v8/commit/34241014663390c72e08c123faef6fedf395be8e);
|
|
681
|
+
napi-rs works around it until the hosts it supports ship that fix.
|
|
682
|
+
|
|
683
|
+
The workaround is on for every addon built for exactly `wasm32-wasip1-threads`
|
|
684
|
+
with `napi` and `napi_build::setup()`. There is nothing to call or configure:
|
|
685
|
+
|
|
686
|
+
```text
|
|
687
|
+
malloc / free / calloc / realloc / ... (Rust's System, wasi-libc, emnapi's `malloc` export)
|
|
688
|
+
-> napi's __wrap_* (napi-build links with --wrap)
|
|
689
|
+
LOCK (spin; sched_yield every 64 spins; never memory.atomic.wait)
|
|
690
|
+
another thread saw a larger memory? memory.grow(0) refresh this thread
|
|
691
|
+
dlmalloc
|
|
692
|
+
-> __wrap_sbrk: the reserve first, else grow >= 16 MiB,
|
|
693
|
+
refresh and publish the new size
|
|
694
|
+
UNLOCK
|
|
695
|
+
|
|
696
|
+
task poll, block_on poll, blocking closure (napi-async-runtime)
|
|
697
|
+
AsyncTask compute, AsyncRuntimeTask poll, (napi)
|
|
698
|
+
napi's default Tokio runtime and spawn_blocking
|
|
699
|
+
-> another thread saw a larger memory? memory.grow(0) one atomic load + one thread-local load
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
- **The allocator lock.** `napi_build::setup()` links with `--wrap` for the 10
|
|
703
|
+
entries of wasi-libc's dlmalloc and for `sbrk`, so every allocation reaches a
|
|
704
|
+
`__wrap_*` function in `napi`. It takes one lock, refreshes the thread's size
|
|
705
|
+
when another thread has seen a larger memory, and runs the real call. The
|
|
706
|
+
thread that grows the memory publishes the new size before it unlocks, so
|
|
707
|
+
every thread is current before dlmalloc touches a byte for it. The lock also
|
|
708
|
+
covers calloc's zero fill and realloc's copy, and C code that calls `sbrk`
|
|
709
|
+
itself takes it too. It spins like dlmalloc's own lock, so it is safe on a
|
|
710
|
+
browser main thread, where `memory.atomic.wait` traps.
|
|
711
|
+
- **The handoff refresh.** Memory can reach a thread without an allocation
|
|
712
|
+
there: a task that allocated on one thread resumes on another, or a closure
|
|
713
|
+
runs on a pool thread. `napi-async-runtime` and napi's own cross-thread
|
|
714
|
+
entries check the size before they run such work.
|
|
715
|
+
- **The heap break.** dlmalloc first uses the pages between the module's own
|
|
716
|
+
memory and the memory the loader created (`napi.wasm.initialMemory`): they
|
|
717
|
+
exist on every thread from the start, so they never need a refresh, and
|
|
718
|
+
nothing grows until they are used. Past them it only uses pages it grew
|
|
719
|
+
itself, at least 16 MiB at a time, so pages another allocator grew never
|
|
720
|
+
reach dlmalloc. The heap never reaches 2 GiB: an allocation that would pass
|
|
721
|
+
it fails. Node's `node:wasi` (v24 and later) answers `EINVAL` to
|
|
722
|
+
`clock_time_get` and `fd_seek` when a pointer is at or above 2 GiB.
|
|
723
|
+
|
|
724
|
+
The threaded `.wasm` exports `malloc` and `free` as before (`@emnapi/core`
|
|
725
|
+
calls them); they now go through the lock. It also exports the 11 `__wrap_*`
|
|
726
|
+
functions, `napi_wasm_heap_sync_stat`, `napi_wasm_thread_crashed` and
|
|
727
|
+
`napi_wasm_thread_crash_flag_address`, because Rust exports every
|
|
728
|
+
`#[no_mangle]` function of a cdylib. They are not an API.
|
|
729
|
+
The threadless `wasm32-wasip1` artifact has none of this.
|
|
730
|
+
|
|
731
|
+
The addon's `napi-build` must be the same package as `napi`'s own
|
|
732
|
+
build-dependency (a path or git `napi` needs `napi-build` from the same
|
|
733
|
+
checkout). With two copies, the addon's `setup()` does not wrap the allocator,
|
|
734
|
+
and the link fails with an undefined symbol,
|
|
735
|
+
`napi_wasi_heap_sync_needs_napi_build_setup_with_wasi_heap_sync`.
|
|
736
|
+
|
|
737
|
+
### Test counters
|
|
738
|
+
|
|
739
|
+
`napi_wasm_heap_sync_stat(index)` returns one counter as a `u32`. It is for
|
|
740
|
+
tests and diagnosis; `examples/wasi-heap-sync/stress.mjs` and rolldown's
|
|
741
|
+
threaded stress test read it by index, so an index keeps its meaning.
|
|
742
|
+
|
|
743
|
+
| index | value |
|
|
744
|
+
| --------- | ------------------------------------------------------------------------------------------- |
|
|
745
|
+
| 0 | heap growths: `memory.grow(n > 0)` calls by `__wrap_sbrk` |
|
|
746
|
+
| 1 | refreshes after taking the lock (including each thread's first) |
|
|
747
|
+
| 2 | blocks that ended past the thread's refreshed size when dlmalloc returned them; must stay 0 |
|
|
748
|
+
| 3 | the heap break, in pages (rounded up); 0 before the first `sbrk` |
|
|
749
|
+
| 4 | `__heap_end` (where the break starts), in pages; 0 before the first `sbrk` |
|
|
750
|
+
| 5 | refreshes at handoffs (napi-async-runtime and napi's cross-thread entries) |
|
|
751
|
+
| any other | `u32::MAX` |
|
|
752
|
+
|
|
753
|
+
With the loader's default memory a small load reads 0 at index 0: the heap fits
|
|
754
|
+
in the reserve and never grows.
|
|
755
|
+
|
|
756
|
+
### Cost
|
|
757
|
+
|
|
758
|
+
Measured in rolldown on its copy of this code, before napi-rs took it over:
|
|
759
|
+
the same lock and break. napi's copy adds the check that keeps other
|
|
760
|
+
allocators' pages out of the break, and keeps the shared size in a cache line
|
|
761
|
+
of its own. Release wasm, Node 24.21 on arm64, 10 interleaved rounds, medians
|
|
762
|
+
in ms:
|
|
763
|
+
|
|
764
|
+
| load | before the lock | with the lock | cost |
|
|
765
|
+
| --------------------------------- | --------------- | ------------- | ---- |
|
|
766
|
+
| MultiThread, 16 builds | 310.5 | 334.5 | 8% |
|
|
767
|
+
| MultiThread, 16 builds, JS plugin | 1518 | 1589.5 | 5% |
|
|
768
|
+
| MultiThread, parse 16x3 | 218.5 | 241 | 10% |
|
|
769
|
+
| CurrentThread, parse 16x3 | 216.5 | 240.5 | 11% |
|
|
770
|
+
| CurrentThread, transform 16x3 | 623.5 | 640 | 3% |
|
|
771
|
+
|
|
772
|
+
"Before the lock" already grew the heap at least 16 MiB at a time. That part
|
|
773
|
+
of the workaround had made the same loads 1.6-3.2x faster than growing in
|
|
774
|
+
dlmalloc's own small steps (transform: no change), and the lock keeps most of
|
|
775
|
+
that gain. The handoff check alone measured at noise level. Other workloads,
|
|
776
|
+
x64, Linux, Windows and browsers are not measured.
|
|
777
|
+
|
|
778
|
+
### Opting out
|
|
779
|
+
|
|
780
|
+
Pass `--cfg napi_wasi_no_heap_sync` in the target rustflags, for example
|
|
781
|
+
`RUSTFLAGS="--cfg napi_wasi_no_heap_sync" napi build --target
|
|
782
|
+
wasm32-wasip1-threads`. `napi` then leaves out the wrappers and its handoff
|
|
783
|
+
checks, and `napi-build` leaves out the `--wrap` link arguments; both read the
|
|
784
|
+
same cfg, so they cannot come apart. `napi-async-runtime`'s check stays, but it
|
|
785
|
+
never fires: nothing publishes a larger size. `napi_build::setup()` declares
|
|
786
|
+
the cfg, so addon code can test `#[cfg(napi_wasi_no_heap_sync)]` too. Without
|
|
787
|
+
the workaround the trap above comes back on hosts without the V8 fix.
|
|
788
|
+
|
|
789
|
+
### What it does not cover
|
|
790
|
+
|
|
791
|
+
- A `#[global_allocator]` that calls `memory.grow` itself (mimalloc's WASI
|
|
792
|
+
build, talc, lol_alloc, the `dlmalloc` crate) is not locked, and its growth
|
|
793
|
+
is never published. One that ends in libc `malloc`, like std's `System`, is
|
|
794
|
+
locked.
|
|
795
|
+
- Memory that reaches a running thread mid-poll (a channel message, an `Arc`)
|
|
796
|
+
and is touched there before that thread's next allocation or handoff, after
|
|
797
|
+
another thread grew the memory. Below the reserve nothing grows. A
|
|
798
|
+
threadsafe-function call, deferred or async work delivered on the JavaScript
|
|
799
|
+
thread is covered: between taking it off its queue and calling back into the
|
|
800
|
+
module, emnapi runs JavaScript frames (`napi_open_handle_scope`,
|
|
801
|
+
`napi_get_reference_value`, `emnapi_is_node_binding_available`,
|
|
802
|
+
`_emnapi_callback_into_module`), and a JavaScript frame handles V8's grow
|
|
803
|
+
interrupt. What remains there is emnapi's own read of the queue node in C, on
|
|
804
|
+
hosts without the trap handler.
|
|
805
|
+
- Work on threads the addon starts or runs itself: a runtime passed to
|
|
806
|
+
`create_custom_tokio_runtime`, direct `tokio::task::spawn_blocking` calls,
|
|
807
|
+
and a host's own threads outside `napi-async-runtime`'s scheduler.
|
|
808
|
+
- A thread that crashes while it holds the lock leaves the others spinning, as
|
|
809
|
+
a crash inside dlmalloc's own lock always did.
|
|
810
|
+
|
|
811
|
+
## Shutdown polls never wait
|
|
812
|
+
|
|
813
|
+
A loader's `dispose()` runs the environment cleanup in two phases with a poll
|
|
814
|
+
between them: `napi_prepare_wasm_env_cleanup_begin`, then
|
|
815
|
+
`napi_wasm_runtime_work_pending` once per event-loop turn until it answers 0,
|
|
816
|
+
then `napi_prepare_wasm_env_cleanup_finish`. The poll never blocks, and that
|
|
817
|
+
includes locks: when a lock the answer is read under is held by another
|
|
818
|
+
thread, it answers 1 and the loader polls again on its next turn. On
|
|
819
|
+
`wasm32-wasip1-threads` a thread that traps unwinds nothing, so a lock it held
|
|
820
|
+
stays held; a poll that waited for it would park the JavaScript thread in
|
|
821
|
+
`memory.atomic.wait32` for good, before the loader can see the crash. A custom
|
|
822
|
+
`AsyncRuntime` backend must keep the same rule in `shutdown_work_pending`. The
|
|
823
|
+
process-exit teardown has no turns to give and makes the single blocking call,
|
|
824
|
+
`napi_prepare_wasm_env_cleanup`.
|
|
825
|
+
|
|
826
|
+
The two phases themselves may wait. With `napi-async-runtime` on
|
|
827
|
+
`wasm32-wasip1-threads` they wait in 1 ms slices on the JavaScript thread, and
|
|
828
|
+
between slices read the addon's crash flag: one 4-byte word in the shared wasm
|
|
829
|
+
memory. Once it is set, the next slice traps, and the loader reports the crash
|
|
830
|
+
instead of hanging.
|
|
831
|
+
|
|
832
|
+
The generated worker sets that word itself, with `Atomics.store`, so it needs
|
|
833
|
+
no wasm instance:
|
|
834
|
+
|
|
835
|
+
```
|
|
836
|
+
loader thread pool worker
|
|
837
|
+
───────────── ───────────
|
|
838
|
+
instantiate; in beforeInit:
|
|
839
|
+
napi_wasm_thread_crash_flag_address()
|
|
840
|
+
view = Int32Array(memory, address, 1)
|
|
841
|
+
spawn → Worker({ workerData: { load → start → run
|
|
842
|
+
crashFlag, crashReport, ...
|
|
843
|
+
addonCrashFlag: view } }) dies (trap, error, failed load):
|
|
844
|
+
... write crashReport
|
|
845
|
+
cleanup waits in slices ◄────────────── Atomics.store(crashFlag, 1)
|
|
846
|
+
view word is 1 → trap Atomics.store(addonCrashFlag, 1)
|
|
847
|
+
catch → crash rejection emnapi's own error report
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
- The Node worker gets the view in `workerData`. The browser worker gets it by
|
|
851
|
+
`postMessage`: the browser pool is created before the wasm is instantiated,
|
|
852
|
+
so the loader posts it to each pool worker after `beforeInit`, and to a
|
|
853
|
+
worker created later right away.
|
|
854
|
+
- The loader's crash flag always goes up first, so when the trap reaches the
|
|
855
|
+
loader it already sees the crash.
|
|
856
|
+
- A worker whose own setup throws (`@napi-rs/wasm-runtime` cannot be
|
|
857
|
+
resolved, say) raises both flags too, then fails as before.
|
|
858
|
+
- A worker that fails while it loads — after the thread spawn that created it
|
|
859
|
+
already returned — raises the flag the same way. `napi_wasm_thread_crashed`,
|
|
860
|
+
which stores into the same word, is only the fallback for a worker that has
|
|
861
|
+
an instance but no view.
|
|
862
|
+
- An addon built with an older napi has no address export: the loader passes
|
|
863
|
+
no view, and the waits stay unbounded, as before.
|
|
864
|
+
|
|
865
|
+
What still cannot raise the flag: a worker that fails before any of its own
|
|
866
|
+
code runs, or that is killed from outside — the `Worker` cannot start, runs
|
|
867
|
+
out of memory, or is terminated by its resource limits. The loader sees those
|
|
868
|
+
only through the worker's `'error'` or `'exit'` event, which needs a turn of
|
|
869
|
+
its event loop, so a cleanup wait already in progress keeps waiting. The same
|
|
870
|
+
holds before `beforeInit`: a thread spawned while the wasm initializes gets a
|
|
871
|
+
worker with no view.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@napi-rs/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.6",
|
|
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
|
},
|