Expand description
A shared, tokio-free async runtime for napi-rs addons.
Two layers, one crate:
- Scheduler (always compiled, napi-free): a pluggable async/CPU/blocking
scheduler with a MultiThread flavor (Rayon-backed worker pool on native
targets) and a CurrentThread flavor (host-driven turns; the only flavor on
WebAssembly). Exposed at the crate root:
spawn,spawn_blocking,block_on,sleep_until,configure,start,shutdown, the host driver SPIs (CurrentThreadTaskDriver,TimerDriver), and the metrics/introspection helpers. - napi adapter (feature
napi, on by default): anunsafe impl AsyncRuntimebackend for napi-rs’async-runtimeSPI plus the JavaScript-facing host protocol (registerCurrentThreadTaskHost,registerTimerHost, config/metrics exports). Hosts callinstallfrom their own#[napi_derive::module_init]hook after resolving their runtime configuration; this crate deliberately ships nomodule_initof its own.
Modules§
- js_
callback - Generic JavaScript-callback alias used by the host protocol exports.
Structs§
- Binding
Host Registration - Binding
Runtime Config - Binding
Runtime Metrics - Binding
Runtime Options - Block
OnDeadlock - Typed panic payload for a self-detected
block_ondeadlock. Thrown withstd::panic::panic_anyat the park DECISION (never merely on aPendingreturn, because a self-waking future is not a deadlock), so the runtime fails loudly instead of freezing the thread (and, in the CurrentThread-on-JS-thread case, the whole JS event loop, where not even test-harness timeouts can fire). When the panic unwinds out of a spawned task’s poll,JoinError::from_panicpreserves this diagnostic’s message. - Current
Thread Task Callback Lease - Retains one admitted CurrentThread host callback in its runtime generation.
- Current
Thread Task Delivery - Current
Thread Task Driver Id - Join
Error - Join
Handle - Runtime
Config Error - Runtime
Metrics Snapshot - Runtime
Options - Runtime
Options Patch - A partial update to the runtime options exposed by the binding.
- Sleep
- Timer future returned by
sleep_until. Resolves at/after its deadline (or early on runtime shutdown drain-fire). Dropping it cancels the underlying registration – thetokio::select!losing-arm semantics the watch coordinator’s debounce loop relies on. - Timer
Driver Id - Handle to one registration in a
TimerDriverRegistry, returned byTimerDriverRegistry::registerand consumed byTimerDriverRegistry::unregister. - Timer
Driver Registry - Host timer drivers keyed by registration order. A driver is owned by a host
context that can die independently
of the process – the Node binding registers one driver per importing napi
env, and a worker’s env teardown kills its driver’s threadsafe function. A
single first-wins slot is therefore unsound: a worker that imports the
binding first and then exits would permanently shadow the slot with a dead
driver, and every CT timer armed afterwards would busy-fail against it.
Selecting only the newest live driver is also
insufficient: a live worker can be CPU-blocked indefinitely while another
environment remains responsive. The registry therefore keeps all
registrants and returns a stable snapshot of every live driver at each
Sleeppoll, sweeping dead entries out as it goes.
Enums§
- Binding
Runtime Flavor - Block
OnDeadlock Kind - Which self-detected
block_ondeadlock class fired. - Runtime
Flavor
Constants§
- DEFAULT_
DRAIN_ LINGER_ MICROS - Default drainer idle-linger budget (µs):
RuntimeOptions::drain_linger’s default, applied when no host configuration orDRAIN_LINGER_ENVoverride resolves a different value. - DRAIN_
LINGER_ ENV - Environment variable overriding the MultiThread drainer idle-linger budget
in MICROSECONDS (
RuntimeOptions::drain_linger, consumed byMultiThreadExecutor::drain). After a drainer runs the runnable/blocking queues empty it parks on its own [DriverParker] (registered withparked_drivers) for up to this long before returning its thread to rayon. A wave of work arriving within the budget is resumed by ONE targeted condvar wake instead of aspawn_fiforespawn through rayon’s idle protocol – whose per-transition cost (a wake cascade plus 33sched_yieldrounds and a full steal sweep on EVERY worker that was woken) is what makes system CPU scale with the worker count under wave-shaped loads.0disables lingering (legacy exit-and-respawn on every empty queue); values aboveMAX_DRAIN_LINGER_MICROSare clamped. A frame’s TOTAL lingering residence is additionally capped at [DRAIN_LINGER_FRAME_FACTOR] times this budget so periodic sub-budget work cannot pin a rayon worker inside one drain frame forever. - MAX_
ASYNC_ RUNTIME_ WORKER_ THREADS - Maximum number of physical workers a scheduler owned by this crate may create.
- MAX_
DRAIN_ LINGER_ MICROS - Ceiling on
RuntimeOptions::drain_lingerin MICROSECONDS (100ms). Validation (and, defensively, executor construction) clamps the budget here: lingering exists to bridge micro-scale gaps between scheduling waves, and a lingering drainer holds a pool worker plus anactive_drainersslot while idle, so budgets beyond this bound stop tuning the wave optimization and start pinning workers. - PARK_
DEADLINE_ ENV - Environment variable that arms deadline-based
block_ondeadlock detection: milliseconds a runtime-owned park may sleep with zero runtime progress before panicking (seeRuntimeOptions::park_deadline). Missing, non-numeric or0all mean “disabled”. The runtime does NOT read this variable itself: the embedding host’s env-resolution parses it once at addon load and hands the result toconfigurethroughRuntimeOptions::park_deadline. The name is the conventional default; hosts may resolve their own variable into the same field.
Traits§
- Current
Thread Task Driver - Host-turn dispatcher for the CurrentThread runnable queue.
- Timer
Driver - Seam for host-delegated timers: the CurrentThread flavor cannot park a
helper thread on a threadless build, so it delegates
sleep_untilto the host event loop through this trait. The Node binding installs asetTimeout-based implementation viaregister_timer_driverat import – one per importing napi env (main thread AND workers).
Functions§
- acknowledge_
current_ thread_ task_ delivery - Acknowledge that one exact host delivery callback returned the successful
claim result from
drive_current_thread_tasks. - block_
on - block_
on_ dyn - cancel_
current_ thread_ task_ dispatch - Cancel an accepted CurrentThread host delivery that failed before it could schedule a fresh turn.
- configure
- configure_
async_ runtime - Override the shared async runtime’s flavor and thread counts.
- configure_
partial - configured_
options - drive_
current_ thread_ tasks - Poll the shared runtime’s CurrentThread queue from a host-dispatched turn.
- fail_
current_ thread_ task_ delivery - Report that one exact host delivery callback failed.
- get_
async_ runtime_ config - Return the effective async runtime configuration.
- get_
async_ runtime_ metrics - Return a snapshot of the shared async runtime’s task and scheduler counters.
- get_
current_ thread_ task_ host_ contract_ version - Return the native CurrentThread task-host ABI expected by the JavaScript package before it invokes either async-runtime host registration. Version 4 reserves and validates an exact registration capability before host installation performs side effects.
- has_
live_ timer_ driver - Whether a LIVE host timer driver is currently registered. Lets the
embedder report CurrentThread timer availability honestly (e.g. through a
capabilities export) instead of guessing: with no
live driver a CurrentThread
sleep_untilwould panic, so the capability must readfalse– including when the only registrants are DEAD (their owning envs torn down), not merely when none ever registered. - install
- Configure the shared scheduler and register it as this addon’s napi
AsyncRuntimebackend. - is_
current_ thread_ host_ registration_ active - Return whether one exact CurrentThread task- or timer-host registration is still live. The JavaScript package revalidates its process-global marker on every module evaluation so native eviction cannot leave a stale installed bit that permanently suppresses replacement registration.
- is_
multi_ threaded - max_
async_ runtime_ worker_ threads - Platform-realizable worker ceiling after applying this crate’s production cap.
- metrics
- register_
current_ thread_ task_ driver - register_
current_ thread_ task_ host - Install a native-owned host turn used to poll CurrentThread runnables without re-entering arbitrary future waker locks. Called once per importing environment. JavaScript callbacks are rejected synchronously.
- register_
timer_ driver - Register a host timer driver for the CurrentThread flavor’s
sleep_until. Every host context that can serve timers registers its own driver (the Node binding: one per importing napi env); every LIVE registrant receives each timer so a starved environment cannot own its only wake source. Returns the handle forunregister_timer_driver. - register_
timer_ host - Install the host timer callback backing the shared async runtime’s CurrentThread timers. Called at import by every binding-loading JS entry with paired setTimeout/clearTimeout callbacks; each importing env (main thread and workers alike) registers its own host, and every live host receives each timer.
- request_
current_ thread_ task_ drain - Request service for work that may have accumulated while no live host dispatcher was registered. A new host receives the existing internal dispatch capability through a fresh registration-scoped delivery token; already accepting hosts keep their attempts.
- reserve_
current_ thread_ host_ registration - Reserve an exact CurrentThread host registration capability before either task or timer installation performs side effects. The JavaScript package validates the returned words and passes them back to one registration call.
- reset_
async_ runtime_ metrics - Reset cumulative async runtime event counters to zero.
- reset_
metrics - resolve_
drain_ linger - Resolve an env-derived drainer idle-linger budget (a raw
[
DRAIN_LINGER_ENV]-style value, MICROSECONDS) intoRuntimeOptions::drain_linger: a missing or non-numeric value keepsfallback(the host-provided field, [DEFAULT_DRAIN_LINGER_MICROS] by default);0disables lingering; anything else is clamped to [MAX_DRAIN_LINGER_MICROS] so an extreme value (e.g.u64::MAX) tunes the wave optimization instead of pinning pool workers on never-expiring lingers.RuntimeOptionsvalidation re-applies the same clamp, so a host bypassing this resolver cannot smuggle an unbounded budget either. - resolve_
thread_ count - Resolve an env-derived thread count: parse the raw value, treat a missing,
non-numeric, OR zero value as “unset” and fall back to
default, then clamp the result tomaximum. A0must never survive here because the host’s module-init path feeds this count into a constructor that rejects0:RuntimeOptionsvalidation rejects a0thread count, which would panic anexpect()during addon load over what may be a simple typo in an environment variable. - shutdown
- sleep_
until - Sleep until
deadlineon the runtime’s timer facility. MultiThread uses the executor-owned heap; CurrentThread requires a LIVE host driver registered viaregister_timer_driverand otherwise fails LOUD (a missing driver must never become a silent never-firing debounce). - spawn
- spawn_
blocking - spawn_
detached - start
- try_
block_ on_ dyn - Drive a borrowed future without consuming it when admission or shutdown fails.
- try_
spawn - Submit a future without consuming it when the runtime is not accepting work.
- try_
spawn_ blocking - Submit blocking work without consuming it when the runtime is not accepting work.
- try_
spawn_ detached - unregister_
current_ thread_ task_ driver - unregister_
current_ thread_ task_ host - Evict exactly one native host installed by
registerCurrentThreadTaskHost. Managed workerd disposal uses this before environment cleanup so a later throwing cleanup hook cannot leave the process-global driver selected. - unregister_
timer_ driver - Remove a driver registered via
register_timer_driver– called when its host context dies (the Node binding evicts on env teardown and on callback failure). Idempotent. - unregister_
timer_ host - Evict exactly one callback installed by
registerTimerHost. Pending sleeps are woken so they can reselect another live environment.