@napi-rs/cli 3.10.6 → 3.10.8

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.
@@ -5,6 +5,7 @@ import {
5
5
  } from './binding-target.js'
6
6
 
7
7
  const WASI_DISPOSE_SYMBOL = 'napi.rs.wasi.dispose'
8
+ const WASI_THREAD_POOL_RECONCILE_SYMBOL = 'napi.rs.wasi.reconcileThreadPool'
8
9
  const WASI_ROLLBACK_REGISTRY_SYMBOL = 'napi.rs.wasi.rollback.registry.v1'
9
10
 
10
11
  /**
@@ -4005,6 +4006,11 @@ function __createWasiWorker(filename) {
4005
4006
  threads && asyncRuntime
4006
4007
  ? `${indent}__releaseCurrentThreadHostTimers()\n`
4007
4008
  : ''
4009
+ // The threaded context defers emnapi's calls into wasm through
4010
+ // `__wasiSetImmediate`, which a crash disposal closes.
4011
+ const emnapiContextOptions = threads
4012
+ ? '{ autoDestroy: false, features: { setImmediate: __wasiSetImmediate } }'
4013
+ : '{ autoDestroy: false }'
4008
4014
  // Only the threaded flavor has pool workers whose wasm thread can die under
4009
4015
  // this one. See `__disposeWasiBindingAtExit`.
4010
4016
  const threadCrashLatch = threads
@@ -4050,6 +4056,34 @@ function __hasWasiThreadCrashed() {
4050
4056
  }
4051
4057
 
4052
4058
  let __wasiThreadCrashDisposePromise
4059
+ // Raised by the crash disposal right before it terminates the workers. See
4060
+ // \`__wasiSetImmediate\`.
4061
+ let __wasiReentryClosed = false
4062
+
4063
+ /**
4064
+ * emnapi's \`features.setImmediate\` for this binding's context. emnapi defers
4065
+ * its calls back into wasm through it: \`_emnapi_set_immediate\` (libuv handle
4066
+ * closes, threadsafe-function finalizers), threadsafe-function dispatch
4067
+ * (\`async-send\`) and the finalizer queue. One queued before a crash disposal
4068
+ * terminates the workers still runs after it, and a worker terminated while it
4069
+ * held a lock in the wasm heap (napi's heap-sync allocator lock spins and never
4070
+ * gives up) leaves that call spinning on this thread for good. The binding is
4071
+ * unusable after a crash disposal, so those calls are dropped.
4072
+ *
4073
+ * Known residual, still able to enter wasm after a crash disposal because emnapi
4074
+ * offers no hook for them: the \`FinalizationRegistry\` callbacks that free
4075
+ * external memory when GC collects a value (\`_free\`, the shared-buffer meta
4076
+ * release), threadsafe-function dispatch of \`async-send\` type 1 and
4077
+ * \`_emnapi_next_tick\` (both \`Promise.resolve().then\`), and every deferred
4078
+ * call under emnapi 1.x, whose \`createContext\` ignores \`features\`.
4079
+ */
4080
+ function __wasiSetImmediate(callback) {
4081
+ return setImmediate(function () {
4082
+ if (!__wasiReentryClosed) {
4083
+ callback()
4084
+ }
4085
+ })
4086
+ }
4053
4087
 
4054
4088
  /**
4055
4089
  * Stores the first error a pool worker reported, with the worker's id. Called
@@ -4200,6 +4234,8 @@ function __disposeWasiBindingAfterThreadCrash() {
4200
4234
  }
4201
4235
  __releaseEmnapiWaitingRequestHandle()
4202
4236
  ${releaseCurrentThreadHostTimers(' ')}\
4237
+ // No call into wasm after this: see \`__wasiSetImmediate\`.
4238
+ __wasiReentryClosed = true
4203
4239
  let workerResult
4204
4240
  try {
4205
4241
  workerResult = __terminateWasiWorkers()
@@ -4299,6 +4335,170 @@ ${releaseCurrentThreadHostTimers(' ')}\
4299
4335
  }
4300
4336
  `
4301
4337
  : ''
4338
+ // Threaded flavor only: keeps emnapi's idle reuse pool at the addon's
4339
+ // configured MultiThread worker count. See docs/wasi.md, "Thread pool
4340
+ // preload".
4341
+ const threadPoolReconcile = threads
4342
+ ? `
4343
+ const __wasiThreadPoolReconcileSymbol = Symbol.for('${WASI_THREAD_POOL_RECONCILE_SYMBOL}')
4344
+
4345
+ /**
4346
+ * Takes a Worker out of emnapi's reuse pool, if it is still there. emnapi
4347
+ * terminates a pooled Worker that failed to load but (up to
4348
+ * @emnapi/wasi-threads 2.1.0) leaves it in the pool, where the next thread
4349
+ * spawn would pop it. A newer emnapi removes it itself, so this is a no-op
4350
+ * then.
4351
+ */
4352
+ function __removeWasiPoolWorker(manager, worker) {
4353
+ const index = manager.unusedWorkers.indexOf(worker)
4354
+ if (index !== -1) {
4355
+ manager.unusedWorkers.splice(index, 1)
4356
+ }
4357
+ }
4358
+
4359
+ /**
4360
+ * Takes a Worker the thread manager just terminated out of \`__wasiWorkers\`
4361
+ * once it has exited, not before. \`terminateWorker\` only starts Node's
4362
+ * asynchronous \`worker.terminate()\` and drops its promise, so a disposal that
4363
+ * begins before the exit has to find this Worker in the set and wait for it
4364
+ * like any other. A second \`terminate()\` settles when the Worker has exited,
4365
+ * the way \`__terminateWasiWorkers\` waits.
4366
+ */
4367
+ function __untrackWasiWorkerOnExit(worker) {
4368
+ const terminated = worker.terminate()
4369
+ if (__isThenable(terminated)) {
4370
+ Promise.resolve(terminated).then(
4371
+ () => {
4372
+ __wasiWorkers.delete(worker)
4373
+ },
4374
+ // Left tracked: disposal terminates it again and reports the error.
4375
+ () => {},
4376
+ )
4377
+ } else {
4378
+ __wasiWorkers.delete(worker)
4379
+ }
4380
+ }
4381
+
4382
+ /**
4383
+ * Matches emnapi's idle reuse pool to the addon's configured MultiThread worker
4384
+ * count, which the addon exports as \`napi_wasm_runtime_pool_workers\`
4385
+ * (napi-async-runtime; 0 under CurrentThread). \`reuseWorker: true\` starts the
4386
+ * pool empty, so without this every pool thread the first async call spawns
4387
+ * boots a Worker and loads the wasm into it first. Here each missing Worker is
4388
+ * created and starts loading now; a spawn later pops one that is already
4389
+ * booting. Idle Workers above the count are terminated, last in first out,
4390
+ * the way a spawn takes them. A Worker a spawn already took is not in the pool
4391
+ * and is left alone.
4392
+ *
4393
+ * Runs once after a successful load and, when the loader wraps it, after every
4394
+ * successful \`configureAsyncRuntime\`. Also reachable as
4395
+ * binding[Symbol.for('${WASI_THREAD_POOL_RECONCILE_SYMBOL}')]().
4396
+ * It never throws and never waits on a Worker: a Worker whose load fails is
4397
+ * dropped from the pool when its load rejects. Nothing at all happens after a
4398
+ * thread crash, once disposal started, or for an addon without the export.
4399
+ */
4400
+ function __reconcileWasiThreadPool() {
4401
+ try {
4402
+ if (__wasiDisposed || __wasiDisposePromise || __hasWasiThreadCrashed()) {
4403
+ return
4404
+ }
4405
+ const read = __napiInstance?.exports?.napi_wasm_runtime_pool_workers
4406
+ if (typeof read !== 'function') {
4407
+ return
4408
+ }
4409
+ const count = read() >>> 0
4410
+ const manager = __getWasiThreadManager()
4411
+ if (
4412
+ !manager ||
4413
+ !Array.isArray(manager.unusedWorkers) ||
4414
+ typeof manager.allocateUnusedWorker !== 'function' ||
4415
+ typeof manager.loadWasmModuleToWorker !== 'function'
4416
+ ) {
4417
+ return
4418
+ }
4419
+ // Both loops are bounded by the difference they start from, so a manager
4420
+ // that does not update \`unusedWorkers\` the way emnapi does cannot spin.
4421
+ for (let excess = manager.unusedWorkers.length - count; excess > 0; excess--) {
4422
+ const worker = manager.unusedWorkers[manager.unusedWorkers.length - 1]
4423
+ manager.terminateWorker(worker)
4424
+ __removeWasiPoolWorker(manager, worker)
4425
+ __untrackWasiWorkerOnExit(worker)
4426
+ // Compatibility with @emnapi/wasi-threads 2.1.0 and older:
4427
+ // \`terminateWorker\` installs a reporter that logs every emnapi message
4428
+ // still queued on the port, so a Worker that finished loading just
4429
+ // before it was terminated prints 'received "loaded" command from
4430
+ // terminated worker'. Nothing listens for that Worker any more, and a
4431
+ // newer emnapi ignores the late 'loaded' itself, so this is harmless
4432
+ // there. \`__terminateWasiWorkers\` does the same.
4433
+ worker.onmessage = undefined
4434
+ }
4435
+ for (let missing = count - manager.unusedWorkers.length; missing > 0; missing--) {
4436
+ let worker
4437
+ try {
4438
+ // Through \`onCreateWorker\`: tracked in \`__wasiWorkers\`, unref'd, and
4439
+ // handed the crash flags like any pool Worker.
4440
+ worker = manager.allocateUnusedWorker()
4441
+ manager
4442
+ .loadWasmModuleToWorker(worker)
4443
+ .then(undefined, () => __removeWasiPoolWorker(manager, worker))
4444
+ } catch {
4445
+ if (worker !== undefined) {
4446
+ __removeWasiPoolWorker(manager, worker)
4447
+ try {
4448
+ manager.terminateWorker(worker)
4449
+ __untrackWasiWorkerOnExit(worker)
4450
+ } catch {}
4451
+ }
4452
+ return
4453
+ }
4454
+ }
4455
+ } catch {}
4456
+ }
4457
+
4458
+ function __publishWasiThreadPoolReconcile(exports) {
4459
+ Object.defineProperty(exports, __wasiThreadPoolReconcileSymbol, {
4460
+ configurable: false,
4461
+ enumerable: false,
4462
+ value: __reconcileWasiThreadPool,
4463
+ writable: false,
4464
+ })
4465
+ }
4466
+ ${
4467
+ asyncRuntime
4468
+ ? `
4469
+ /**
4470
+ * Replaces the addon's \`configureAsyncRuntime\` export, if it has one, with a
4471
+ * wrapper that reconciles the pool after every successful call: a configure
4472
+ * changes the count the pool was preloaded for. A configure that throws changed
4473
+ * nothing, so its error propagates and the pool stays as it is. Matched by
4474
+ * name, like the CurrentThread host install: it is the export
4475
+ * napi-async-runtime's adapter defines, and an addon that defines its own under
4476
+ * that name gets the same reconcile. Never fails the load.
4477
+ */
4478
+ function __wrapWasiConfigureAsyncRuntime(binding) {
4479
+ let configure
4480
+ try {
4481
+ configure = binding.configureAsyncRuntime
4482
+ } catch {
4483
+ return
4484
+ }
4485
+ if (typeof configure !== 'function') {
4486
+ return
4487
+ }
4488
+ try {
4489
+ binding.configureAsyncRuntime = function configureAsyncRuntime(...args) {
4490
+ const result = Reflect.apply(configure, this, args)
4491
+ try {
4492
+ __reconcileWasiThreadPool()
4493
+ } catch {}
4494
+ return result
4495
+ }
4496
+ } catch {}
4497
+ }
4498
+ `
4499
+ : ''
4500
+ }`
4501
+ : ''
4302
4502
  const workerRuntimeImport = threads
4303
4503
  ? ` createOnMessage: __wasmCreateOnMessageForFsProxy,\n`
4304
4504
  : ''
@@ -4410,6 +4610,7 @@ const { createContext: __emnapiCreateContext } = require('@emnapi/runtime')
4410
4610
  ${asyncRuntimeImport}\
4411
4611
  ${workerExecArgv}\
4412
4612
  ${threadCrashLatch}\
4613
+ ${threadPoolReconcile}\
4413
4614
 
4414
4615
  const __cwd = process.cwd()
4415
4616
  const __rootDir = __nodePath.parse(__cwd).root
@@ -4631,7 +4832,7 @@ try {
4631
4832
  const __finishAutoDestroyCapture = __captureEmnapiAutoDestroyListener()
4632
4833
  try {
4633
4834
  __emnapiContext = __wrapEmnapiContextDestroyForSettlement(
4634
- __emnapiCreateContext({ autoDestroy: false }),
4835
+ __emnapiCreateContext(${emnapiContextOptions}),
4635
4836
  __prepareWasmEnvCleanup,
4636
4837
  __isPreparingWasmEnvCleanup,
4637
4838
  )
@@ -4675,7 +4876,9 @@ ${captureAddonCrashFlag}\
4675
4876
  },
4676
4877
  }))
4677
4878
  __publishWasiDispose(__napiModule.exports)
4879
+ ${threads ? ' __publishWasiThreadPoolReconcile(__napiModule.exports)\n' : ''}\
4678
4880
  ${installAsyncRuntimeHosts}\
4881
+ ${threads && asyncRuntime ? ' __wrapWasiConfigureAsyncRuntime(__napiModule.exports)\n' : ''}\
4679
4882
  // The CommonJS tail below aliases \`__napiModule.exports\`; a named module
4680
4883
  // export does not travel with it, so carry the marker on the binding itself
4681
4884
  // too. Three things pin the stamp to exactly this spot:
@@ -4704,5 +4907,14 @@ ${installAsyncRuntimeHosts}\
4704
4907
  __runWasiInitializationRollback(rollback)
4705
4908
  throw rollback.error
4706
4909
  }
4910
+ ${
4911
+ threads
4912
+ ? `// Preload the pool for the count the addon configured during registration.
4913
+ // See \`__reconcileWasiThreadPool\`.
4914
+ try {
4915
+ __reconcileWasiThreadPool()
4916
+ } catch {}
4707
4917
  `
4918
+ : ''
4919
+ }`
4708
4920
  }
@@ -51,6 +51,13 @@ Generated by [AVA](https://avajs.dev).
51
51
  platformArchABI: 'win32-arm64-msvc',
52
52
  triple: 'aarch64-pc-windows-msvc',
53
53
  },
54
+ {
55
+ abi: null,
56
+ arch: 'arm64',
57
+ platform: 'freebsd',
58
+ platformArchABI: 'freebsd-arm64',
59
+ triple: 'aarch64-unknown-freebsd',
60
+ },
54
61
  {
55
62
  abi: null,
56
63
  arch: 'x64',
package/src/utils/misc.ts CHANGED
@@ -633,7 +633,7 @@ export async function withPackageFileSystemReconciliation<T>(
633
633
  return acquire(0)
634
634
  }
635
635
 
636
- function managedPackagePathIsWithin(root: string, path: string) {
636
+ export function managedPackagePathIsWithin(root: string, path: string) {
637
637
  const relativePath = relative(resolve(root), resolve(path))
638
638
  return (
639
639
  relativePath === '' ||
@@ -643,7 +643,7 @@ function managedPackagePathIsWithin(root: string, path: string) {
643
643
  )
644
644
  }
645
645
 
646
- function canonicalizeManagedPackagePath(path: string) {
646
+ export function canonicalizeManagedPackagePath(path: string) {
647
647
  let current = resolve(path)
648
648
  const missingSegments: string[] = []
649
649
  while (true) {
@@ -17,6 +17,7 @@ export const AVAILABLE_TARGETS = [
17
17
  'aarch64-unknown-linux-musl',
18
18
  'aarch64-unknown-linux-ohos',
19
19
  'aarch64-pc-windows-msvc',
20
+ 'aarch64-unknown-freebsd',
20
21
  'x86_64-apple-darwin',
21
22
  'x86_64-pc-windows-msvc',
22
23
  'x86_64-pc-windows-gnu',