Skip to main content

napi_async_runtime/
async_runtime.rs

1//! Shared async/CPU/blocking scheduler.
2//! See the implementation notes in this crate's README.
3
4use std::{
5  any::Any,
6  collections::VecDeque,
7  fmt,
8  future::Future,
9  mem::ManuallyDrop,
10  panic::{AssertUnwindSafe, catch_unwind, resume_unwind},
11  pin::Pin,
12  sync::{
13    Arc, LazyLock, Weak,
14    atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering},
15  },
16  task::{Context, Poll, Waker},
17  time::{Duration, Instant},
18};
19
20use arc_swap::ArcSwapOption;
21use rustc_hash::{FxHashMap, FxHashSet};
22
23use crate::MAX_ASYNC_RUNTIME_WORKER_THREADS;
24use crate::sync::{BoundedWaits, Condvar, Mutex, MutexGuard, UnboundedCondvar};
25// Both users are MultiThread pool tests.
26#[cfg(all(test, napi_runtime_os_threads))]
27use async_task::Task;
28use async_task::{FallibleTask, Runnable};
29use futures::{
30  FutureExt,
31  future::{AbortHandle, AbortRegistration, Abortable},
32};
33
34#[cfg(napi_runtime_os_threads)]
35use crossbeam_deque::{Injector, Steal};
36#[cfg(napi_runtime_os_threads)]
37use futures::channel::oneshot;
38#[cfg(all(test, napi_runtime_os_threads))]
39use rayon::max_num_threads;
40#[cfg(napi_runtime_os_threads)]
41use rayon::{ThreadPool, ThreadPoolBuilder};
42
43/// Stable scheduler identities are equality capabilities. Exhaustion must fail
44/// before reuse can authorize a stale handle or collide in an indexed queue.
45fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
46  counter
47    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
48    .expect(exhausted)
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RuntimeFlavor {
53  CurrentThread,
54  MultiThread,
55}
56
57#[derive(Debug, Clone)]
58pub struct RuntimeOptions {
59  pub flavor: RuntimeFlavor,
60  /// Physical worker count for [`RuntimeFlavor::MultiThread`]; forced to 1 on
61  /// [`RuntimeFlavor::CurrentThread`]. Validation clamps it to
62  /// `2..=max_async_runtime_worker_threads()`, so MultiThread always has at
63  /// least two lanes.
64  ///
65  /// The scheduler NEVER probes the CPU count on a wasm target, because there
66  /// is nothing truthful to probe: `std::thread::available_parallelism()`
67  /// answers `Ok(1)` on `wasm32-wasip1-threads`, so the default lands on the
68  /// clamped minimum of two. The host has the real number
69  /// (`os.availableParallelism()` in Node, `navigator.hardwareConcurrency` in
70  /// a browser) and must pass it through [`configure`] or
71  /// [`configure_partial`] before the first async call.
72  ///
73  /// Peak thread count is `worker_threads + 1`: [`sleep_until`] lazily spawns
74  /// one non-Rayon timekeeper thread on the first timer registration. Under
75  /// `wasm32-wasip1-threads` each of those is a Node Worker, so budget for
76  /// `worker_threads + 1` of them, not `worker_threads`.
77  pub worker_threads: usize,
78  pub max_blocking_tasks: usize,
79  pub thread_name_prefix: String,
80  /// Deadline-based `block_on` deadlock detection: bounds how long a
81  /// runtime-owned park (the CurrentThread `block_on` parker and
82  /// MultiThread COOPERATIVE driver parks -- never the foreign/napi
83  /// whole-build park) may sleep with ZERO runtime progress before
84  /// panicking with the typed [`BlockOnDeadlock`] diagnostic. `None` (the
85  /// default) disables deadline detection: a legitimately long park (e.g.
86  /// awaiting a slow JS plugin) must never panic a production build. The
87  /// runtime itself never reads the environment; the embedder resolves its
88  /// own configuration (conventionally a [`PARK_DEADLINE_ENV`]-style
89  /// variable) into this field at addon load. The
90  /// threadless-wasm CERTAIN deadlock check is independent of this knob and
91  /// always on.
92  pub park_deadline: Option<Duration>,
93  /// MultiThread drainer idle-linger budget (see `MultiThreadExecutor::drain`):
94  /// after running the runnable/blocking queues empty a drainer parks on its
95  /// own parker for up to this long before returning its thread to rayon, so
96  /// a wave landing within the budget resumes it with one targeted wake
97  /// instead of a `spawn_fifo` respawn through rayon's idle protocol.
98  /// `Duration::ZERO` disables lingering (legacy exit-and-respawn on every
99  /// empty queue). Validation clamps the budget to
100  /// [`MAX_DRAIN_LINGER_MICROS`]: a linger occupies a pool worker and an
101  /// `active_drainers` slot while idle, so an unbounded value would pin
102  /// workers indefinitely. As with `park_deadline`, the runtime itself never
103  /// reads the environment; the embedder resolves its configuration
104  /// (conventionally [`DRAIN_LINGER_ENV`], which the napi adapter's `install`
105  /// resolves for hosts that use it) into this field at addon load. Ignored
106  /// by the CurrentThread flavor, which has no pool drainers.
107  pub drain_linger: Duration,
108}
109
110/// A partial update to the runtime options exposed by the binding.
111///
112/// [`configure_partial`] merges these fields into the latest configured
113/// options, validates the merged candidate, and commits it while holding the
114/// runtime controller lock. Fields that are not exposed for partial updates,
115/// such as `thread_name_prefix` and `park_deadline`, remain unchanged.
116#[derive(Debug, Clone, Copy, Default)]
117pub struct RuntimeOptionsPatch {
118  pub flavor: Option<RuntimeFlavor>,
119  pub worker_threads: Option<usize>,
120  pub max_blocking_tasks: Option<usize>,
121  /// `Some(Duration::ZERO)` disables lingering; other values are clamped to
122  /// [`MAX_DRAIN_LINGER_MICROS`] by validation.
123  pub drain_linger: Option<Duration>,
124}
125
126impl RuntimeOptionsPatch {
127  fn apply_to(self, options: &mut RuntimeOptions) {
128    if let Some(flavor) = self.flavor {
129      options.flavor = flavor;
130    }
131    if let Some(worker_threads) = self.worker_threads {
132      options.worker_threads = worker_threads;
133    }
134    if let Some(max_blocking_tasks) = self.max_blocking_tasks {
135      options.max_blocking_tasks = max_blocking_tasks;
136    }
137    if let Some(drain_linger) = self.drain_linger {
138      options.drain_linger = drain_linger;
139    }
140  }
141}
142
143impl Default for RuntimeOptions {
144  fn default() -> Self {
145    let worker_threads = std::thread::available_parallelism()
146      .map_or(1, usize::from)
147      .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
148    Self {
149      flavor: if cfg!(target_family = "wasm") {
150        RuntimeFlavor::CurrentThread
151      } else {
152        RuntimeFlavor::MultiThread
153      },
154      worker_threads,
155      max_blocking_tasks: worker_threads,
156      thread_name_prefix: "napi-async-runtime".to_string(),
157      park_deadline: None,
158      drain_linger: DEFAULT_DRAIN_LINGER,
159    }
160  }
161}
162
163impl RuntimeOptions {
164  fn validate(self) -> Result<Self, RuntimeConfigError> {
165    #[cfg(not(napi_runtime_os_threads))]
166    let rayon_max_threads = None;
167    #[cfg(napi_runtime_os_threads)]
168    let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
169    self.validate_with_rayon_max_threads(rayon_max_threads)
170  }
171
172  fn validate_with_rayon_max_threads(
173    mut self,
174    rayon_max_threads: Option<usize>,
175  ) -> Result<Self, RuntimeConfigError> {
176    if self.worker_threads == 0 {
177      return Err(RuntimeConfigError(
178        "worker_threads must be greater than zero".to_string(),
179      ));
180    }
181    if self.max_blocking_tasks == 0 {
182      return Err(RuntimeConfigError(
183        "max_blocking_tasks must be greater than zero".to_string(),
184      ));
185    }
186    // A lingering drainer occupies a pool worker and an `active_drainers`
187    // slot while idle, so an unbounded budget (e.g. an env typo resolving to
188    // u64::MAX microseconds) would pin workers indefinitely and wedge every
189    // idle-wait. Clamp instead of erroring: like the thread-count clamps
190    // below, an out-of-range value is a tuning excess, not a topology error.
191    // ZERO passes through unchanged and means "lingering disabled".
192    self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
193    if self.flavor == RuntimeFlavor::CurrentThread {
194      self.worker_threads = 1;
195      self.max_blocking_tasks = 1;
196    } else {
197      let Some(rayon_max_threads) = rayon_max_threads else {
198        return Err(RuntimeConfigError(
199          "the multi-thread runtime is unavailable in this threadless WebAssembly build \
200           (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
201            .to_string(),
202        ));
203      };
204      if rayon_max_threads < 2 {
205        return Err(RuntimeConfigError(
206          "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
207        ));
208      }
209      // Blocking closures execute on the shared Rayon pool. Keep one execution
210      // lane available for runnable futures and timer service even when every
211      // admitted blocking job is stalled. MultiThread therefore has a truthful
212      // minimum of two physical/configured workers rather than a hidden reserve.
213      // Apply both this crate's production ceiling and Rayon's platform cap
214      // before deriving the blocking allowance, so a silently reduced physical
215      // pool cannot consume that reserved lane.
216      self.worker_threads = self
217        .worker_threads
218        .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
219        .min(rayon_max_threads);
220      let blocking_capacity = self.worker_threads - 1;
221      self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
222    }
223    Ok(self)
224  }
225}
226
227/// Environment variable that arms deadline-based `block_on` deadlock
228/// detection: milliseconds a runtime-owned park may sleep with zero runtime
229/// progress before panicking (see [`RuntimeOptions::park_deadline`]).
230/// Missing, non-numeric or `0` all mean "disabled". The runtime does NOT
231/// read this variable itself: the embedding host's env-resolution parses it
232/// once at addon load and hands the result to [`configure`] through
233/// [`RuntimeOptions::park_deadline`]. The name is the conventional default;
234/// hosts may resolve their own variable into the same field.
235pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
236
237/// Environment variable overriding the MultiThread drainer idle-linger budget
238/// in MICROSECONDS ([`RuntimeOptions::drain_linger`], consumed by
239/// `MultiThreadExecutor::drain`). After a drainer runs the runnable/blocking
240/// queues empty it parks on its own [`DriverParker`] (registered with
241/// `parked_drivers`) for up to this long before returning its thread to
242/// rayon. A wave of work arriving within the budget is resumed by ONE
243/// targeted condvar wake instead of a `spawn_fifo` respawn through rayon's
244/// idle protocol -- whose per-transition cost (a wake cascade plus 33
245/// `sched_yield` rounds and a full steal sweep on EVERY worker that was
246/// woken) is what makes system CPU scale with the worker count under
247/// wave-shaped loads. `0` disables lingering (legacy exit-and-respawn on
248/// every empty queue); values above [`MAX_DRAIN_LINGER_MICROS`] are clamped.
249/// A frame's TOTAL lingering residence is additionally capped at
250/// [`DRAIN_LINGER_FRAME_FACTOR`] times this budget so periodic sub-budget
251/// work cannot pin a rayon worker inside one drain frame forever.
252///
253/// Like [`PARK_DEADLINE_ENV`], the runtime does NOT read this variable
254/// itself: it is resolved once at addon load into
255/// [`RuntimeOptions::drain_linger`] -- by the napi adapter's `install` (via
256/// `resolve_drain_linger`) for hosts that use it, while hosts owning their
257/// own env resolution may resolve their own variable into the same field. A
258/// present-but-non-numeric value keeps the host-provided field (which
259/// defaults to [`DEFAULT_DRAIN_LINGER_MICROS`]).
260pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
261
262/// Default drainer idle-linger budget (µs): [`RuntimeOptions::drain_linger`]'s
263/// default, applied when no host configuration or [`DRAIN_LINGER_ENV`]
264/// override resolves a different value.
265pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
266
267/// Ceiling on [`RuntimeOptions::drain_linger`] in MICROSECONDS (100ms).
268/// Validation (and, defensively, executor construction) clamps the budget
269/// here: lingering exists to bridge micro-scale gaps between scheduling
270/// waves, and a lingering drainer holds a pool worker plus an
271/// `active_drainers` slot while idle, so budgets beyond this bound stop
272/// tuning the wave optimization and start pinning workers.
273pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
274
275/// [`DEFAULT_DRAIN_LINGER_MICROS`] as a `Duration`.
276const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
277
278/// [`MAX_DRAIN_LINGER_MICROS`] as a `Duration`.
279const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
280
281/// Bound on a single drain frame's TOTAL lingering residence, as a multiple
282/// of the per-episode idle budget (default 16 x 500us = 8ms). The per-episode
283/// deadline alone does NOT bound how long a drain frame occupies its rayon
284/// worker: every executed work unit re-arms it, so periodic sub-budget work
285/// (e.g. a timer publishing one short task every <500us) would keep the frame
286/// resident FOREVER -- and a raw rayon job spawned onto the pool (which only
287/// a worker that returned to rayon's scheduler can run) would starve behind
288/// it indefinitely. Once cumulative residence past the frame's FIRST idle
289/// observation reaches this bound, the drainer exits through the unchanged
290/// `finish_draining` path even though more sub-budget work may be arriving;
291/// the exit-and-respawn through rayon's queue is exactly the interleaving
292/// point that lets pending raw rayon work (par_iter splits, `pool.spawn`
293/// jobs) take the worker before the re-armed FIFO drain.
294#[cfg(napi_runtime_os_threads)]
295const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
296
297/// Ceiling on the frame residence bound so an absurd [`DRAIN_LINGER_ENV`]
298/// value cannot arm a deadline beyond the platform's representable range.
299#[cfg(napi_runtime_os_threads)]
300const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
301
302/// True on builds where no second thread can EVER deliver a wake to a parked
303/// `block_on`: every wasm build except the threaded WASI target (i.e. the
304/// single-thread `wasm32-wasip1` build and `wasm32-unknown-unknown`). On such
305/// a build a CurrentThread park decision with an empty queue and no pending
306/// wake token is a PROVABLE deadlock. The threaded wasi build
307/// (`wasm32-wasip1-threads`) has real OS threads, so its parks are not
308/// provably dead and fall under the optional deadline detection instead, like
309/// native builds.
310///
311/// `napi_runtime_wasi_threads` is emitted by this crate's build.rs for the exact
312/// `wasm32-wasip1-threads` cargo TARGET. It is NOT derivable from built-in
313/// cfgs: on current rustc the two WASI targets expose identical cfg sets --
314/// `cfg!(target_feature = "atomics")` is false even on the threads target
315/// (verified empirically; embedders needing the same discrimination use the
316/// same build.rs mechanism).
317const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
318
319/// Threaded WASI: refresh this thread's view of the shared memory size where
320/// work that another thread built starts or resumes on it. A no-op on every
321/// other target.
322///
323/// V8 updates a shared wasm memory's size only on the thread that grew it.
324/// The other threads keep checking `memory.fill`, `memory.copy` and atomics
325/// (and, without V8's trap handler, every load and store) against their old
326/// size, so they trap on pages another thread grew. napi's allocator
327/// wrappers refresh under their lock, which covers a block the thread
328/// allocates itself, but not one handed over from another thread: a task
329/// that allocated on one worker and resumes on another, or a blocking
330/// closure whose captures the submitter built. The receiving thread may fill
331/// or copy into that memory without allocating first. So this runs at every
332/// handoff:
333///
334/// - before each `Runnable::run`, in both flavors: every task poll, and
335///   async-task's header atomics, which run before the future's own `poll`;
336/// - before each `block_on` poll (CurrentThread, and MultiThread's parking
337///   and cooperative loops): the caller parks between polls while other
338///   threads grow the memory and finish the awaited work;
339/// - at the start of `RegisteredBlockingFunction::run`: every queued
340///   blocking closure, in both flavors;
341/// - before the CurrentThread inline blocking closure. It runs on the
342///   submitting thread, so there is no handoff, but that thread may be
343///   behind all the same.
344///
345/// The check is one atomic load and one thread-local load, and it calls
346/// `memory.grow(0)` only when another thread has seen a larger memory
347/// (`napi_sys::wasi_heap_sync::refresh_if_behind`, which also counts those
348/// refreshes). The state lives in napi-sys because napi's allocator writes it
349/// and napi cannot depend on this crate (a cargo cycle through the default
350/// `napi` feature). There is no public API: hosts get the refresh by running
351/// work on this scheduler.
352#[cfg(napi_runtime_wasi_threads)]
353#[inline(always)]
354fn on_thread_handoff() {
355  napi_sys::wasi_heap_sync::refresh_if_behind();
356}
357
358/// See the threaded-WASI version above; nothing to refresh here.
359#[cfg(not(napi_runtime_wasi_threads))]
360#[inline(always)]
361fn on_thread_handoff() {}
362
363#[derive(Debug, Clone)]
364pub struct RuntimeConfigError(String);
365
366impl fmt::Display for RuntimeConfigError {
367  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368    f.write_str(&self.0)
369  }
370}
371
372impl std::error::Error for RuntimeConfigError {}
373
374/// Which self-detected `block_on` deadlock class fired.
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
376pub enum BlockOnDeadlockKind {
377  /// CurrentThread on a threadless build: the park decision found an empty
378  /// queue and no pending wake token, and no other thread exists that could
379  /// ever deliver one -- a PROVABLE deadlock, detected unconditionally (not
380  /// timing-based).
381  CurrentThreadCertain,
382  /// [`Self::CurrentThreadCertain`] detected while a HOST timer was pending.
383  /// The timer does not make the park any less dead on a threadless build:
384  /// its host callback (the JS `setTimeout` relay) can only run on the very
385  /// thread that is about to park, so it can never fire while `block_on`
386  /// holds that thread. A distinct kind only so the diagnostic can name the
387  /// shape the user actually hit.
388  CurrentThreadCertainTimerBacked,
389  /// Threaded CurrentThread: a `block_on` park outlived the configured
390  /// deadline with zero runtime progress.
391  CurrentThreadDeadline,
392  /// MultiThread: a cooperative pool driver's re-entrant `block_on` park
393  /// outlived the configured deadline with zero executor progress. The
394  /// foreign/napi whole-build park is exempt by design.
395  MultiThreadCooperativeDeadline,
396}
397
398/// Typed panic payload for a self-detected `block_on` deadlock. Thrown with
399/// `std::panic::panic_any` at the park DECISION (never merely on a `Pending`
400/// return, because a self-waking future is not a deadlock), so the
401/// runtime fails loudly instead of freezing the thread (and, in the
402/// CurrentThread-on-JS-thread case, the whole JS event loop, where not even
403/// test-harness timeouts can fire). When the panic unwinds out of a spawned
404/// task's poll, `JoinError::from_panic` preserves this diagnostic's message.
405#[derive(Debug, Clone)]
406pub struct BlockOnDeadlock {
407  pub kind: BlockOnDeadlockKind,
408  /// The armed deadline for the deadline-based kinds; `None` for the provable
409  /// threadless case.
410  pub park_deadline: Option<Duration>,
411}
412
413impl BlockOnDeadlock {
414  fn current_thread_certain() -> Self {
415    Self {
416      kind: BlockOnDeadlockKind::CurrentThreadCertain,
417      park_deadline: None,
418    }
419  }
420
421  fn current_thread_certain_timer_backed() -> Self {
422    Self {
423      kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
424      park_deadline: None,
425    }
426  }
427
428  fn current_thread_deadline(deadline: Duration) -> Self {
429    Self {
430      kind: BlockOnDeadlockKind::CurrentThreadDeadline,
431      park_deadline: Some(deadline),
432    }
433  }
434
435  // The MultiThread executor (and thus this constructor's caller) does not
436  // exist on wasm builds.
437  #[cfg(napi_runtime_os_threads)]
438  fn multi_thread_cooperative(deadline: Duration) -> Self {
439    Self {
440      kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
441      park_deadline: Some(deadline),
442    }
443  }
444}
445
446impl fmt::Display for BlockOnDeadlock {
447  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448    let deadline_ms = self
449      .park_deadline
450      .map_or(0, |deadline| deadline.as_millis());
451    match self.kind {
452      BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
453        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
454         park with an empty task queue and no pending wake on a threadless build -- no other \
455         thread exists that could ever deliver the wake, so this park would never end. This is \
456         the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
457         (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
458         dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
459         continuation is the one now parking.",
460      ),
461      BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
462        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
463         park with an empty task queue on a threadless build while a host timer is pending. \
464         The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
465         only run on the very thread that is now parking, so it can never fire, and no other \
466         thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
467         hazard: native code called `block_on` on a future that (transitively) awaits a JS \
468         continuation, but the only thread that could run that continuation is the one now \
469         parking.",
470      ),
471      BlockOnDeadlockKind::CurrentThreadDeadline => write!(
472        f,
473        "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
474         {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
475         task queue, no pending wake and ZERO runtime progress across the whole window. This is \
476         the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
477         awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
478         once this thread returns to the event loop. If this park is legitimate, raise or unset \
479         the deadline.",
480      ),
481      BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
482        f,
483        "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
484         parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
485         no runnable or blocking work available and ZERO executor progress across the whole \
486         window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
487         The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
488         If this park is legitimate, raise or unset the deadline.",
489      ),
490    }
491  }
492}
493
494impl std::error::Error for BlockOnDeadlock {}
495
496#[derive(Debug)]
497pub struct JoinError {
498  message: String,
499}
500
501impl JoinError {
502  fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
503    Self {
504      // The typed deadlock diagnostic first: a deadline firing inside a
505      // spawned task's poll unwinds into the spawn wrapper's catch_unwind and
506      // surfaces here -- the diagnostic text must survive the trip.
507      message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
508        deadlock.to_string()
509      } else if let Some(message) = panic.downcast_ref::<&str>() {
510        (*message).to_string()
511      } else if let Some(message) = panic.downcast_ref::<String>() {
512        message.clone()
513      } else {
514        "async runtime task panicked".to_string()
515      },
516    }
517  }
518}
519
520impl fmt::Display for JoinError {
521  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522    f.write_str(&self.message)
523  }
524}
525
526impl std::error::Error for JoinError {}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529enum BlockOnOutcome {
530  Completed,
531  Stopped,
532}
533
534fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
535  // The original unwind is already caught, so ordinary payloads should be
536  // destroyed normally. A hostile payload destructor can panic again; catch
537  // that nested unwind and leak only its new payload, whose destructor is
538  // likewise untrusted.
539  if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
540    std::mem::forget(nested_payload);
541  }
542}
543
544#[derive(Default)]
545struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
546
547impl RetainedPanicPayload {
548  fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
549    if self.0.is_some() {
550      discard_panic_payload(payload);
551    } else {
552      self.0 = Some(payload);
553    }
554  }
555
556  fn is_some(&self) -> bool {
557    self.0.is_some()
558  }
559
560  fn resume(mut self) -> ! {
561    resume_unwind(
562      self
563        .0
564        .take()
565        .expect("a retained panic payload must remain owned"),
566    )
567  }
568}
569
570impl Drop for RetainedPanicPayload {
571  fn drop(&mut self) {
572    if let Some(payload) = self.0.take() {
573      discard_panic_payload(payload);
574    }
575  }
576}
577
578fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
579  match catch_unwind(AssertUnwindSafe(function)) {
580    Ok(value) => Some(value),
581    Err(payload) => {
582      discard_panic_payload(payload);
583      None
584    }
585  }
586}
587
588struct ContainedArc<T: ?Sized> {
589  value: Option<Arc<T>>,
590}
591
592impl<T: ?Sized> ContainedArc<T> {
593  fn new(value: Arc<T>) -> Self {
594    Self { value: Some(value) }
595  }
596
597  fn get(&self) -> &T {
598    self
599      .value
600      .as_deref()
601      .expect("the contained Arc must remain owned")
602  }
603
604  #[cfg(test)]
605  fn into_inner(mut self) -> Arc<T> {
606    self
607      .value
608      .take()
609      .expect("the contained Arc must remain owned")
610  }
611}
612
613impl<T: ?Sized> Drop for ContainedArc<T> {
614  fn drop(&mut self) {
615    if let Some(value) = self.value.take() {
616      let _ = catch_unwind_contained(|| drop(value));
617    }
618  }
619}
620
621struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
622  values: Vec<(I, Arc<T>)>,
623}
624
625impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
626  fn new(values: Vec<(I, Arc<T>)>) -> Self {
627    Self { values }
628  }
629
630  fn as_slice(&self) -> &[(I, Arc<T>)] {
631    &self.values
632  }
633
634  fn len(&self) -> usize {
635    self.values.len()
636  }
637
638  fn is_empty(&self) -> bool {
639    self.values.is_empty()
640  }
641
642  fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
643    self
644      .values
645      .pop()
646      .map(|(id, value)| (id, ContainedArc::new(value)))
647  }
648}
649
650impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
651  fn drop(&mut self) {
652    while let Some((_, value)) = self.values.pop() {
653      let _ = catch_unwind_contained(|| drop(value));
654    }
655  }
656}
657
658struct PanicContainedWaker {
659  waker: ManuallyDrop<Waker>,
660  generation: Option<u64>,
661}
662
663impl std::task::Wake for PanicContainedWaker {
664  fn wake(self: Arc<Self>) {
665    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
666    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
667  }
668
669  fn wake_by_ref(self: &Arc<Self>) {
670    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
671    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
672  }
673}
674
675impl Drop for PanicContainedWaker {
676  fn drop(&mut self) {
677    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
678    let _ = catch_unwind_contained(|| {
679      // SAFETY: this is the sole destruction path for the underlying waker.
680      unsafe { ManuallyDrop::drop(&mut self.waker) };
681    });
682  }
683}
684
685fn panic_contained_waker(waker: Waker) -> Waker {
686  Waker::from(Arc::new(PanicContainedWaker {
687    waker: ManuallyDrop::new(waker),
688    generation: active_runtime_generation(),
689  }))
690}
691
692struct PanicContainedWakerSource {
693  waker: ManuallyDrop<Waker>,
694  generation: Option<u64>,
695}
696
697impl PanicContainedWakerSource {
698  fn new(waker: Waker) -> Self {
699    Self {
700      waker: ManuallyDrop::new(waker),
701      generation: active_runtime_generation(),
702    }
703  }
704
705  fn as_waker(&self) -> &Waker {
706    &self.waker
707  }
708}
709
710impl Drop for PanicContainedWakerSource {
711  fn drop(&mut self) {
712    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
713    let _ = catch_unwind_contained(|| {
714      // SAFETY: this is the sole destruction path for the source waker.
715      unsafe { ManuallyDrop::drop(&mut self.waker) };
716    });
717  }
718}
719
720struct PanicContainedWakerCache {
721  source: PanicContainedWakerSource,
722  contained: Waker,
723}
724
725impl PanicContainedWakerCache {
726  fn new(waker: &Waker) -> Self {
727    let generation = active_runtime_generation();
728    let _generation = RuntimeGenerationGuard::enter_optional(generation);
729    let source = PanicContainedWakerSource::new(waker.clone());
730    let contained = panic_contained_waker(source.as_waker().clone());
731    Self { source, contained }
732  }
733
734  fn update(&mut self, waker: &Waker) {
735    let will_wake = {
736      let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
737      self.source.as_waker().will_wake(waker)
738    };
739    if !will_wake {
740      *self = Self::new(waker);
741    }
742  }
743
744  fn contained(&self) -> &Waker {
745    &self.contained
746  }
747}
748
749fn wake_waker_contained(waker: Waker) {
750  // `Waker::wake` consumes the waker, so a panicking wake followed by a
751  // panicking RawWaker destructor would double-panic before the outer catch
752  // could regain control. Borrow for the wake and contain destruction
753  // separately.
754  let _ = catch_unwind_contained(|| waker.wake_by_ref());
755  let _ = catch_unwind_contained(|| drop(waker));
756}
757
758fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
759  match catch_unwind(AssertUnwindSafe(function)) {
760    Ok(value) => Ok(value),
761    Err(payload) => {
762      let error = JoinError::from_panic(&*payload);
763      discard_panic_payload(payload);
764      Err(error)
765    }
766  }
767}
768
769fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
770  let error = JoinError::from_panic(&*payload);
771  discard_panic_payload(payload);
772  error
773}
774
775struct ContainedTaskOutput<T> {
776  output: ManuallyDrop<T>,
777  generation: u64,
778}
779
780impl<T> ContainedTaskOutput<T> {
781  fn new(output: T, generation: u64) -> Self {
782    Self {
783      output: ManuallyDrop::new(output),
784      generation,
785    }
786  }
787
788  fn into_inner(mut self) -> T {
789    // SAFETY: ownership moves to the JoinHandle caller and `self` is then
790    // forgotten so its Drop implementation cannot run a second time.
791    let output = unsafe { ManuallyDrop::take(&mut self.output) };
792    std::mem::forget(self);
793    output
794  }
795}
796
797impl<T> Drop for ContainedTaskOutput<T> {
798  fn drop(&mut self) {
799    let _generation = RuntimeGenerationGuard::enter(self.generation);
800    let _ = catch_unwind_contained(|| {
801      // SAFETY: this is the sole destruction path while the output remains
802      // retained by a JoinHandle.
803      unsafe { ManuallyDrop::drop(&mut self.output) };
804    });
805  }
806}
807
808#[cfg(napi_runtime_os_threads)]
809#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
810struct BlockingJobId(u64);
811
812#[cfg(napi_runtime_os_threads)]
813static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
814
815#[cfg(all(test, napi_runtime_os_threads))]
816thread_local! {
817  static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
818    const { std::cell::RefCell::new(None) };
819  static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
820    const { std::cell::RefCell::new(None) };
821  static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
822    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
823      const { std::cell::RefCell::new(None) };
824  static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
825    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
826      const { std::cell::RefCell::new(None) };
827  static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
828    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
829      const { std::cell::RefCell::new(None) };
830  static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
831    const { std::cell::RefCell::new(None) };
832  static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
833    const { std::cell::RefCell::new(None) };
834  static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
835    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
836      const { std::cell::RefCell::new(None) };
837  static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
838    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
839      const { std::cell::RefCell::new(None) };
840  static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
841    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
842      const { std::cell::RefCell::new(None) };
843  static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
844    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
845      const { std::cell::RefCell::new(None) };
846  static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
847    const { std::cell::RefCell::new(None) };
848  static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
849    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
850      const { std::cell::RefCell::new(None) };
851  static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
852    const { std::cell::RefCell::new(None) };
853}
854
855#[cfg(all(test, napi_runtime_os_threads))]
856struct WorkerTlsDropProbe {
857  action: Option<WorkerTlsDropAction>,
858}
859
860// LOADER-LOCK RULE for every probe action below: TLS destructors run at
861// thread exit -- on Windows inside `LdrShutdownThread`, which HOLDS the
862// process loader lock while walking TLS callbacks. Any cross-thread wait
863// here (condvar, channel recv, barrier, joining a thread) therefore freezes
864// every other `thread::spawn` and thread-exit in the process -- and, with
865// RUST_BACKTRACE=1, even panic reporting (dbghelp loads DLLs under the same
866// lock). A previous `Wait { entered, release }` action parked the FIRST
867// exiting worker's destructor on a condvar; the SECOND worker then could not
868// even enter its destructor (loader lock), the release condition (both
869// entered) became unsatisfiable, and the whole test suite went silent until
870// the CI step timeout (Windows x64 jobs, runs 29195160953 / 86662331487).
871// Destructor actions may only RECORD (atomic stores, non-blocking sends);
872// ordering invariants are asserted from OUTSIDE, after `shutdown()` returns.
873#[cfg(all(test, napi_runtime_os_threads))]
874// `ReenterLifecycle`'s only constructor re-execs the test binary, which WASI
875// cannot do, so on wasm the variant is declared but never built.
876#[cfg_attr(target_family = "wasm", allow(dead_code))]
877enum WorkerTlsDropAction {
878  /// Record-only: bump the shared counter so the harness can assert -- the
879  /// instant `shutdown()` returns -- that every worker TLS destructor had
880  /// already completed (the join barrier includes destructor completion).
881  Record { completed: Arc<AtomicUsize> },
882  ReenterLifecycle {
883    controller: Arc<RuntimeController>,
884    completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
885  },
886}
887
888#[cfg(all(test, napi_runtime_os_threads))]
889impl Drop for WorkerTlsDropProbe {
890  fn drop(&mut self) {
891    match self
892      .action
893      .take()
894      .expect("worker TLS drop probe action already consumed")
895    {
896      WorkerTlsDropAction::Record { completed } => {
897        completed.fetch_add(1, Ordering::SeqCst);
898      }
899      // Both lifecycle re-entries FAIL FAST from a stopping-generation worker
900      // (short state-mutex acquisitions, no cross-thread wait), and the send
901      // is non-blocking -- loader-lock safe. The owning test additionally
902      // isolates itself in a watchdogged subprocess.
903      WorkerTlsDropAction::ReenterLifecycle {
904        controller,
905        completed,
906      } => {
907        let start = controller.start().map_err(|error| error.to_string());
908        let shutdown = controller.shutdown().map_err(|error| error.to_string());
909        let _ = completed.send((start, shutdown));
910      }
911    }
912  }
913}
914
915/// Whether `std` runs `thread_local!` destructors when a spawned thread exits
916/// on this target. `std` documents wasm as "for now we just leak everything",
917/// and Rust 1.99.0 widened that arm to `wasm32-wasip1-threads`
918/// (rust-lang/rust#163748), so a join barrier that "includes destructor
919/// completion" has nothing to include there. Probed at runtime rather than
920/// keyed on a toolchain version so the assertion comes back by itself once
921/// the toolchain runs them again.
922#[cfg(all(test, napi_runtime_os_threads))]
923fn platform_runs_thread_local_destructors() -> bool {
924  struct Bump(Arc<AtomicUsize>);
925  impl Drop for Bump {
926    fn drop(&mut self) {
927      self.0.fetch_add(1, Ordering::SeqCst);
928    }
929  }
930  thread_local! {
931    static PROBE: std::cell::Cell<Option<Bump>> = const { std::cell::Cell::new(None) };
932  }
933  let ran = Arc::new(AtomicUsize::new(0));
934  let probe = Arc::clone(&ran);
935  std::thread::spawn(move || PROBE.with(|slot| slot.set(Some(Bump(probe)))))
936    .join()
937    .expect("TLS destructor probe thread must not panic");
938  ran.load(Ordering::SeqCst) == 1
939}
940
941#[cfg(all(test, napi_runtime_os_threads))]
942fn run_dependency_claim_test_hook() {
943  if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
944    hook();
945  }
946}
947
948#[cfg(all(test, napi_runtime_os_threads))]
949fn run_current_thread_dispatch_claim_test_hook() {
950  if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
951  {
952    hook();
953  }
954}
955
956#[cfg(all(test, napi_runtime_os_threads))]
957fn run_current_thread_dispatch_publication_blocked_test_hook() {
958  if let Some(hook) =
959    CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
960  {
961    hook();
962  }
963}
964
965#[cfg(all(test, napi_runtime_os_threads))]
966fn run_current_thread_dispatch_recovery_test_hook() {
967  if let Some(hook) =
968    CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
969  {
970    hook();
971  }
972}
973
974#[cfg(all(test, napi_runtime_os_threads))]
975fn run_current_thread_failed_publication_retired_test_hook() {
976  if let Some(hook) =
977    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
978  {
979    hook();
980  }
981}
982
983#[cfg(all(test, napi_runtime_os_threads))]
984fn run_current_thread_dispatch_call_test_hook() {
985  if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
986    hook();
987  }
988}
989
990#[cfg(all(test, napi_runtime_os_threads))]
991fn run_current_thread_dispatch_returned_test_hook() {
992  if let Some(hook) =
993    CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
994  {
995    hook();
996  }
997}
998
999#[cfg(all(test, napi_runtime_os_threads))]
1000fn run_current_thread_blocking_claim_test_hook() {
1001  if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1002  {
1003    hook();
1004  }
1005}
1006
1007#[cfg(all(test, napi_runtime_os_threads))]
1008fn run_current_thread_runnable_claim_test_hook() {
1009  if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1010  {
1011    hook();
1012  }
1013}
1014
1015#[cfg(all(test, napi_runtime_os_threads))]
1016fn run_multi_thread_work_claimed_test_hook() {
1017  if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1018    hook();
1019  }
1020}
1021
1022#[cfg(all(test, napi_runtime_os_threads))]
1023fn run_multi_thread_work_started_test_hook() {
1024  if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1025    hook();
1026  }
1027}
1028
1029#[cfg(all(test, napi_runtime_os_threads))]
1030fn run_dependency_predicate_test_hook() {
1031  if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1032    hook();
1033  }
1034}
1035
1036#[cfg(all(test, napi_runtime_os_threads))]
1037fn run_before_owned_blocking_queue_claim_test_hook() {
1038  if let Some(hook) =
1039    BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1040  {
1041    hook();
1042  }
1043}
1044
1045#[cfg(napi_runtime_os_threads)]
1046#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1047struct BlockingDependency {
1048  job: BlockingJobId,
1049  owner: Option<BlockingOwnerToken>,
1050}
1051
1052#[cfg(napi_runtime_os_threads)]
1053#[derive(Debug)]
1054struct DependencyClaim {
1055  claimed: AtomicBool,
1056  transition: Mutex<()>,
1057}
1058
1059#[cfg(napi_runtime_os_threads)]
1060impl DependencyClaim {
1061  fn new() -> Self {
1062    Self {
1063      claimed: AtomicBool::new(false),
1064      transition: Mutex::new(()),
1065    }
1066  }
1067
1068  fn is_live(&self) -> bool {
1069    !self.claimed.load(Ordering::Acquire)
1070  }
1071
1072  fn cancel_link(&self, link: &DependencyLink) {
1073    let _transition = self
1074      .transition
1075      .lock()
1076      .unwrap_or_else(std::sync::PoisonError::into_inner);
1077    link.cancel();
1078  }
1079
1080  fn try_claim(&self, link: &DependencyLink) -> bool {
1081    let _transition = self
1082      .transition
1083      .lock()
1084      .unwrap_or_else(std::sync::PoisonError::into_inner);
1085    #[cfg(test)]
1086    run_dependency_claim_test_hook();
1087    if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1088      return false;
1089    }
1090    self.claimed.store(true, Ordering::Release);
1091    true
1092  }
1093}
1094
1095#[cfg(napi_runtime_os_threads)]
1096#[derive(Debug)]
1097struct DependencyLink {
1098  live: AtomicBool,
1099  parent: Option<Weak<DependencyLink>>,
1100}
1101
1102#[cfg(napi_runtime_os_threads)]
1103impl DependencyLink {
1104  fn root() -> Arc<Self> {
1105    Arc::new(Self {
1106      live: AtomicBool::new(true),
1107      parent: None,
1108    })
1109  }
1110
1111  fn derived(parent: &Arc<Self>) -> Arc<Self> {
1112    Arc::new(Self {
1113      live: AtomicBool::new(true),
1114      parent: Some(Arc::downgrade(parent)),
1115    })
1116  }
1117
1118  fn is_live(&self) -> bool {
1119    if !self.live.load(Ordering::Acquire) {
1120      return false;
1121    }
1122    let Some(parent) = &self.parent else {
1123      return true;
1124    };
1125    let Some(mut current) = parent.upgrade() else {
1126      return false;
1127    };
1128    loop {
1129      if !current.live.load(Ordering::Acquire) {
1130        return false;
1131      }
1132      let Some(parent) = &current.parent else {
1133        return true;
1134      };
1135      let Some(next) = parent.upgrade() else {
1136        return false;
1137      };
1138      current = next;
1139    }
1140  }
1141
1142  fn cancel(&self) {
1143    self.live.store(false, Ordering::Release);
1144  }
1145}
1146
1147#[cfg(napi_runtime_os_threads)]
1148#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1149struct DependencyPublicationIdentity {
1150  job: BlockingJobId,
1151  claim: *const DependencyClaim,
1152  link: *const DependencyLink,
1153}
1154
1155#[cfg(napi_runtime_os_threads)]
1156#[derive(Clone, Debug)]
1157struct DependencyPublication {
1158  dependency: BlockingDependency,
1159  claim: Arc<DependencyClaim>,
1160  link: Arc<DependencyLink>,
1161}
1162
1163#[cfg(napi_runtime_os_threads)]
1164impl DependencyPublication {
1165  fn owned(dependency: BlockingDependency) -> Self {
1166    Self {
1167      dependency,
1168      claim: Arc::new(DependencyClaim::new()),
1169      link: DependencyLink::root(),
1170    }
1171  }
1172
1173  fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1174    Self {
1175      dependency: BlockingDependency {
1176        owner: self.dependency.owner.or(owner),
1177        ..self.dependency
1178      },
1179      claim: Arc::clone(&self.claim),
1180      link: DependencyLink::derived(&self.link),
1181    }
1182  }
1183
1184  fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1185    self.dependency.owner = self.dependency.owner.or(owner);
1186    self
1187  }
1188
1189  fn same_as(&self, other: &Self) -> bool {
1190    self.dependency == other.dependency
1191      && Arc::ptr_eq(&self.claim, &other.claim)
1192      && Arc::ptr_eq(&self.link, &other.link)
1193  }
1194
1195  fn identity(&self) -> DependencyPublicationIdentity {
1196    DependencyPublicationIdentity {
1197      job: self.dependency.job,
1198      claim: Arc::as_ptr(&self.claim),
1199      link: Arc::as_ptr(&self.link),
1200    }
1201  }
1202
1203  fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1204    Some(DependencyPublicationIdentity {
1205      job: self.dependency.job,
1206      claim: Arc::as_ptr(&self.claim),
1207      link: self.link.parent.as_ref()?.as_ptr(),
1208    })
1209  }
1210
1211  fn is_derived_from(&self, source: &Self) -> bool {
1212    self.immediate_source_identity() == Some(source.identity())
1213  }
1214
1215  fn is_live(&self) -> bool {
1216    self.claim.is_live() && self.link.is_live()
1217  }
1218
1219  fn cancel(&self) {
1220    self.claim.cancel_link(&self.link);
1221  }
1222
1223  fn try_claim(&self) -> bool {
1224    self.claim.try_claim(&self.link)
1225  }
1226}
1227
1228#[cfg(napi_runtime_os_threads)]
1229struct TaskDependencyEntry {
1230  sequence: u64,
1231  publication: DependencyPublication,
1232}
1233
1234#[cfg(napi_runtime_os_threads)]
1235struct SelectedDependencyPublication {
1236  sequence: u64,
1237  publication: DependencyPublication,
1238}
1239
1240#[cfg(napi_runtime_os_threads)]
1241#[derive(Clone, Copy)]
1242enum OwnerDependencyMode {
1243  Available,
1244  Exact,
1245}
1246
1247#[cfg(napi_runtime_os_threads)]
1248#[derive(Default)]
1249struct TaskDependencyState {
1250  current: VecDeque<TaskDependencyEntry>,
1251  next_sequence: u64,
1252  unowned_sequences: VecDeque<u64>,
1253  owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1254  // NOTE: there is deliberately no `job_sequences` index. It used to be a
1255  // `FxHashMap<BlockingJobId, FxHashSet<u64>>` costing TWO heap allocations
1256  // per blocking dispatch (the map's table plus an inner set that almost
1257  // always held exactly one sequence). Its only job was to let a dropped
1258  // blocking `JoinHandle` find the entry it had published, and the handle can
1259  // simply remember the `sequence` `append` gave it: `current` is positionally
1260  // indexed by sequence via `entry_in`, so the handle retires its own entry in
1261  // O(1) with no map, no hash, and no scan. `next_sequence` never rewinds, so
1262  // a remembered sequence can never alias a later entry.
1263  waiter: Option<Waker>,
1264}
1265
1266#[cfg(napi_runtime_os_threads)]
1267impl TaskDependencyState {
1268  /// Publish `publication` and return the sequence it was filed under, so the
1269  /// publisher can retire exactly this entry later in O(1).
1270  fn append(&mut self, publication: DependencyPublication) -> u64 {
1271    let sequence = self.next_sequence;
1272    self.next_sequence = self
1273      .next_sequence
1274      .checked_add(1)
1275      .expect("task dependency sequence space exhausted");
1276    match publication.dependency.owner {
1277      Some(owner) => self
1278        .owner_sequences
1279        .entry(owner)
1280        .or_default()
1281        .push_back(sequence),
1282      None => self.unowned_sequences.push_back(sequence),
1283    }
1284    self.current.push_back(TaskDependencyEntry {
1285      sequence,
1286      publication,
1287    });
1288    sequence
1289  }
1290
1291  fn entry_in(
1292    current: &VecDeque<TaskDependencyEntry>,
1293    sequence: u64,
1294  ) -> Option<&TaskDependencyEntry> {
1295    let front = current.front()?.sequence;
1296    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1297    let entry = current.get(index)?;
1298    (entry.sequence == sequence).then_some(entry)
1299  }
1300
1301  fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1302    Self::entry_in(&self.current, sequence)
1303  }
1304
1305  fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1306    let front = self.current.front()?.sequence;
1307    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1308    let entry = self.current.get_mut(index)?;
1309    (entry.sequence == sequence).then_some(entry)
1310  }
1311
1312  fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1313    match dependency.owner {
1314      Some(owner) => {
1315        let remove_owner = self
1316          .owner_sequences
1317          .get_mut(&owner)
1318          .is_some_and(|sequences| {
1319            if sequences.front() == Some(&sequence) {
1320              sequences.pop_front();
1321            }
1322            sequences.is_empty()
1323          });
1324        if remove_owner {
1325          self.owner_sequences.remove(&owner);
1326        }
1327      }
1328      None => {
1329        if self.unowned_sequences.front() == Some(&sequence) {
1330          self.unowned_sequences.pop_front();
1331        }
1332      }
1333    }
1334  }
1335
1336  fn clear_indexes(&mut self) {
1337    self.unowned_sequences.clear();
1338    self.owner_sequences.clear();
1339  }
1340}
1341
1342#[cfg(napi_runtime_os_threads)]
1343struct TaskDependency {
1344  state: Mutex<TaskDependencyState>,
1345  owner_hint: Option<BlockingOwnerToken>,
1346  has_current: AtomicBool,
1347  generation: u64,
1348  #[cfg(test)]
1349  entry_visits: AtomicUsize,
1350}
1351
1352#[cfg(napi_runtime_os_threads)]
1353impl Default for TaskDependency {
1354  fn default() -> Self {
1355    Self::new(u64::MAX, None)
1356  }
1357}
1358
1359#[cfg(napi_runtime_os_threads)]
1360impl TaskDependency {
1361  fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1362    Self {
1363      state: Mutex::new(TaskDependencyState::default()),
1364      owner_hint,
1365      has_current: AtomicBool::new(false),
1366      generation,
1367      #[cfg(test)]
1368      entry_visits: AtomicUsize::new(0),
1369    }
1370  }
1371
1372  fn new_task(generation: u64) -> Self {
1373    Self::new(generation, None)
1374  }
1375
1376  fn notify_waiter(&self, waiter: Option<Waker>) {
1377    if let Some(waiter) = waiter {
1378      let _generation = RuntimeGenerationGuard::enter(self.generation);
1379      wake_waker_contained(waiter);
1380    }
1381  }
1382
1383  fn clear_waiter(&self) {
1384    let waiter = self
1385      .state
1386      .lock()
1387      .unwrap_or_else(std::sync::PoisonError::into_inner)
1388      .waiter
1389      .take();
1390    if let Some(waiter) = waiter {
1391      let _generation = RuntimeGenerationGuard::enter(self.generation);
1392      let _ = catch_unwind_contained(|| drop(waiter));
1393    }
1394  }
1395
1396  fn clear(&self) {
1397    let waiter = {
1398      let mut state = self
1399        .state
1400        .lock()
1401        .unwrap_or_else(std::sync::PoisonError::into_inner);
1402      if state.current.is_empty() {
1403        None
1404      } else {
1405        for current in state.current.drain(..) {
1406          current.publication.cancel();
1407        }
1408        state.clear_indexes();
1409        self.has_current.store(false, Ordering::Release);
1410        state.waiter.take()
1411      }
1412    };
1413    self.notify_waiter(waiter);
1414  }
1415
1416  /// Retire the single entry filed under `sequence`, in O(1).
1417  ///
1418  /// This replaces a job-id lookup through a `job_sequences` index. `current`
1419  /// is positionally indexed by sequence, so the publisher that remembers the
1420  /// sequence `append` returned can cancel exactly its own entry without a
1421  /// map and without a scan. `entry_in` re-checks `entry.sequence == sequence`
1422  /// after the positional lookup and `next_sequence` never rewinds, so a
1423  /// sequence whose entry has already been drained (`begin_poll`) or pruned
1424  /// resolves to `None` rather than aliasing a later entry.
1425  fn retire(&self, sequence: u64) {
1426    let waiter = {
1427      let mut state = self
1428        .state
1429        .lock()
1430        .unwrap_or_else(std::sync::PoisonError::into_inner);
1431      let cancelled = match state.entry_mut(sequence) {
1432        Some(current) => {
1433          self.note_entry_visit();
1434          current.publication.cancel();
1435          true
1436        }
1437        None => false,
1438      };
1439      if cancelled {
1440        self.prune_stale_prefix(&mut state);
1441        state.waiter.take()
1442      } else {
1443        None
1444      }
1445    };
1446    self.notify_waiter(waiter);
1447  }
1448
1449  fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1450    let Some(first_publication) = publications.first() else {
1451      return;
1452    };
1453    let publication_identities = (publications.len() > 1).then(|| {
1454      publications
1455        .iter()
1456        .map(DependencyPublication::identity)
1457        .collect::<FxHashSet<_>>()
1458    });
1459    let waiter = {
1460      let mut state = self
1461        .state
1462        .lock()
1463        .unwrap_or_else(std::sync::PoisonError::into_inner);
1464      let removed = state
1465        .current
1466        .iter_mut()
1467        .filter_map(|current| {
1468          self.note_entry_visit();
1469          let is_derived = publication_identities.as_ref().map_or_else(
1470            || current.publication.is_derived_from(first_publication),
1471            |identities| {
1472              current
1473                .publication
1474                .immediate_source_identity()
1475                .is_some_and(|identity| identities.contains(&identity))
1476            },
1477          );
1478          if !is_derived {
1479            return None;
1480          }
1481          current.publication.cancel();
1482          Some(current.sequence)
1483        })
1484        .collect::<Vec<_>>();
1485      // No `job_sequences` index to maintain; the cancelled entries stay in
1486      // `current` until `prune_stale_prefix` retires them, exactly as before.
1487      if removed.is_empty() {
1488        None
1489      } else {
1490        self.prune_stale_prefix(&mut state);
1491        state.waiter.take()
1492      }
1493    };
1494    self.notify_waiter(waiter);
1495  }
1496
1497  /// Publish an owned dependency and return the sequence it was filed under,
1498  /// so the caller can [`retire`](Self::retire) exactly this entry in O(1).
1499  fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1500    let publication = DependencyPublication::owned(BlockingDependency {
1501      owner: dependency.owner.or(self.owner_hint),
1502      ..dependency
1503    });
1504    self.set_entry(publication)
1505  }
1506
1507  /// Whether `sequence` still resolves to a LIVE entry in this state.
1508  ///
1509  /// A publisher uses this to decide whether its earlier publication still
1510  /// stands. `false` means the entry was drained by `begin_poll`, pruned, or
1511  /// cancelled (by a lending claim, say), so a fresh publication is needed.
1512  fn holds_live(&self, sequence: u64) -> bool {
1513    let state = self
1514      .state
1515      .lock()
1516      .unwrap_or_else(std::sync::PoisonError::into_inner);
1517    TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1518      self.note_entry_visit();
1519      entry.publication.is_live()
1520    })
1521  }
1522
1523  fn set_borrowed(&self, publication: &DependencyPublication) {
1524    let _sequence = self.set_entry(publication.derived(self.owner_hint));
1525  }
1526
1527  fn set_entry(&self, publication: DependencyPublication) -> u64 {
1528    let (sequence, waiter) = {
1529      let mut state = self
1530        .state
1531        .lock()
1532        .unwrap_or_else(std::sync::PoisonError::into_inner);
1533      // Owned publications and propagated publications both create a fresh
1534      // local liveness link, so identity cannot already exist in this state.
1535      let sequence = state.append(publication);
1536      self.has_current.store(true, Ordering::Release);
1537      (sequence, state.waiter.take())
1538    };
1539    self.notify_waiter(waiter);
1540    sequence
1541  }
1542
1543  fn note_entry_visit(&self) {
1544    #[cfg(test)]
1545    self.entry_visits.fetch_add(1, Ordering::Relaxed);
1546  }
1547
1548  fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1549    while state.current.front().is_some_and(|current| {
1550      self.note_entry_visit();
1551      !current.publication.is_live()
1552    }) {
1553      let current = state
1554        .current
1555        .pop_front()
1556        .expect("the stale prefix entry must exist");
1557      state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1558    }
1559    self
1560      .has_current
1561      .store(!state.current.is_empty(), Ordering::Release);
1562  }
1563
1564  fn first_live_sequence(
1565    &self,
1566    current: &VecDeque<TaskDependencyEntry>,
1567    sequences: &mut VecDeque<u64>,
1568  ) -> Option<u64> {
1569    loop {
1570      let sequence = *sequences.front()?;
1571      let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1572        sequences.pop_front();
1573        continue;
1574      };
1575      self.note_entry_visit();
1576      if entry.publication.is_live() {
1577        return Some(sequence);
1578      }
1579      sequences.pop_front();
1580    }
1581  }
1582
1583  fn first_live_owner_sequence(
1584    &self,
1585    state: &mut TaskDependencyState,
1586    owner: BlockingOwnerToken,
1587  ) -> Option<u64> {
1588    let sequence = {
1589      let current = &state.current;
1590      state
1591        .owner_sequences
1592        .get_mut(&owner)
1593        .and_then(|sequences| self.first_live_sequence(current, sequences))
1594    };
1595    if sequence.is_none() {
1596      state.owner_sequences.remove(&owner);
1597    }
1598    sequence
1599  }
1600
1601  fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1602    self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1603  }
1604
1605  #[cfg(test)]
1606  fn get(&self) -> Option<DependencyPublication> {
1607    self
1608      .state
1609      .lock()
1610      .unwrap_or_else(std::sync::PoisonError::into_inner)
1611      .current
1612      .iter()
1613      .find(|current| current.publication.is_live())
1614      .map(|current| current.publication.clone())
1615  }
1616
1617  fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1618    self
1619      .state
1620      .lock()
1621      .unwrap_or_else(std::sync::PoisonError::into_inner)
1622      .current
1623      .iter()
1624      .filter(|current| {
1625        self.note_entry_visit();
1626        current.publication.is_live()
1627      })
1628      .map(|current| current.publication.clone())
1629      .collect()
1630  }
1631
1632  fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1633    let mut state = self
1634      .state
1635      .lock()
1636      .unwrap_or_else(std::sync::PoisonError::into_inner);
1637    self.prune_stale_prefix(&mut state);
1638    let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1639    let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1640    let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1641      (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1642      (Some(_) | None, Some(unowned)) => (unowned, true),
1643      (Some(exact), None) => (exact, false),
1644      (None, None) => return None,
1645    };
1646    if bind_owner {
1647      let removed = state.unowned_sequences.pop_front();
1648      debug_assert_eq!(removed, Some(sequence));
1649      let current = state
1650        .entry_mut(sequence)
1651        .expect("the selected dependency must remain indexed");
1652      debug_assert_eq!(current.publication.dependency.owner, None);
1653      current.publication.dependency.owner = Some(owner);
1654      // A previous exact-owner query may have observed no entry. Publishing
1655      // this sequence at the front makes the newly bound dependency visible
1656      // immediately without restarting a global scan.
1657      state
1658        .owner_sequences
1659        .entry(owner)
1660        .or_default()
1661        .push_front(sequence);
1662    }
1663    let publication = state
1664      .entry(sequence)
1665      .expect("the selected dependency must remain indexed")
1666      .publication
1667      .clone();
1668    Some(SelectedDependencyPublication {
1669      sequence,
1670      publication,
1671    })
1672  }
1673
1674  fn has_live_owner_dependency(
1675    &self,
1676    owner: BlockingOwnerToken,
1677    mode: OwnerDependencyMode,
1678  ) -> bool {
1679    #[cfg(all(test, napi_runtime_os_threads))]
1680    run_dependency_predicate_test_hook();
1681    let mut state = self
1682      .state
1683      .lock()
1684      .unwrap_or_else(std::sync::PoisonError::into_inner);
1685    self.prune_stale_prefix(&mut state);
1686    if self.first_live_owner_sequence(&mut state, owner).is_some() {
1687      return true;
1688    }
1689    matches!(mode, OwnerDependencyMode::Available)
1690      && self.first_live_unowned_sequence(&mut state).is_some()
1691  }
1692
1693  #[cfg(test)]
1694  fn get_dependency(&self) -> Option<BlockingDependency> {
1695    self.get().map(|publication| publication.dependency)
1696  }
1697
1698  #[cfg(test)]
1699  fn get_dependencies(&self) -> Vec<BlockingDependency> {
1700    self
1701      .snapshot_all_live()
1702      .into_iter()
1703      .map(|publication| publication.dependency)
1704      .collect()
1705  }
1706
1707  #[cfg(test)]
1708  fn reset_entry_visits(&self) {
1709    self.entry_visits.store(0, Ordering::Relaxed);
1710  }
1711
1712  #[cfg(test)]
1713  fn entry_visits(&self) -> usize {
1714    self.entry_visits.load(Ordering::Relaxed)
1715  }
1716
1717  /// Atomically reserve `job` for exact-dependency execution. A dependency
1718  /// transition that wins this race makes the claim fail, so lending can never
1719  /// execute a stale job copied from an earlier poll.
1720  fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1721    let mut state = self
1722      .state
1723      .lock()
1724      .unwrap_or_else(std::sync::PoisonError::into_inner);
1725    let Some(current) = state.entry(selected.sequence) else {
1726      return false;
1727    };
1728    self.note_entry_visit();
1729    if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1730      return false;
1731    }
1732    let dependency = current.publication.dependency;
1733    state
1734      .entry_mut(selected.sequence)
1735      .expect("the claimed dependency must remain indexed")
1736      .publication
1737      .cancel();
1738    state.remove_sequence_indexes(selected.sequence, dependency);
1739    self.prune_stale_prefix(&mut state);
1740    true
1741  }
1742
1743  fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1744    let _generation = RuntimeGenerationGuard::enter(self.generation);
1745    let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1746      return Vec::new();
1747    };
1748    let (previous, unused, current) = {
1749      let mut state = self
1750        .state
1751        .lock()
1752        .unwrap_or_else(std::sync::PoisonError::into_inner);
1753      let current = state
1754        .current
1755        .iter()
1756        .filter(|current| current.publication.is_live())
1757        .map(|current| current.publication.clone())
1758        .collect();
1759      if state
1760        .waiter
1761        .as_ref()
1762        .is_some_and(|previous| previous.will_wake(&waker))
1763      {
1764        (None, Some(waker), current)
1765      } else {
1766        (state.waiter.replace(waker), None, current)
1767      }
1768    };
1769    if let Some(previous) = previous {
1770      let _ = catch_unwind_contained(|| drop(previous));
1771    }
1772    if let Some(unused) = unused {
1773      let _ = catch_unwind_contained(|| drop(unused));
1774    }
1775    current
1776  }
1777
1778  fn begin_poll(&self) {
1779    if !self.has_current.load(Ordering::Acquire) {
1780      return;
1781    }
1782    let waiter = {
1783      let mut state = self
1784        .state
1785        .lock()
1786        .unwrap_or_else(std::sync::PoisonError::into_inner);
1787      if state.current.is_empty() {
1788        self.has_current.store(false, Ordering::Release);
1789        None
1790      } else {
1791        for previous in state.current.drain(..) {
1792          previous.publication.cancel();
1793        }
1794        state.clear_indexes();
1795        self.has_current.store(false, Ordering::Release);
1796        state.waiter.take()
1797      }
1798    };
1799    self.notify_waiter(waiter);
1800  }
1801
1802  #[cfg(test)]
1803  fn register_waiter(&self, waker: &Waker) {
1804    let _ = self.register_waiter_and_get(waker);
1805  }
1806}
1807
1808#[cfg(napi_runtime_os_threads)]
1809impl Drop for TaskDependency {
1810  fn drop(&mut self) {
1811    let state = self
1812      .state
1813      .get_mut()
1814      .unwrap_or_else(std::sync::PoisonError::into_inner);
1815    for current in state.current.drain(..) {
1816      current.publication.cancel();
1817    }
1818    if let Some(waiter) = state.waiter.take() {
1819      let _generation = RuntimeGenerationGuard::enter(self.generation);
1820      let _ = catch_unwind_contained(|| drop(waiter));
1821    }
1822  }
1823}
1824
1825#[cfg(napi_runtime_os_threads)]
1826thread_local! {
1827  static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1828    const { std::cell::RefCell::new(Vec::new()) };
1829}
1830
1831#[cfg(napi_runtime_os_threads)]
1832struct TaskDependencyGuard(Arc<TaskDependency>);
1833
1834#[cfg(napi_runtime_os_threads)]
1835impl TaskDependencyGuard {
1836  fn enter(dependency: &Arc<TaskDependency>) -> Self {
1837    let dependency = Arc::clone(dependency);
1838    CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1839    Self(dependency)
1840  }
1841}
1842
1843#[cfg(napi_runtime_os_threads)]
1844impl Drop for TaskDependencyGuard {
1845  fn drop(&mut self) {
1846    CURRENT_DEPENDENCY_STACK.with(|stack| {
1847      let current = stack
1848        .borrow_mut()
1849        .pop()
1850        .expect("blocking dependency context underflow");
1851      debug_assert!(Arc::ptr_eq(&current, &self.0));
1852    });
1853  }
1854}
1855
1856#[cfg(napi_runtime_os_threads)]
1857struct BlockOnDependencyGuard {
1858  dependency: Arc<TaskDependency>,
1859  _context: TaskDependencyGuard,
1860}
1861
1862#[cfg(napi_runtime_os_threads)]
1863impl BlockOnDependencyGuard {
1864  fn enter(generation: u64) -> Self {
1865    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1866    let dependency = Arc::new(TaskDependency::new(generation, owner));
1867    let context = TaskDependencyGuard::enter(&dependency);
1868    Self {
1869      dependency,
1870      _context: context,
1871    }
1872  }
1873
1874  fn clear(&self) {
1875    self.dependency.clear();
1876  }
1877}
1878
1879/// A dependency this handle published, remembered well enough to retire it in
1880/// O(1).
1881///
1882/// The sequence alone is not enough: sequences are allocated per
1883/// `TaskDependency`, so applying one to a different state would cancel an
1884/// unrelated entry. The state's identity therefore travels with it, as a
1885/// `Weak` rather than a raw pointer so a freed state cannot be impersonated by
1886/// a later allocation reusing its address -- a `Weak` keeps the allocation
1887/// itself reserved, so the pointer stays unique even after the state is gone.
1888///
1889/// It is deliberately NOT a strong `Arc`: a handle can be polled under many
1890/// frames over its life, and holding each one alive to remember it would retain
1891/// one dead `TaskDependency`, with its deque and index maps, per frame the
1892/// handle has ever seen.
1893#[cfg(napi_runtime_os_threads)]
1894struct PublishedBlockingDependency {
1895  state: Weak<TaskDependency>,
1896  sequence: u64,
1897}
1898
1899/// Every publication a blocking handle has made, one per dependency frame it
1900/// has been polled under.
1901///
1902/// A handle publishes once per Pending poll into whichever frame is on top of
1903/// `CURRENT_DEPENDENCY_STACK`. Re-polling under a frame it has already
1904/// published into supersedes that frame's entry, so the common case is exactly
1905/// one publication and `rest` never allocates.
1906///
1907/// Retiring EVERY publication is load-bearing. Dropping a blocking handle
1908/// detaches rather than dequeues, so any entry left live keeps a job that
1909/// nobody awaits selectable for owner-lane lending -- and `select_for_owner`
1910/// picks the LOWEST live sequence, so a forgotten entry outranks dependencies
1911/// published after it. Lending then runs the detached job inline in the owner's
1912/// single lane, which deadlocks if that job waits on the dependency it
1913/// displaced. The job-id index this replaces cancelled every entry for the job
1914/// in the frame it was called on, so nothing was forgotten; a handle that
1915/// remembered only its newest publication would forget both a repeat poll in
1916/// one frame and a poll under a nested frame.
1917#[cfg(napi_runtime_os_threads)]
1918#[derive(Default)]
1919struct PublishedBlockingDependencies {
1920  first: Option<PublishedBlockingDependency>,
1921  rest: Vec<PublishedBlockingDependency>,
1922}
1923
1924#[cfg(napi_runtime_os_threads)]
1925impl PublishedBlockingDependency {
1926  /// Whether this publication is still worth remembering.
1927  ///
1928  /// A token stops standing when its frame is gone -- a dropped
1929  /// `TaskDependency` already cancelled every entry it held -- or when the
1930  /// frame no longer holds the entry as live, because its `begin_poll` drained
1931  /// `current`, `prune_stale_prefix` retired it, or a lending claim took it.
1932  ///
1933  /// Absence from this thread's dependency stack deliberately does NOT count.
1934  /// It would bound this list more tightly, but it is not sound: a `JoinHandle`
1935  /// is `Send + Unpin`, so a safe adapter can serialise one behind a mutex and
1936  /// poll it from several tasks, and then a frame parked on another thread can
1937  /// still be awaiting a handle that is being polled here. Retiring on local
1938  /// absence would take that frame's publication away while it still needs it
1939  /// for owner-lane lending.
1940  fn still_stands(&self) -> bool {
1941    self
1942      .state
1943      .upgrade()
1944      .is_some_and(|state| state.holds_live(self.sequence))
1945  }
1946}
1947
1948#[cfg(napi_runtime_os_threads)]
1949impl PublishedBlockingDependencies {
1950  /// Forget publications whose frame is gone.
1951  ///
1952  /// A dropped `TaskDependency` cancels every entry it holds, so an expired
1953  /// token has nothing left to retire. Discarding it here is what keeps this
1954  /// list at the handle's currently-live frame count rather than the total
1955  /// number of frames it has ever been polled under: without it, a pending
1956  /// handle carried through a sequence of completed tasks would accumulate one
1957  /// token per task and the lookup below would grow with it.
1958  /// Forget publications that no longer stand.
1959  ///
1960  /// A token is obsolete once its frame is gone -- a dropped `TaskDependency`
1961  /// cancels every entry it holds, so there is nothing left to retire -- or
1962  /// once that frame no longer holds the entry, because its `begin_poll`
1963  /// drained `current`. Dropping both kinds keeps this list at the handle's
1964  /// currently-live frame count rather than the number of frames it has ever
1965  /// been polled under.
1966  ///
1967  /// Testing the second condition costs a lock, so it is confined to the
1968  /// multi-frame path: a handle polled under a single frame -- the
1969  /// overwhelmingly common case -- reuses its one token and never reaches it.
1970  /// Without that condition a handle carried through many tasks whose
1971  /// `JoinHandle`s are still held would keep one token per task, since those
1972  /// handles keep the states alive, and both this walk and detach would grow
1973  /// with that count.
1974  fn prune(&mut self) {
1975    if self.rest.is_empty() {
1976      if self
1977        .first
1978        .as_ref()
1979        .is_some_and(|first| first.state.strong_count() == 0)
1980      {
1981        self.first = None;
1982      }
1983      return;
1984    }
1985    self.rest.retain(PublishedBlockingDependency::still_stands);
1986    if self
1987      .first
1988      .as_ref()
1989      .is_some_and(|first| !first.still_stands())
1990    {
1991      self.first = self.rest.pop();
1992    }
1993  }
1994
1995  fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1996    self.prune();
1997    let target = Arc::as_ptr(&state);
1998    if let Some(existing) = self
1999      .first
2000      .iter_mut()
2001      .chain(self.rest.iter_mut())
2002      .find(|published| std::ptr::eq(published.state.as_ptr(), target))
2003    {
2004      // The entry this handle already published into this frame still says the
2005      // frame depends on this job, so a repeat Pending poll must LEAVE IT
2006      // ALONE. Publishing again would append at the tail, and
2007      // `select_for_owner` picks the LOWEST live sequence, so re-polling a
2008      // handle would push its own dependency behind ones published after it.
2009      // In an H1 -> H2 -> H1 poll pass that reorders H1 behind H2, and if H2
2010      // waits on H1 the owner lane lends H2 inline while H1 stays queued
2011      // forever. Keeping the original sequence preserves the publication order
2012      // the job index produced, and skips a `DependencyClaim`/`DependencyLink`
2013      // pair per repeat poll.
2014      if state.holds_live(existing.sequence) {
2015        return;
2016      }
2017      // The entry was drained, pruned or claimed, so it no longer stands and
2018      // this frame needs a fresh one. Reusing the token keeps the list at one
2019      // entry per frame.
2020      existing.sequence = state.set_owned(dependency);
2021      return;
2022    }
2023    let sequence = state.set_owned(dependency);
2024    let published = PublishedBlockingDependency {
2025      state: Arc::downgrade(&state),
2026      sequence,
2027    };
2028    match &mut self.first {
2029      slot @ None => *slot = Some(published),
2030      Some(_) => self.rest.push(published),
2031    }
2032  }
2033
2034  /// Retire every publication, each against the state it was filed in.
2035  ///
2036  /// This is exactly inverse to publication, so it can never cancel an entry
2037  /// this handle did not create. It cannot strand a frame either: a frame is
2038  /// only stranded if it is parked awaiting something, and it cannot be parked
2039  /// awaiting a handle that is concurrently being dropped, because the handle
2040  /// is owned by the future that frame is driving. A frame that has already
2041  /// gone cancelled its entries in its own `Drop`, so an expired token is
2042  /// simply skipped.
2043  fn retire_all(self) {
2044    for published in self.first.into_iter().chain(self.rest) {
2045      if let Some(state) = published.state.upgrade() {
2046        state.retire(published.sequence);
2047      }
2048    }
2049  }
2050}
2051
2052#[cfg(napi_runtime_os_threads)]
2053fn record_blocking_dependency(
2054  published: &mut PublishedBlockingDependencies,
2055  dependency: BlockingDependency,
2056) {
2057  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2058  if let Some(current) = current {
2059    let dependency = BlockingDependency {
2060      owner: dependency
2061        .owner
2062        .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
2063      ..dependency
2064    };
2065    published.publish(current, dependency);
2066  }
2067}
2068
2069#[cfg(napi_runtime_os_threads)]
2070fn propagate_blocking_dependency(publication: DependencyPublication) {
2071  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2072  if let Some(current) = current {
2073    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2074    let publication = publication.with_owner_if_none(owner);
2075    current.set_borrowed(&publication);
2076  }
2077}
2078
2079#[cfg(napi_runtime_os_threads)]
2080fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2081  // Retire against the states that were published to, NOT against whatever
2082  // frame happens to be on top of the stack at drop time. The frame that
2083  // published is the frame whose dependency graph is now wrong.
2084  published.retire_all();
2085}
2086
2087#[cfg(napi_runtime_os_threads)]
2088fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2089  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2090  if let Some(current) = current {
2091    current.clear_if_publications(publications);
2092  }
2093}
2094
2095enum JoinHandleInner<T> {
2096  Task {
2097    task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2098    awaiter: Option<PanicContainedWakerCache>,
2099    #[cfg(napi_runtime_os_threads)]
2100    dependency: Arc<TaskDependency>,
2101  },
2102  #[cfg(napi_runtime_os_threads)]
2103  Blocking {
2104    receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2105    dependency: BlockingDependency,
2106    /// Every dependency this handle has published, so detaching retires each
2107    /// in O(1) instead of searching for them by job id.
2108    published: PublishedBlockingDependencies,
2109  },
2110  Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2111}
2112
2113pub struct JoinHandle<T>(JoinHandleInner<T>);
2114
2115impl<T> Unpin for JoinHandle<T> {}
2116
2117impl<T> JoinHandle<T> {
2118  fn detach_task(&mut self) {
2119    let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2120    match inner {
2121      JoinHandleInner::Task {
2122        task,
2123        awaiter: _,
2124        #[cfg(napi_runtime_os_threads)]
2125        dependency,
2126      } => {
2127        #[cfg(napi_runtime_os_threads)]
2128        let current_dependencies = dependency.snapshot_all_live();
2129        #[cfg(napi_runtime_os_threads)]
2130        dependency.clear_waiter();
2131        task.detach();
2132        #[cfg(napi_runtime_os_threads)]
2133        // Dependency cleanup may wake an arbitrary caller-provided waker.
2134        // Retire the task first, then contain the notification boundary so
2135        // reentrant wake code cannot observe a half-detached handle.
2136        let _ = catch_unwind_contained(|| {
2137          clear_propagated_blocking_dependencies(&current_dependencies);
2138        });
2139      }
2140      #[cfg(napi_runtime_os_threads)]
2141      JoinHandleInner::Blocking {
2142        receiver,
2143        dependency: _,
2144        published,
2145      } => {
2146        // A completed result may already be buffered in the receiver. Its
2147        // destructor is user code and must receive the same containment as an
2148        // immediate CurrentThread result.
2149        let _ = catch_unwind_contained(|| drop(receiver));
2150        // Notify only after receiver retirement, and keep that arbitrary waker
2151        // behind an independent unwind boundary.
2152        let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2153      }
2154      JoinHandleInner::Ready(result) => {
2155        let _ = catch_unwind_contained(|| drop(result));
2156      }
2157    }
2158  }
2159
2160  pub fn detach(mut self) {
2161    self.detach_task();
2162  }
2163}
2164
2165impl<T> Drop for JoinHandle<T> {
2166  fn drop(&mut self) {
2167    // Tokio's JoinHandle detaches on drop. Preserve that compatibility:
2168    // fire-and-forget callers must not silently cancel work merely because
2169    // they ignore the returned handle.
2170    self.detach_task();
2171  }
2172}
2173
2174impl<T> Future for JoinHandle<T> {
2175  type Output = Result<T, JoinError>;
2176
2177  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2178    match &mut self.get_mut().0 {
2179      JoinHandleInner::Task {
2180        task,
2181        awaiter: awaiter_slot,
2182        #[cfg(napi_runtime_os_threads)]
2183        dependency,
2184      } => {
2185        let awaiter = match awaiter_slot {
2186          Some(awaiter) => {
2187            awaiter.update(cx.waker());
2188            awaiter
2189          }
2190          None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2191        };
2192        let contained_waker = awaiter.contained();
2193        let mut contained_cx = Context::from_waker(contained_waker);
2194        let poll = Pin::new(task).poll(&mut contained_cx);
2195        #[cfg(napi_runtime_os_threads)]
2196        if poll.is_pending() {
2197          for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2198            propagate_blocking_dependency(current_dependency);
2199          }
2200        } else {
2201          dependency.clear_waiter();
2202        }
2203        if poll.is_ready() {
2204          *awaiter_slot = None;
2205        }
2206        match poll {
2207          Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2208          Poll::Ready(None) => Poll::Ready(Err(JoinError {
2209            message: "async runtime stopped before the task completed".to_string(),
2210          })),
2211          Poll::Pending => Poll::Pending,
2212        }
2213      }
2214      #[cfg(napi_runtime_os_threads)]
2215      JoinHandleInner::Blocking {
2216        receiver,
2217        dependency,
2218        published,
2219      } => {
2220        let poll = Pin::new(receiver).poll(cx);
2221        if poll.is_pending() {
2222          record_blocking_dependency(published, *dependency);
2223        }
2224        match poll {
2225          Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2226          Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2227            message: "async runtime stopped before the blocking task completed".to_string(),
2228          })),
2229          Poll::Pending => Poll::Pending,
2230        }
2231      }
2232      JoinHandleInner::Ready(result) => Poll::Ready(
2233        result
2234          .take()
2235          .expect("JoinHandle polled after completion")
2236          .map(ContainedTaskOutput::into_inner),
2237      ),
2238    }
2239  }
2240}
2241
2242#[derive(Debug, Clone, Copy)]
2243pub struct RuntimeMetricsSnapshot {
2244  pub flavor: RuntimeFlavor,
2245  pub worker_threads: usize,
2246  pub max_blocking_tasks: usize,
2247  pub tasks_spawned: u64,
2248  pub tasks_completed: u64,
2249  pub tasks_panicked: u64,
2250  pub runnable_schedules: u64,
2251  pub runnable_polls: u64,
2252  pub queued_runnables: u64,
2253  pub max_queued_runnables: u64,
2254  pub active_runnables: u64,
2255  pub max_active_runnables: u64,
2256  pub blocking_tasks_started: u64,
2257  pub blocking_tasks_completed: u64,
2258  pub active_blocking_tasks: u64,
2259  pub max_active_blocking_tasks: u64,
2260}
2261
2262#[derive(Default)]
2263struct RuntimeMetrics {
2264  tasks_spawned: AtomicU64,
2265  tasks_completed: AtomicU64,
2266  tasks_panicked: AtomicU64,
2267  runnable_schedules: AtomicU64,
2268  runnable_polls: AtomicU64,
2269  queued_runnables: AtomicU64,
2270  max_queued_runnables: AtomicU64,
2271  active_runnables: AtomicU64,
2272  max_active_runnables: AtomicU64,
2273  // Bumped at ENQUEUE time (before the queue push), the blocking-side twin of
2274  // `runnable_schedules`. Internal-only (not exported in the public snapshot):
2275  // it exists so the deadlock detector's progress fingerprint can observe a
2276  // blocking submission the moment it is made -- `blocking_tasks_started`
2277  // only advances once a worker RUNS the job, which may be arbitrarily later.
2278  blocking_tasks_scheduled: AtomicU64,
2279  blocking_tasks_started: AtomicU64,
2280  blocking_tasks_completed: AtomicU64,
2281  active_blocking_tasks: AtomicU64,
2282  max_active_blocking_tasks: AtomicU64,
2283  // Seqlock generation around resets. The deadlock detector snapshots
2284  // resettable counters, so it must distinguish identical counter values from
2285  // different reset generations instead of mistaking that ABA for no progress.
2286  reset_generation: AtomicU64,
2287  reset_lock: Mutex<()>,
2288}
2289
2290#[cfg(test)]
2291thread_local! {
2292  static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2293    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2294      const { std::cell::RefCell::new(None) };
2295}
2296
2297#[cfg(test)]
2298fn run_runtime_metrics_high_water_test_hook() {
2299  if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2300    hook();
2301  }
2302}
2303
2304impl RuntimeMetrics {
2305  fn runnable_scheduled(&self) {
2306    self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2307    let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2308    #[cfg(test)]
2309    run_runtime_metrics_high_water_test_hook();
2310    self
2311      .max_queued_runnables
2312      .fetch_max(queued, Ordering::Relaxed);
2313  }
2314
2315  fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2316    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2317    self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2318    let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2319    #[cfg(test)]
2320    run_runtime_metrics_high_water_test_hook();
2321    self
2322      .max_active_runnables
2323      .fetch_max(active, Ordering::Relaxed);
2324    ActiveRunnableGuard { metrics: self }
2325  }
2326
2327  #[cfg(any(napi_runtime_os_threads, test))]
2328  fn runnable_cancelled(&self) {
2329    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2330  }
2331
2332  /// Enqueue-time twin of [`Self::runnable_scheduled`] for blocking work:
2333  /// called BEFORE the blocking-queue push, so the deadlock detector's
2334  /// fingerprint sees a submission even when no worker has started the job.
2335  fn blocking_scheduled(&self) {
2336    self
2337      .blocking_tasks_scheduled
2338      .fetch_add(1, Ordering::Relaxed);
2339  }
2340
2341  fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2342    self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2343    if count_active_lane {
2344      let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2345      #[cfg(test)]
2346      run_runtime_metrics_high_water_test_hook();
2347      self
2348        .max_active_blocking_tasks
2349        .fetch_max(active, Ordering::Relaxed);
2350    }
2351    BlockingTaskGuard {
2352      metrics: Arc::clone(self),
2353      count_active_lane,
2354    }
2355  }
2356
2357  fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2358    let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2359    let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2360    let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2361    RuntimeMetricsSnapshot {
2362      flavor: options.flavor,
2363      worker_threads: options.worker_threads,
2364      max_blocking_tasks: options.max_blocking_tasks,
2365      tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2366      tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2367      tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2368      runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2369      runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2370      queued_runnables,
2371      max_queued_runnables: self
2372        .max_queued_runnables
2373        .load(Ordering::Relaxed)
2374        .max(queued_runnables),
2375      active_runnables,
2376      max_active_runnables: self
2377        .max_active_runnables
2378        .load(Ordering::Relaxed)
2379        .max(active_runnables),
2380      blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2381      blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2382      active_blocking_tasks,
2383      max_active_blocking_tasks: self
2384        .max_active_blocking_tasks
2385        .load(Ordering::Relaxed)
2386        .max(active_blocking_tasks),
2387    }
2388  }
2389
2390  /// Coarse liveness fingerprint for deadline-based deadlock detection (wake
2391  /// -path §8.5): any of these counters advancing between two reads means the
2392  /// runtime did SOMETHING (scheduled or polled a runnable, completed a task,
2393  /// scheduled, started or finished a blocking job), so a parked driver
2394  /// observing an advance re-arms its deadline instead of firing. Both ENQUEUE
2395  /// counters (`runnable_schedules`, `blocking_tasks_scheduled`) are included
2396  /// so a submission is progress the moment it is made -- not only once a
2397  /// worker gets to run it.
2398  fn progress_fingerprint(&self) -> ProgressFingerprint {
2399    loop {
2400      let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2401      if !reset_generation.is_multiple_of(2) {
2402        std::hint::spin_loop();
2403        continue;
2404      }
2405      let fingerprint = ProgressFingerprint {
2406        reset_generation,
2407        runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2408        runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2409        tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2410        blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2411        blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2412        blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2413      };
2414      if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2415        return fingerprint;
2416      }
2417    }
2418  }
2419
2420  fn reset(&self) {
2421    let reset_guard = self
2422      .reset_lock
2423      .lock()
2424      .unwrap_or_else(std::sync::PoisonError::into_inner);
2425    // Event counters are safe to reset independently. Live gauges are not:
2426    // queued/active guards may still hold a future decrement, and zeroing a
2427    // gauge underneath them wraps the unsigned counter on completion.
2428    //
2429    // High-water gauges remain lifetime values. Resetting them concurrently
2430    // with fetch_max would either lose a real peak or require synchronization
2431    // on every hot-path metric update.
2432    let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2433    let Some(resetting_generation) = reset_generation.checked_add(1) else {
2434      drop(reset_guard);
2435      panic!("runtime metrics reset generation space exhausted");
2436    };
2437    let Some(settled_generation) = resetting_generation.checked_add(1) else {
2438      drop(reset_guard);
2439      panic!("runtime metrics reset generation space exhausted");
2440    };
2441    self
2442      .reset_generation
2443      .store(resetting_generation, Ordering::SeqCst);
2444    self.tasks_spawned.store(0, Ordering::Relaxed);
2445    self.tasks_completed.store(0, Ordering::Relaxed);
2446    self.tasks_panicked.store(0, Ordering::Relaxed);
2447    self.runnable_schedules.store(0, Ordering::Relaxed);
2448    self.runnable_polls.store(0, Ordering::Relaxed);
2449    self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2450    self.blocking_tasks_started.store(0, Ordering::Relaxed);
2451    self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2452    self
2453      .reset_generation
2454      .store(settled_generation, Ordering::SeqCst);
2455  }
2456}
2457
2458/// See [`RuntimeMetrics::progress_fingerprint`].
2459#[derive(Clone, Copy, PartialEq, Eq)]
2460struct ProgressFingerprint {
2461  reset_generation: u64,
2462  runnable_schedules: u64,
2463  runnable_polls: u64,
2464  tasks_completed: u64,
2465  blocking_tasks_scheduled: u64,
2466  blocking_tasks_started: u64,
2467  blocking_tasks_completed: u64,
2468}
2469
2470struct ActiveRunnableGuard<'a> {
2471  metrics: &'a RuntimeMetrics,
2472}
2473
2474impl Drop for ActiveRunnableGuard<'_> {
2475  fn drop(&mut self) {
2476    self
2477      .metrics
2478      .active_runnables
2479      .fetch_sub(1, Ordering::Relaxed);
2480  }
2481}
2482
2483struct BlockingTaskGuard {
2484  metrics: Arc<RuntimeMetrics>,
2485  count_active_lane: bool,
2486}
2487
2488impl Drop for BlockingTaskGuard {
2489  fn drop(&mut self) {
2490    if self.count_active_lane {
2491      self
2492        .metrics
2493        .active_blocking_tasks
2494        .fetch_sub(1, Ordering::Relaxed);
2495    }
2496    self
2497      .metrics
2498      .blocking_tasks_completed
2499      .fetch_add(1, Ordering::Relaxed);
2500  }
2501}
2502
2503static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2504
2505fn next_runtime_generation() -> u64 {
2506  next_unique_id(
2507    &NEXT_RUNTIME_GENERATION,
2508    "async runtime generation id space exhausted",
2509  )
2510}
2511
2512thread_local! {
2513  static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2514    const { std::cell::Cell::new(None) };
2515}
2516
2517fn active_runtime_generation() -> Option<u64> {
2518  ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2519}
2520
2521struct RuntimeGenerationGuard {
2522  previous: Option<u64>,
2523}
2524
2525impl RuntimeGenerationGuard {
2526  fn enter(generation: u64) -> Self {
2527    Self::enter_optional(Some(generation))
2528  }
2529
2530  fn enter_optional(generation: Option<u64>) -> Self {
2531    Self {
2532      previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2533    }
2534  }
2535}
2536
2537impl Drop for RuntimeGenerationGuard {
2538  fn drop(&mut self) {
2539    ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2540  }
2541}
2542
2543struct ContainedFuture<F> {
2544  future: ManuallyDrop<F>,
2545  generation: u64,
2546}
2547
2548impl<F> ContainedFuture<F> {
2549  fn new(future: F, generation: u64) -> Self {
2550    Self {
2551      future: ManuallyDrop::new(future),
2552      generation,
2553    }
2554  }
2555}
2556
2557impl<F: Future> Future for ContainedFuture<F> {
2558  type Output = F::Output;
2559
2560  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2561    // SAFETY: `future` is never moved after `ContainedFuture` is pinned.
2562    let this = unsafe { self.get_unchecked_mut() };
2563    let _generation = RuntimeGenerationGuard::enter(this.generation);
2564    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2565    // structurally pinned for the lifetime of `self`.
2566    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2567  }
2568}
2569
2570impl<F> Drop for ContainedFuture<F> {
2571  fn drop(&mut self) {
2572    let _generation = RuntimeGenerationGuard::enter(self.generation);
2573    let _ = catch_unwind_contained(|| {
2574      // SAFETY: this is the sole destruction path for `future`.
2575      unsafe { ManuallyDrop::drop(&mut self.future) };
2576    });
2577  }
2578}
2579
2580#[derive(Default)]
2581struct GenerationStop {
2582  stopping: AtomicBool,
2583  publication: Mutex<()>,
2584  parkers: Mutex<Vec<Weak<DriverParker>>>,
2585}
2586
2587impl GenerationStop {
2588  fn is_stopping(&self) -> bool {
2589    self.stopping.load(Ordering::Acquire)
2590  }
2591
2592  fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2593    #[cfg(all(test, napi_runtime_os_threads))]
2594    run_before_generation_stop_publication_lock_test_hook();
2595    self.runnable_claim_guard()
2596  }
2597
2598  fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2599    GenerationStopPublicationGuard {
2600      stop: self,
2601      _publication: self
2602        .publication
2603        .lock()
2604        .unwrap_or_else(std::sync::PoisonError::into_inner),
2605    }
2606  }
2607
2608  fn publish_stopping(&self) {
2609    self.publication_guard().publish_stopping();
2610  }
2611
2612  fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2613    {
2614      let mut parkers = self
2615        .parkers
2616        .lock()
2617        .unwrap_or_else(std::sync::PoisonError::into_inner);
2618      parkers.retain(|candidate| candidate.strong_count() != 0);
2619      parkers.push(Arc::downgrade(parker));
2620    }
2621    if self.is_stopping() {
2622      parker.unpark();
2623    }
2624    GenerationStopGuard {
2625      stop: Arc::clone(self),
2626      parker: Arc::downgrade(parker),
2627    }
2628  }
2629
2630  /// Wake every active `block_on` frame without changing the generation state.
2631  /// The number of parkers is bounded by concurrent explicit drivers. Waking
2632  /// all avoids directing a queue wake only to a newer driver that is blocked
2633  /// inside its poll while an older driver is already asleep.
2634  fn wake_all(&self) -> usize {
2635    let mut count = 0;
2636    let mut registered = self
2637      .parkers
2638      .lock()
2639      .unwrap_or_else(std::sync::PoisonError::into_inner);
2640    registered.retain(|candidate| {
2641      let Some(parker) = candidate.upgrade() else {
2642        return false;
2643      };
2644      count += 1;
2645      // `DriverParker::unpark` invokes no user code and never enters the
2646      // generation registry, so waking under this lock is non-reentrant.
2647      parker.unpark();
2648      true
2649    });
2650    count
2651  }
2652
2653  fn begin_shutdown(&self) {
2654    self.publish_stopping();
2655    let parkers = {
2656      let mut registered = self
2657        .parkers
2658        .lock()
2659        .unwrap_or_else(std::sync::PoisonError::into_inner);
2660      let parkers = registered
2661        .iter()
2662        .filter_map(Weak::upgrade)
2663        .collect::<Vec<_>>();
2664      registered.clear();
2665      parkers
2666    };
2667    for parker in parkers {
2668      parker.unpark();
2669    }
2670  }
2671}
2672
2673struct GenerationStopPublicationGuard<'a> {
2674  stop: &'a GenerationStop,
2675  _publication: MutexGuard<'a, ()>,
2676}
2677
2678impl GenerationStopPublicationGuard<'_> {
2679  fn is_stopping(&self) -> bool {
2680    self.stop.is_stopping()
2681  }
2682
2683  fn publish_stopping(&self) {
2684    self.stop.stopping.store(true, Ordering::Release);
2685  }
2686}
2687
2688struct GenerationStopGuard {
2689  stop: Arc<GenerationStop>,
2690  parker: Weak<DriverParker>,
2691}
2692
2693impl Drop for GenerationStopGuard {
2694  fn drop(&mut self) {
2695    let Some(parker) = self.parker.upgrade() else {
2696      return;
2697    };
2698    let mut registered = self
2699      .stop
2700      .parkers
2701      .lock()
2702      .unwrap_or_else(std::sync::PoisonError::into_inner);
2703    if let Some(index) = registered.iter().position(|candidate| {
2704      candidate
2705        .upgrade()
2706        .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2707    }) {
2708      registered.swap_remove(index);
2709    }
2710  }
2711}
2712
2713/// Whether a work-pending read may wait for the locks its sources live under.
2714#[derive(Clone, Copy)]
2715enum PendingRead {
2716  /// Wait for each lock, for the exact answer. Only `begin_shutdown` reads
2717  /// its verdict this way; the host's poll never does.
2718  Locked,
2719  /// Never wait: a source whose lock another thread holds reads as pending.
2720  /// For the host's poll, which must never block; see
2721  /// `RuntimeController::runtime_work_pending` for why a held lock is not
2722  /// always about to come back.
2723  NonBlocking,
2724}
2725
2726impl PendingRead {
2727  /// `pending` applied to the state behind `mutex`, or `true` without looking
2728  /// when this is [`Self::NonBlocking`] and another thread holds the lock.
2729  /// Poison is ignored, as at every other lock site here.
2730  fn read<T>(self, mutex: &Mutex<T>, pending: impl FnOnce(&T) -> bool) -> bool {
2731    let state = match self {
2732      Self::Locked => mutex.lock(),
2733      Self::NonBlocking => match mutex.try_lock() {
2734        Ok(state) => Ok(state),
2735        Err(std::sync::TryLockError::Poisoned(poisoned)) => Err(poisoned),
2736        Err(std::sync::TryLockError::WouldBlock) => return true,
2737      },
2738    };
2739    pending(&state.unwrap_or_else(std::sync::PoisonError::into_inner))
2740  }
2741}
2742
2743struct GenerationWorkState {
2744  closed: bool,
2745  next_task_id: u64,
2746  active: usize,
2747  abort_handles: FxHashMap<u64, AbortHandle>,
2748}
2749
2750struct GenerationWork {
2751  id: u64,
2752  state: Mutex<GenerationWorkState>,
2753  idle: Condvar,
2754}
2755
2756impl GenerationWork {
2757  fn new() -> Arc<Self> {
2758    Arc::new(Self {
2759      id: next_runtime_generation(),
2760      state: Mutex::new(GenerationWorkState {
2761        closed: false,
2762        next_task_id: 0,
2763        active: 0,
2764        abort_handles: FxHashMap::default(),
2765      }),
2766      idle: Condvar::new(),
2767    })
2768  }
2769
2770  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2771    let (abort_handle, abort_registration) = AbortHandle::new_pair();
2772    let mut state = self
2773      .state
2774      .lock()
2775      .unwrap_or_else(std::sync::PoisonError::into_inner);
2776    if state.closed {
2777      return None;
2778    }
2779    let task_id = state.next_task_id;
2780    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2781      drop(state);
2782      panic!("async runtime generation task id space exhausted");
2783    };
2784    state.next_task_id = next_task_id;
2785    state.active += 1;
2786    state.abort_handles.insert(task_id, abort_handle);
2787    Some((
2788      abort_registration,
2789      GenerationWorkGuard {
2790        work: Arc::clone(self),
2791        task_id: Some(task_id),
2792      },
2793    ))
2794  }
2795
2796  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2797    let mut state = self
2798      .state
2799      .lock()
2800      .unwrap_or_else(std::sync::PoisonError::into_inner);
2801    if state.closed {
2802      return None;
2803    }
2804    state.active += 1;
2805    Some(GenerationWorkGuard {
2806      work: Arc::clone(self),
2807      task_id: None,
2808    })
2809  }
2810
2811  fn close_and_abort(&self) {
2812    // Take the handles out instead of cloning them into a new `Vec`: nothing
2813    // is allocated under the state lock, and no guard retiring later frees a
2814    // handle under it (see `GenerationWorkGuard::drop`). Admission is closed
2815    // in the same critical section, so no handle can be added afterwards.
2816    let abort_handles = {
2817      let mut state = self
2818        .state
2819        .lock()
2820        .unwrap_or_else(std::sync::PoisonError::into_inner);
2821      state.closed = true;
2822      std::mem::take(&mut state.abort_handles)
2823    };
2824    for abort_handle in abort_handles.values() {
2825      abort_handle.abort();
2826    }
2827  }
2828
2829  fn wait_until_idle(&self) {
2830    self.wait_until_idle_retiring(|| false, || {});
2831  }
2832
2833  /// The predicate `wait_until_idle` sleeps on, read without sleeping;
2834  /// `read` decides whether it may wait for the state lock.
2835  fn has_active_work(&self, read: PendingRead) -> bool {
2836    read.read(&self.state, |state| state.active != 0)
2837  }
2838
2839  /// Wait until every registered guard has retired, retiring deferred work
2840  /// on this stack whenever `has_deferred` reports some. Such work (a runnable
2841  /// rejected after the executor queue closed, or the runnables a terminal
2842  /// failure transition moved aside on a waker's stack) holds guards, so the
2843  /// generation can only become idle once the waiter drops them; `retire`
2844  /// runs with no lock held. `notify_idle_waiters` publishes new deferred
2845  /// work under the state lock, so a check made here cannot miss it.
2846  fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2847    let mut state = self
2848      .state
2849      .lock()
2850      .unwrap_or_else(std::sync::PoisonError::into_inner);
2851    while state.active != 0 {
2852      if has_deferred() {
2853        drop(state);
2854        retire();
2855        state = self
2856          .state
2857          .lock()
2858          .unwrap_or_else(std::sync::PoisonError::into_inner);
2859        continue;
2860      }
2861      state = self
2862        .idle
2863        .wait(state)
2864        .unwrap_or_else(std::sync::PoisonError::into_inner);
2865    }
2866  }
2867
2868  fn notify_idle_waiters(&self) {
2869    let _state = self
2870      .state
2871      .lock()
2872      .unwrap_or_else(std::sync::PoisonError::into_inner);
2873    self.idle.notify_all();
2874  }
2875}
2876
2877struct GenerationWorkGuard {
2878  work: Arc<GenerationWork>,
2879  task_id: Option<u64>,
2880}
2881
2882impl Drop for GenerationWorkGuard {
2883  fn drop(&mut self) {
2884    // The removed handle is usually the last owner of the task's abort state,
2885    // so dropping it frees memory and drops the waker parked there. Neither
2886    // runs under the state lock: the host's work-pending poll reads it, and
2887    // on `wasm32-wasip1-threads` a trap in there unwinds nothing, which
2888    // would leave the lock held for good.
2889    let abort_handle = {
2890      let mut state = self
2891        .work
2892        .state
2893        .lock()
2894        .unwrap_or_else(std::sync::PoisonError::into_inner);
2895      let abort_handle = self
2896        .task_id
2897        .and_then(|task_id| state.abort_handles.remove(&task_id));
2898      state.active = state
2899        .active
2900        .checked_sub(1)
2901        .expect("generation work count underflow");
2902      if state.active == 0 {
2903        self.work.idle.notify_all();
2904      }
2905      abort_handle
2906    };
2907    drop(abort_handle);
2908  }
2909}
2910
2911/// Keeps accepted async work registered until the complete task generator has
2912/// been destroyed. In particular, an executor queue may close after
2913/// registration but before the initial runnable is published. `async-task`
2914/// then drops the generator unpolled, whose compiler-generated capture order
2915/// must not be allowed to retire the generation guard before user captures.
2916struct RegisteredTaskFuture<F> {
2917  future: ManuallyDrop<F>,
2918  registration: Option<GenerationWorkGuard>,
2919  generation: u64,
2920}
2921
2922impl<F> RegisteredTaskFuture<F> {
2923  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2924    Self {
2925      future: ManuallyDrop::new(future),
2926      registration: Some(registration),
2927      generation,
2928    }
2929  }
2930}
2931
2932impl<F: Future> Future for RegisteredTaskFuture<F> {
2933  type Output = F::Output;
2934
2935  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2936    // SAFETY: `future` is never moved after `RegisteredTaskFuture` is pinned.
2937    let this = unsafe { self.get_unchecked_mut() };
2938    let _generation = RuntimeGenerationGuard::enter(this.generation);
2939    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2940    // structurally pinned for the lifetime of `self`.
2941    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2942  }
2943}
2944
2945impl<F> Drop for RegisteredTaskFuture<F> {
2946  fn drop(&mut self) {
2947    let _generation = RuntimeGenerationGuard::enter(self.generation);
2948    let _ = catch_unwind_contained(|| {
2949      // SAFETY: this is the sole destruction path for `future`.
2950      unsafe { ManuallyDrop::drop(&mut self.future) };
2951    });
2952    // Shutdown/restart may observe this generation idle only after every user
2953    // capture and caught panic payload above has retired.
2954    drop(self.registration.take());
2955  }
2956}
2957
2958#[cfg(napi_runtime_os_threads)]
2959struct RegisteredBlockingFunction<F> {
2960  function: Option<F>,
2961  registration: Option<GenerationWorkGuard>,
2962  generation: u64,
2963}
2964
2965#[cfg(napi_runtime_os_threads)]
2966impl<F> RegisteredBlockingFunction<F> {
2967  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2968    Self {
2969      function: Some(function),
2970      registration: Some(registration),
2971      generation,
2972    }
2973  }
2974
2975  fn run<T>(mut self) -> Result<T, JoinError>
2976  where
2977    F: FnOnce() -> T,
2978  {
2979    on_thread_handoff();
2980    let _generation = RuntimeGenerationGuard::enter(self.generation);
2981    let result = catch_unwind_join_error(
2982      self
2983        .function
2984        .take()
2985        .expect("blocking function must be available"),
2986    );
2987    drop(self.registration.take());
2988    result
2989  }
2990}
2991
2992#[cfg(napi_runtime_os_threads)]
2993impl<F> Drop for RegisteredBlockingFunction<F> {
2994  fn drop(&mut self) {
2995    let _generation = RuntimeGenerationGuard::enter(self.generation);
2996    if let Some(function) = self.function.take() {
2997      let _ = catch_unwind_contained(|| drop(function));
2998    }
2999    // A submission accepted by the controller may reach a queue that shutdown
3000    // already closed. Keep it registered until all captured user state above
3001    // has been destroyed on the submitting thread.
3002    drop(self.registration.take());
3003  }
3004}
3005
3006fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
3007  on_thread_handoff();
3008  let _active = metrics.runnable_started();
3009  let _ = catch_unwind_contained(|| runnable.run());
3010}
3011
3012#[cfg(napi_runtime_os_threads)]
3013enum RunnableClaim {
3014  Empty,
3015  Run(Runnable),
3016  Cancel(Runnable),
3017}
3018
3019static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
3020static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
3021
3022fn next_current_thread_dispatch_id() -> u64 {
3023  next_unique_id(
3024    &NEXT_CURRENT_THREAD_DISPATCH_ID,
3025    "CurrentThread dispatch id space exhausted",
3026  )
3027  .checked_add(1)
3028  .expect("CurrentThread dispatch id space exhausted")
3029}
3030
3031struct CurrentThreadBlockingAdmission {
3032  state: Mutex<CurrentThreadBlockingAdmissionState>,
3033}
3034
3035struct CurrentThreadBlockingAdmissionState {
3036  owner: Option<BlockingOwnerToken>,
3037  closed: bool,
3038  #[cfg(napi_runtime_os_threads)]
3039  queue: BlockingQueue,
3040}
3041
3042struct CurrentThreadExecutor {
3043  id: u64,
3044  generation: u64,
3045  stop: Arc<GenerationStop>,
3046  queue: Mutex<CurrentThreadQueue>,
3047  blocking_admission: CurrentThreadBlockingAdmission,
3048  draining: AtomicBool,
3049  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
3050  scheduler_idle: Condvar,
3051  /// Exact nonzero internal capability for the currently published host turn.
3052  /// The host registry maps this to distinct registration-scoped delivery
3053  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
3054  dispatch_pending: AtomicU64,
3055  /// Exact capability of the one replacement dispatch allowed after a host
3056  /// scheduler rejects an accepted callback. A second matching cancellation
3057  /// fails the queued work instead of retrying forever.
3058  recovery_dispatch: AtomicU64,
3059  /// Terminal dispatch failure rejects scheduler publications and admission
3060  /// from the moment it is claimed (under `scheduler_idle_lock`) until
3061  /// `cancel_work_after_host_dispatch_failure` reopens the executor under the
3062  /// queue lock: after cancelling the queued work on a clean stack, or as
3063  /// soon as it has moved that work aside on a waker's stack.
3064  cancelling_failed_dispatch: AtomicBool,
3065  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3066  task_drivers: &'static CurrentThreadTaskDriverRegistry,
3067  metrics: Arc<RuntimeMetrics>,
3068  // Deadlock detection. `threadless`: no second thread can EVER deliver a
3069  // wake to a parked `block_on`, so a park decision with an
3070  // empty queue and no pending wake token is a PROVABLE deadlock (panic
3071  // immediately, no timing involved). `park_deadline`: on threaded builds,
3072  // the optional bound for a park with zero runtime progress -- off unless
3073  // configured, because a legitimately long park must never panic a
3074  // production build.
3075  threadless: bool,
3076  park_deadline: Option<Duration>,
3077  // HOST-driver timers currently armed on this runtime.
3078  // On the THREADED CT flavor a pending entry is a future wake token -- the
3079  // host event loop's timer callback -- so the deadline verdict treats a LIVE
3080  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
3081  // build it is NOT a wake token (the host relay shares the parked thread and
3082  // can never run); there the CERTAIN check only consults it to pick the
3083  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
3084  // futures minted for this executor (they insert at first poll and remove on
3085  // completion/drop).
3086  host_timers: Arc<HostTimerRegistry>,
3087  // The generation this executor serves, attached by its `RuntimeBackend`.
3088  // A runnable rejected after the queue closed still owns its
3089  // `GenerationWorkGuard`, so `schedule` must wake the generation's idle
3090  // waiter, which retires such runnables on its own stack.
3091  generation_work: ArcSwapOption<GenerationWork>,
3092}
3093
3094impl Drop for CurrentThreadExecutor {
3095  fn drop(&mut self) {
3096    // Last drain point for rejected work (each runnable still owns its
3097    // `GenerationWorkGuard`) when no idle wait ran after the queue closed.
3098    self.retire_rejected_work();
3099  }
3100}
3101
3102struct CurrentThreadQueue {
3103  closed: bool,
3104  runnables: VecDeque<Runnable>,
3105  /// Runnables the executor owes a cancellation: the work that was queued
3106  /// when a terminal dispatch failure happened (moved here by
3107  /// `cancel_work_after_host_dispatch_failure`), a schedule that arrived
3108  /// while that transition held the executor closed, or a schedule that
3109  /// arrived after shutdown closed the queue. Their `queued_runnables` debit
3110  /// has already been taken. They are dropped only on clean stacks: by the
3111  /// transition itself when it is not on a waker's stack, and otherwise by
3112  /// `retire_rejected_work` at host entry points (a host turn, delivery
3113  /// completion, driver unregistration, a drain request) outside `schedule`
3114  /// and on the shutdown side (`begin_shutdown`, the generation's idle wait,
3115  /// `wait_until_scheduler_idle`, the executor's destructor). Never on the
3116  /// waker's stack: that waker may run while its producer holds a mutex
3117  /// (`futures::Shared` wakes awaiters under its `wakers` lock) that the
3118  /// rejected future's destructor re-locks. See `CurrentThreadScheduleScope`.
3119  rejected: Vec<Runnable>,
3120  /// Blocking jobs queued when a terminal dispatch failure happened. Same
3121  /// contract as `rejected`: dropping a job destroys its closure and its
3122  /// result sender, which wakes the awaiting task.
3123  #[cfg(napi_runtime_os_threads)]
3124  rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3125}
3126
3127#[derive(Default)]
3128struct CurrentThreadSchedulerState {
3129  active_host_turns: usize,
3130  active_dispatch_calls: usize,
3131  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3132}
3133
3134struct CurrentThreadDispatchPublication {
3135  dispatch: u64,
3136  republish_requested: bool,
3137}
3138
3139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3140enum CurrentThreadDispatchRequest {
3141  Accepted,
3142  Coalesced,
3143  Unavailable,
3144}
3145
3146#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3147struct CurrentThreadTaskDispatchFailure {
3148  dispatch: u64,
3149  epoch: u64,
3150}
3151
3152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3153enum CurrentThreadHostDispatchResult {
3154  Accepted,
3155  Unavailable,
3156  Failed(CurrentThreadTaskDispatchFailure),
3157}
3158
3159enum CurrentThreadDispatchPublicationStep {
3160  Republish,
3161  Complete(Option<CurrentThreadHostDispatchResult>),
3162}
3163
3164struct CurrentThreadHostTurn {
3165  executor: Arc<CurrentThreadExecutor>,
3166  draining: bool,
3167  _generation: RuntimeGenerationGuard,
3168}
3169
3170impl CurrentThreadHostTurn {
3171  fn drive(&mut self) {
3172    let executor = Arc::clone(&self.executor);
3173    executor.drive_admitted_host_turn(self);
3174  }
3175
3176  fn release_draining(&mut self) {
3177    if !self.draining {
3178      return;
3179    }
3180    let _scheduler = self
3181      .executor
3182      .scheduler_idle_lock
3183      .lock()
3184      .unwrap_or_else(std::sync::PoisonError::into_inner);
3185    self.executor.draining.store(false, Ordering::Release);
3186    self.draining = false;
3187  }
3188}
3189
3190impl Drop for CurrentThreadHostTurn {
3191  fn drop(&mut self) {
3192    let mut scheduler = self
3193      .executor
3194      .scheduler_idle_lock
3195      .lock()
3196      .unwrap_or_else(std::sync::PoisonError::into_inner);
3197    if self.draining {
3198      self.executor.draining.store(false, Ordering::Release);
3199      self.draining = false;
3200    }
3201    scheduler.active_host_turns = scheduler
3202      .active_host_turns
3203      .checked_sub(1)
3204      .expect("CurrentThread active host-turn count underflow");
3205    self.executor.scheduler_idle.notify_all();
3206  }
3207}
3208
3209/// Retains one admitted CurrentThread host callback in its runtime generation.
3210///
3211/// The host must keep this opaque lease alive until its native callback has
3212/// acknowledged or failed the delivery and destroyed every callback-owned
3213/// payload. The `Rc` marker prevents moving a TLS generation guard to another
3214/// thread before it restores the callback thread's prior runtime identity.
3215#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3216pub struct CurrentThreadTaskCallbackLease {
3217  _host_turn: CurrentThreadHostTurn,
3218  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3219}
3220
3221impl CurrentThreadTaskCallbackLease {
3222  fn new(host_turn: CurrentThreadHostTurn) -> Self {
3223    Self {
3224      _host_turn: host_turn,
3225      _not_send: std::marker::PhantomData,
3226    }
3227  }
3228}
3229
3230struct CurrentThreadDispatchCall<'a> {
3231  executor: &'a CurrentThreadExecutor,
3232  publication: Option<u64>,
3233  _generation: RuntimeGenerationGuard,
3234}
3235
3236impl CurrentThreadDispatchCall<'_> {
3237  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3238    let dispatch = self
3239      .publication
3240      .expect("the CurrentThread dispatch call must own a publication");
3241    let index = scheduler
3242      .dispatch_publications
3243      .iter()
3244      .position(|publication| publication.dispatch == dispatch)
3245      .expect("the CurrentThread dispatch publication must remain registered");
3246    scheduler.dispatch_publications.swap_remove(index);
3247    self.publication = None;
3248  }
3249}
3250
3251impl Drop for CurrentThreadDispatchCall<'_> {
3252  fn drop(&mut self) {
3253    let mut scheduler = self
3254      .executor
3255      .scheduler_idle_lock
3256      .lock()
3257      .unwrap_or_else(std::sync::PoisonError::into_inner);
3258    if let Some(dispatch) = self.publication.take()
3259      && let Some(index) = scheduler
3260        .dispatch_publications
3261        .iter()
3262        .position(|publication| publication.dispatch == dispatch)
3263    {
3264      scheduler.dispatch_publications.swap_remove(index);
3265    }
3266    scheduler.active_dispatch_calls = scheduler
3267      .active_dispatch_calls
3268      .checked_sub(1)
3269      .expect("CurrentThread active dispatch-call count underflow");
3270    self.executor.scheduler_idle.notify_all();
3271  }
3272}
3273
3274thread_local! {
3275  /// Depth of the `CurrentThreadExecutor::schedule` frames on this thread that
3276  /// a waker could have entered. `async-task` runs its scheduler callback for
3277  /// two different reasons: a wake, which any `Waker` can raise from any stack,
3278  /// and the very first schedule of a freshly submitted task, which `spawn`
3279  /// makes on its caller's own stack. Only the wake can arrive while a foreign
3280  /// mutex is held (`futures::Shared` wakes its awaiters under its `wakers`
3281  /// lock), so only `schedule` enters this scope; `schedule_submission` does
3282  /// not. A nonzero depth therefore marks a stack that may be a waker's, and
3283  /// nothing that destroys user futures inline may run on it: their destructors
3284  /// can re-lock that same mutex.
3285  static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3286}
3287
3288/// RAII marker for one waker-reachable `CurrentThreadExecutor::schedule` frame;
3289/// see `CURRENT_THREAD_SCHEDULE_DEPTH`.
3290struct CurrentThreadScheduleScope;
3291
3292impl CurrentThreadScheduleScope {
3293  fn enter() -> Self {
3294    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3295      depth.set(
3296        depth
3297          .get()
3298          .checked_add(1)
3299          .expect("CurrentThread schedule depth exhausted"),
3300      );
3301    });
3302    Self
3303  }
3304
3305  /// True while this thread is inside `schedule`: the stack may be a waker's.
3306  fn is_active() -> bool {
3307    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3308  }
3309}
3310
3311impl Drop for CurrentThreadScheduleScope {
3312  fn drop(&mut self) {
3313    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3314      depth.set(
3315        depth
3316          .get()
3317          .checked_sub(1)
3318          .expect("CurrentThread schedule depth underflow"),
3319      );
3320    });
3321  }
3322}
3323
3324thread_local! {
3325  /// Set while this thread is inside the retirement publication a waker-stack
3326  /// terminal failure issues for itself; see
3327  /// `CurrentThreadExecutor::request_rejected_work_retirement`. That
3328  /// publication calls the host driver again, so a runnable another thread
3329  /// queues meanwhile can drive a nested terminal transition on this same
3330  /// stack. Without this flag the nested transition would publish again, and a
3331  /// producer that keeps queueing work could recurse the stack without bound.
3332  ///
3333  /// It is thread-local, not per-executor: the bound it enforces is a stack
3334  /// bound. A terminal transition on ANOTHER thread finds this flag clear and
3335  /// keeps publishing its own continuation, which is what it should do -- its
3336  /// stack is its own.
3337  ///
3338  /// A nested transition records nothing when it returns. What it owes is
3339  /// executor state, not a message: it moved work into `queue.rejected` and
3340  /// consumed the dispatch that was outstanding, and the owning frame re-reads
3341  /// exactly that (`needs_rejected_work_retirement_dispatch`) before each of
3342  /// its turns. A separate "requested" flag used to carry it and could be
3343  /// discarded -- by a nested call whose request the owner never took, and
3344  /// then by the owner itself when the attempt budget ran out under it.
3345  static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3346    const { std::cell::Cell::new(false) };
3347}
3348
3349/// RAII marker for the frame that owns this thread's retirement publication;
3350/// see `CURRENT_THREAD_RETIRING_REJECTED_WORK`.
3351struct CurrentThreadRejectedWorkRetirementScope;
3352
3353impl CurrentThreadRejectedWorkRetirementScope {
3354  /// `None` when this thread is already publishing a retirement turn. The
3355  /// caller returns without publishing, and loses nothing by it: what it owes
3356  /// is the executor state the owning frame re-reads before its next turn.
3357  fn enter() -> Option<Self> {
3358    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3359      if active.replace(true) {
3360        // The owning frame keeps the flag. Do not write this as `then_some`:
3361        // its argument is eager, so the guard would be constructed here and
3362        // its destructor would clear the owner's flag on the way out.
3363        return None;
3364      }
3365      Some(Self)
3366    })
3367  }
3368}
3369
3370impl Drop for CurrentThreadRejectedWorkRetirementScope {
3371  fn drop(&mut self) {
3372    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3373  }
3374}
3375
3376struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3377
3378impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3379  fn drop(&mut self) {
3380    self
3381      .0
3382      .cancelling_failed_dispatch
3383      .store(false, Ordering::Release);
3384  }
3385}
3386
3387enum CurrentThreadDispatchFailureAction<'a> {
3388  None,
3389  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3390  Publish {
3391    dispatch: u64,
3392    call: CurrentThreadDispatchCall<'a>,
3393  },
3394}
3395
3396struct CurrentThreadDispatchFailureRecovery<'a> {
3397  action: CurrentThreadDispatchFailureAction<'a>,
3398  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3399}
3400
3401struct CurrentThreadRegistryCancellationGuard<'a> {
3402  registry: &'a CurrentThreadTaskDriverRegistry,
3403  failure: Option<CurrentThreadTaskDispatchFailure>,
3404}
3405
3406impl CurrentThreadRegistryCancellationGuard<'_> {
3407  fn new(
3408    registry: &CurrentThreadTaskDriverRegistry,
3409    failure: CurrentThreadTaskDispatchFailure,
3410  ) -> CurrentThreadRegistryCancellationGuard<'_> {
3411    CurrentThreadRegistryCancellationGuard {
3412      registry,
3413      failure: Some(failure),
3414    }
3415  }
3416}
3417
3418impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3419  fn drop(&mut self) {
3420    if let Some(failure) = self.failure.take() {
3421      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3422    }
3423  }
3424}
3425
3426struct CurrentThreadBlockingGuard<'a> {
3427  executor: &'a CurrentThreadExecutor,
3428  owner: BlockingOwnerToken,
3429  previous_owner: Option<BlockingOwnerToken>,
3430  owns_lane: bool,
3431}
3432
3433impl CurrentThreadBlockingGuard<'_> {
3434  fn counts_active_lane(&self) -> bool {
3435    self.owns_lane
3436  }
3437}
3438
3439impl Drop for CurrentThreadBlockingGuard<'_> {
3440  fn drop(&mut self) {
3441    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3442    if !self.owns_lane {
3443      return;
3444    }
3445    let mut state = self
3446      .executor
3447      .blocking_admission
3448      .state
3449      .lock()
3450      .unwrap_or_else(std::sync::PoisonError::into_inner);
3451    debug_assert_eq!(state.owner, Some(self.owner));
3452    state.owner = None;
3453    drop(state);
3454    self.executor.stop.wake_all();
3455  }
3456}
3457
3458impl CurrentThreadExecutor {
3459  /// Bound one host callback so a self-waking task cannot monopolize the
3460  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
3461  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3462  /// Force one blocking turn after this many consecutive runnable polls.
3463  #[cfg(napi_runtime_os_threads)]
3464  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3465  /// How many retirement turns one waker-stack terminal failure may take --
3466  /// the turns another thread's submissions can inflate. The one offer nothing
3467  /// can inflate follows them; see `request_rejected_work_retirement`.
3468  const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3469
3470  /// Build-default construction, kept for the pre-existing unit tests
3471  /// (production goes through [`Self::with_detection`] in
3472  /// `RuntimeBackend::new`, which also resolves the env deadline).
3473  #[cfg(test)]
3474  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3475    Self::with_detection(metrics, THREADLESS_BUILD, None)
3476  }
3477
3478  /// Full constructor for the deadlock-detection knobs: `new` fixes
3479  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
3480  /// `RuntimeBackend::new` resolves the deadline from
3481  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
3482  /// native `threadless` stand-in and short deadlines through it.
3483  #[cfg(test)]
3484  fn with_detection(
3485    metrics: Arc<RuntimeMetrics>,
3486    threadless: bool,
3487    park_deadline: Option<Duration>,
3488  ) -> Self {
3489    Self::with_generation(
3490      metrics,
3491      threadless,
3492      park_deadline,
3493      u64::MAX,
3494      Arc::new(GenerationStop::default()),
3495    )
3496  }
3497
3498  fn with_generation(
3499    metrics: Arc<RuntimeMetrics>,
3500    threadless: bool,
3501    park_deadline: Option<Duration>,
3502    generation: u64,
3503    stop: Arc<GenerationStop>,
3504  ) -> Self {
3505    Self {
3506      id: next_unique_id(
3507        &NEXT_RUNTIME_EXECUTOR_ID,
3508        "async runtime executor id space exhausted",
3509      ),
3510      generation,
3511      stop,
3512      queue: Mutex::new(CurrentThreadQueue {
3513        closed: false,
3514        runnables: VecDeque::new(),
3515        rejected: Vec::new(),
3516        #[cfg(napi_runtime_os_threads)]
3517        rejected_blocking: Vec::new(),
3518      }),
3519      blocking_admission: CurrentThreadBlockingAdmission {
3520        state: Mutex::new(CurrentThreadBlockingAdmissionState {
3521          owner: None,
3522          closed: false,
3523          #[cfg(napi_runtime_os_threads)]
3524          queue: BlockingQueue {
3525            closed: false,
3526            head: None,
3527            tail: None,
3528            jobs: FxHashMap::default(),
3529          },
3530        }),
3531      },
3532      draining: AtomicBool::new(false),
3533      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3534      scheduler_idle: Condvar::new(),
3535      dispatch_pending: AtomicU64::new(0),
3536      recovery_dispatch: AtomicU64::new(0),
3537      cancelling_failed_dispatch: AtomicBool::new(false),
3538      task_dispatch: dispatch_current_thread_tasks,
3539      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3540      metrics,
3541      threadless,
3542      park_deadline,
3543      host_timers: Arc::new(HostTimerRegistry::default()),
3544      generation_work: ArcSwapOption::empty(),
3545    }
3546  }
3547
3548  fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3549    self.generation_work.store(Some(Arc::clone(work)));
3550  }
3551
3552  /// Whether `queue.rejected` (or `queue.rejected_blocking`) holds work the
3553  /// executor still owes a cancellation.
3554  fn has_rejected_work(&self) -> bool {
3555    self.rejected_work_pending(PendingRead::Locked)
3556  }
3557
3558  /// [`Self::has_rejected_work`]; `read` decides whether it may wait for the
3559  /// queue lock.
3560  fn rejected_work_pending(&self, read: PendingRead) -> bool {
3561    read.read(&self.queue, |queue| {
3562      #[cfg(napi_runtime_os_threads)]
3563      let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3564      #[cfg(not(napi_runtime_os_threads))]
3565      let has_rejected_blocking = false;
3566      !queue.rejected.is_empty() || has_rejected_blocking
3567    })
3568  }
3569
3570  /// The predicate `wait_until_scheduler_idle` sleeps on, read without
3571  /// sleeping; `read` decides whether it may wait for the scheduler lock.
3572  fn scheduler_work_pending(&self, read: PendingRead) -> bool {
3573    read.read(&self.scheduler_idle_lock, |idle| {
3574      idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3575    })
3576  }
3577
3578  /// Cancel every rejected runnable and blocking job. Runs on the caller's
3579  /// stack with no executor lock held; the caller must not be inside a waker
3580  /// invocation (see `CurrentThreadQueue::rejected`). Dropping a runnable can
3581  /// wake a parked task whose schedule is rejected again (the queue closed,
3582  /// or a nested terminal transition moved it here), so drain until nothing
3583  /// remains. This terminates: every drop closes one task for good, so the
3584  /// closed-queue rejections are bounded by the live tasks, and a nested
3585  /// transition needs two more host-failed deliveries of its own.
3586  fn retire_rejected_work(&self) {
3587    if !self.has_rejected_work() {
3588      return;
3589    }
3590    let _generation = RuntimeGenerationGuard::enter(self.generation);
3591    loop {
3592      #[cfg(napi_runtime_os_threads)]
3593      let (rejected, rejected_blocking) = {
3594        let mut queue = self
3595          .queue
3596          .lock()
3597          .unwrap_or_else(std::sync::PoisonError::into_inner);
3598        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3599          return;
3600        }
3601        (
3602          std::mem::take(&mut queue.rejected),
3603          std::mem::take(&mut queue.rejected_blocking),
3604        )
3605      };
3606      #[cfg(not(napi_runtime_os_threads))]
3607      let rejected = {
3608        let mut queue = self
3609          .queue
3610          .lock()
3611          .unwrap_or_else(std::sync::PoisonError::into_inner);
3612        if queue.rejected.is_empty() {
3613          return;
3614        }
3615        std::mem::take(&mut queue.rejected)
3616      };
3617      for runnable in rejected {
3618        let _ = catch_unwind_contained(|| drop(runnable));
3619      }
3620      #[cfg(napi_runtime_os_threads)]
3621      for job in rejected_blocking {
3622        let _ = catch_unwind_contained(|| drop(job));
3623      }
3624    }
3625  }
3626
3627  /// Host-side entry points (a host turn, delivery completion, driver
3628  /// registration or unregistration, drain requests) retire rejected work
3629  /// here. They are clean stacks unless the host called back synchronously
3630  /// from inside a publication that a waker's `schedule` started; that case
3631  /// leaves the work for the next entry.
3632  fn retire_rejected_work_off_waker_stack(&self) {
3633    if CurrentThreadScheduleScope::is_active() {
3634      return;
3635    }
3636    self.retire_rejected_work();
3637  }
3638
3639  /// Whether the executor still owes its rejected work a continuation: work is
3640  /// parked in `queue.rejected` (or `queue.rejected_blocking`) and no host
3641  /// dispatch is outstanding to bring the clean stack that retires it.
3642  ///
3643  /// This is the whole state a retirement turn acts on. Nothing else needs to
3644  /// be remembered across a turn, on this thread or on the executor: a nested
3645  /// terminal transition that ran inside a turn's publication changed exactly
3646  /// these two things (it moved more work aside and consumed the dispatch),
3647  /// so re-reading them is the same as being told.
3648  fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3649    self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3650  }
3651
3652  /// The continuation a terminal failure reached on a waker's stack owes
3653  /// itself.
3654  ///
3655  /// That branch of `cancel_work_after_host_dispatch_failure` may not destroy
3656  /// anything where it stands, so it parks the work in `queue.rejected` and
3657  /// reopens. Reopening alone is not a continuation: the transition consumed
3658  /// the executor's one outstanding host request (`dispatch_pending` is 0) and
3659  /// left the queue empty, so `has_serviceable_work` is false and nothing --
3660  /// not `request_drain_if_work_remains`, not a wake of an already-parked task
3661  /// -- publishes again. A wake that loses both of its publications therefore
3662  /// produced no host turn at all, and the parked runnables kept their
3663  /// `GenerationWorkGuard`s and their `JoinHandle`s forever.
3664  ///
3665  /// A host turn is the clean stack that retires them: `drive_admitted_host_turn`
3666  /// calls `retire_rejected_work_off_waker_stack` before it polls anything. So
3667  /// ask for one, and keep asking while the host refuses and the work is still
3668  /// owed a stack -- the host is racing its own teardown, not necessarily dead,
3669  /// and a refusal says nothing about the driver that answers next (a second
3670  /// registered host, or the same one once its delivery backlog clears).
3671  ///
3672  /// What may be published on this stack is bounded, in two different ways,
3673  /// because there are two different things to bound.
3674  ///
3675  /// * `REJECTED_WORK_RETIREMENT_ATTEMPTS` bounds the turns a PRODUCER can
3676  ///   inflate. Each turn calls the driver again, so another thread can queue
3677  ///   a runnable while the publication is in flight; the host can then fail
3678  ///   that delivery and the replacement it reserves for the new work, and a
3679  ///   second terminal transition runs nested on this stack. It may not
3680  ///   publish where it stands -- that is what would recurse without bound --
3681  ///   so `CURRENT_THREAD_RETIRING_REJECTED_WORK` sends it back, and this
3682  ///   frame takes its next turn instead. Every such turn is two more host
3683  ///   callbacks and two more producer submissions, and the producer's own
3684  ///   drain request coalesces behind the publication already in flight, so it
3685  ///   never runs dry: measured without a budget, 32768 submissions produced
3686  ///   32769 deliveries inside one wake, and a producer left running for 400ms
3687  ///   held this single `schedule` callback -- and the `futures::Shared`
3688  ///   `wakers` mutex under it -- for 432ms. Eight turns cap that at 16 host
3689  ///   callbacks, a small constant to run under a foreign lock; two turns are
3690  ///   all the known interleaving needs.
3691  /// * `publish_final_rejected_work_retirement` bounds nothing, because
3692  ///   nothing can inflate it. It is the one delivery this stack offers with
3693  ///   the terminal-cancellation claim held, so no producer can interleave
3694  ///   with it and no nested transition can consume it. It is what the budget
3695  ///   may not be allowed to swallow: a producer that queues its last runnable
3696  ///   inside the eighth turn spends the budget and then goes quiescent, and
3697  ///   without this offer a host that would have accepted the very next
3698  ///   delivery is never asked, leaving the `JoinHandle`s pending and their
3699  ///   `GenerationWorkGuard`s held until unrelated work or shutdown arrives.
3700  ///
3701  /// * One ordinary publication follows an ACCEPTED final offer whose dispatch
3702  ///   the host consumed on this stack. A driver may claim, drive and
3703  ///   acknowledge the delivery inside `dispatch`, and that turn can do
3704  ///   nothing: it is on this waker's stack, so
3705  ///   `retire_rejected_work_off_waker_stack` returns at nonzero depth, and the
3706  ///   claim is still held, so `take_runnable` returns `None`. All it did was
3707  ///   take the continuation. The state it leaves -- work parked, no dispatch
3708  ///   outstanding -- is the state a REFUSED offer leaves, so it is owed the
3709  ///   same thing, and the same condition
3710  ///   (`needs_rejected_work_retirement_dispatch`) says so. The executor is
3711  ///   open again by then, which is what `5857092f` reopened it for, so this
3712  ///   takes the ordinary path. It is one publication and not a loop: a
3713  ///   terminal transition nested inside it finds
3714  ///   `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and publishes
3715  ///   nothing, and this frame is returning.
3716  ///
3717  /// So the turns stop at the first of: a dispatch outstanding (a delivery the
3718  /// host accepted, or one it failed and replaced, is the continuation --
3719  /// `drive_admitted_host_turn` retires on it), nothing left owed, no live
3720  /// host, and the budget -- and the budget is followed by the final offer,
3721  /// which is followed by one ordinary publication if the host took it without
3722  /// leaving the work a stack.
3723  ///
3724  /// Nothing is destroyed here: this adds no `retire_rejected_work` call. A
3725  /// nested transition inside any of these publications still sees the outer
3726  /// `CurrentThreadScheduleScope` and takes the waker branch, and the
3727  /// `complete_current_thread_task_delivery` the driver runs there reaches only
3728  /// `retire_rejected_work_off_waker_stack`, which returns at nonzero depth.
3729  ///
3730  /// When the host refuses the final offer too -- or consumes it and then the
3731  /// publication that follows it, never leaving this waker's stack -- that is
3732  /// terminal by construction and honest: the work waits for the next host that
3733  /// registers
3734  /// (`request_drain_if_queued` retires before it publishes) or for shutdown
3735  /// (`begin_shutdown`, `wait_until_idle_retiring`, `wait_until_scheduler_idle`,
3736  /// `Drop`). With no host that accepts there is no stack on which user futures
3737  /// may be destroyed. Handing them to a helper thread was rejected --
3738  /// threadless wasm has none, and host-affine futures must be destroyed on a
3739  /// host or shutdown thread.
3740  ///
3741  /// Rejected alternatives: a time budget -- it leaves the number of host
3742  /// callbacks open, pins nothing an assertion can check, and reads a clock on
3743  /// a stack meant to do as little as possible; yielding the thread -- it does
3744  /// not release the producer's mutex, which is the thing being held; parking
3745  /// the leftover need on the executor for the next depth-0 entry -- those
3746  /// entries already retire `queue.rejected` unconditionally, and in the
3747  /// interleaving that needs it there is no next entry at all, which is why
3748  /// the continuation has to be a delivery; and making EVERY turn a claimed
3749  /// one like the final offer -- it is uninflatable for the same reason it
3750  /// cancels a runnable that races it, and paying that for the ordinary turns
3751  /// would undo what `5857092f` reopened the executor for.
3752  fn request_rejected_work_retirement(self: &Arc<Self>) {
3753    if !self.has_rejected_work() {
3754      return;
3755    }
3756    let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3757      // Nested inside this thread's own retirement publication. Publishing
3758      // here is what would recurse the stack; the frame that owns the
3759      // publication takes the next turn, and reads what this one moved aside
3760      // off the executor rather than off a flag.
3761      return;
3762    };
3763    for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3764      if !self.needs_rejected_work_retirement_dispatch() {
3765        return;
3766      }
3767      if matches!(
3768        self.request_drain_with(next_current_thread_dispatch_id),
3769        CurrentThreadDispatchRequest::Unavailable
3770      ) {
3771        // Stopping, or no live driver: there is no host to offer anything to.
3772        return;
3773      }
3774    }
3775    if self.publish_final_rejected_work_retirement()
3776      && self.needs_rejected_work_retirement_dispatch()
3777    {
3778      // The host ACCEPTED the offer and consumed its dispatch without leaving
3779      // the work a stack: a turn it claims and drives inside `dispatch` runs
3780      // on this same waker's stack, where `retire_rejected_work_off_waker_stack`
3781      // returns at nonzero depth, and it runs with the claim held, where
3782      // `take_runnable` returns `None`. Such a turn can do nothing at all; all
3783      // it did was take the continuation. The state it left is the state a
3784      // refused offer leaves, and the invariant is the same, so it is owed the
3785      // same thing: work is parked and no dispatch is outstanding, so publish
3786      // one.
3787      //
3788      // This is the ordinary publication path -- the executor is open again,
3789      // which is what `5857092f` reopened it for -- and it is made once, not
3790      // looped. It cannot recurse: a terminal transition nested inside it
3791      // finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and
3792      // publishes nothing, and this frame has already returned its budget. A
3793      // host that consumes this one on the waker's stack too has never left
3794      // that stack, which is the limit already documented above: with no host
3795      // turn on a clean stack there is nowhere to destroy a user future.
3796      let _ = self.request_drain_with(next_current_thread_dispatch_id);
3797    }
3798  }
3799
3800  /// The one delivery this stack offers that a producer cannot inflate, made
3801  /// after `REJECTED_WORK_RETIREMENT_ATTEMPTS` turns were spent without
3802  /// leaving a dispatch outstanding.
3803  ///
3804  /// It holds the terminal-cancellation claim across the host callback. That
3805  /// claim is what makes it terminal rather than the ninth of an unbounded
3806  /// series: `schedule` consults it under the queue lock and parks a racing
3807  /// runnable in `queue.rejected` instead of queueing it, so no serviceable
3808  /// work can appear during the publication, and
3809  /// `begin_host_dispatch_failure_transition_locked` returns `None` while it is
3810  /// held, so a failure of this delivery reserves no replacement and reaches no
3811  /// terminal transition. One host callback, no recursion, no budget needed.
3812  ///
3813  /// A runnable that does race it is cancelled with the work already parked
3814  /// rather than queued. That is the same outcome the nested terminal
3815  /// transitions in the turns above give it, and this is reached only after
3816  /// the host has refused every one of them.
3817  ///
3818  /// Returns whether the host ACCEPTED the offer. An accepted offer is not by
3819  /// itself a continuation: a driver may claim, drive and acknowledge the
3820  /// delivery inside `dispatch`, and that turn runs on this waker's stack with
3821  /// the claim held, so it can neither retire (`retire_rejected_work_off_waker_stack`
3822  /// returns at nonzero depth) nor drain (`take_runnable` returns `None` while
3823  /// the claim is held) -- it only consumes `dispatch_pending`. The caller
3824  /// re-reads `needs_rejected_work_retirement_dispatch` and publishes once more
3825  /// when that happened.
3826  fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3827    if !self.needs_rejected_work_retirement_dispatch() {
3828      return false;
3829    }
3830    let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3831      // Another terminal transition owns the executor; it carries its own
3832      // continuation.
3833      return false;
3834    };
3835    let dispatch = next_current_thread_dispatch_id();
3836    let dispatch_call = {
3837      let mut scheduler = self.lock_scheduler_for_publication();
3838      self.dispatch_pending.store(dispatch, Ordering::Release);
3839      self
3840        .begin_host_dispatch_publication(&mut scheduler, dispatch)
3841        .expect("a fresh CurrentThread retirement dispatch must own its publication")
3842    };
3843    let result = self.publish_host_dispatch(dispatch, dispatch_call);
3844    {
3845      // Reopen while holding the queue lock, the way the transition does:
3846      // `schedule` consults the flag under it, so every runnable parked during
3847      // the publication is already in `queue.rejected` when the executor opens.
3848      let _queue = self
3849        .queue
3850        .lock()
3851        .unwrap_or_else(std::sync::PoisonError::into_inner);
3852      drop(cancelling);
3853    }
3854    // Those parked runnables carry their `GenerationWorkGuard`s and were
3855    // pushed without notifying; a waiting shutdown retires them on its stack.
3856    if let Some(work) = self.generation_work.load_full() {
3857      work.notify_idle_waiters();
3858    }
3859    result == CurrentThreadHostDispatchResult::Accepted
3860  }
3861
3862  fn fresh_owner_token(&self) -> BlockingOwnerToken {
3863    BlockingOwnerToken {
3864      executor_id: self.id,
3865      frame: next_unique_id(
3866        &NEXT_BLOCKING_FRAME,
3867        "blocking owner frame id space exhausted",
3868      ),
3869    }
3870  }
3871
3872  fn enter_blocking_owner(
3873    &self,
3874    owner: BlockingOwnerToken,
3875    owns_lane: bool,
3876  ) -> CurrentThreadBlockingGuard<'_> {
3877    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3878    CurrentThreadBlockingGuard {
3879      executor: self,
3880      owner,
3881      previous_owner,
3882      owns_lane,
3883    }
3884  }
3885
3886  fn schedule_blocking<F, T>(
3887    self: &Arc<Self>,
3888    function: F,
3889    registration: GenerationWorkGuard,
3890  ) -> JoinHandle<T>
3891  where
3892    F: FnOnce() -> T + Send + 'static,
3893    T: Send + 'static,
3894  {
3895    // See the implementation notes in this crate's README.
3896    self.metrics.blocking_scheduled();
3897    let mut function = Some(function);
3898    let mut registration = Some(registration);
3899    let ambient_owner = BLOCKING_OWNER
3900      .with(std::cell::Cell::get)
3901      .filter(|owner| owner.executor_id == self.id);
3902    let mut run_owner = None;
3903    let mut owns_lane = false;
3904    let mut rejected = false;
3905
3906    #[cfg(napi_runtime_os_threads)]
3907    let (sender, receiver) = oneshot::channel();
3908    #[cfg(napi_runtime_os_threads)]
3909    let dependency = BlockingDependency {
3910      job: BlockingJobId(next_unique_id(
3911        &NEXT_BLOCKING_JOB_ID,
3912        "blocking job id space exhausted",
3913      )),
3914      owner: ambient_owner,
3915    };
3916    #[cfg(napi_runtime_os_threads)]
3917    let mut queued = false;
3918
3919    {
3920      let mut state = self
3921        .blocking_admission
3922        .state
3923        .lock()
3924        .unwrap_or_else(std::sync::PoisonError::into_inner);
3925      #[cfg(all(test, napi_runtime_os_threads))]
3926      run_current_thread_blocking_claim_test_hook();
3927      // Shutdown publishes generation stop before taking this mutex. Recheck
3928      // it while admission is serialized so work cannot claim or queue for the
3929      // CurrentThread lane after stop publication.
3930      if self.stop.is_stopping()
3931        || state.closed
3932        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3933      {
3934        rejected = true;
3935      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3936        run_owner = ambient_owner;
3937      } else {
3938        #[cfg(napi_runtime_os_threads)]
3939        let queue_empty = state.queue.is_empty();
3940        #[cfg(not(napi_runtime_os_threads))]
3941        let queue_empty = true;
3942
3943        if state.owner.is_none() && queue_empty {
3944          let owner = self.fresh_owner_token();
3945          state.owner = Some(owner);
3946          run_owner = Some(owner);
3947          owns_lane = true;
3948        } else {
3949          #[cfg(napi_runtime_os_threads)]
3950          {
3951            let function = function
3952              .take()
3953              .expect("blocking function must be available");
3954            let registration = registration
3955              .take()
3956              .expect("blocking registration must be available");
3957            let generation = self.generation;
3958            let function = RegisteredBlockingFunction::new(function, registration, generation);
3959            state.queue.push(QueuedBlocking {
3960              id: dependency.job,
3961              run: Box::new(move || {
3962                let _generation = RuntimeGenerationGuard::enter(generation);
3963                let result = function
3964                  .run()
3965                  .map(|output| ContainedTaskOutput::new(output, generation));
3966                let _ = sender.send(result);
3967              }),
3968            });
3969            queued = true;
3970          }
3971          #[cfg(not(napi_runtime_os_threads))]
3972          {
3973            // This arm is threadless by construction: without
3974            // `napi_runtime_os_threads` no second thread exists to reach
3975            // unrelated concurrent admission. Same-stack nesting carries
3976            // `ambient_owner` and takes the borrowing branch above. On
3977            // `wasm32-wasip1-threads` a second thread DOES reach here, which
3978            // is why that target takes the queueing branch instead.
3979            rejected = true;
3980          }
3981        }
3982      }
3983    }
3984
3985    #[cfg(napi_runtime_os_threads)]
3986    if queued {
3987      self.stop.wake_all();
3988      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3989        self.request_drain();
3990      }
3991      return JoinHandle(JoinHandleInner::Blocking {
3992        receiver,
3993        dependency,
3994        published: PublishedBlockingDependencies::default(),
3995      });
3996    }
3997
3998    if rejected {
3999      let _generation = RuntimeGenerationGuard::enter(self.generation);
4000      let _ = catch_unwind_contained(|| drop(function.take()));
4001      drop(registration.take());
4002      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
4003        message: "the async runtime stopped before the blocking task could start".to_string(),
4004      }))));
4005    }
4006
4007    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
4008    let blocking = self.enter_blocking_owner(owner, owns_lane);
4009    let _registration = registration
4010      .take()
4011      .expect("blocking registration must be available");
4012    let _generation = RuntimeGenerationGuard::enter(self.generation);
4013    let result = {
4014      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
4015      on_thread_handoff();
4016      catch_unwind_join_error(
4017        function
4018          .take()
4019          .expect("blocking function must be available"),
4020      )
4021    }
4022    .map(|output| ContainedTaskOutput::new(output, self.generation));
4023    drop(blocking);
4024    #[cfg(napi_runtime_os_threads)]
4025    if owns_lane && !self.draining.load(Ordering::Acquire) {
4026      // An admitted host turn already owns a bounded queue driver. Let its
4027      // outer loop service the released FIFO so blocking work cannot bypass
4028      // the host-turn budget and monopolize the JavaScript event loop.
4029      self.service_queued_blocking_work();
4030    }
4031    JoinHandle(JoinHandleInner::Ready(Some(result)))
4032  }
4033
4034  #[cfg(napi_runtime_os_threads)]
4035  fn take_queued_blocking(
4036    &self,
4037  ) -> Option<(
4038    Box<dyn FnOnce() + Send + 'static>,
4039    CurrentThreadBlockingGuard<'_>,
4040  )> {
4041    let (job, owner) = {
4042      let mut state = self
4043        .blocking_admission
4044        .state
4045        .lock()
4046        .unwrap_or_else(std::sync::PoisonError::into_inner);
4047      #[cfg(all(test, napi_runtime_os_threads))]
4048      run_current_thread_blocking_claim_test_hook();
4049      if self.stop.is_stopping()
4050        || state.closed
4051        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4052        || state.owner.is_some()
4053      {
4054        return None;
4055      }
4056      let job = state.queue.pop_front()?;
4057      let owner = self.fresh_owner_token();
4058      state.owner = Some(owner);
4059      (job, owner)
4060    };
4061    Some((job, self.enter_blocking_owner(owner, true)))
4062  }
4063
4064  #[cfg(napi_runtime_os_threads)]
4065  fn run_one_blocking(&self) -> bool {
4066    let Some((job, blocking)) = self.take_queued_blocking() else {
4067      return false;
4068    };
4069    let _generation = RuntimeGenerationGuard::enter(self.generation);
4070    {
4071      let _task = self.metrics.blocking_started(true);
4072      let _ = catch_unwind_contained(job);
4073    }
4074    drop(blocking);
4075    true
4076  }
4077
4078  #[cfg(napi_runtime_os_threads)]
4079  fn service_queued_blocking_work(&self) {
4080    while self.run_one_blocking() {}
4081  }
4082
4083  #[cfg(napi_runtime_os_threads)]
4084  fn has_serviceable_blocking_work(&self) -> bool {
4085    let state = self
4086      .blocking_admission
4087      .state
4088      .lock()
4089      .unwrap_or_else(std::sync::PoisonError::into_inner);
4090    !state.closed && state.owner.is_none() && !state.queue.is_empty()
4091  }
4092
4093  #[cfg(napi_runtime_os_threads)]
4094  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
4095    let Some(ambient_owner) = BLOCKING_OWNER
4096      .with(std::cell::Cell::get)
4097      .filter(|owner| owner.executor_id == self.id)
4098    else {
4099      return false;
4100    };
4101    let Some(current) = dependency.select_for_owner(ambient_owner) else {
4102      return false;
4103    };
4104    let job = {
4105      let mut state = self
4106        .blocking_admission
4107        .state
4108        .lock()
4109        .unwrap_or_else(std::sync::PoisonError::into_inner);
4110      #[cfg(all(test, napi_runtime_os_threads))]
4111      run_current_thread_blocking_claim_test_hook();
4112      if self.stop.is_stopping()
4113        || state.closed
4114        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4115        || state.owner != Some(ambient_owner)
4116      {
4117        return false;
4118      }
4119      if dependency.try_claim(&current) {
4120        state.queue.remove(current.publication.dependency.job)
4121      } else {
4122        None
4123      }
4124    };
4125    let Some(job) = job else {
4126      return false;
4127    };
4128    let _generation = RuntimeGenerationGuard::enter(self.generation);
4129    let _task = self.metrics.blocking_started(false);
4130    let _ = catch_unwind_contained(job);
4131    true
4132  }
4133
4134  fn close_blocking_admission(&self) {
4135    #[cfg(napi_runtime_os_threads)]
4136    let queued = {
4137      let mut state = self
4138        .blocking_admission
4139        .state
4140        .lock()
4141        .unwrap_or_else(std::sync::PoisonError::into_inner);
4142      state.closed = true;
4143      state.queue.closed = true;
4144      state.queue.take_all()
4145    };
4146    #[cfg(not(napi_runtime_os_threads))]
4147    {
4148      let mut state = self
4149        .blocking_admission
4150        .state
4151        .lock()
4152        .unwrap_or_else(std::sync::PoisonError::into_inner);
4153      state.closed = true;
4154    }
4155    #[cfg(napi_runtime_os_threads)]
4156    for job in queued {
4157      let _ = catch_unwind_contained(|| drop(job));
4158    }
4159  }
4160
4161  #[cfg(test)]
4162  fn with_task_dispatch(
4163    metrics: Arc<RuntimeMetrics>,
4164    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4165  ) -> Self {
4166    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4167  }
4168
4169  #[cfg(test)]
4170  fn with_task_dispatch_registry(
4171    metrics: Arc<RuntimeMetrics>,
4172    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4173    task_drivers: &'static CurrentThreadTaskDriverRegistry,
4174  ) -> Self {
4175    let mut executor = Self::new(metrics);
4176    executor.task_dispatch = task_dispatch;
4177    executor.task_drivers = task_drivers;
4178    executor
4179  }
4180
4181  /// A wake, or any other schedule whose stack the executor does not own.
4182  /// `async-task` hands every `Waker::wake` to this callback, so the frame may
4183  /// belong to a producer that is holding its own mutex; see
4184  /// `CURRENT_THREAD_SCHEDULE_DEPTH`.
4185  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4186    let _scope = CurrentThreadScheduleScope::enter();
4187    self.schedule_runnable(runnable);
4188  }
4189
4190  /// The first schedule of a freshly submitted task, made by the caller of
4191  /// `spawn` on its own stack. No waker is involved, so a terminal dispatch
4192  /// failure reached from here may cancel the rejected work inline -- and it
4193  /// must, because a submission that publishes no host delivery has no other
4194  /// continuation: nothing would ever reach `retire_rejected_work`, and the
4195  /// returned `JoinHandle` would stay pending with its generation guard held.
4196  fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4197    self.schedule_runnable(runnable);
4198  }
4199
4200  fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4201    self.metrics.runnable_scheduled();
4202    {
4203      let mut queue = self
4204        .queue
4205        .lock()
4206        .unwrap_or_else(std::sync::PoisonError::into_inner);
4207      // Never drop a rejected runnable here: that destroys the future inline
4208      // on the waker's thread, and a waker can be invoked while its producer
4209      // holds a mutex that this future's destructor needs (`futures::Shared`
4210      // wakes under its `wakers` lock and its clone re-locks it on drop).
4211      if queue.closed {
4212        // A wake can be in flight when shutdown starts: `async-task` marks
4213        // the task SCHEDULED before it invokes this closure, so the abort
4214        // that `close_and_abort` issues coalesces with the in-flight wake and
4215        // `begin_shutdown` closes the queue without waiting for it to land.
4216        // The runnable still owns its `GenerationWorkGuard`; hand it to the
4217        // shutdown side and wake the generation's idle waiter to retire it.
4218        self
4219          .metrics
4220          .queued_runnables
4221          .fetch_sub(1, Ordering::Relaxed);
4222        queue.rejected.push(runnable);
4223        drop(queue);
4224        if let Some(work) = self.generation_work.load_full() {
4225          work.notify_idle_waiters();
4226        }
4227        return;
4228      }
4229      if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4230        // A terminal failure transition is claimed but has not yet moved the
4231        // queue and reopened (it does both under this lock): the runnable
4232        // joins the work that transition cancels.
4233        self
4234          .metrics
4235          .queued_runnables
4236          .fetch_sub(1, Ordering::Relaxed);
4237        queue.rejected.push(runnable);
4238        return;
4239      }
4240      queue.runnables.push_back(runnable);
4241    }
4242    // Queue work and only then wake an explicit block_on driver. This is still
4243    // enqueue-only with respect to the wake caller: polling happens on the
4244    // driver's thread, never inline from an arbitrary waker invocation.
4245    self.stop.wake_all();
4246    if !self.draining.load(Ordering::Acquire) {
4247      self.request_drain();
4248    }
4249  }
4250
4251  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
4252  ///
4253  /// A `Waker` may be invoked while its producer holds an internal mutex
4254  /// (`futures::Shared` does this). Polling inline from the scheduler callback
4255  /// can then re-enter that future and self-deadlock on the same mutex. Once a
4256  /// host dispatcher has accepted work, this executor stays host-driven for
4257  /// the rest of its generation; a temporarily missing env leaves work queued
4258  /// for the next registration or for shutdown cancellation.
4259  fn request_drain(self: &Arc<Self>) {
4260    let _ = self.request_drain_with(next_current_thread_dispatch_id);
4261  }
4262
4263  fn lock_scheduler_for_publication(&self) -> MutexGuard<'_, CurrentThreadSchedulerState> {
4264    #[cfg(all(test, napi_runtime_os_threads))]
4265    match self.scheduler_idle_lock.try_lock() {
4266      Ok(scheduler) => scheduler,
4267      Err(std::sync::TryLockError::WouldBlock) => {
4268        run_current_thread_dispatch_publication_blocked_test_hook();
4269        self
4270          .scheduler_idle_lock
4271          .lock()
4272          .unwrap_or_else(std::sync::PoisonError::into_inner)
4273      }
4274      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4275    }
4276    #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4277    self
4278      .scheduler_idle_lock
4279      .lock()
4280      .unwrap_or_else(std::sync::PoisonError::into_inner)
4281  }
4282
4283  fn request_drain_with(
4284    self: &Arc<Self>,
4285    next_dispatch: impl FnOnce() -> u64,
4286  ) -> CurrentThreadDispatchRequest {
4287    let mut scheduler = self.lock_scheduler_for_publication();
4288    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4289      return CurrentThreadDispatchRequest::Unavailable;
4290    }
4291    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4292      return CurrentThreadDispatchRequest::Coalesced;
4293    }
4294    let dispatch = next_dispatch();
4295    if self
4296      .dispatch_pending
4297      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4298      .is_err()
4299    {
4300      return CurrentThreadDispatchRequest::Coalesced;
4301    }
4302    let dispatch_call = self
4303      .begin_host_dispatch_publication(&mut scheduler, dispatch)
4304      .expect("a fresh CurrentThread dispatch must own its publication");
4305    drop(scheduler);
4306    match self.publish_host_dispatch(dispatch, dispatch_call) {
4307      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4308        CurrentThreadDispatchRequest::Accepted
4309      }
4310      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4311    }
4312  }
4313
4314  fn claim_dispatch_call<'a>(
4315    &'a self,
4316    scheduler: &mut CurrentThreadSchedulerState,
4317  ) -> CurrentThreadDispatchCall<'a> {
4318    let generation = RuntimeGenerationGuard::enter(self.generation);
4319    let active_dispatch_calls = scheduler
4320      .active_dispatch_calls
4321      .checked_add(1)
4322      .expect("CurrentThread active dispatch-call count exhausted");
4323    scheduler.active_dispatch_calls = active_dispatch_calls;
4324    CurrentThreadDispatchCall {
4325      executor: self,
4326      publication: None,
4327      _generation: generation,
4328    }
4329  }
4330
4331  fn begin_host_dispatch_publication<'a>(
4332    &'a self,
4333    scheduler: &mut CurrentThreadSchedulerState,
4334    dispatch: u64,
4335  ) -> Option<CurrentThreadDispatchCall<'a>> {
4336    // See the implementation notes in this crate's README.
4337    if let Some(publication) = scheduler
4338      .dispatch_publications
4339      .iter_mut()
4340      .find(|publication| publication.dispatch == dispatch)
4341    {
4342      publication.republish_requested = true;
4343      return None;
4344    }
4345    let mut dispatch_call = self.claim_dispatch_call(scheduler);
4346    scheduler
4347      .dispatch_publications
4348      .push(CurrentThreadDispatchPublication {
4349        dispatch,
4350        republish_requested: false,
4351      });
4352    dispatch_call.publication = Some(dispatch);
4353    Some(dispatch_call)
4354  }
4355
4356  fn publish_host_dispatch(
4357    self: &Arc<Self>,
4358    dispatch: u64,
4359    mut dispatch_call: CurrentThreadDispatchCall<'_>,
4360  ) -> CurrentThreadHostDispatchResult {
4361    let mut panic_payload = RetainedPanicPayload::default();
4362    loop {
4363      let result = match catch_unwind(AssertUnwindSafe(|| {
4364        #[cfg(all(test, napi_runtime_os_threads))]
4365        run_current_thread_dispatch_call_test_hook();
4366        let result = (self.task_dispatch)(dispatch);
4367        #[cfg(all(test, napi_runtime_os_threads))]
4368        run_current_thread_dispatch_returned_test_hook();
4369        result
4370      })) {
4371        Ok(result) => Some(result),
4372        Err(payload) => {
4373          panic_payload.capture(payload);
4374          None
4375        }
4376      };
4377
4378      let step = catch_unwind(AssertUnwindSafe(|| {
4379        let (republish, cancelling, failure, result) = {
4380          let mut scheduler = self
4381            .scheduler_idle_lock
4382            .lock()
4383            .unwrap_or_else(std::sync::PoisonError::into_inner);
4384          let index = scheduler
4385            .dispatch_publications
4386            .iter()
4387            .position(|publication| publication.dispatch == dispatch)
4388            .expect("the CurrentThread dispatch publication must remain registered");
4389          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4390          let republish = dispatch_remains_pending
4391            && !self.stop.is_stopping()
4392            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4393            && scheduler.dispatch_publications[index].republish_requested;
4394          if republish {
4395            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4396              let _ = self.task_drivers.rearm_failed_publication(failure);
4397            }
4398            scheduler.dispatch_publications[index].republish_requested = false;
4399            (true, None, None, result)
4400          } else {
4401            dispatch_call.finish_publication(&mut scheduler);
4402            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4403              #[cfg(all(test, napi_runtime_os_threads))]
4404              run_current_thread_failed_publication_retired_test_hook();
4405              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4406            } else {
4407              None
4408            };
4409            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4410              && dispatch_remains_pending)
4411              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4412              .flatten();
4413            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4414              && !dispatch_remains_pending
4415            {
4416              // A synchronous callback may already have consumed, cancelled, or
4417              // superseded this exact capability.
4418              Some(CurrentThreadHostDispatchResult::Accepted)
4419            } else {
4420              result
4421            };
4422            (false, cancelling, failure, result)
4423          }
4424        };
4425
4426        if republish {
4427          return CurrentThreadDispatchPublicationStep::Republish;
4428        }
4429        if let Some(cancelling) = cancelling {
4430          self.cancel_work_after_host_dispatch_failure(cancelling);
4431        }
4432        if let Some(failure) = failure {
4433          self.run_host_dispatch_failure(failure.action);
4434        }
4435        CurrentThreadDispatchPublicationStep::Complete(result)
4436      }));
4437
4438      match step {
4439        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4440        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4441          if panic_payload.is_some() {
4442            panic_payload.resume();
4443          }
4444          return result
4445            .expect("a CurrentThread host dispatch without a result must resume its panic");
4446        }
4447        Err(payload) => {
4448          panic_payload.capture(payload);
4449          panic_payload.resume();
4450        }
4451      }
4452    }
4453  }
4454
4455  fn claim_host_turn(
4456    self: &Arc<Self>,
4457    scheduler: &mut CurrentThreadSchedulerState,
4458  ) -> CurrentThreadHostTurn {
4459    scheduler.active_host_turns = scheduler
4460      .active_host_turns
4461      .checked_add(1)
4462      .expect("CurrentThread active host-turn count exhausted");
4463    self.draining.store(true, Ordering::Release);
4464    CurrentThreadHostTurn {
4465      executor: Arc::clone(self),
4466      draining: true,
4467      _generation: RuntimeGenerationGuard::enter(self.generation),
4468    }
4469  }
4470
4471  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4472    let mut scheduler = self
4473      .scheduler_idle_lock
4474      .lock()
4475      .unwrap_or_else(std::sync::PoisonError::into_inner);
4476    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4477      return None;
4478    }
4479    if dispatch == 0
4480      || self
4481        .dispatch_pending
4482        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4483        .is_err()
4484    {
4485      return None;
4486    }
4487    #[cfg(all(test, napi_runtime_os_threads))]
4488    run_current_thread_dispatch_claim_test_hook();
4489    let _ =
4490      self
4491        .recovery_dispatch
4492        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4493    Some(self.claim_host_turn(&mut scheduler))
4494  }
4495
4496  #[cfg(test)]
4497  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4498    let mut scheduler = self
4499      .scheduler_idle_lock
4500      .lock()
4501      .unwrap_or_else(std::sync::PoisonError::into_inner);
4502    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4503      return None;
4504    }
4505    Some(self.claim_host_turn(&mut scheduler))
4506  }
4507
4508  #[cfg(test)]
4509  fn drive_host_turn(self: &Arc<Self>) {
4510    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4511    let host_turn = if dispatch == 0 {
4512      self.try_admit_host_turn_without_dispatch()
4513    } else {
4514      self.try_admit_host_turn(dispatch)
4515    };
4516    if let Some(mut host_turn) = host_turn {
4517      host_turn.drive();
4518    }
4519  }
4520
4521  #[cfg(test)]
4522  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4523    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4524      host_turn.drive();
4525    }
4526  }
4527
4528  /// Drive a host turn whose scheduler role was claimed while the controller
4529  /// still held the lifecycle mutex. See
4530  /// the implementation notes in this crate's README.
4531  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4532    // A host turn is the continuation a terminal failure on a waker's stack
4533    // relies on: cancel the work it moved aside before polling anything new.
4534    self.retire_rejected_work_off_waker_stack();
4535    #[cfg(napi_runtime_os_threads)]
4536    let mut runnable_streak = 0usize;
4537    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4538      #[cfg(napi_runtime_os_threads)]
4539      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4540        runnable_streak = 0;
4541        if self.run_one_blocking() {
4542          continue;
4543        }
4544      }
4545      if self.drain_one() {
4546        #[cfg(napi_runtime_os_threads)]
4547        {
4548          runnable_streak += 1;
4549        }
4550        continue;
4551      }
4552      #[cfg(napi_runtime_os_threads)]
4553      if self.run_one_blocking() {
4554        runnable_streak = 0;
4555        continue;
4556      }
4557      break;
4558    }
4559    // A bounded turn releases queue-drain exclusivity before publishing its
4560    // continuation, but remains an active host turn until that publication
4561    // returns. Shutdown waits for the latter lifecycle role, not merely the
4562    // former exclusivity bit.
4563    host_turn.release_draining();
4564    self.request_drain_if_work_remains();
4565  }
4566
4567  fn request_drain_if_queued(self: &Arc<Self>) {
4568    self.retire_rejected_work_off_waker_stack();
4569    if !self.has_serviceable_work() {
4570      return;
4571    }
4572    let mut scheduler = self.lock_scheduler_for_publication();
4573    if self.stop.is_stopping()
4574      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4575      || self.draining.load(Ordering::Acquire)
4576    {
4577      return;
4578    }
4579    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4580    if dispatch == 0 {
4581      drop(scheduler);
4582      self.request_drain_if_work_remains();
4583      return;
4584    }
4585
4586    // A newly registered host joins the existing all-host race through its own
4587    // delivery token. Reusing the executor capability preserves every other
4588    // host's accepted attempt and lets old registration tokens fail closed.
4589    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4590      return;
4591    };
4592    drop(scheduler);
4593    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4594  }
4595
4596  fn retire_unavailable_host_dispatch_locked(
4597    &self,
4598    dispatch: u64,
4599  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4600    if self
4601      .dispatch_pending
4602      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4603      .is_err()
4604    {
4605      return None;
4606    }
4607    if self
4608      .recovery_dispatch
4609      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4610      .is_ok()
4611    {
4612      self.begin_failed_dispatch_cancellation()
4613    } else {
4614      None
4615    }
4616  }
4617
4618  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4619    // See the implementation notes in this crate's README.
4620    let failure = {
4621      let mut scheduler = self
4622        .scheduler_idle_lock
4623        .lock()
4624        .unwrap_or_else(std::sync::PoisonError::into_inner);
4625      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4626    };
4627    if let Some(failure) = failure {
4628      self.run_host_dispatch_failure(failure.action);
4629    }
4630    self.retire_rejected_work_off_waker_stack();
4631  }
4632
4633  fn begin_host_dispatch_failure_locked<'a>(
4634    &'a self,
4635    scheduler: &mut CurrentThreadSchedulerState,
4636    failure: CurrentThreadTaskDispatchFailure,
4637  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4638    if !self.task_drivers.is_current_failure(failure) {
4639      return None;
4640    }
4641    let registry_cancellation =
4642      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4643    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4644    Some(CurrentThreadDispatchFailureRecovery {
4645      action,
4646      _registry_cancellation: registry_cancellation,
4647    })
4648  }
4649
4650  fn begin_host_dispatch_failure_transition_locked<'a>(
4651    &'a self,
4652    scheduler: &mut CurrentThreadSchedulerState,
4653    dispatch: u64,
4654  ) -> CurrentThreadDispatchFailureAction<'a> {
4655    if dispatch == 0
4656      || self
4657        .dispatch_pending
4658        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4659        .is_err()
4660    {
4661      return CurrentThreadDispatchFailureAction::None;
4662    }
4663    #[cfg(all(test, napi_runtime_os_threads))]
4664    run_current_thread_dispatch_recovery_test_hook();
4665
4666    if self
4667      .recovery_dispatch
4668      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4669      .is_ok()
4670    {
4671      // The one fresh-turn replacement failed too. Polling inline here would
4672      // re-enter the host callback, while another retry could spin forever.
4673      // Claim terminal cancellation before allowing another publication.
4674      return self.begin_failed_dispatch_cancellation().map_or(
4675        CurrentThreadDispatchFailureAction::None,
4676        CurrentThreadDispatchFailureAction::Cancel,
4677      );
4678    }
4679
4680    if self.stop.is_stopping()
4681      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4682      || self.draining.load(Ordering::Acquire)
4683      || !self.has_serviceable_work()
4684    {
4685      return CurrentThreadDispatchFailureAction::None;
4686    }
4687
4688    // Consume the failed capability and reserve its sole replacement while
4689    // publication is excluded. A newly registered host can only join this
4690    // tagged replacement, never publish an untagged dispatch in between.
4691    let replacement = next_current_thread_dispatch_id();
4692    self.recovery_dispatch.store(replacement, Ordering::Release);
4693    self.dispatch_pending.store(replacement, Ordering::Release);
4694    let dispatch_call = self
4695      .begin_host_dispatch_publication(scheduler, replacement)
4696      .expect("a reserved CurrentThread replacement must own its publication");
4697    CurrentThreadDispatchFailureAction::Publish {
4698      dispatch: replacement,
4699      call: dispatch_call,
4700    }
4701  }
4702
4703  #[cfg(test)]
4704  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4705    let failure = {
4706      let mut scheduler = self
4707        .scheduler_idle_lock
4708        .lock()
4709        .unwrap_or_else(std::sync::PoisonError::into_inner);
4710      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4711    };
4712    self.run_host_dispatch_failure(failure);
4713  }
4714
4715  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4716    match failure {
4717      CurrentThreadDispatchFailureAction::None => {}
4718      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4719        self.cancel_work_after_host_dispatch_failure(cancelling);
4720      }
4721      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4722        let _ = self.publish_host_dispatch(dispatch, call);
4723      }
4724    }
4725  }
4726
4727  fn has_serviceable_work(&self) -> bool {
4728    let has_queued_runnable = {
4729      let queue = self
4730        .queue
4731        .lock()
4732        .unwrap_or_else(std::sync::PoisonError::into_inner);
4733      !queue.closed && !queue.runnables.is_empty()
4734    };
4735    #[cfg(napi_runtime_os_threads)]
4736    let has_queued_blocking = self.has_serviceable_blocking_work();
4737    #[cfg(not(napi_runtime_os_threads))]
4738    let has_queued_blocking = false;
4739    has_queued_runnable || has_queued_blocking
4740  }
4741
4742  fn request_drain_if_work_remains(self: &Arc<Self>) {
4743    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4744      self.request_drain();
4745    }
4746  }
4747
4748  fn begin_failed_dispatch_cancellation(
4749    &self,
4750  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4751    if self
4752      .cancelling_failed_dispatch
4753      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4754      .is_err()
4755    {
4756      return None;
4757    }
4758    self.dispatch_pending.store(0, Ordering::Release);
4759    self.recovery_dispatch.store(0, Ordering::Release);
4760    Some(CurrentThreadDispatchCancellationGuard(self))
4761  }
4762
4763  /// Terminal failure transition. The work queued at this point (runnables
4764  /// and blocking jobs) is what the failure cancels. It is MOVED into the
4765  /// rejected lists under the queue lock, with its `queued_runnables` debit,
4766  /// and what happens next depends on the stack:
4767  ///
4768  /// * On a clean stack -- a host entry point, or the submission `spawn` made
4769  ///   on its caller's own stack -- the rejected work is dropped right here,
4770  ///   with the executor still closed so that the tasks those drops wake are
4771  ///   rejected and cancelled with it, and the executor reopens under the
4772  ///   queue lock once nothing remains. This is the observable behaviour
4773  ///   terminal cancellation always had: queued work and everything it wakes
4774  ///   settle at once, and no later host work is published. A submission whose
4775  ///   two publications both failed has no other continuation -- it produced no
4776  ///   host turn -- so cancelling here is what keeps its `JoinHandle` from
4777  ///   staying pending forever with its generation guard held.
4778  /// * On a waker's stack (reached through `schedule`, whose publication the
4779  ///   host failed synchronously) nothing may be dropped: the waker's
4780  ///   producer may hold a lock the destructors re-lock. The executor reopens
4781  ///   at once instead -- staying closed would leave it without a continuation
4782  ///   while the driver stays registered -- and then publishes one fresh host
4783  ///   dispatch of its own (`request_rejected_work_retirement`). That turn is
4784  ///   the clean stack on which `retire_rejected_work` cancels the moved work;
4785  ///   without it a wake that lost both of its publications left no request
4786  ///   behind at all, and the moved runnables kept their `JoinHandle`s and
4787  ///   `GenerationWorkGuard`s until an unrelated entry happened to arrive.
4788  ///
4789  /// This cannot repeat unboundedly inside one `schedule`. Reaching here
4790  /// again needs a fresh publication and two more host-failed deliveries, and
4791  /// the queue is already empty once the move is done; the publications
4792  /// `request_rejected_work_retirement` makes are the only way such a fresh
4793  /// publication can start on this stack, and
4794  /// `CURRENT_THREAD_RETIRING_REJECTED_WORK` keeps a nested transition from
4795  /// publishing there -- it returns, and the frame that owns the publication
4796  /// reads what it moved aside off the executor and takes the next turn. Those
4797  /// turns stop at `REJECTED_WORK_RETIREMENT_ATTEMPTS`, so a sustained
4798  /// producer cannot turn this stack into a service loop, and are followed by
4799  /// the one offer a producer cannot inflate; see
4800  /// `request_rejected_work_retirement`.
4801  fn cancel_work_after_host_dispatch_failure(
4802    self: &Arc<Self>,
4803    cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4804  ) {
4805    let mut cancelling = Some(cancelling);
4806    // Blocking work first: the claimed flag blocks admission and claims, so
4807    // nothing can join or leave the FIFO between this take and the reopen.
4808    #[cfg(napi_runtime_os_threads)]
4809    let queued_blocking = {
4810      let mut state = self
4811        .blocking_admission
4812        .state
4813        .lock()
4814        .unwrap_or_else(std::sync::PoisonError::into_inner);
4815      state.queue.take_all()
4816    };
4817    {
4818      let mut queue = self
4819        .queue
4820        .lock()
4821        .unwrap_or_else(std::sync::PoisonError::into_inner);
4822      let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4823      // Rejected runnables carry their `queued_runnables` debit already.
4824      self
4825        .metrics
4826        .queued_runnables
4827        .fetch_sub(moved, Ordering::Relaxed);
4828      let runnables = std::mem::take(&mut queue.runnables);
4829      queue.rejected.extend(runnables);
4830      #[cfg(napi_runtime_os_threads)]
4831      queue.rejected_blocking.extend(queued_blocking);
4832      if CurrentThreadScheduleScope::is_active() {
4833        // Reopen while holding the queue lock: `schedule` consults the flag
4834        // under this same lock, so every runnable this transition rejects is
4835        // already in the list, and every later schedule is queued and
4836        // published.
4837        drop(cancelling.take());
4838        drop(queue);
4839        // The moved runnables still own their `GenerationWorkGuard`s; a
4840        // waiting shutdown retires them on its own stack.
4841        if let Some(work) = self.generation_work.load_full() {
4842          work.notify_idle_waiters();
4843        }
4844        // During normal operation there is no such waiter, and this branch
4845        // published no host delivery. Ask for the host turn that retires the
4846        // moved work: it is the only continuation this stack may leave behind.
4847        self.request_rejected_work_retirement();
4848        return;
4849      }
4850    }
4851
4852    // Clean stack: cancel now. Dropping a runnable can wake a parked task
4853    // whose schedule `schedule` then rejects into `queue.rejected` (the flag
4854    // is still set); drop those too, outside any lock, until none remain.
4855    // Reopen while holding the queue lock, so no runnable can be rejected
4856    // after the final pass.
4857    let _generation = RuntimeGenerationGuard::enter(self.generation);
4858    loop {
4859      #[cfg(napi_runtime_os_threads)]
4860      let (rejected, rejected_blocking) = {
4861        let mut queue = self
4862          .queue
4863          .lock()
4864          .unwrap_or_else(std::sync::PoisonError::into_inner);
4865        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4866          drop(cancelling.take());
4867          break;
4868        }
4869        (
4870          std::mem::take(&mut queue.rejected),
4871          std::mem::take(&mut queue.rejected_blocking),
4872        )
4873      };
4874      #[cfg(not(napi_runtime_os_threads))]
4875      let rejected = {
4876        let mut queue = self
4877          .queue
4878          .lock()
4879          .unwrap_or_else(std::sync::PoisonError::into_inner);
4880        if queue.rejected.is_empty() {
4881          drop(cancelling.take());
4882          break;
4883        }
4884        std::mem::take(&mut queue.rejected)
4885      };
4886      for runnable in rejected {
4887        let _ = catch_unwind_contained(|| drop(runnable));
4888      }
4889      #[cfg(napi_runtime_os_threads)]
4890      for job in rejected_blocking {
4891        let _ = catch_unwind_contained(|| drop(job));
4892      }
4893    }
4894  }
4895
4896  fn take_runnable(&self) -> Option<Runnable> {
4897    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
4898    // generation shutdown publishes while holding `stop.publication`. Claiming
4899    // under both locks gives each publication one exact ordering edge with the
4900    // runnable queue. The guards are released before polling user code.
4901    let scheduler = self
4902      .scheduler_idle_lock
4903      .lock()
4904      .unwrap_or_else(std::sync::PoisonError::into_inner);
4905    #[cfg(all(test, napi_runtime_os_threads))]
4906    run_current_thread_runnable_claim_test_hook();
4907    let stop_publication = self.stop.publication_guard();
4908    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4909      return None;
4910    }
4911    let runnable = {
4912      let mut queue = self
4913        .queue
4914        .lock()
4915        .unwrap_or_else(std::sync::PoisonError::into_inner);
4916      if queue.closed {
4917        None
4918      } else {
4919        queue.runnables.pop_front()
4920      }
4921    };
4922    drop(stop_publication);
4923    drop(scheduler);
4924    runnable
4925  }
4926
4927  fn drain_one(&self) -> bool {
4928    let runnable = self.take_runnable();
4929    if let Some(runnable) = runnable {
4930      #[cfg(napi_runtime_os_threads)]
4931      let _non_owner = BlockingOwnerGuard::enter(None);
4932      run_runnable(&self.metrics, runnable);
4933      true
4934    } else {
4935      false
4936    }
4937  }
4938
4939  /// Drive `future` to completion on this thread, draining queued runnables
4940  /// between polls. This loop OWNS its park site: the future is polled with
4941  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
4942  /// inspect the wake token directly instead of handing it to
4943  /// `futures::executor::block_on`'s opaque parker:
4944  ///
4945  ///   * `Pending` + queue empty + wake token pending: a self-waking future
4946  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
4947  ///     park, NOT a deadlock; panic only at the park decision, never at
4948  ///     Pending-return.
4949  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
4950  ///     deadlock -- no other thread exists that could ever deliver the wake
4951  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
4952  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
4953  ///     rescue here: its `setTimeout` relay shares this very thread, so it
4954  ///     can never fire while the park holds it -- it only selects the
4955  ///     timer-backed variant of the same certain diagnostic.
4956  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
4957  ///     `park_deadline` is armed, the park is bounded with progress-based
4958  ///     reset (see [`park_with_deadline`]) and panics typed if a full
4959  ///     window passes with zero runtime progress.
4960  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4961    let parker = Arc::new(DriverParker::default());
4962    let _stop_registration = self.stop.register(&parker);
4963    #[cfg(napi_runtime_os_threads)]
4964    let dependency = BlockOnDependencyGuard::enter(self.generation);
4965    let waker = std::task::Waker::from(Arc::clone(&parker));
4966    let mut cx = Context::from_waker(&waker);
4967    #[cfg(napi_runtime_os_threads)]
4968    let mut runnable_streak = 0usize;
4969    loop {
4970      if self.stop.is_stopping() {
4971        return BlockOnOutcome::Stopped;
4972      }
4973      #[cfg(napi_runtime_os_threads)]
4974      dependency.clear();
4975      on_thread_handoff();
4976      if future.as_mut().poll(&mut cx).is_ready() {
4977        return BlockOnOutcome::Completed;
4978      }
4979      // Shutdown must outrank queue work and self-wake permits. Otherwise an
4980      // always-self-waking future can continue before reaching the park-site
4981      // check forever while its generation work guard blocks quiescence.
4982      if self.stop.is_stopping() {
4983        return BlockOnOutcome::Stopped;
4984      }
4985      #[cfg(napi_runtime_os_threads)]
4986      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4987        runnable_streak = 0;
4988        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4989          continue;
4990        }
4991      }
4992      if self.drain_one() {
4993        #[cfg(napi_runtime_os_threads)]
4994        {
4995          runnable_streak += 1;
4996        }
4997        continue;
4998      }
4999      #[cfg(napi_runtime_os_threads)]
5000      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
5001        runnable_streak = 0;
5002        continue;
5003      }
5004      #[cfg(napi_runtime_os_threads)]
5005      {
5006        runnable_streak = 0;
5007      }
5008      // PARK DECISION: the future is Pending and the queue is empty. A token
5009      // stored during the poll (or by a racing thread) means a wake is
5010      // already pending: consume it and re-poll instead of parking.
5011      if parker.consume_permit() {
5012        continue;
5013      }
5014      // Threadless timer invariant: on a THREADLESS build even a pending
5015      // HOST timer is NOT a wake-token source -- the host event loop's
5016      // `setTimeout` relay can only run on the very thread that is about to
5017      // park, so it can never fire while `block_on` holds it. EVERY threadless
5018      // park at this decision is therefore provably dead; a pending timer only
5019      // selects the more precise diagnostic. Falling through instead would hit
5020      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
5021      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
5022      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
5023      // below stays: it serves the THREADED CT flavor, where the host loop
5024      // genuinely runs concurrently.)
5025      if self.threadless {
5026        if self.host_timers.has_pending() {
5027          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
5028        }
5029        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
5030      }
5031      match self.park_deadline {
5032        // A wake landing between `consume_permit` above and the park is
5033        // stored as the parker's permit, so `park` returns immediately --
5034        // never lost.
5035        None => parker.park(),
5036        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
5037          DeadlineParkOutcome::Woken => {}
5038          DeadlineParkOutcome::Expired(armed) => {
5039            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
5040            // a wake can land in the instructions after the window
5041            // closed, and the fingerprint read inside `park_with_deadline`
5042            // is stale by now. Re-check both before declaring death; a hit
5043            // means the park was healthy -- continue the loop (re-poll,
5044            // drain, fresh full window on the next park). A LIVE host-timer
5045            // wait also vetoes, re-checked LAST -- arming a host timer bumps
5046            // no progress
5047            // counter, so it is invisible to the fingerprint read: waiting
5048            // out a host timer with zero progress is legitimate, its wake is
5049            // scheduled on the host loop. `deadline` doubles as the grace
5050            // for host firing lag; a timer past due by more than one full
5051            // window without firing is the frozen-JS-loop signature this
5052            // detection exists for, so it stops vetoing.
5053            if parker.consume_permit()
5054              || self.metrics.progress_fingerprint() != armed
5055              || self.host_timers.has_live_wait(deadline)
5056            {
5057              continue;
5058            }
5059            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
5060          }
5061        },
5062      }
5063    }
5064  }
5065
5066  fn begin_shutdown(&self) {
5067    let _generation = RuntimeGenerationGuard::enter(self.generation);
5068    self.stop.begin_shutdown();
5069    self.close_blocking_admission();
5070    let (queued, rejected) = {
5071      let mut queue = self
5072        .queue
5073        .lock()
5074        .unwrap_or_else(std::sync::PoisonError::into_inner);
5075      queue.closed = true;
5076      #[cfg(napi_runtime_os_threads)]
5077      let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
5078      let rejected = std::mem::take(&mut queue.rejected);
5079      #[cfg(napi_runtime_os_threads)]
5080      let rejected = (rejected, rejected_blocking);
5081      (std::mem::take(&mut queue.runnables), rejected)
5082    };
5083    self.dispatch_pending.store(0, Ordering::Release);
5084    self.recovery_dispatch.store(0, Ordering::Release);
5085    self.task_drivers.retire_for_shutdown();
5086    for runnable in queued {
5087      self
5088        .metrics
5089        .queued_runnables
5090        .fetch_sub(1, Ordering::Relaxed);
5091      // Dropping the last runnable cancels its detached async-task and retires
5092      // the generation guard. Isolate user future destructors from shutdown.
5093      let _ = catch_unwind_contained(|| drop(runnable));
5094    }
5095    // Rejected work (already unaccounted above) is cancelled by whichever
5096    // side takes it first: this one, or a host entry that got there earlier.
5097    #[cfg(napi_runtime_os_threads)]
5098    let (rejected, rejected_blocking) = rejected;
5099    for runnable in rejected {
5100      let _ = catch_unwind_contained(|| drop(runnable));
5101    }
5102    #[cfg(napi_runtime_os_threads)]
5103    for job in rejected_blocking {
5104      let _ = catch_unwind_contained(|| drop(job));
5105    }
5106    self.host_timers.shutdown();
5107  }
5108
5109  fn wait_until_scheduler_idle(&self) {
5110    // Registered work has retired by now on the backend path; this catches
5111    // rejections from unregistered tasks (tests) before the executor goes.
5112    self.retire_rejected_work();
5113    let mut idle = self
5114      .scheduler_idle_lock
5115      .lock()
5116      .unwrap_or_else(std::sync::PoisonError::into_inner);
5117    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5118      idle = self
5119        .scheduler_idle
5120        .wait(idle)
5121        .unwrap_or_else(std::sync::PoisonError::into_inner);
5122    }
5123    debug_assert!(
5124      idle.dispatch_publications.is_empty(),
5125      "an idle CurrentThread scheduler must not retain dispatch publications"
5126    );
5127  }
5128}
5129
5130// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
5131// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
5132// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
5133// cooperatively instead of parking the worker. Carrying the executor id (not a
5134// bare bool) keeps this marker scoped exactly like
5135// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
5136// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
5137// none of B's accounting, so it parks like any foreign-thread caller rather
5138// than driving B's queues as an unaccounted within-cap driver. Under the single
5139// global executor used in production these ids always match, so this is a
5140// defensive scoping gate (consistent with the blocking-owner machinery), not a
5141// reachable production change.
5142#[cfg(napi_runtime_os_threads)]
5143thread_local! {
5144  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5145  /// Identifies the dedicated timer thread for lifecycle self-wait
5146  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
5147  /// timer thread must never become a cooperative Rayon scheduler driver.
5148  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5149}
5150
5151// Identifies scheduler-driver frames on a pool worker. Unlike
5152// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
5153// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
5154// must not place work in the per-thread LIFO slot because no drain frame may
5155// remain to consume it after the Rayon job returns.
5156#[cfg(napi_runtime_os_threads)]
5157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5158struct SchedulerDriverRole {
5159  executor_id: u64,
5160  can_run_blocking: bool,
5161}
5162
5163#[cfg(napi_runtime_os_threads)]
5164thread_local! {
5165  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5166    const { std::cell::Cell::new(None) };
5167}
5168
5169// Per-worker LIFO slot (tokio-style): holds at most ONE
5170// runnable that was scheduled FROM this pool worker (typically a task the
5171// currently running runnable just woke). It is popped by this same thread's
5172// next drain/run_one iteration BEFORE the shared FIFO, so the hot
5173// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
5174// wake and no drainer spawn. Tagged with the executor id -- the same scoping
5175// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
5176// drain/run_one/flush of the SAME executor may pop it.
5177//
5178// STRANDING INVARIANT: slot work is invisible to every other
5179// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
5180// owning thread MUST either run its slot entry or flush it to the shared FIFO
5181// before it stops draining -- see the flush calls at `drain`'s returns, the
5182// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
5183// unwinds. A runnable left here while the thread returns to rayon would be a
5184// silently lost task and a `queued_runnables` leak.
5185#[cfg(napi_runtime_os_threads)]
5186thread_local! {
5187  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5188    const { std::cell::Cell::new(None) };
5189}
5190
5191#[cfg(napi_runtime_os_threads)]
5192struct OnPoolWorkerGuard {
5193  previous_worker: Option<u64>,
5194  previous_driver: Option<SchedulerDriverRole>,
5195}
5196
5197#[cfg(napi_runtime_os_threads)]
5198impl OnPoolWorkerGuard {
5199  // Save the previous marker and install `id`, so nested/re-entrant drains
5200  // (possibly from different executors on the same thread) restore the exact
5201  // prior executor id on drop instead of unconditionally clearing it.
5202  fn enter(id: u64) -> Self {
5203    Self::enter_with_role(id, true)
5204  }
5205
5206  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5207    Self {
5208      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5209      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5210        flag.replace(Some(SchedulerDriverRole {
5211          executor_id: id,
5212          can_run_blocking,
5213        }))
5214      }),
5215    }
5216  }
5217}
5218
5219#[cfg(napi_runtime_os_threads)]
5220impl Drop for OnPoolWorkerGuard {
5221  fn drop(&mut self) {
5222    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5223    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5224  }
5225}
5226
5227// Identifies one specific counted-blocking owner frame on one specific executor.
5228//
5229// `executor_id` is assigned once per executor (so a stale token left on a
5230// thread by a shut-down executor can never authorize work on a replacement);
5231// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
5232// half of dependency lending: the exact job half comes from the pending
5233// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
5234// runnable driver owns no frame, so it must respect normal blocking admission.
5235//
5236// CurrentThread uses the same identity to distinguish same-stack lane borrowing
5237// from cross-driver contention. MultiThread additionally reserves active owner
5238// frames while an otherwise-idle cooperative driver reuses their admitted lane.
5239#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5240struct BlockingOwnerToken {
5241  executor_id: u64,
5242  frame: u64,
5243}
5244
5245#[cfg(napi_runtime_os_threads)]
5246enum OwnedBlockingOutcome {
5247  Ineligible,
5248  OwnerBusy(BlockingOwnerToken),
5249  ReservationReleased {
5250    owner: BlockingOwnerToken,
5251    ran: bool,
5252  },
5253}
5254
5255// Process-global owner-frame source for per-frame dependency-lending
5256// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
5257static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5258#[cfg(napi_runtime_os_threads)]
5259static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5260
5261// The owner frame (if any) currently running a counted blocking closure on THIS
5262// thread. The exact frame identity scopes any over-cap dependency lending.
5263thread_local! {
5264  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5265    const { std::cell::Cell::new(None) };
5266}
5267
5268// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
5269// running a counted blocking closure, or with `None` while a cooperative driver
5270// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
5271// runnable an owner happens to drive via `run_one` is a logical non-owner and
5272// must not inherit the owner's over-cap privilege). Restored on drop, so the
5273// pattern is panic-safe even though `run_runnable` already catches panics.
5274#[cfg(napi_runtime_os_threads)]
5275struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5276
5277#[cfg(napi_runtime_os_threads)]
5278impl BlockingOwnerGuard {
5279  fn enter(token: Option<BlockingOwnerToken>) -> Self {
5280    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5281  }
5282}
5283
5284#[cfg(napi_runtime_os_threads)]
5285impl Drop for BlockingOwnerGuard {
5286  fn drop(&mut self) {
5287    BLOCKING_OWNER.with(|cell| cell.set(self.0));
5288  }
5289}
5290
5291#[cfg(napi_runtime_os_threads)]
5292#[derive(Default)]
5293struct ActiveBlockingOwners {
5294  // `None` is available; `Some(id)` is reserved by exactly that lending
5295  // transfer. The identity prevents a delayed stale drop from releasing a
5296  // newer reservation of the same owner frame.
5297  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5298}
5299
5300#[cfg(napi_runtime_os_threads)]
5301impl ActiveBlockingOwners {
5302  fn register(&self, owner: BlockingOwnerToken) {
5303    let previous = self
5304      .owners
5305      .lock()
5306      .unwrap_or_else(std::sync::PoisonError::into_inner)
5307      .insert(owner, None);
5308    debug_assert!(previous.is_none());
5309  }
5310
5311  fn unregister(&self, owner: BlockingOwnerToken) {
5312    let removed = self
5313      .owners
5314      .lock()
5315      .unwrap_or_else(std::sync::PoisonError::into_inner)
5316      .remove(&owner);
5317    debug_assert!(removed.is_some());
5318  }
5319
5320  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5321    let mut owners = self
5322      .owners
5323      .lock()
5324      .unwrap_or_else(std::sync::PoisonError::into_inner);
5325    let reservation = owners.get_mut(&owner)?;
5326    if reservation.is_some() {
5327      return None;
5328    }
5329    let id = next_unique_id(
5330      &NEXT_OWNER_RESERVATION,
5331      "blocking owner reservation id space exhausted",
5332    );
5333    *reservation = Some(id);
5334    Some(id)
5335  }
5336
5337  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5338    let mut owners = self
5339      .owners
5340      .lock()
5341      .unwrap_or_else(std::sync::PoisonError::into_inner);
5342    if let Some(current) = owners.get_mut(&owner)
5343      && *current == Some(reservation)
5344    {
5345      *current = None;
5346    }
5347  }
5348
5349  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5350    self
5351      .owners
5352      .lock()
5353      .unwrap_or_else(std::sync::PoisonError::into_inner)
5354      .get(&owner)
5355      .is_some_and(Option::is_none)
5356  }
5357}
5358
5359#[cfg(napi_runtime_os_threads)]
5360struct ActiveBlockingOwnerGuard<'a> {
5361  owners: &'a ActiveBlockingOwners,
5362  owner: BlockingOwnerToken,
5363}
5364
5365#[cfg(napi_runtime_os_threads)]
5366impl<'a> ActiveBlockingOwnerGuard<'a> {
5367  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5368    owners.register(owner);
5369    Self { owners, owner }
5370  }
5371}
5372
5373#[cfg(napi_runtime_os_threads)]
5374impl Drop for ActiveBlockingOwnerGuard<'_> {
5375  fn drop(&mut self) {
5376    self.owners.unregister(self.owner);
5377  }
5378}
5379
5380#[cfg(napi_runtime_os_threads)]
5381struct OwnerLaneReservation<'a> {
5382  owners: &'a ActiveBlockingOwners,
5383  owner: BlockingOwnerToken,
5384  reservation: u64,
5385}
5386
5387#[cfg(napi_runtime_os_threads)]
5388impl<'a> OwnerLaneReservation<'a> {
5389  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5390    let reservation = owners.reserve(owner)?;
5391    Some(Self {
5392      owners,
5393      owner,
5394      reservation,
5395    })
5396  }
5397}
5398
5399#[cfg(napi_runtime_os_threads)]
5400impl Drop for OwnerLaneReservation<'_> {
5401  fn drop(&mut self) {
5402    self.owners.release(self.owner, self.reservation);
5403  }
5404}
5405
5406// A blocking job queued for the pool. Its stable id is copied into the returned
5407// JoinHandle and propagated through async JoinHandle dependencies, allowing a
5408// saturated owner to run exactly the job its nested block_on awaits.
5409#[cfg(napi_runtime_os_threads)]
5410type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5411
5412#[cfg(napi_runtime_os_threads)]
5413struct QueuedBlocking {
5414  id: BlockingJobId,
5415  run: BlockingClosure,
5416}
5417
5418#[cfg(napi_runtime_os_threads)]
5419struct BlockingQueueNode {
5420  previous: Option<BlockingJobId>,
5421  next: Option<BlockingJobId>,
5422  run: Box<dyn FnOnce() + Send + 'static>,
5423}
5424
5425#[cfg(napi_runtime_os_threads)]
5426struct BlockingQueue {
5427  closed: bool,
5428  head: Option<BlockingJobId>,
5429  tail: Option<BlockingJobId>,
5430  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5431}
5432
5433#[cfg(napi_runtime_os_threads)]
5434impl BlockingQueue {
5435  fn push(&mut self, job: QueuedBlocking) {
5436    let previous = self.tail;
5437    let replaced = self.jobs.insert(
5438      job.id,
5439      BlockingQueueNode {
5440        previous,
5441        next: None,
5442        run: job.run,
5443      },
5444    );
5445    debug_assert!(replaced.is_none());
5446    if let Some(previous) = previous {
5447      self
5448        .jobs
5449        .get_mut(&previous)
5450        .expect("blocking queue tail must be indexed")
5451        .next = Some(job.id);
5452    } else {
5453      self.head = Some(job.id);
5454    }
5455    self.tail = Some(job.id);
5456  }
5457
5458  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5459    self.head.and_then(|id| self.remove(id))
5460  }
5461
5462  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5463    let node = self.jobs.remove(&id)?;
5464    if let Some(previous) = node.previous {
5465      self
5466        .jobs
5467        .get_mut(&previous)
5468        .expect("blocking queue predecessor must be indexed")
5469        .next = node.next;
5470    } else {
5471      self.head = node.next;
5472    }
5473    if let Some(next) = node.next {
5474      self
5475        .jobs
5476        .get_mut(&next)
5477        .expect("blocking queue successor must be indexed")
5478        .previous = node.previous;
5479    } else {
5480      self.tail = node.previous;
5481    }
5482    Some(node.run)
5483  }
5484
5485  fn is_empty(&self) -> bool {
5486    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5487    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5488    self.jobs.is_empty()
5489  }
5490
5491  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5492    self.head = None;
5493    self.tail = None;
5494    std::mem::take(&mut self.jobs)
5495      .into_values()
5496      .map(|node| node.run)
5497      .collect()
5498  }
5499}
5500
5501#[cfg(napi_runtime_os_threads)]
5502struct WorkerLifecycle {
5503  remaining: Mutex<usize>,
5504  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5505}
5506
5507#[cfg(napi_runtime_os_threads)]
5508impl WorkerLifecycle {
5509  fn new(worker_threads: usize) -> Arc<Self> {
5510    Arc::new(Self {
5511      remaining: Mutex::new(worker_threads),
5512      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5513    })
5514  }
5515
5516  fn retain(&self, handle: std::thread::JoinHandle<()>) {
5517    self
5518      .handles
5519      .lock()
5520      .unwrap_or_else(std::sync::PoisonError::into_inner)
5521      .as_mut()
5522      .expect("runtime worker handles already joined")
5523      .push(handle);
5524  }
5525
5526  fn worker_exited(&self) {
5527    let mut remaining = self
5528      .remaining
5529      .lock()
5530      .unwrap_or_else(std::sync::PoisonError::into_inner);
5531    *remaining = remaining
5532      .checked_sub(1)
5533      .expect("runtime worker exit count underflow");
5534  }
5535
5536  fn wait_for_all_workers(&self) {
5537    let handles = self
5538      .handles
5539      .lock()
5540      .unwrap_or_else(std::sync::PoisonError::into_inner)
5541      .take()
5542      .expect("runtime worker handles already joined");
5543    for handle in handles {
5544      if let Err(payload) = crate::sync::join(handle) {
5545        discard_panic_payload(payload);
5546      }
5547    }
5548  }
5549
5550  #[cfg(test)]
5551  fn remaining(&self) -> usize {
5552    *self
5553      .remaining
5554      .lock()
5555      .unwrap_or_else(std::sync::PoisonError::into_inner)
5556  }
5557}
5558
5559#[cfg(napi_runtime_os_threads)]
5560struct MultiThreadExecutor {
5561  generation: u64,
5562  stop: Arc<GenerationStop>,
5563  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
5564  // executor can never authorize an over-cap escape on a replacement.
5565  id: u64,
5566  pool: ThreadPool,
5567  // Lock-free MPMC injector replacing the former `Mutex<VecDeque<Runnable>>`.
5568  // Invariant: every push goes through `push_to_queue_and_wake` and every
5569  // emptiness check through `has_queued_runnable` -- their paired SeqCst fences
5570  // restore the removed mutex's synchronizes-with edge (full lost-wakeup proof
5571  // at `push_to_queue_and_wake`). A raw `injector.push`/`is_empty` at a new site
5572  // silently breaks the pairing.
5573  injector: Injector<Runnable>,
5574  blocking_queue: Mutex<BlockingQueue>,
5575  active_drainers: AtomicUsize,
5576  active_blocking: AtomicUsize,
5577  // Guards the idle-waiter count. `finish_draining` runs once per drain batch
5578  // -- i.e. per task whenever the drainer catches the producer -- and used to
5579  // call `notify_all` unconditionally. On Linux `Condvar::notify_all` issues a
5580  // `futex_wake_all` SYSCALL even with an empty waiter set, and the only waiter
5581  // site in this crate (`wait_until_scheduler_idle`) is reachable only from
5582  // shutdown/restart. Counting waiters under this same mutex lets the steady
5583  // state skip the syscall entirely.
5584  scheduler_idle_lock: Mutex<usize>,
5585  scheduler_idle: Condvar,
5586  worker_lifecycle: Arc<WorkerLifecycle>,
5587  // Registry of pool workers currently parked inside a re-entrant cooperative
5588  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
5589  // has a fixed number of OS threads, so a parked worker cannot rely on
5590  // `ensure_drainer` spawning a replacement (there is no free thread to run
5591  // it) -- it must wake and run the work itself. Future-wakes do NOT go
5592  // through this registry: each driver's own `DriverParker` is its awaited
5593  // future's waker (see the wake-domain note at `DriverParker`).
5594  parked_drivers: ParkedDrivers,
5595  active_blocking_owners: ActiveBlockingOwners,
5596  #[cfg(test)]
5597  owner_lending_attempts: AtomicUsize,
5598  // Linger-lifecycle observability for tests (see the idle-linger block in
5599  // `drain`): counts every linger-parker REGISTRATION into `parked_drivers`
5600  // and every idle-budget EXPIRY exit (the deadline-elapsed `break` paths,
5601  // counted AFTER the parker is deregistered, so `expiries` rising implies
5602  // the linger parker has permanently left the registry for that episode).
5603  // Tests that must observe a lingering drainer's full register->expire
5604  // lifecycle wait on these instead of sampling the non-monotonic registry
5605  // count, which is transiently at its settled value BEFORE the linger
5606  // parker registers.
5607  #[cfg(test)]
5608  drain_linger_registrations: AtomicUsize,
5609  #[cfg(test)]
5610  drain_linger_expiries: AtomicUsize,
5611  max_drainers: usize,
5612  max_blocking: usize,
5613  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
5614  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
5615  // design because it parks for entire builds and a deadline there would
5616  // panic healthy production runs. Off unless
5617  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
5618  // `PARK_DEADLINE_ENV` into it).
5619  park_deadline: Option<Duration>,
5620  // Idle-linger budget for pool drainers (`None` = disabled); resolved from
5621  // `RuntimeOptions::drain_linger` (`ZERO` = disabled, embedder-resolved from
5622  // `DRAIN_LINGER_ENV` at addon load) at construction and ceiling-clamped
5623  // there so even direct unvalidated test constructions stay bounded.
5624  drain_linger: Option<Duration>,
5625  // Shared with the timer heap because firing removes the timer before invoking
5626  // its waker. The admission remains active until that wake has published any
5627  // resulting runnable, so a final verdict cannot observe both sources empty.
5628  deadlock_state: Arc<MultiThreadDeadlockState>,
5629  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
5630  // Cooperative parks also fire due timers after timer-bounded waits.
5631  timers: TimerHeap,
5632  metrics: Arc<RuntimeMetrics>,
5633}
5634
5635#[cfg(napi_runtime_os_threads)]
5636impl MultiThreadExecutor {
5637  /// Max work units before a drainer yields. Cooperative drivers use the same
5638  /// value as the LIFO streak cap.
5639  const RUNNABLE_BUDGET: usize = 64;
5640  /// Force one blocking admission after this many consecutive runnable polls
5641  /// when blocking capacity and queued work are both available.
5642  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5643
5644  #[cfg(test)]
5645  fn new(
5646    options: &RuntimeOptions,
5647    metrics: Arc<RuntimeMetrics>,
5648  ) -> Result<Self, RuntimeConfigError> {
5649    Self::new_for_generation(
5650      options,
5651      metrics,
5652      u64::MAX,
5653      Arc::new(GenerationStop::default()),
5654    )
5655  }
5656
5657  fn new_for_generation(
5658    options: &RuntimeOptions,
5659    metrics: Arc<RuntimeMetrics>,
5660    generation: u64,
5661    stop: Arc<GenerationStop>,
5662  ) -> Result<Self, RuntimeConfigError> {
5663    let id = next_unique_id(
5664      &NEXT_RUNTIME_EXECUTOR_ID,
5665      "async runtime executor id space exhausted",
5666    );
5667    let thread_name_prefix = options.thread_name_prefix.clone();
5668    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5669      options.park_deadline.is_some(),
5670    ));
5671    let timers = TimerHeap::new(
5672      generation,
5673      id,
5674      format!("{thread_name_prefix}-timer"),
5675      Arc::clone(&deadlock_state),
5676    );
5677    // Production options are validated before construction, so this exact
5678    // physical count is also the configured/reported count. Direct executor
5679    // unit tests may still construct a one-thread topology intentionally.
5680    let pool_threads = options.worker_threads;
5681    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5682    let exit_lifecycle = Arc::clone(&worker_lifecycle);
5683    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5684    let pool = ThreadPoolBuilder::new()
5685      .num_threads(pool_threads)
5686      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5687      .start_handler(move |_| {
5688        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5689      })
5690      .exit_handler(move |_| {
5691        IN_SCHEDULER_DRIVER.with(|driver| {
5692          if driver.get().is_some_and(|role| role.executor_id == id) {
5693            driver.set(None);
5694          }
5695        });
5696        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
5697        // run after this Rayon hook and may re-enter lifecycle APIs while the
5698        // external shutdown caller is joining this worker; retaining the
5699        // executor identity makes that re-entry fail fast instead of waiting
5700        // in Stopping and deadlocking the join.
5701        exit_lifecycle.worker_exited();
5702      })
5703      .spawn_handler(move |thread| {
5704        let mut builder = std::thread::Builder::new();
5705        if let Some(name) = thread.name() {
5706          builder = builder.name(name.to_string());
5707        }
5708        if let Some(stack_size) = thread.stack_size() {
5709          builder = builder.stack_size(stack_size);
5710        }
5711        let handle = builder.spawn(move || thread.run())?;
5712        spawn_lifecycle.retain(handle);
5713        Ok(())
5714      })
5715      .build()
5716      .map_err(|error| {
5717        worker_lifecycle.wait_for_all_workers();
5718        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5719      })?;
5720    Ok(Self {
5721      generation,
5722      stop,
5723      id,
5724      pool,
5725      injector: Injector::new(),
5726      blocking_queue: Mutex::new(BlockingQueue {
5727        closed: false,
5728        head: None,
5729        tail: None,
5730        jobs: FxHashMap::default(),
5731      }),
5732      active_drainers: AtomicUsize::new(0),
5733      active_blocking: AtomicUsize::new(0),
5734      scheduler_idle_lock: Mutex::new(0),
5735      scheduler_idle: Condvar::new(),
5736      worker_lifecycle,
5737      parked_drivers: ParkedDrivers::default(),
5738      active_blocking_owners: ActiveBlockingOwners::default(),
5739      #[cfg(test)]
5740      owner_lending_attempts: AtomicUsize::new(0),
5741      #[cfg(test)]
5742      drain_linger_registrations: AtomicUsize::new(0),
5743      #[cfg(test)]
5744      drain_linger_expiries: AtomicUsize::new(0),
5745      max_drainers: pool_threads,
5746      max_blocking: options.max_blocking_tasks,
5747      park_deadline: options.park_deadline,
5748      drain_linger: {
5749        // Defensive re-clamp: production options were already clamped by
5750        // `RuntimeOptions::validate`, but direct executor unit tests construct
5751        // from unvalidated options.
5752        let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5753        (!budget.is_zero()).then_some(budget)
5754      },
5755      deadlock_state,
5756      timers,
5757      metrics,
5758    })
5759  }
5760
5761  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5762    self.deadlock_state.begin_admission()
5763  }
5764
5765  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5766    self.deadlock_state.try_begin_verdict()
5767  }
5768
5769  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5770    // See the implementation notes in this crate's README.
5771    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5772    self.parked_drivers.wake_one_owner_dependency(owner)
5773  }
5774
5775  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5776    let owner = reservation.owner;
5777    // Keep release and its targeted handoff in one detector-visible critical
5778    // section. Unwind drops the mutex after the lane is already available, so
5779    // a later verdict rechecks that availability even if selection panicked.
5780    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5781    drop(reservation);
5782    self.parked_drivers.wake_one_owner_dependency(owner)
5783  }
5784
5785  fn schedule(self: &Arc<Self>, runnable: Runnable) {
5786    let mut admission = self.begin_deadlock_admission();
5787    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
5788    // still "queued" until popped (`runnable_started` balances it either way).
5789    self.metrics.runnable_scheduled();
5790    // LIFO fast path: a runnable scheduled from a pool
5791    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
5792    // shared FIFO -- this thread pops it before its next FIFO look, so no
5793    // queue mutex, no wake, no drainer spawn.
5794    //
5795    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
5796    // on the stack (drain/run_one loop iterations, cooperative polls). It is
5797    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
5798    // frame including `blocking()` calls, but the slot is only popped after
5799    // the closure returns -- so a closure that spawns and then synchronously
5800    // waits on the child would deadlock with the child slotted (no wake, no
5801    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
5802    // while straight-line blocking-closure code runs (drain/run_one blocking
5803    // branches, the over-cap escape) and `None` while runnables run (the
5804    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
5805    // slot and take the FIFO+wake path whenever an owner frame is ambient.
5806    // (A cooperative poll entered FROM a blocking closure also carries the
5807    // closure's token and loses the fast path -- a rare, conservative,
5808    // correctness-first trade.)
5809    if IN_SCHEDULER_DRIVER
5810      .with(std::cell::Cell::get)
5811      .is_some_and(|role| role.executor_id == self.id)
5812      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5813    {
5814      match LIFO_SLOT.with(std::cell::Cell::take) {
5815        None => {
5816          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5817          if let Some(admission) = admission.as_mut() {
5818            admission.mark_published();
5819          }
5820          return;
5821        }
5822        // Same-executor occupant: the NEWEST runnable takes the slot (it is
5823        // the hottest); the displaced occupant falls back to the shared FIFO
5824        // with a normal wake so other workers can pick it up.
5825        Some((id, displaced)) if id == self.id => {
5826          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5827          self.push_to_queue_and_wake(displaced);
5828          if let Some(admission) = admission.as_mut() {
5829            admission.mark_published();
5830          }
5831          return;
5832        }
5833        // Foreign-executor occupant (defensive: requires a thread acting as a
5834        // worker of two executors, e.g. under a test-installed marker; the
5835        // production process has one global executor). We must not flush
5836        // another executor's runnable onto OUR queue, so leave it in place --
5837        // its own executor's drain on this thread pops or flushes it -- and
5838        // route the new runnable to the FIFO.
5839        Some(foreign) => {
5840          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5841        }
5842      }
5843    }
5844    self.push_to_queue_and_wake(runnable);
5845    if let Some(admission) = admission.as_mut() {
5846      admission.mark_published();
5847    }
5848  }
5849
5850  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
5851  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
5852  /// already happened at their original schedule).
5853  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5854    self.injector.push(runnable);
5855    // The former queue mutex provided the synchronizes-with edge the
5856    // lost-wakeup proof depends on (see `wake_one`). With a lock-free injector
5857    // that edge is gone, so restore it explicitly: a SeqCst fence between the
5858    // push and BOTH wake mechanisms (`wake_one`'s SeqCst `count` load and
5859    // `ensure_drainer`'s `active_drainers` load), mirrored by the SeqCst fence
5860    // in `has_queued_runnable` before the waiter/drainer re-check. This is the
5861    // Dekker ordering rayon-core's injector+sleep protocol uses; without both
5862    // fences a worker can park next to stealable work.
5863    std::sync::atomic::fence(Ordering::SeqCst);
5864    self.wake_for_new_work();
5865  }
5866
5867  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
5868  /// parked cooperative driver; when none is parked, fall back to spawning a
5869  /// drainer. A single wake suffices because the woken driver re-checks
5870  /// every work source in its loop before re-parking, and a wake it absorbs
5871  /// without consuming is handed on at `cooperative_block_on` exit (miss
5872  /// compensation). MUST be called AFTER the work was pushed
5873  /// under its queue mutex -- see the lost-wakeup argument at
5874  /// [`ParkedDrivers::wake_one`].
5875  fn wake_for_new_work(self: &Arc<Self>) {
5876    if !self.parked_drivers.wake_one() {
5877      self.ensure_drainer();
5878    }
5879  }
5880
5881  fn wake_for_blocking_work(self: &Arc<Self>) {
5882    // Prefer a cooperative driver that can consume the blocking FIFO;
5883    // otherwise arm a normal Rayon drainer.
5884    if !self.parked_drivers.wake_one_blocking() {
5885      self.ensure_drainer();
5886    }
5887  }
5888
5889  #[cfg(test)]
5890  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5891  where
5892    F: FnOnce() -> T + Send + 'static,
5893    T: Send + 'static,
5894  {
5895    self.schedule_blocking_result(move || catch_unwind_join_error(function))
5896  }
5897
5898  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5899  where
5900    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5901    T: Send + 'static,
5902  {
5903    let mut admission = self.begin_deadlock_admission();
5904    let (sender, receiver) = oneshot::channel();
5905    let generation = self.generation;
5906    let dependency = BlockingDependency {
5907      job: BlockingJobId(next_unique_id(
5908        &NEXT_BLOCKING_JOB_ID,
5909        "blocking job id space exhausted",
5910      )),
5911      owner: BLOCKING_OWNER
5912        .with(std::cell::Cell::get)
5913        .filter(|owner| owner.executor_id == self.id),
5914    };
5915    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
5916    // detector's fingerprint must be able to observe
5917    // this submission even while the job merely sits queued -- symmetric with
5918    // `runnable_scheduled` on the runnable path.
5919    self.metrics.blocking_scheduled();
5920    let queued = QueuedBlocking {
5921      id: dependency.job,
5922      run: Box::new(move || {
5923        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5924        let _ = sender.send(result);
5925      }),
5926    };
5927    let rejected = {
5928      let mut queue = self
5929        .blocking_queue
5930        .lock()
5931        .unwrap_or_else(std::sync::PoisonError::into_inner);
5932      if queue.closed {
5933        Some(queued)
5934      } else {
5935        queue.push(queued);
5936        None
5937      }
5938    };
5939    if let Some(rejected) = rejected {
5940      // Shutdown may close the queue after the controller registered this
5941      // generation work but before it reaches the executor. Drop captured
5942      // user values outside the queue lock and contain hostile destructors,
5943      // matching the queued-job shutdown path.
5944      let _generation = RuntimeGenerationGuard::enter(self.generation);
5945      let _ = catch_unwind_contained(|| drop(rejected));
5946    } else {
5947      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
5948      self.wake_for_blocking_work();
5949      if let Some(admission) = admission.as_mut() {
5950        admission.mark_published();
5951      }
5952    }
5953    JoinHandle(JoinHandleInner::Blocking {
5954      receiver,
5955      dependency,
5956      published: PublishedBlockingDependencies::default(),
5957    })
5958  }
5959
5960  fn ensure_drainer(self: &Arc<Self>) {
5961    loop {
5962      let active = self.active_drainers.load(Ordering::Acquire);
5963      if active >= self.max_drainers {
5964        return;
5965      }
5966      if self
5967        .active_drainers
5968        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5969        .is_ok()
5970      {
5971        let executor = Arc::clone(self);
5972        self.pool.spawn_fifo(move || executor.drain());
5973        return;
5974      }
5975    }
5976  }
5977
5978  fn drain(self: Arc<Self>) {
5979    // LINGER GATE: captured BEFORE the guard installs the marker below. In
5980    // production `drain` only ever runs via `pool.spawn_fifo`, i.e. on a pool
5981    // worker whose start handler already set `ON_POOL_WORKER`, so lingering is
5982    // active there; a direct test call from a foreign thread keeps the legacy
5983    // return-when-empty shape.
5984    let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5985    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
5986    // drives the queue cooperatively instead of parking the worker.
5987    let _on_pool = OnPoolWorkerGuard::enter(self.id);
5988    // Unwind backstop: never leave a runnable stranded in
5989    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
5990    // below (so its re-check can see the flushed runnable); this guard only
5991    // covers unwinds, which should be impossible (runnables and blocking
5992    // wrappers are catch_unwind'd) but must not lose a task if they happen.
5993    let _slot_backstop = LifoSlotFlushGuard(&self);
5994
5995    let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5996    // Linger state (parker + generation-stop registration), created lazily on
5997    // this frame's first idle episode and reused for the rest of the frame.
5998    let mut linger: Option<DrainLinger> = None;
5999    // Absolute exit deadline of the CURRENT idle episode, armed on the first
6000    // empty observation after work ran. Spurious wakes (timer churn, a stolen
6001    // queue wake) re-park with the REMAINING time rather than a fresh budget,
6002    // so a lingering drainer occupies its pool slot for at most
6003    // `linger_budget` past the last work unit it executed.
6004    let mut idle_deadline: Option<Instant> = None;
6005    // Absolute residence deadline of the WHOLE frame, armed on the frame's
6006    // FIRST idle observation and -- unlike `idle_deadline` -- never reset by
6007    // executed work. Without it, periodic sub-budget work re-arms the episode
6008    // deadline forever and this frame never returns its rayon worker; see
6009    // [`DRAIN_LINGER_FRAME_FACTOR`].
6010    let mut frame_deadline: Option<Instant> = None;
6011
6012    'frame: loop {
6013      let mut runnable_streak = 0usize;
6014      for _ in 0..Self::RUNNABLE_BUDGET {
6015        if self.run_one_fair(&mut runnable_streak) {
6016          idle_deadline = None;
6017          continue;
6018        }
6019        // Queue observed empty. MANDATORY flush before the shared exit AND
6020        // before any park: slot work is invisible to other threads and to
6021        // `finish_draining`'s re-check, so no path may leave this loop (or
6022        // sleep) with an occupied slot. On this path the slot was observed
6023        // empty at the top of the iteration and no user code ran since, so
6024        // the flush is defensive -- kept unconditional so every exit upholds
6025        // the stranding invariant by construction.
6026        self.flush_lifo_slot();
6027        // IDLE LINGER: instead of exiting and paying a full
6028        // spawn_fifo/rayon-idle-protocol round trip on the NEXT wave, park on
6029        // our own parker, registered in `parked_drivers` so
6030        // `wake_for_new_work`/`wake_for_blocking_work` deliver ONE targeted
6031        // wake. `active_drainers` stays counted for the whole linger: this
6032        // frame is still a live drainer, so `ensure_drainer`'s cap and the
6033        // idle-wait predicate keep their meaning.
6034        //
6035        // LOST-WAKEUP ARGUMENT (identical protocol to a cooperative driver's
6036        // park): register FIRST (the registry `count` store), THEN re-check
6037        // the shared queues (`has_queued_work_for_role`'s SeqCst fence pairs
6038        // with `push_to_queue_and_wake`'s post-push fence), THEN park -- see
6039        // [`ParkedDrivers::wake_one`]. On the timeout path, `wake_one` may
6040        // have popped this parker (counting the wake as DELIVERED, so the
6041        // producer spawned no drainer) while its `unpark` is still in flight:
6042        // the pop precedes our `deregister` on the registry mutex, so the
6043        // producer's push (made BEFORE its wake) is visible to the
6044        // post-deregister permit/queue re-checks below, which then RESUME
6045        // draining instead of exiting. Only a truly empty re-check falls
6046        // through to `finish_draining`, whose own post-decrement re-check
6047        // remains the existing final net for pushes racing the exit.
6048        let Some(budget) = linger_budget else {
6049          break 'frame;
6050        };
6051        if self.stop.is_stopping() {
6052          break 'frame;
6053        }
6054        let now = Instant::now();
6055        // FRAME RESIDENCE BOUND: exiting here (or via the min-capped park
6056        // below) funnels through the UNCHANGED finish_draining path, whose
6057        // re-check re-arms a FIFO drain via ensure_drainer/spawn_fifo when
6058        // work remains -- that re-entry through rayon's queue is exactly the
6059        // interleaving point that lets pending raw rayon work run.
6060        let frame = *frame_deadline.get_or_insert_with(|| {
6061          now
6062            + budget
6063              .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
6064              .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
6065        });
6066        let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
6067        if now >= deadline {
6068          // Deadline EXPIRY observed at the top: either a wake consumed the
6069          // rest of the episode's idle budget, or the frame residence bound
6070          // elapsed while work kept re-arming the episode deadline. Either
6071          // way this frame's lingering is over, and the linger parker of the
6072          // previous episode -- if one registered -- was already deregistered
6073          // after its park.
6074          #[cfg(test)]
6075          self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6076          break 'frame;
6077        }
6078        let state = match &linger {
6079          Some(state) => state,
6080          None => linger.insert(DrainLinger::new(&self)),
6081        };
6082        self
6083          .parked_drivers
6084          .register_with_role(&state.parker, true, None);
6085        #[cfg(test)]
6086        self
6087          .drain_linger_registrations
6088          .fetch_add(1, Ordering::SeqCst);
6089        if self.has_queued_work_for_role(true) {
6090          self.parked_drivers.deregister(&state.parker);
6091          continue 'frame;
6092        }
6093        // Stop re-check AFTER registering: `begin_shutdown` unparks every
6094        // generation-stop-registered parker (ours included, via
6095        // `DrainLinger`'s registration), so a stop publishing after this
6096        // check still cuts the park short instead of waiting out the budget.
6097        if self.stop.is_stopping() {
6098          self.parked_drivers.deregister(&state.parker);
6099          break 'frame;
6100        }
6101        let woken = state.parker.park_timeout(deadline - now);
6102        self.parked_drivers.deregister(&state.parker);
6103        if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
6104          continue 'frame;
6105        }
6106        // Idle-budget EXPIRY: the park timed out with no wake, no permit and
6107        // no queued work. Counted AFTER the deregister above, so a test
6108        // observing `drain_linger_expiries` rise knows the linger parker has
6109        // permanently left the registry for this idle episode.
6110        #[cfg(test)]
6111        self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6112        break 'frame;
6113      }
6114      // Budget exhausted: yield this worker back to rayon exactly as before
6115      // the linger existed -- `finish_draining` re-arms a FIFO drain job
6116      // BEHIND any pending rayon work (par_iter splits, nested jobs), which
6117      // is the pool-fairness property the budget exists for.
6118      break 'frame;
6119    }
6120
6121    // MANDATORY flush before `finish_draining` so its
6122    // re-check sees a slot runnable on the shared FIFO and re-arms a drainer.
6123    self.flush_lifo_slot();
6124    self.finish_draining();
6125  }
6126
6127  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
6128  /// A foreign executor's entry is left untouched (see the scoping note at
6129  /// `LIFO_SLOT`).
6130  fn pop_lifo_slot(&self) -> Option<Runnable> {
6131    LIFO_SLOT.with(|slot| match slot.take() {
6132      Some((id, runnable)) if id == self.id => Some(runnable),
6133      other => {
6134        slot.set(other);
6135        None
6136      }
6137    })
6138  }
6139
6140  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
6141  /// with a wake so any worker can pick it up. MANDATORY on every path where
6142  /// this thread stops draining this executor: `drain`'s
6143  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
6144  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
6145  /// slot while its thread leaves executor code is a silently lost task.
6146  fn flush_lifo_slot(self: &Arc<Self>) {
6147    let mut admission = self.begin_deadlock_admission();
6148    if let Some(runnable) = self.pop_lifo_slot() {
6149      self.push_to_queue_and_wake(runnable);
6150      if let Some(admission) = admission.as_mut() {
6151        admission.mark_published();
6152      }
6153    }
6154  }
6155
6156  /// Mint a fresh owner token for a counted blocking closure entering on this
6157  /// executor. Each entry gets a unique `frame` so nested owners on the same
6158  /// thread can never run each other's queued jobs over the cap.
6159  fn fresh_owner_token(&self) -> BlockingOwnerToken {
6160    BlockingOwnerToken {
6161      executor_id: self.id,
6162      frame: next_unique_id(
6163        &NEXT_BLOCKING_FRAME,
6164        "blocking owner frame id space exhausted",
6165      ),
6166    }
6167  }
6168
6169  fn finish_draining(self: &Arc<Self>) {
6170    // LIFO-slot invariant: this re-check reads only the
6171    // SHARED queues -- per-worker slots are invisible to it, and that is
6172    // sound because slot work never needs a foreign observer. A slot entry is
6173    // always either popped and run by its owning thread (drain / run_one) or
6174    // flushed to the shared FIFO by that same thread BEFORE it stops draining
6175    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
6176    // covers unwinds; cooperative parks flush defensively). No thread leaves
6177    // executor code with its slot occupied, so the exit-then-respawn window
6178    // below cannot hide slot work from `ensure_drainer`.
6179    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6180    let has_runnable = self.has_queued_runnable();
6181    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6182      && !self
6183        .blocking_queue
6184        .lock()
6185        .unwrap_or_else(std::sync::PoisonError::into_inner)
6186        .is_empty();
6187    if has_runnable || has_blocking {
6188      self.ensure_drainer();
6189    }
6190    // The lock is still taken unconditionally: it is the release edge that
6191    // publishes the `active_drainers` decrement above to a waiter that is about
6192    // to evaluate the predicate. Only the wake is now conditional, and the
6193    // waiter count is read under the very mutex the waiter must hold to
6194    // register, so a waiter is never missed. See `wait_until_scheduler_idle`.
6195    let idle = self
6196      .scheduler_idle_lock
6197      .lock()
6198      .unwrap_or_else(std::sync::PoisonError::into_inner);
6199    if *idle > 0 {
6200      self.scheduler_idle.notify_all();
6201    }
6202  }
6203
6204  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6205    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6206      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
6207      // freeze a worker that the awaited future may itself need to make
6208      // progress. Drive the queue cooperatively instead (mirrors the
6209      // CurrentThread drive loop). The id check mirrors the
6210      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
6211      // owns none of this executor's accounting, so it must take the parking
6212      // path below rather than drive these queues as an unaccounted driver.
6213      self.cooperative_block_on(future)
6214    } else {
6215      // Non-pool (e.g. napi caller) thread, or a pool worker of another
6216      // executor: park without driving queues. The runtime owns this park so
6217      // generation shutdown can wake and cancel it.
6218      self.parking_block_on(future)
6219    }
6220  }
6221
6222  fn parking_block_on(
6223    self: &Arc<Self>,
6224    mut future: Pin<&mut dyn Future<Output = ()>>,
6225  ) -> BlockOnOutcome {
6226    let parker = Arc::new(DriverParker::default());
6227    let _stop_registration = self.stop.register(&parker);
6228    let waker = Waker::from(Arc::clone(&parker));
6229    let mut cx = Context::from_waker(&waker);
6230    loop {
6231      if self.stop.is_stopping() {
6232        return BlockOnOutcome::Stopped;
6233      }
6234      on_thread_handoff();
6235      if future.as_mut().poll(&mut cx).is_ready() {
6236        return BlockOnOutcome::Completed;
6237      }
6238      if self.stop.is_stopping() {
6239        return BlockOnOutcome::Stopped;
6240      }
6241      parker.park();
6242    }
6243  }
6244
6245  fn run_runnable(
6246    self: &Arc<Self>,
6247    runnable: Runnable,
6248    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6249  ) {
6250    on_thread_handoff();
6251    // Ownership is lexical to a blocking closure. A runnable driven from that
6252    // closure must not borrow its over-cap privilege.
6253    let _non_owner = BlockingOwnerGuard::enter(None);
6254    let _active = self.metrics.runnable_started();
6255    #[cfg(test)]
6256    run_multi_thread_work_started_test_hook();
6257    // The queue/slot removal is invisible to the deadline verdict until start
6258    // accounting advances. Keep the detector gate open across exactly that gap,
6259    // but never across a user poll.
6260    drop(admission);
6261    let _ = catch_unwind_contained(|| runnable.run());
6262  }
6263
6264  fn cancel_runnable(
6265    &self,
6266    runnable: Runnable,
6267    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6268  ) {
6269    self.metrics.runnable_cancelled();
6270    // Cancellation accounting closes the dequeue gap just as start accounting
6271    // does for a runnable that will be polled.
6272    drop(admission);
6273    let _generation = RuntimeGenerationGuard::enter(self.generation);
6274    let _ = catch_unwind_contained(|| drop(runnable));
6275  }
6276
6277  fn finish_runnable_claim(
6278    self: &Arc<Self>,
6279    claim: RunnableClaim,
6280    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6281  ) -> bool {
6282    match claim {
6283      RunnableClaim::Empty => {
6284        drop(admission);
6285        false
6286      }
6287      RunnableClaim::Run(runnable) => {
6288        #[cfg(test)]
6289        run_multi_thread_work_claimed_test_hook();
6290        self.run_runnable(runnable, admission);
6291        true
6292      }
6293      RunnableClaim::Cancel(runnable) => {
6294        #[cfg(test)]
6295        run_multi_thread_work_claimed_test_hook();
6296        self.cancel_runnable(runnable, admission);
6297        true
6298      }
6299    }
6300  }
6301
6302  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6303    match runnable {
6304      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6305      Some(runnable) => RunnableClaim::Run(runnable),
6306      None => RunnableClaim::Empty,
6307    }
6308  }
6309
6310  /// Lock-free single-item steal from the shared injector, retrying the
6311  /// transient `Steal::Retry` contention state. Replaces the former
6312  /// `queue.lock().pop_front()`; `Injector` preserves FIFO order.
6313  fn steal_one(&self) -> Option<Runnable> {
6314    loop {
6315      match self.injector.steal() {
6316        Steal::Success(runnable) => return Some(runnable),
6317        Steal::Empty => return None,
6318        Steal::Retry => continue,
6319      }
6320    }
6321  }
6322
6323  /// Test-only raw FIFO enqueue: the former `queue.lock().push_back()` staging
6324  /// that tests use to seed queued work WITHOUT the accompanying wake (so no
6325  /// drainer races the manual `run_one`/`drain` calls). Keeps the post-push
6326  /// SeqCst fence from `push_to_queue_and_wake` so any wake/park interaction a
6327  /// test performs afterwards still sees the Dekker pairing documented on
6328  /// `injector`.
6329  #[cfg(test)]
6330  fn push_fifo_raw(&self, runnable: Runnable) {
6331    self.injector.push(runnable);
6332    std::sync::atomic::fence(Ordering::SeqCst);
6333  }
6334
6335  fn claim_fifo_runnable(&self) -> RunnableClaim {
6336    let stop_publication = self.stop.runnable_claim_guard();
6337    let runnable = self.steal_one();
6338    let stopping = stop_publication.is_stopping();
6339    drop(stop_publication);
6340    Self::runnable_claim(stopping, runnable)
6341  }
6342
6343  fn claim_runnable(&self) -> RunnableClaim {
6344    let stop_publication = self.stop.runnable_claim_guard();
6345    let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6346    let stopping = stop_publication.is_stopping();
6347    drop(stop_publication);
6348    Self::runnable_claim(stopping, runnable)
6349  }
6350
6351  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6352    let admission = self.begin_deadlock_admission();
6353    let claim = self.claim_fifo_runnable();
6354    self.finish_runnable_claim(claim, admission)
6355  }
6356
6357  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
6358  fn run_one_runnable(self: &Arc<Self>) -> bool {
6359    let admission = self.begin_deadlock_admission();
6360    // Same owner-clear as the FIFO-only path: a runnable from the slot must
6361    // not smuggle an owner frame's over-cap privilege.
6362    let claim = self.claim_runnable();
6363    self.finish_runnable_claim(claim, admission)
6364  }
6365
6366  fn run_one_blocking(self: &Arc<Self>) -> bool {
6367    if let Some((blocking, admission)) = self.take_blocking() {
6368      let _generation = RuntimeGenerationGuard::enter(self.generation);
6369      {
6370        let _task = self.metrics.blocking_started(true);
6371        #[cfg(test)]
6372        run_multi_thread_work_started_test_hook();
6373        // Publish start before the dequeue admission retires. The detector can
6374        // then observe either the queued job, the active admission, or progress.
6375        drop(admission);
6376        let owner = self.fresh_owner_token();
6377        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6378        let _owner = BlockingOwnerGuard::enter(Some(owner));
6379        // The queued wrapper contains panics from the user function, but
6380        // delivering its result can still drop user-owned state when the
6381        // receiver is gone. Contain that destructor boundary as well so an
6382        // unwind cannot bypass blocking-slot and drainer retirement.
6383        let _ = catch_unwind_contained(blocking);
6384      }
6385      self.active_blocking.fetch_sub(1, Ordering::Release);
6386      return true;
6387    }
6388    false
6389  }
6390
6391  /// Run one fair scheduler unit. Runnable locality remains the normal
6392  /// priority, but a continuously hot runnable stream must yield one quantum
6393  /// to the blocking FIFO.
6394  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6395    self.run_one_fair_with_exact(runnable_streak, None)
6396  }
6397
6398  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
6399  /// been satisfied, a live exact dependency must claim its owner's lane before
6400  /// unrelated FIFO blocking work can consume the last ordinary slot.
6401  fn run_one_fair_with_exact(
6402    self: &Arc<Self>,
6403    runnable_streak: &mut usize,
6404    exact_dependency: Option<&TaskDependency>,
6405  ) -> bool {
6406    let mut exact_attempted = false;
6407    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6408      *runnable_streak = 0;
6409      if let Some(dependency) = exact_dependency {
6410        exact_attempted = true;
6411        if self.try_owned_blocking_over_cap(dependency) {
6412          return true;
6413        }
6414      }
6415      if self.run_one_blocking() {
6416        return true;
6417      }
6418    }
6419    if self.run_one_runnable() {
6420      *runnable_streak += 1;
6421      return true;
6422    }
6423    if !exact_attempted
6424      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6425    {
6426      *runnable_streak = 0;
6427      return true;
6428    }
6429    if self.run_one_blocking() {
6430      *runnable_streak = 0;
6431      return true;
6432    }
6433    false
6434  }
6435
6436  #[cfg(test)]
6437  fn run_one(self: &Arc<Self>) -> bool {
6438    let mut runnable_streak = 0;
6439    self.run_one_fair(&mut runnable_streak)
6440  }
6441
6442  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
6443  /// park while the blocking cap is saturated. The dependency carries its
6444  /// exact owner frame through JoinHandle lineage. Reserving that frame
6445  /// transfers its counted lane to this otherwise-idle cooperative driver.
6446  ///
6447  /// Owner lending is lexical: the cooperative driver must carry the exact
6448  /// blocking frame whose counted lane it reuses. Rayon does not propagate
6449  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
6450  /// prove that an untagged worker belongs to the sole active owner.
6451  fn run_owned_blocking_over_cap(
6452    self: &Arc<Self>,
6453    dependency: &TaskDependency,
6454  ) -> OwnedBlockingOutcome {
6455    #[cfg(test)]
6456    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6457
6458    let Some(ambient_owner) = BLOCKING_OWNER
6459      .with(std::cell::Cell::get)
6460      .filter(|owner| owner.executor_id == self.id)
6461    else {
6462      return OwnedBlockingOutcome::Ineligible;
6463    };
6464    let Some(current) = dependency.select_for_owner(ambient_owner) else {
6465      return OwnedBlockingOutcome::Ineligible;
6466    };
6467    let admission = self.begin_deadlock_admission();
6468    let owner = current
6469      .publication
6470      .dependency
6471      .owner
6472      .expect("selected dependency must carry its owner");
6473    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6474    else {
6475      return OwnedBlockingOutcome::OwnerBusy(owner);
6476    };
6477    #[cfg(test)]
6478    run_before_owned_blocking_queue_claim_test_hook();
6479    let job = {
6480      let mut queue = self
6481        .blocking_queue
6482        .lock()
6483        .unwrap_or_else(std::sync::PoisonError::into_inner);
6484      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
6485      // admission states before consuming the dependency so a shutdown that
6486      // reached this claim first leaves the job queued for cancellation.
6487      if queue.closed || self.stop.is_stopping() {
6488        None
6489      // Claim while holding the queue lock. A concurrent clear/switch that
6490      // wins first makes this fail; once the claim succeeds, no stale snapshot
6491      // can authorize removal of the old job.
6492      } else if dependency.try_claim(&current) {
6493        queue.remove(current.publication.dependency.job)
6494      } else {
6495        None
6496      }
6497    };
6498    let ran = if let Some(job) = job {
6499      #[cfg(test)]
6500      run_multi_thread_work_claimed_test_hook();
6501      let _generation = RuntimeGenerationGuard::enter(self.generation);
6502      {
6503        let _task = self.metrics.blocking_started(false);
6504        #[cfg(test)]
6505        run_multi_thread_work_started_test_hook();
6506        // The exact job is no longer visible in the indexed FIFO. Publish its
6507        // start before allowing a deadline verdict to close the admission gate.
6508        drop(admission);
6509        let nested_owner = self.fresh_owner_token();
6510        let _active_owner =
6511          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6512        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6513        let _ = catch_unwind_contained(job);
6514      }
6515      true
6516    } else {
6517      drop(admission);
6518      false
6519    };
6520    // Every reservation release can make another exact dependency serviceable,
6521    // including failed-claim and already-removed-job exits.
6522    self.release_owner_lane_and_wake(reservation);
6523    OwnedBlockingOutcome::ReservationReleased { owner, ran }
6524  }
6525
6526  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6527    matches!(
6528      self.run_owned_blocking_over_cap(dependency),
6529      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6530    )
6531  }
6532
6533  fn service_owner_handoff(
6534    self: &Arc<Self>,
6535    parker: &DriverParker,
6536    dependency: &TaskDependency,
6537    can_run_blocking: bool,
6538  ) -> Option<bool> {
6539    let handoff_owner = parker.take_owner_handoff()?;
6540    if !can_run_blocking {
6541      self.wake_one_owner_dependency(handoff_owner);
6542      return Some(false);
6543    }
6544    match self.run_owned_blocking_over_cap(dependency) {
6545      OwnedBlockingOutcome::Ineligible => {
6546        self.wake_one_owner_dependency(handoff_owner);
6547        Some(false)
6548      }
6549      OwnedBlockingOutcome::OwnerBusy(owner) => {
6550        if owner != handoff_owner {
6551          self.wake_one_owner_dependency(handoff_owner);
6552        }
6553        Some(false)
6554      }
6555      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6556        if owner != handoff_owner {
6557          self.wake_one_owner_dependency(handoff_owner);
6558        }
6559        Some(ran)
6560      }
6561    }
6562  }
6563
6564  fn forward_owner_handoff(&self, parker: &DriverParker) {
6565    if let Some(owner) = parker.take_owner_handoff() {
6566      self.wake_one_owner_dependency(owner);
6567    }
6568  }
6569
6570  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6571    let Some(ambient_owner) = BLOCKING_OWNER
6572      .with(std::cell::Cell::get)
6573      .filter(|owner| owner.executor_id == self.id)
6574    else {
6575      return false;
6576    };
6577    self.active_blocking_owners.is_available(ambient_owner)
6578      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6579  }
6580
6581  fn has_exact_owner_work_for_role(
6582    &self,
6583    parker: &DriverParker,
6584    dependency: &TaskDependency,
6585    can_run_blocking: bool,
6586  ) -> bool {
6587    parker.has_owner_handoff()
6588      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6589  }
6590
6591  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
6592  /// of parking the worker (which would freeze one of the pool's fixed OS
6593  /// threads), it drives the awaited future while servicing queued work. When
6594  /// nothing is immediately runnable it parks on its own [`DriverParker`],
6595  /// which is woken directly by the awaited future's waker and through the
6596  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
6597  /// wake reaches this worker even when every worker is parked and
6598  /// `ensure_drainer` has no free thread to spawn a replacement.
6599  fn cooperative_block_on(
6600    self: &Arc<Self>,
6601    mut future: Pin<&mut dyn Future<Output = ()>>,
6602  ) -> BlockOnOutcome {
6603    // One parker per `block_on` frame. Nested re-entrant frames each own one:
6604    // only the innermost frame runs or parks at any moment, and an outer
6605    // frame's future-wake is stored as its parker's permit, consumed when
6606    // control returns to that frame's loop.
6607    let parker = Arc::new(DriverParker::default());
6608    let _stop_registration = self.stop.register(&parker);
6609    let dependency = BlockOnDependencyGuard::enter(self.generation);
6610    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6611    let can_run_blocking =
6612      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6613    let _exit = CooperativeDriverExitGuard {
6614      executor: self,
6615      parker: &parker,
6616      can_run_blocking,
6617    };
6618    // WAKE-DOMAIN SPLIT: the awaited future is polled with
6619    // THIS driver's own parker as its waker, so a future-wake targets exactly
6620    // the driver that awaits it -- delivered as a stored permit (skip next
6621    // park) when this driver is currently running, so it is never lost.
6622    let waker = std::task::Waker::from(Arc::clone(&parker));
6623    let mut cx = Context::from_waker(&waker);
6624    // Consecutive `run_one` successes since the last yield point: the
6625    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
6626    // it, a hot slot chain (each runnable schedules a successor into this
6627    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
6628    // loop inside `run_one` forever and the shared FIFO would never be
6629    // reached from this thread -- a deadlock on a 1-worker pool when the
6630    // awaited future depends on a FIFO task.
6631    let mut runnable_streak = 0usize;
6632    let mut work_streak = 0usize;
6633    let mut force_fifo = false;
6634    loop {
6635      if self.stop.is_stopping() {
6636        return BlockOnOutcome::Stopped;
6637      }
6638      dependency.clear();
6639      on_thread_handoff();
6640      if future.as_mut().poll(&mut cx).is_ready() {
6641        return BlockOnOutcome::Completed;
6642      }
6643      if self.stop.is_stopping() {
6644        return BlockOnOutcome::Stopped;
6645      }
6646      if self
6647        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6648        .is_some_and(|ran| ran)
6649      {
6650        continue;
6651      }
6652      if force_fifo {
6653        force_fifo = false;
6654        if self.run_one_fifo_runnable() {
6655          runnable_streak += 1;
6656          work_streak += 1;
6657          continue;
6658        }
6659      }
6660      let ran_work = if can_run_blocking {
6661        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6662      } else if self.run_one_runnable() {
6663        runnable_streak += 1;
6664        true
6665      } else {
6666        false
6667      };
6668      if ran_work {
6669        work_streak += 1;
6670        if work_streak >= Self::RUNNABLE_BUDGET {
6671          // Budget yield: move a hot slot occupant to the shared FIFO (with
6672          // a wake), so the next `run_one` pops the FIFO front (the oldest
6673          // work) and other workers can steal the chain. The awaited future
6674          // is polled every iteration regardless, so it is never starved.
6675          self.flush_lifo_slot();
6676          work_streak = 0;
6677          force_fifo = true;
6678        }
6679        continue;
6680      }
6681      runnable_streak = 0;
6682      work_streak = 0;
6683      // MANDATORY pre-park flush. Defensive on every
6684      // currently reachable path: `run_one` above popped the slot and no user
6685      // code ran since -- but parking with a stranded slot runnable would
6686      // silently lose that task, so the invariant is upheld unconditionally.
6687      self.flush_lifo_slot();
6688      // Park protocol: register FIRST, then re-check, then park -- the
6689      // waiter-side mirror of the push-then-wake schedule side. See the
6690      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
6691      self.parked_drivers.register_with_role(
6692        &parker,
6693        can_run_blocking,
6694        Some(Arc::clone(&dependency.dependency)),
6695      );
6696      if self.has_queued_work_for_role(can_run_blocking)
6697        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6698      {
6699        self.parked_drivers.deregister(&parker);
6700        continue;
6701      }
6702      if self.stop.is_stopping() {
6703        return BlockOnOutcome::Stopped;
6704      }
6705      // TIMER-BOUNDED PARK: while any heap
6706      // timer is pending, this park is a TIMER WAIT, not a deadlock
6707      // candidate -- the wall clock is a guaranteed wake source -- so it uses
6708      // its own bounded primitive instead of `park`/`park_with_deadline` and
6709      // fires whatever came due on the way out. An armed park deadline is
6710      // deliberately NOT evaluated during a timer wait: the fire schedules
6711      // the woken task (fingerprint progress), and the next timerless park
6712      // arms a fresh full window, so genuine-deadlock detection is delayed
6713      // past the timer, never lost. This is also load-bearing for liveness:
6714      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
6715      // job has no free thread to run on, so this driver must fire due
6716      // timers itself. (A timer registered between the deadline read and the
6717      // park re-arms this parker through `note_earlier_timer`'s
6718      // `wake_one` -- we are registered -- or wakes the timekeeper.)
6719      if let Some(next) = self.next_timer_deadline() {
6720        let now = Instant::now();
6721        if next > now {
6722          parker.park_timeout(next - now);
6723        }
6724        self.parked_drivers.deregister(&parker);
6725        self.fire_due_timers();
6726        continue;
6727      }
6728      match self.park_deadline {
6729        None => parker.park(),
6730        Some(deadline) => {
6731          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
6732          // candidate deadlock only when a
6733          // FULL deadline window passes with zero executor progress. The
6734          // foreign-thread whole-build park in `block_on`'s else-branch is
6735          // exempt by design.
6736          let deadlock_publications = self.deadlock_state.publications();
6737          match park_with_deadline(&parker, deadline, &self.metrics) {
6738            DeadlineParkOutcome::Woken => {}
6739            DeadlineParkOutcome::Expired(armed) => {
6740              #[cfg(test)]
6741              run_deadline_expiry_test_hook();
6742              // EXPIRY DECISION. While this parker was
6743              // registered, a racing `wake_one` may have popped it, stored
6744              // its permit and reported the wake as DELIVERED (so the
6745              // scheduler spawned no drainer) -- panicking now would swallow
6746              // a healthy wake and kill a live build. Deregister FIRST so no
6747              // FURTHER wake can be counted against this parker, then
6748              // re-check every way a wake could have landed around the
6749              // expiry:
6750              //   * permit: a `wake_one` (or direct future-wake) that landed
6751              //     while still registered MUST be acted on;
6752              //   * queued work: the work behind such a wake was pushed
6753              //     under the queue mutex BEFORE `wake_one` popped us, so
6754              //     even if its `unpark` is still in flight the queue
6755              //     re-check observes it;
6756              //   * pending timers: arming a timer bumps NO progress counter
6757              //     by design, so a registration racing this already-
6758              //     committed park (it read an empty heap before parking) is
6759              //     invisible to every other re-check -- yet its wake (the
6760              //     fire) is wall-clock-guaranteed, so this park is healthy.
6761              //     The loop then re-parks TIMER-BOUNDED (see above) and
6762              //     fires the timer itself if needed;
6763              //   * fingerprint: the verdict requires ZERO progress, and the
6764              //     read inside `park_with_deadline` is stale by the time we
6765              //     get here -- re-read as the LAST step. Both ENQUEUE
6766              //     counters are in the fingerprint (`runnable_schedules`
6767              //     and `blocking_tasks_scheduled`, both bumped before the
6768              //     push), so a submission of EITHER kind
6769              //     landing after the queue re-check above is still caught
6770              //     here.
6771              // Any hit => not a deadlock: continue the cooperative loop
6772              // (fresh future poll, `run_one` picks up the work, and the
6773              // next park arms a fresh FULL deadline window). The residue is
6774              // exactly a BARE direct future-wake -- no queue push, no
6775              // counter movement -- landing in the few instructions after
6776              // these re-checks: the inherent, unclosable residue of any
6777              // timing-based detector, one reason the deadline is opt-in.
6778              self.parked_drivers.deregister(&parker);
6779              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6780                continue;
6781              }
6782              // Test-only seam between the permit/queued-work re-checks and
6783              // the fingerprint verdict.
6784              #[cfg(test)]
6785              run_deadline_verdict_test_hook();
6786              if self.metrics.progress_fingerprint() != armed {
6787                continue;
6788              }
6789              // Pending-timer re-check LAST, after even the fingerprint:
6790              // arming a timer bumps no counter, so it is the one healthy
6791              // wake source that can land after every earlier re-check and
6792              // still be invisible to the fingerprint read above. (A
6793              // registration racing the instructions after this line is the
6794              // same inherent residue class as the bare future-wake -- and
6795              // its timer is still served: `note_earlier_timer` claims the
6796              // timekeeper independently of this driver.)
6797              if self.next_timer_deadline().is_some() {
6798                continue;
6799              }
6800              // Test-only seam for a cross-thread scheduler admission after
6801              // every legacy final check and before the synchronized verdict.
6802              #[cfg(test)]
6803              run_deadline_final_verdict_test_hook();
6804              let Some(verdict) = self.try_begin_deadlock_verdict() else {
6805                continue;
6806              };
6807              #[cfg(test)]
6808              run_deadline_gated_verdict_test_hook();
6809              // Lock order is verdict gate -> stop publication -> exact-owner
6810              // publication. The controller takes lifecycle -> stop
6811              // publication and never acquires the verdict gate or exact-owner
6812              // publication while retaining either lock.
6813              let stop_publication = self.stop.publication_guard();
6814              #[cfg(test)]
6815              run_deadline_synchronized_stop_test_hook();
6816              if stop_publication.is_stopping() {
6817                return BlockOnOutcome::Stopped;
6818              }
6819              let owner_handoff_publication = self
6820                .deadlock_state
6821                .owner_handoff_publication_guard()
6822                .expect("a deadline verdict requires exact-owner publication");
6823              #[cfg(test)]
6824              run_deadline_exact_owner_verdict_test_hook(&parker);
6825              // The closed gate excludes later publications. Any admission
6826              // that began earlier either remains counted (so gate acquisition
6827              // failed above) or completed before this recheck and advanced
6828              // the publication epoch. The stop-publication guard also keeps
6829              // the lifecycle in Running if this verdict linearized first. The
6830              // exact-owner publication guard similarly orders reservation
6831              // release and targeted handoff with the final predicates below.
6832              if self.has_exact_owner_work_for_role(
6833                &parker,
6834                &dependency.dependency,
6835                can_run_blocking,
6836              ) || parker.consume_permit()
6837                || self.has_queued_work_for_role(can_run_blocking)
6838                || self.deadlock_state.publications() != deadlock_publications
6839                || self.metrics.progress_fingerprint() != armed
6840                || self.next_timer_deadline().is_some()
6841              {
6842                continue;
6843              }
6844              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6845              // Panic hooks run before unwind. Release every synchronization
6846              // guard first so a hook that submits work, releases an owner
6847              // lane, or starts shutdown cannot wait for the panic that invokes
6848              // it to unwind.
6849              drop(owner_handoff_publication);
6850              drop(stop_publication);
6851              drop(verdict);
6852              std::panic::panic_any(diagnostic);
6853            }
6854          }
6855        }
6856      }
6857      // `wake_one` removes the parkers it pops; a direct future-wake does
6858      // not. Deregister-if-present covers both.
6859      self.parked_drivers.deregister(&parker);
6860    }
6861  }
6862
6863  /// Is there work a cooperative driver could pick up right now? Used by the
6864  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
6865  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
6866  /// blocking job within the cap). The exact over-cap dependency is handled
6867  /// separately before registration.
6868  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6869    if self.has_queued_runnable() {
6870      return true;
6871    }
6872    can_run_blocking
6873      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6874      && !self
6875        .blocking_queue
6876        .lock()
6877        .unwrap_or_else(std::sync::PoisonError::into_inner)
6878        .is_empty()
6879  }
6880
6881  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6882    if self.has_queued_runnable() {
6883      self.wake_for_new_work();
6884    }
6885    // Shutdown-aborted runnables still need a driver so their generators can
6886    // retire, but queued blocking closures are cancelled by begin_shutdown and
6887    // must never be admitted by exit compensation after stop is observable.
6888    if self.stop.is_stopping() {
6889      return;
6890    }
6891    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6892      && !self
6893        .blocking_queue
6894        .lock()
6895        .unwrap_or_else(std::sync::PoisonError::into_inner)
6896        .is_empty()
6897    {
6898      // This driver may have absorbed the queue wake that made its awaited
6899      // future ready. Runnable and blocking residue are independent. If no
6900      // other blocking-capable driver is available, merely spawning a Rayon
6901      // job is insufficient: this worker may return into arbitrary
6902      // synchronous user code while every other Rayon lane is busy.
6903      // Consume one admitted job before exit, then hand any residue to another
6904      // blocking-capable driver.
6905      let consumed = can_run_blocking && self.run_one_blocking();
6906      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6907        && !self
6908          .blocking_queue
6909          .lock()
6910          .unwrap_or_else(std::sync::PoisonError::into_inner)
6911          .is_empty();
6912      if !self.stop.is_stopping() && (!consumed || has_residue) {
6913        self.wake_for_blocking_work();
6914      }
6915    }
6916  }
6917
6918  fn has_queued_runnable(&self) -> bool {
6919    // Waiter-side mirror of the `push_to_queue_and_wake` fence: a parking
6920    // driver (pre-park re-check) and the drainer-exit re-check in
6921    // `finish_draining` must not observe the injector empty when a racing push
6922    // is already visible to a wake. This SeqCst fence pairs with the post-push
6923    // fence so the Dekker ordering holds without the former queue mutex. Only
6924    // on park/quiesce decision paths, never the per-task hot path.
6925    std::sync::atomic::fence(Ordering::SeqCst);
6926    !self.injector.is_empty()
6927  }
6928
6929  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
6930  /// driver may run any job within `max_blocking`; only the over-cap escape is
6931  /// restricted to an exact dependency id.
6932  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6933    let admission = self.begin_deadlock_admission();
6934    loop {
6935      let active = self.active_blocking.load(Ordering::Acquire);
6936      if active >= self.max_blocking {
6937        return None;
6938      }
6939      let mut queue = self
6940        .blocking_queue
6941        .lock()
6942        .unwrap_or_else(std::sync::PoisonError::into_inner);
6943      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6944        return None;
6945      }
6946      if self
6947        .active_blocking
6948        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6949        .is_ok()
6950      {
6951        let blocking = queue
6952          .pop_front()
6953          .expect("a non-empty blocking queue must have a FIFO head");
6954        drop(queue);
6955        #[cfg(test)]
6956        run_multi_thread_work_claimed_test_hook();
6957        return Some((blocking, admission));
6958      }
6959    }
6960  }
6961}
6962
6963#[cfg(napi_runtime_os_threads)]
6964struct MultiThreadDeadlockState {
6965  enabled: bool,
6966  verdict_closed: AtomicBool,
6967  admissions: AtomicUsize,
6968  publications: AtomicU64,
6969  owner_handoff_publication: Mutex<()>,
6970}
6971
6972#[cfg(napi_runtime_os_threads)]
6973impl MultiThreadDeadlockState {
6974  fn new(enabled: bool) -> Self {
6975    Self {
6976      enabled,
6977      verdict_closed: AtomicBool::new(false),
6978      admissions: AtomicUsize::new(0),
6979      publications: AtomicU64::new(0),
6980      owner_handoff_publication: Mutex::new(()),
6981    }
6982  }
6983
6984  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6985    if !self.enabled {
6986      return None;
6987    }
6988    loop {
6989      while self.verdict_closed.load(Ordering::SeqCst) {
6990        #[cfg(test)]
6991        run_deadlock_closed_gate_admission_test_hook();
6992        std::hint::spin_loop();
6993      }
6994      self.admissions.fetch_add(1, Ordering::SeqCst);
6995      if !self.verdict_closed.load(Ordering::SeqCst) {
6996        return Some(MultiThreadDeadlockAdmissionGuard {
6997          state: Arc::clone(self),
6998          published: false,
6999        });
7000      }
7001      self.admissions.fetch_sub(1, Ordering::SeqCst);
7002    }
7003  }
7004
7005  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
7006    debug_assert!(
7007      self.enabled,
7008      "a deadline verdict requires an enabled admission state"
7009    );
7010    if self
7011      .verdict_closed
7012      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
7013      .is_err()
7014    {
7015      return None;
7016    }
7017    if self.admissions.load(Ordering::SeqCst) != 0 {
7018      self.verdict_closed.store(false, Ordering::SeqCst);
7019      return None;
7020    }
7021    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
7022  }
7023
7024  fn publications(&self) -> u64 {
7025    self.publications.load(Ordering::SeqCst)
7026  }
7027
7028  fn owner_handoff_publication_guard(&self) -> Option<MutexGuard<'_, ()>> {
7029    self.enabled.then(|| {
7030      self
7031        .owner_handoff_publication
7032        .lock()
7033        .unwrap_or_else(std::sync::PoisonError::into_inner)
7034    })
7035  }
7036}
7037
7038#[cfg(napi_runtime_os_threads)]
7039struct MultiThreadDeadlockAdmissionGuard {
7040  state: Arc<MultiThreadDeadlockState>,
7041  published: bool,
7042}
7043
7044#[cfg(napi_runtime_os_threads)]
7045impl MultiThreadDeadlockAdmissionGuard {
7046  fn mark_published(&mut self) {
7047    self.published = true;
7048  }
7049}
7050
7051#[cfg(napi_runtime_os_threads)]
7052impl Drop for MultiThreadDeadlockAdmissionGuard {
7053  fn drop(&mut self) {
7054    if self.published {
7055      self.state.publications.fetch_add(1, Ordering::SeqCst);
7056    }
7057    // Publication and every scheduler-state write above must precede the final
7058    // admission decrement observed by a closed verdict gate.
7059    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
7060  }
7061}
7062
7063#[cfg(napi_runtime_os_threads)]
7064struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
7065
7066#[cfg(napi_runtime_os_threads)]
7067impl Drop for MultiThreadDeadlockVerdictGuard {
7068  fn drop(&mut self) {
7069    self.0.verdict_closed.store(false, Ordering::SeqCst);
7070  }
7071}
7072
7073/// Cooperative-driver exit backstop. A user-future panic can unwind from any
7074/// poll, including after this driver absorbed a queue wake, received an exact
7075/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
7076/// independently so one cleanup failure cannot suppress the others or
7077/// double-panic the process.
7078#[cfg(napi_runtime_os_threads)]
7079struct CooperativeDriverExitGuard<'a> {
7080  executor: &'a Arc<MultiThreadExecutor>,
7081  parker: &'a Arc<DriverParker>,
7082  can_run_blocking: bool,
7083}
7084
7085#[cfg(napi_runtime_os_threads)]
7086impl Drop for CooperativeDriverExitGuard<'_> {
7087  fn drop(&mut self) {
7088    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
7089    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
7090    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
7091    let _ = catch_unwind_contained(|| {
7092      self
7093        .executor
7094        .compensate_queued_work_wake(self.can_run_blocking);
7095    });
7096  }
7097}
7098
7099/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
7100/// unwinding drain can never strand a runnable in the
7101/// thread-local slot of a thread that is about to return to rayon. Normal
7102/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
7103/// no-op (the slot is already empty).
7104#[cfg(napi_runtime_os_threads)]
7105struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
7106
7107#[cfg(napi_runtime_os_threads)]
7108impl Drop for LifoSlotFlushGuard<'_> {
7109  fn drop(&mut self) {
7110    self.0.flush_lifo_slot();
7111  }
7112}
7113
7114/// Per-drain-frame idle-linger state, created lazily on the frame's first
7115/// idle episode (see the linger block in `MultiThreadExecutor::drain`): the
7116/// parker queue wakes are delivered to while this drainer lingers, plus its
7117/// generation-stop registration so `begin_shutdown` cuts a linger short
7118/// instead of `wait_until_scheduler_idle` waiting out the idle budget. The
7119/// registration guard deregisters on drop when the drain frame exits.
7120#[cfg(napi_runtime_os_threads)]
7121struct DrainLinger {
7122  parker: Arc<DriverParker>,
7123  _stop_registration: GenerationStopGuard,
7124}
7125
7126#[cfg(napi_runtime_os_threads)]
7127impl DrainLinger {
7128  fn new(executor: &MultiThreadExecutor) -> Self {
7129    let parker = Arc::new(DriverParker::default());
7130    let stop_registration = executor.stop.register(&parker);
7131    Self {
7132      parker,
7133      _stop_registration: stop_registration,
7134    }
7135  }
7136}
7137
7138// ---------------------------------------------------------------------------
7139// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
7140//
7141// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
7142//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
7143//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
7144//     driver that awaits it. The permit bit makes a wake delivered while that
7145//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
7146//     next park instead of losing the wake.
7147//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
7148//     serve queue work, so these go through the `ParkedDrivers` registry and
7149//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
7150//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
7151//     exit (miss compensation).
7152// Foreign-thread (napi caller) parks in `block_on` use
7153// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
7154// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
7155// frame, no registry) so it too OWNS its park site for deadlock detection
7156// while `ParkedDrivers` remains MultiThread-only.
7157
7158/// No permit; the driver is running (or consumed its last permit).
7159const PARKER_EMPTY: usize = 0;
7160/// A wake permit is stored; the next `park` consumes it without sleeping.
7161const PARKER_NOTIFIED: usize = 1;
7162/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
7163const PARKER_SLEEPING: usize = 2;
7164
7165/// One driver's private parker: a saturating one-permit token plus a condvar
7166/// to sleep on when no permit is stored. Also the `Waker` for the future that
7167/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
7168#[derive(Default)]
7169struct DriverParker {
7170  state: AtomicUsize,
7171  lock: Mutex<()>,
7172  condvar: UnboundedCondvar,
7173  #[cfg(napi_runtime_os_threads)]
7174  owner_handoff_pending: AtomicBool,
7175  #[cfg(napi_runtime_os_threads)]
7176  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7177}
7178
7179impl DriverParker {
7180  #[cfg(napi_runtime_os_threads)]
7181  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7182    let mut pending = self
7183      .owner_handoff
7184      .lock()
7185      .unwrap_or_else(std::sync::PoisonError::into_inner);
7186    debug_assert!(pending.is_none());
7187    *pending = Some(owner);
7188    self.owner_handoff_pending.store(true, Ordering::Release);
7189  }
7190
7191  #[cfg(napi_runtime_os_threads)]
7192  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7193    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7194      return None;
7195    }
7196    self
7197      .owner_handoff
7198      .lock()
7199      .unwrap_or_else(std::sync::PoisonError::into_inner)
7200      .take()
7201  }
7202
7203  #[cfg(napi_runtime_os_threads)]
7204  fn has_owner_handoff(&self) -> bool {
7205    self.owner_handoff_pending.load(Ordering::Acquire)
7206  }
7207
7208  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
7209  /// permit is stored and the driver's next [`Self::park`] returns
7210  /// immediately -- a wake delivered while the driver is running is never
7211  /// lost. Multiple grants coalesce into one permit, which is safe because a
7212  /// woken driver re-checks every work source in its loop before it can park
7213  /// again.
7214  fn unpark(&self) {
7215    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7216      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
7217      // releases the lock only inside `condvar.wait`; the empty critical
7218      // section here fences the notify so it cannot fire in the window
7219      // between the sleeper's state store and its wait.
7220      drop(
7221        self
7222          .lock
7223          .lock()
7224          .unwrap_or_else(std::sync::PoisonError::into_inner),
7225      );
7226      self.condvar.notify_one();
7227    }
7228  }
7229
7230  /// Consume a stored wake permit if one is present, without ever sleeping.
7231  /// This is the park DECISION's token check: a `true` return means a wake is
7232  /// already pending, so the caller must re-poll instead of parking (or
7233  /// panicking).
7234  fn consume_permit(&self) -> bool {
7235    self
7236      .state
7237      .compare_exchange(
7238        PARKER_NOTIFIED,
7239        PARKER_EMPTY,
7240        Ordering::SeqCst,
7241        Ordering::SeqCst,
7242      )
7243      .is_ok()
7244  }
7245
7246  /// Block until the permit is granted, consuming it. Returns immediately if
7247  /// a permit is already stored. Only the owning driver calls `park`, so at
7248  /// most one thread ever sleeps here.
7249  fn park(&self) {
7250    // Fast path: consume a stored permit without touching the lock.
7251    if self.consume_permit() {
7252      return;
7253    }
7254    let mut guard = self
7255      .lock
7256      .lock()
7257      .unwrap_or_else(std::sync::PoisonError::into_inner);
7258    match self.state.compare_exchange(
7259      PARKER_EMPTY,
7260      PARKER_SLEEPING,
7261      Ordering::SeqCst,
7262      Ordering::SeqCst,
7263    ) {
7264      Ok(_) => {}
7265      // The permit arrived between the fast path and here: consume it. (Only
7266      // this thread stores PARKER_SLEEPING, so the observed value can only be
7267      // PARKER_NOTIFIED.)
7268      Err(_) => {
7269        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7270        return;
7271      }
7272    }
7273    loop {
7274      guard = self
7275        .condvar
7276        .wait(guard)
7277        .unwrap_or_else(std::sync::PoisonError::into_inner);
7278      if self
7279        .state
7280        .compare_exchange(
7281          PARKER_NOTIFIED,
7282          PARKER_EMPTY,
7283          Ordering::SeqCst,
7284          Ordering::SeqCst,
7285        )
7286        .is_ok()
7287      {
7288        return;
7289      }
7290      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
7291    }
7292  }
7293
7294  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
7295  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
7296  /// A `false` return has retracted the SLEEPING claim and left the parker
7297  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
7298  /// detection.
7299  fn park_timeout(&self, timeout: Duration) -> bool {
7300    if self.consume_permit() {
7301      return true;
7302    }
7303    let mut guard = self
7304      .lock
7305      .lock()
7306      .unwrap_or_else(std::sync::PoisonError::into_inner);
7307    match self.state.compare_exchange(
7308      PARKER_EMPTY,
7309      PARKER_SLEEPING,
7310      Ordering::SeqCst,
7311      Ordering::SeqCst,
7312    ) {
7313      Ok(_) => {}
7314      // The permit arrived between the fast path and here: consume it. (Only
7315      // this thread stores PARKER_SLEEPING, so the observed value can only be
7316      // PARKER_NOTIFIED.)
7317      Err(_) => {
7318        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7319        return true;
7320      }
7321    }
7322    let Some(deadline) = Instant::now().checked_add(timeout) else {
7323      // An unrepresentable deadline is farther away than this platform can
7324      // observe. Treat it as an unbounded wait instead of panicking on a
7325      // user-provided deadlock-detection duration.
7326      loop {
7327        guard = self
7328          .condvar
7329          .wait(guard)
7330          .unwrap_or_else(std::sync::PoisonError::into_inner);
7331        if self
7332          .state
7333          .compare_exchange(
7334            PARKER_NOTIFIED,
7335            PARKER_EMPTY,
7336            Ordering::SeqCst,
7337            Ordering::SeqCst,
7338          )
7339          .is_ok()
7340        {
7341          return true;
7342        }
7343      }
7344    };
7345    loop {
7346      if self
7347        .state
7348        .compare_exchange(
7349          PARKER_NOTIFIED,
7350          PARKER_EMPTY,
7351          Ordering::SeqCst,
7352          Ordering::SeqCst,
7353        )
7354        .is_ok()
7355      {
7356        return true;
7357      }
7358      let now = Instant::now();
7359      if now >= deadline {
7360        // Timed out: retract the SLEEPING claim -- unless a permit landed in
7361        // the meantime (an `unpark` that already saw SLEEPING may be waiting
7362        // on `lock` to notify; consuming its permit here IS the wake it
7363        // intended to deliver, so report "woken", not "timed out").
7364        return match self.state.compare_exchange(
7365          PARKER_SLEEPING,
7366          PARKER_EMPTY,
7367          Ordering::SeqCst,
7368          Ordering::SeqCst,
7369        ) {
7370          Ok(_) => false,
7371          Err(_) => {
7372            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7373            true
7374          }
7375        };
7376      }
7377      let (returned_guard, _timed_out) = self
7378        .condvar
7379        .wait_timeout(guard, deadline - now)
7380        .unwrap_or_else(std::sync::PoisonError::into_inner);
7381      guard = returned_guard;
7382      // Loop re-checks the permit first, then the deadline (spurious wakeups
7383      // simply wait out the remaining time).
7384    }
7385  }
7386}
7387
7388impl std::task::Wake for DriverParker {
7389  fn wake(self: Arc<Self>) {
7390    self.unpark();
7391  }
7392
7393  fn wake_by_ref(self: &Arc<Self>) {
7394    self.unpark();
7395  }
7396}
7397
7398/// Outcome of [`park_with_deadline`].
7399enum DeadlineParkOutcome {
7400  /// Woken by a permit (consumed); the park was healthy.
7401  Woken,
7402  /// A full deadline window elapsed with zero observed progress. Carries the
7403  /// fingerprint captured when that window was ARMED so the caller can make
7404  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
7405  /// verdict: a wake can land in the instructions after the window closes,
7406  /// and for MultiThread a `wake_one` can still pop the
7407  /// still-registered parker and count its wake as DELIVERED. Callers must
7408  /// deregister (MT), then re-check permit / queued work / fingerprint before
7409  /// panicking.
7410  Expired(ProgressFingerprint),
7411}
7412
7413/// Deadline-bounded park with PROGRESS-BASED reset.
7414/// Every time `deadline` elapses without a wake, the runtime's progress
7415/// fingerprint is compared against the value captured when the deadline was
7416/// ARMED: any advance re-arms the deadline instead of firing, because a
7417/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
7418/// proceeds -- must never be declared dead. `Expired` is a *candidate*
7419/// deadlock only; the panic decision lives with the caller (see
7420/// [`DeadlineParkOutcome`]).
7421fn park_with_deadline(
7422  parker: &DriverParker,
7423  deadline: Duration,
7424  metrics: &RuntimeMetrics,
7425) -> DeadlineParkOutcome {
7426  let mut armed = metrics.progress_fingerprint();
7427  loop {
7428    if parker.park_timeout(deadline) {
7429      return DeadlineParkOutcome::Woken;
7430    }
7431    let current = metrics.progress_fingerprint();
7432    if current != armed {
7433      armed = current;
7434      continue;
7435    }
7436    return DeadlineParkOutcome::Expired(armed);
7437  }
7438}
7439
7440// Test-only injection seam for the deadline-expiry race: fired on the driver
7441// thread immediately after
7442// `park_with_deadline` reports `Expired`, while the parker is STILL
7443// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
7444// exact window in which a racing `wake_one` can pop the parker, store its
7445// permit and report the wake as delivered. Lets a test interleave that race
7446// deterministically instead of wall-clock lottery.
7447#[cfg(all(test, napi_runtime_os_threads))]
7448thread_local! {
7449  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7450    const { std::cell::RefCell::new(None) };
7451}
7452
7453#[cfg(all(test, napi_runtime_os_threads))]
7454fn run_deadline_expiry_test_hook() {
7455  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7456    hook();
7457  }
7458}
7459
7460// Second test-only injection seam for the enqueue/verdict race: fired AFTER
7461// the expiry decision's permit and queued-work re-checks both came up
7462// empty (parker already deregistered) and BEFORE the final fingerprint
7463// verdict -- the window in which an enqueue is invisible to every earlier
7464// re-check and only its ENQUEUE counter in the fingerprint can prevent a
7465// false BlockOnDeadlock.
7466#[cfg(all(test, napi_runtime_os_threads))]
7467thread_local! {
7468  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7469    const { std::cell::RefCell::new(None) };
7470}
7471
7472#[cfg(all(test, napi_runtime_os_threads))]
7473fn run_deadline_verdict_test_hook() {
7474  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7475    hook();
7476  }
7477}
7478
7479// Final test-only injection seam: fired after every permit/queue/fingerprint/
7480// timer check from the legacy verdict and immediately before the synchronized
7481// admission gate closes. A separate OS thread can complete a publication here
7482// to prove the final verdict recheck has a real cross-thread ordering edge.
7483#[cfg(all(test, napi_runtime_os_threads))]
7484thread_local! {
7485  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7486    const { std::cell::RefCell::new(None) };
7487}
7488
7489#[cfg(all(test, napi_runtime_os_threads))]
7490fn run_deadline_final_verdict_test_hook() {
7491  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7492    hook();
7493  }
7494}
7495
7496// Fired after the synchronized verdict gate has closed and before the verdict
7497// takes the stop-publication mutex for its final rechecks. A test can pause
7498// here while shutdown publishes generation stop and its abort wake reaches the
7499// closed admission gate.
7500#[cfg(all(test, napi_runtime_os_threads))]
7501thread_local! {
7502  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7503    const { std::cell::RefCell::new(None) };
7504}
7505
7506#[cfg(all(test, napi_runtime_os_threads))]
7507fn run_deadline_gated_verdict_test_hook() {
7508  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7509    hook();
7510  }
7511}
7512
7513// Fired after the final verdict has acquired the stop-publication mutex. A
7514// shutdown test can retain that guard while another thread reaches the
7515// lifecycle -> stop-publication lock edge.
7516#[cfg(all(test, napi_runtime_os_threads))]
7517thread_local! {
7518  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7519    const { std::cell::RefCell::new(None) };
7520}
7521
7522#[cfg(all(test, napi_runtime_os_threads))]
7523fn run_deadline_synchronized_stop_test_hook() {
7524  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7525    hook();
7526  }
7527}
7528
7529// Fired after the final verdict owns exact-owner publication and before it
7530// checks handoff/lane predicates. Tests can remove unrelated saving predicates
7531// while retaining the synchronized owner state under examination.
7532#[cfg(all(test, napi_runtime_os_threads))]
7533type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7534
7535#[cfg(all(test, napi_runtime_os_threads))]
7536thread_local! {
7537  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7538    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7539      const { std::cell::RefCell::new(None) };
7540}
7541
7542#[cfg(all(test, napi_runtime_os_threads))]
7543fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7544  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7545    hook(parker);
7546  }
7547}
7548
7549// Fired immediately before a test thread takes the stop-publication mutex.
7550// Installed only on the shutdown thread, this proves the controller still
7551// holds the lifecycle in Running while waiting for a verdict-owned guard.
7552#[cfg(all(test, napi_runtime_os_threads))]
7553thread_local! {
7554  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7555    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7556      const { std::cell::RefCell::new(None) };
7557}
7558
7559#[cfg(all(test, napi_runtime_os_threads))]
7560fn run_before_generation_stop_publication_lock_test_hook() {
7561  if let Some(hook) =
7562    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7563  {
7564    hook();
7565  }
7566}
7567
7568// Fired on an admission thread after it observes a closed verdict gate. The
7569// hook is thread-local so a shutdown test can prove that an abort wake reached
7570// this exact wait without affecting unrelated scheduler threads.
7571#[cfg(all(test, napi_runtime_os_threads))]
7572thread_local! {
7573  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7574    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7575      const { std::cell::RefCell::new(None) };
7576}
7577
7578#[cfg(all(test, napi_runtime_os_threads))]
7579fn run_deadlock_closed_gate_admission_test_hook() {
7580  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7581  {
7582    hook();
7583  }
7584}
7585
7586// Timer registration enters the shared verdict admission before this seam.
7587// Tests can pause there to prove the timer remains an announced publication
7588// until its heap entry becomes visible.
7589#[cfg(all(test, napi_runtime_os_threads))]
7590thread_local! {
7591  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7592    const { std::cell::RefCell::new(None) };
7593}
7594
7595#[cfg(all(test, napi_runtime_os_threads))]
7596fn run_timer_registration_admission_test_hook() {
7597  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7598    hook();
7599  }
7600}
7601
7602#[cfg(all(test, napi_runtime_os_threads))]
7603thread_local! {
7604  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7605    const { std::cell::RefCell::new(None) };
7606}
7607
7608#[cfg(all(test, napi_runtime_os_threads))]
7609fn run_owner_handoff_selected_test_hook() {
7610  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7611    hook();
7612  }
7613}
7614
7615/// Registry of parked (or committed-to-parking) cooperative drivers, used by
7616/// queue-wakes to wake exactly one of them.
7617#[cfg(napi_runtime_os_threads)]
7618#[derive(Default)]
7619struct ParkedDrivers {
7620  parked: Mutex<Vec<ParkedDriver>>,
7621  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
7622  // by `wake_one`'s no-waiter fast path.
7623  count: AtomicUsize,
7624}
7625
7626#[cfg(napi_runtime_os_threads)]
7627struct ParkedDriver {
7628  parker: Arc<DriverParker>,
7629  can_run_blocking: bool,
7630  dependency: Option<Arc<TaskDependency>>,
7631}
7632
7633#[cfg(napi_runtime_os_threads)]
7634impl ParkedDrivers {
7635  /// Register `parker` as parked-or-parking. MUST precede the caller's final
7636  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
7637  #[cfg(test)]
7638  fn register(&self, parker: &Arc<DriverParker>) {
7639    self.register_with_role(parker, true, None);
7640  }
7641
7642  fn register_with_role(
7643    &self,
7644    parker: &Arc<DriverParker>,
7645    can_run_blocking: bool,
7646    dependency: Option<Arc<TaskDependency>>,
7647  ) {
7648    let mut parked = self
7649      .parked
7650      .lock()
7651      .unwrap_or_else(std::sync::PoisonError::into_inner);
7652    parked.push(ParkedDriver {
7653      parker: Arc::clone(parker),
7654      can_run_blocking,
7655      dependency,
7656    });
7657    self.count.store(parked.len(), Ordering::SeqCst);
7658  }
7659
7660  /// Remove `parker` if still registered. `wake_one` removes the parkers it
7661  /// pops, while a direct future-wake does not, so drivers call this after
7662  /// every park (and after a re-check aborts one).
7663  fn deregister(&self, parker: &Arc<DriverParker>) {
7664    let removed = {
7665      let mut parked = self
7666        .parked
7667        .lock()
7668        .unwrap_or_else(std::sync::PoisonError::into_inner);
7669      let removed = parked
7670        .iter()
7671        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7672        .map(|index| parked.swap_remove(index));
7673      self.count.store(parked.len(), Ordering::SeqCst);
7674      removed
7675    };
7676    drop(removed);
7677  }
7678
7679  /// Wake one parked driver -- most recently parked first, its stack and
7680  /// caches are the warmest -- returning whether anyone was woken.
7681  ///
7682  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
7683  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
7684  /// registers itself (the `count` store above, under the registry mutex),
7685  /// THEN re-checks the work queues under Q, and only then parks. Order the
7686  /// two Q critical sections:
7687  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
7688  ///     it, the re-check observes the work and the driver does not park --
7689  ///     skipping the wake (fast path below) is safe.
7690  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
7691  ///     push locked it. The driver's `count` store is sequenced before its
7692  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
7693  ///     lock of Q, and the push is sequenced before the fast-path load
7694  ///     below; the load therefore happens-after the `count` store and must
7695  ///     observe `count >= 1`: the slow path pops a registered parker and
7696  ///     wakes it.
7697  ///
7698  /// A wake delivered to a driver that raced out of its park (or aborted it)
7699  /// is stored as that driver's permit; the driver re-checks every work
7700  /// source before it can sleep on the next park, so the wake still lands.
7701  fn wake_one(&self) -> bool {
7702    if self.count.load(Ordering::SeqCst) == 0 {
7703      // No-waiter fast path: nobody is parked or committed to parking (any
7704      // in-flight parker that missed this check re-checks the queues after
7705      // registering, per the argument above).
7706      return false;
7707    }
7708    let driver = {
7709      let mut parked = self
7710        .parked
7711        .lock()
7712        .unwrap_or_else(std::sync::PoisonError::into_inner);
7713      let driver = parked.pop();
7714      self.count.store(parked.len(), Ordering::SeqCst);
7715      driver
7716    };
7717    match driver {
7718      Some(driver) => {
7719        driver.parker.unpark();
7720        true
7721      }
7722      None => false,
7723    }
7724  }
7725
7726  /// Wake the most recently parked cooperative driver that is eligible to
7727  /// consume blocking work.
7728  fn wake_one_blocking(&self) -> bool {
7729    if self.count.load(Ordering::SeqCst) == 0 {
7730      return false;
7731    }
7732    let driver = {
7733      let mut parked = self
7734        .parked
7735        .lock()
7736        .unwrap_or_else(std::sync::PoisonError::into_inner);
7737      let driver = parked
7738        .iter()
7739        .rposition(|driver| driver.can_run_blocking)
7740        .map(|index| parked.swap_remove(index));
7741      self.count.store(parked.len(), Ordering::SeqCst);
7742      driver
7743    };
7744    match driver {
7745      Some(driver) => {
7746        driver.parker.unpark();
7747        true
7748      }
7749      None => false,
7750    }
7751  }
7752
7753  /// Wake one parked driver whose live dependency belongs to `owner`.
7754  ///
7755  /// The dependency is published with the parker, so an unrelated newer
7756  /// driver cannot consume the bounded owner-lane handoff.
7757  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7758    loop {
7759      if self.count.load(Ordering::SeqCst) == 0 {
7760        return false;
7761      }
7762      let candidates = {
7763        let parked = self
7764          .parked
7765          .lock()
7766          .unwrap_or_else(std::sync::PoisonError::into_inner);
7767        parked
7768          .iter()
7769          .rev()
7770          .filter_map(|driver| {
7771            if !driver.can_run_blocking {
7772              return None;
7773            }
7774            driver
7775              .dependency
7776              .as_ref()
7777              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7778          })
7779          .collect::<Vec<_>>()
7780      };
7781      let selected = candidates.into_iter().find(|(_, dependency)| {
7782        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7783      });
7784      let Some((selected_parker, selected_dependency)) = selected else {
7785        return false;
7786      };
7787      let driver = {
7788        let mut parked = self
7789          .parked
7790          .lock()
7791          .unwrap_or_else(std::sync::PoisonError::into_inner);
7792        let driver = parked
7793          .iter()
7794          .rposition(|driver| {
7795            Arc::ptr_eq(&driver.parker, &selected_parker)
7796              && driver
7797                .dependency
7798                .as_ref()
7799                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7800          })
7801          .map(|index| parked.swap_remove(index));
7802        // Publish the exact-owner token before releasing the registry lock. A
7803        // direct future wake can race this selection, but the driver must
7804        // deregister under this same lock before it can poll Ready and forward
7805        // an unconsumed handoff on exit.
7806        if let Some(driver) = &driver {
7807          driver.parker.grant_owner_handoff(owner);
7808        }
7809        self.count.store(parked.len(), Ordering::SeqCst);
7810        driver
7811      };
7812      if let Some(driver) = driver {
7813        #[cfg(test)]
7814        run_owner_handoff_selected_test_hook();
7815        driver.parker.unpark();
7816        return true;
7817      }
7818    }
7819  }
7820
7821  /// Wake every timer-bounded driver so cancellation of the earliest timer
7822  /// cannot leave pool workers parked until a stale deadline.
7823  fn wake_all(&self) {
7824    if self.count.load(Ordering::SeqCst) == 0 {
7825      return;
7826    }
7827    let drivers = {
7828      let mut parked = self
7829        .parked
7830        .lock()
7831        .unwrap_or_else(std::sync::PoisonError::into_inner);
7832      let drivers = parked.drain(..).collect::<Vec<_>>();
7833      self.count.store(0, Ordering::SeqCst);
7834      drivers
7835    };
7836    for driver in drivers {
7837      driver.parker.unpark();
7838    }
7839  }
7840}
7841
7842// ---------------------------------------------------------------------------
7843// Runtime timer facility: `sleep_until` without a tokio
7844// reactor. Two drivers, selected by runtime flavor:
7845//
7846//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
7847//     timekeeper thread. Timer waits never consume the configured CPU workers.
7848//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
7849//     deadline, so the parked driver can fire due timers without depending on
7850//     any particular inter-thread wake ordering.
7851//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
7852//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
7853//     driver at import; wasi-single uses the same path). A CT runtime without
7854//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
7855//
7856// DEADLOCK-DETECTION INTERACTIONS:
7857//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
7858//     clock is a guaranteed wake source. Timer waits therefore use their own
7859//     primitive (a plain `park_timeout` bounded by the earliest deadline),
7860//     NEVER `park_with_deadline`, so an armed park deadline
7861//     shorter than the next timer cannot fire on a legitimate timer wait; the
7862//     fire itself schedules the woken task, which IS fingerprint progress for
7863//     every other parked driver. The expiry VERDICT additionally re-checks
7864//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
7865//     counter by design, so a registration racing an already-committed
7866//     deadline park must veto the panic there.
7867//   * CT certain check (threadless): a pending HOST timer is NOT a wake
7868//     token -- the JS setTimeout relay can only run on the very thread that
7869//     is parking, so it can never fire while `block_on` holds it. The check
7870//     panics unconditionally on a threadless build; a pending timer merely
7871//     selects the timer-backed variant of the typed diagnostic. The CT
7872//     deadline verdict (threaded flavor, where the host loop genuinely runs
7873//     concurrently) counts only LIVE waits (deadline still in the future): a
7874//     pending timer whose deadline passed without firing is the
7875//     frozen-JS-event-loop signature that the deadline-based detection
7876//     exists to catch, so it must NOT suppress the panic.
7877
7878/// Host-turn dispatcher for the CurrentThread runnable queue.
7879///
7880/// The NAPI binding registers one weak native threadsafe-function driver per
7881/// importing environment. Its custom Node-API callback owns the fresh host turn
7882/// and keeps delivery capabilities out of JavaScript. Scheduling through a
7883/// fresh host turn is required for soundness: arbitrary futures may invoke
7884/// their waker while holding internal locks, so polling the runnable inline
7885/// from that wake can self-deadlock.
7886pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7887  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
7888  /// [`CurrentThreadTaskDelivery::capability`].
7889  ///
7890  /// The delivery capability is scoped to this exact host registration and
7891  /// attempt. The registry maps it back to the executor's one internal
7892  /// dispatch capability only while this attempt remains current.
7893  /// `false` means this driver can no longer dispatch and must be swept. After
7894  /// returning `true`, the driver must eventually call either
7895  /// [`acknowledge_current_thread_task_delivery`] or
7896  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
7897  /// acknowledgement is accepted only after
7898  /// [`drive_current_thread_tasks`] claimed this exact delivery.
7899  ///
7900  /// `dispatch` must return promptly and must not wait on another thread. In
7901  /// particular it must never block on the thread that will acknowledge or
7902  /// fail this delivery (for instance by spawning a helper that calls
7903  /// [`fail_current_thread_task_delivery`] and joining it). The executor
7904  /// calls `dispatch` from `schedule`, which may be a waker's stack: a waker
7905  /// can be invoked while its producer holds a mutex (`futures::Shared` wakes
7906  /// its awaiters under its `wakers` lock). When a delivery failure turns
7907  /// terminal, the rejected work is cancelled on the stack of the thread that
7908  /// failed it, and a rejected future's destructor may re-lock the very mutex
7909  /// the blocked waker still holds. A `dispatch` that waits on that thread
7910  /// therefore deadlocks with it.
7911  ///
7912  /// Failing the delivery synchronously from inside `dispatch`, on the
7913  /// calling thread, is handled: the executor recognises that stack as a
7914  /// possible waker's, moves the rejected work aside and cancels it on the
7915  /// next clean stack (see `CurrentThreadQueue::rejected`). Failing or
7916  /// acknowledging it later, from any thread, is handled too. The Node-API
7917  /// driver satisfies this contract: it makes one nonblocking
7918  /// threadsafe-function call and returns `false` at once when the
7919  /// environment is closing.
7920  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7921
7922  fn is_live(&self) -> bool {
7923    true
7924  }
7925
7926  /// Called after this driver has been removed from the registry, with no
7927  /// registry lock held. Must be idempotent with explicit host cleanup.
7928  fn on_swept(&self) {}
7929}
7930
7931#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7932pub struct CurrentThreadTaskDriverId(u64);
7933
7934type CurrentThreadTaskDriverSnapshot =
7935  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7936
7937#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7938pub struct CurrentThreadTaskDelivery {
7939  driver: CurrentThreadTaskDriverId,
7940  capability: u64,
7941  dispatch: u64,
7942}
7943
7944impl CurrentThreadTaskDelivery {
7945  /// Opaque driver capability for this exact registration attempt.
7946  pub fn capability(self) -> u64 {
7947    self.capability
7948  }
7949}
7950
7951struct CurrentThreadTaskDeliveryState {
7952  delivery: CurrentThreadTaskDelivery,
7953  claimed: bool,
7954  accepted: bool,
7955}
7956
7957struct CurrentThreadTaskDriverEntry {
7958  driver: Arc<dyn CurrentThreadTaskDriver>,
7959  delivery: Option<CurrentThreadTaskDeliveryState>,
7960  pending: Option<u64>,
7961  previous: Option<CurrentThreadTaskDriverId>,
7962  next: Option<CurrentThreadTaskDriverId>,
7963}
7964
7965#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7966enum CurrentThreadDispatchPhase {
7967  #[default]
7968  Pending,
7969  Serviced,
7970  Cancelling,
7971}
7972
7973#[derive(Default)]
7974struct CurrentThreadDispatchState {
7975  phase: CurrentThreadDispatchPhase,
7976  epoch: u64,
7977  publications: usize,
7978  references: usize,
7979  accepted: bool,
7980  accepted_failure: bool,
7981}
7982
7983impl CurrentThreadDispatchState {
7984  fn is_serviced(&self) -> bool {
7985    self.phase == CurrentThreadDispatchPhase::Serviced
7986  }
7987
7988  fn is_cancelling(&self) -> bool {
7989    self.phase == CurrentThreadDispatchPhase::Cancelling
7990  }
7991
7992  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7993    CurrentThreadTaskDispatchFailure {
7994      dispatch,
7995      epoch: self.epoch,
7996    }
7997  }
7998}
7999
8000#[derive(Default)]
8001struct CurrentThreadTaskDriverRegistryState {
8002  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
8003  oldest: Option<CurrentThreadTaskDriverId>,
8004  newest: Option<CurrentThreadTaskDriverId>,
8005  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
8006  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
8007}
8008
8009impl CurrentThreadTaskDriverRegistryState {
8010  fn insert_driver(
8011    &mut self,
8012    id: CurrentThreadTaskDriverId,
8013    driver: Arc<dyn CurrentThreadTaskDriver>,
8014  ) {
8015    let previous = self.newest;
8016    let replaced = self.entries.insert(
8017      id,
8018      CurrentThreadTaskDriverEntry {
8019        driver,
8020        delivery: None,
8021        pending: None,
8022        previous,
8023        next: None,
8024      },
8025    );
8026    assert!(
8027      replaced.is_none(),
8028      "CurrentThread task driver id must be unique"
8029    );
8030    if let Some(previous) = previous {
8031      self
8032        .entries
8033        .get_mut(&previous)
8034        .expect("the newest CurrentThread task driver must remain registered")
8035        .next = Some(id);
8036    } else {
8037      self.oldest = Some(id);
8038    }
8039    self.newest = Some(id);
8040  }
8041
8042  fn remove_driver(
8043    &mut self,
8044    id: CurrentThreadTaskDriverId,
8045  ) -> Option<CurrentThreadTaskDriverEntry> {
8046    let entry = self.entries.remove(&id)?;
8047    if let Some(previous) = entry.previous {
8048      self
8049        .entries
8050        .get_mut(&previous)
8051        .expect("the previous CurrentThread task driver must remain registered")
8052        .next = entry.next;
8053    } else {
8054      self.oldest = entry.next;
8055    }
8056    if let Some(next) = entry.next {
8057      self
8058        .entries
8059        .get_mut(&next)
8060        .expect("the next CurrentThread task driver must remain registered")
8061        .previous = entry.previous;
8062    } else {
8063      self.newest = entry.previous;
8064    }
8065    if let Some(delivery) = &entry.delivery {
8066      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
8067      debug_assert_eq!(
8068        indexed,
8069        Some(id),
8070        "delivery capability index must match its driver"
8071      );
8072    }
8073    Some(entry)
8074  }
8075
8076  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8077    let mut snapshot = Vec::with_capacity(self.entries.len());
8078    let mut current = self.oldest;
8079    while let Some(id) = current {
8080      let entry = self
8081        .entries
8082        .get(&id)
8083        .expect("CurrentThread task driver order must reference a live entry");
8084      snapshot.push((id, Arc::clone(&entry.driver)));
8085      current = entry.next;
8086    }
8087    debug_assert_eq!(
8088      snapshot.len(),
8089      self.entries.len(),
8090      "CurrentThread task driver order must contain every entry exactly once"
8091    );
8092    snapshot
8093  }
8094
8095  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
8096    if self.entries.len() != expected.len() {
8097      return false;
8098    }
8099    let mut current = self.oldest;
8100    for expected in expected {
8101      if current != Some(*expected) {
8102        return false;
8103      }
8104      current = self
8105        .entries
8106        .get(expected)
8107        .expect("CurrentThread task driver order must reference a live entry")
8108        .next;
8109    }
8110    current.is_none()
8111  }
8112}
8113
8114struct CurrentThreadTaskDriverSelection {
8115  live: CurrentThreadTaskDriverSnapshot,
8116  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8117}
8118
8119#[derive(Default)]
8120struct CurrentThreadTaskDeliveryCompletion {
8121  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8122  redispatch: bool,
8123}
8124
8125struct CurrentThreadTaskDispatchCompletion {
8126  result: CurrentThreadHostDispatchResult,
8127  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8128}
8129
8130struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8131  registry: &'a CurrentThreadTaskDriverRegistry,
8132  dispatch: Option<u64>,
8133}
8134
8135impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8136  fn finish(mut self) -> CurrentThreadHostDispatchResult {
8137    let dispatch = self
8138      .dispatch
8139      .expect("the CurrentThread task dispatch publication must remain armed");
8140    let result = self.registry.finish_publication(dispatch);
8141    self.dispatch = None;
8142    result
8143  }
8144}
8145
8146impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8147  fn drop(&mut self) {
8148    if let Some(dispatch) = self.dispatch.take() {
8149      self.registry.abort_publication(dispatch);
8150    }
8151  }
8152}
8153
8154#[derive(Default)]
8155struct CurrentThreadTaskDriverRegistry {
8156  state: Mutex<CurrentThreadTaskDriverRegistryState>,
8157  next_id: AtomicU64,
8158  next_delivery: AtomicU64,
8159}
8160
8161impl CurrentThreadTaskDriverRegistry {
8162  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8163    let id = next_unique_id(
8164      &self.next_id,
8165      "CurrentThread task driver id space exhausted",
8166    );
8167    let id = CurrentThreadTaskDriverId(id);
8168    self
8169      .state
8170      .lock()
8171      .unwrap_or_else(std::sync::PoisonError::into_inner)
8172      .insert_driver(id, driver);
8173    id
8174  }
8175
8176  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8177    let (removed, failed_dispatches) = {
8178      let mut state = self
8179        .state
8180        .lock()
8181        .unwrap_or_else(std::sync::PoisonError::into_inner);
8182      let removed = state.remove_driver(id);
8183      let mut failed_dispatches = Vec::new();
8184      if let Some(entry) = &removed {
8185        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8186      }
8187      (removed, failed_dispatches)
8188    };
8189    let _ = catch_unwind_contained(|| drop(removed));
8190    CurrentThreadTaskDeliveryCompletion {
8191      failed_dispatches,
8192      redispatch: false,
8193    }
8194  }
8195
8196  fn live(&self) -> CurrentThreadTaskDriverSelection {
8197    let mut failed_dispatches = Vec::new();
8198    loop {
8199      let snapshot = CurrentThreadTaskDriverSnapshot::new({
8200        let state = self
8201          .state
8202          .lock()
8203          .unwrap_or_else(std::sync::PoisonError::into_inner);
8204        state.ordered_snapshot()
8205      });
8206      let mut dead_ids = FxHashSet::default();
8207      let mut live_ids = Vec::new();
8208      for (id, driver) in snapshot.as_slice() {
8209        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8210          live_ids.push(*id);
8211        } else {
8212          dead_ids.insert(*id);
8213        }
8214      }
8215
8216      let (live, swept, retry) = {
8217        let mut state = self
8218          .state
8219          .lock()
8220          .unwrap_or_else(std::sync::PoisonError::into_inner);
8221        let mut swept = Vec::new();
8222        let mut current = state.oldest;
8223        while let Some(id) = current {
8224          let next = state
8225            .entries
8226            .get(&id)
8227            .expect("CurrentThread task driver order must reference a live entry")
8228            .next;
8229          if dead_ids.contains(&id)
8230            && let Some(entry) = state.remove_driver(id)
8231          {
8232            swept.push(entry);
8233          }
8234          current = next;
8235        }
8236        for entry in &swept {
8237          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8238        }
8239        let retry = !state.ordered_ids_match(&live_ids);
8240        let live = if retry {
8241          Vec::new()
8242        } else {
8243          state.ordered_snapshot()
8244        };
8245        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8246      };
8247
8248      // A reentrant probe can unregister multiple snapshot-only drivers.
8249      // See the implementation notes in this crate's README.
8250      drop(snapshot);
8251      for entry in swept {
8252        let _ = catch_unwind_contained(|| entry.driver.on_swept());
8253        let _ = catch_unwind_contained(|| drop(entry));
8254      }
8255      if !retry {
8256        return CurrentThreadTaskDriverSelection {
8257          live,
8258          failed_dispatches,
8259        };
8260      }
8261    }
8262  }
8263
8264  #[cfg(test)]
8265  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8266    let mut live = self.live().live;
8267    live.pop().map(|(id, driver)| (id, driver.into_inner()))
8268  }
8269
8270  fn next_delivery(
8271    &self,
8272    driver: CurrentThreadTaskDriverId,
8273    dispatch: u64,
8274  ) -> CurrentThreadTaskDelivery {
8275    let capability = next_unique_id(
8276      &self.next_delivery,
8277      "CurrentThread task delivery id space exhausted",
8278    )
8279    .checked_add(1)
8280    .expect("CurrentThread task delivery id space exhausted");
8281    CurrentThreadTaskDelivery {
8282      driver,
8283      capability,
8284      dispatch,
8285    }
8286  }
8287
8288  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8289    let dispatch_state = state
8290      .dispatches
8291      .get_mut(&dispatch)
8292      .expect("a referenced CurrentThread dispatch must remain registered");
8293    dispatch_state.references = dispatch_state
8294      .references
8295      .checked_add(1)
8296      .expect("CurrentThread dispatch reference count exhausted");
8297  }
8298
8299  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8300    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8301      return;
8302    };
8303    dispatch_state.references = dispatch_state
8304      .references
8305      .checked_sub(1)
8306      .expect("CurrentThread dispatch reference count underflow");
8307  }
8308
8309  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8310    let publication = {
8311      let mut state = self
8312        .state
8313        .lock()
8314        .unwrap_or_else(std::sync::PoisonError::into_inner);
8315      let dispatch_state = state.dispatches.entry(dispatch).or_default();
8316      if dispatch_state.is_serviced() {
8317        return CurrentThreadTaskDispatchCompletion {
8318          result: CurrentThreadHostDispatchResult::Accepted,
8319          failed_dispatches: Vec::new(),
8320        };
8321      }
8322      if dispatch_state.is_cancelling() {
8323        return CurrentThreadTaskDispatchCompletion {
8324          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8325          failed_dispatches: Vec::new(),
8326        };
8327      }
8328      dispatch_state.publications = dispatch_state
8329        .publications
8330        .checked_add(1)
8331        .expect("CurrentThread dispatch publication count exhausted");
8332      CurrentThreadTaskDispatchPublicationGuard {
8333        registry: self,
8334        dispatch: Some(dispatch),
8335      }
8336    };
8337
8338    let CurrentThreadTaskDriverSelection {
8339      mut live,
8340      mut failed_dispatches,
8341    } = self.live();
8342    let planned_deliveries = {
8343      let mut state = self
8344        .state
8345        .lock()
8346        .unwrap_or_else(std::sync::PoisonError::into_inner);
8347      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8348        return CurrentThreadTaskDispatchCompletion {
8349          result: CurrentThreadHostDispatchResult::Unavailable,
8350          failed_dispatches,
8351        };
8352      };
8353      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8354        Vec::new()
8355      } else {
8356        // Reserve every capability and all output storage before mutating an
8357        // entry. If ID allocation unwinds, the publication guard can retire
8358        // the barrier without leaving an undispatched physical slot occupied.
8359        let mut reserved_deliveries = Vec::with_capacity(live.len());
8360        for (id, _) in live.as_slice().iter().rev() {
8361          let Some(entry) = state.entries.get(id) else {
8362            continue;
8363          };
8364          if entry.delivery.is_none() {
8365            reserved_deliveries.push(self.next_delivery(*id, dispatch));
8366          }
8367        }
8368        state.delivery_drivers.reserve(reserved_deliveries.len());
8369        let mut reserved_deliveries = reserved_deliveries.into_iter();
8370        let mut planned_deliveries = Vec::with_capacity(live.len());
8371        let mut retired = Vec::with_capacity(live.len());
8372        let mut accepted_reservation = false;
8373        for (id, _) in live.as_slice().iter().rev() {
8374          let Some(entry) = state.entries.get(id) else {
8375            continue;
8376          };
8377          if let Some(delivery) = &entry.delivery
8378            && delivery.delivery.dispatch == dispatch
8379          {
8380            accepted_reservation |= delivery.accepted;
8381            continue;
8382          }
8383          if entry.delivery.is_some() {
8384            let previous = state
8385              .entries
8386              .get_mut(id)
8387              .expect("the CurrentThread task driver must remain registered")
8388              .pending
8389              .replace(dispatch);
8390            if previous != Some(dispatch) {
8391              if let Some(previous) = previous {
8392                Self::remove_dispatch_reference(&mut state, previous);
8393              }
8394              Self::add_dispatch_reference(&mut state, dispatch);
8395            }
8396            retired.push(previous);
8397            accepted_reservation = true;
8398            continue;
8399          }
8400          let delivery = reserved_deliveries
8401            .next()
8402            .expect("every available CurrentThread host must have a reserved delivery");
8403          let pending = state
8404            .entries
8405            .get_mut(id)
8406            .expect("the CurrentThread task driver must remain registered")
8407            .pending
8408            .take();
8409          let accepted = pending == Some(dispatch);
8410          if !accepted && pending.is_some() {
8411            retired.push(pending);
8412          }
8413          match pending {
8414            Some(pending) if pending == dispatch => {}
8415            Some(pending) => {
8416              Self::remove_dispatch_reference(&mut state, pending);
8417              Self::add_dispatch_reference(&mut state, dispatch);
8418            }
8419            None => Self::add_dispatch_reference(&mut state, dispatch),
8420          }
8421          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8422          assert!(
8423            indexed.is_none(),
8424            "CurrentThread delivery capability must be unique"
8425          );
8426          state
8427            .entries
8428            .get_mut(id)
8429            .expect("the CurrentThread task driver must remain registered")
8430            .delivery = Some(CurrentThreadTaskDeliveryState {
8431            delivery,
8432            claimed: false,
8433            accepted,
8434          });
8435          accepted_reservation |= accepted;
8436          planned_deliveries.push((*id, delivery));
8437        }
8438        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8439        debug_assert!(
8440          all_reserved_deliveries_installed,
8441          "every reserved CurrentThread delivery must be installed"
8442        );
8443        if accepted_reservation {
8444          state
8445            .dispatches
8446            .get_mut(&dispatch)
8447            .expect("the publishing dispatch state must remain registered")
8448            .accepted = true;
8449        }
8450        for dispatch in retired.into_iter().flatten() {
8451          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8452        }
8453        planned_deliveries
8454      }
8455    };
8456
8457    // Pair newest-first delivery plans with contained driver owners after
8458    // releasing the registry lock. Normal, rejection, and unwind cleanup can
8459    // then destroy every selected driver independently without re-entering the
8460    // registry under its mutex.
8461    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8462    let mut planned_deliveries = planned_deliveries.into_iter();
8463    let mut next_delivery = planned_deliveries.next();
8464    while let Some((id, driver)) = live.pop() {
8465      let Some((planned_id, delivery)) = next_delivery else {
8466        break;
8467      };
8468      if planned_id == id {
8469        deliveries.push((id, driver, delivery));
8470        next_delivery = planned_deliveries.next();
8471      } else {
8472        next_delivery = Some((planned_id, delivery));
8473      }
8474    }
8475    debug_assert!(
8476      next_delivery.is_none() && planned_deliveries.len() == 0,
8477      "every planned CurrentThread delivery must retain its selected driver"
8478    );
8479
8480    for (id, driver, delivery) in deliveries {
8481      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8482        self.accept_delivery(id, delivery);
8483      } else {
8484        let (entry, failures) = self.reject_delivery(id, delivery);
8485        failed_dispatches.extend(failures);
8486        if let Some(entry) = entry {
8487          let _ = catch_unwind_contained(|| entry.driver.on_swept());
8488          let _ = catch_unwind_contained(|| drop(entry));
8489        }
8490      }
8491    }
8492
8493    let result = publication.finish();
8494    debug_assert!(
8495      !failed_dispatches
8496        .iter()
8497        .any(|failure| failure.dispatch == dispatch),
8498      "the active publication must report its own terminal outcome directly"
8499    );
8500    CurrentThreadTaskDispatchCompletion {
8501      result,
8502      failed_dispatches,
8503    }
8504  }
8505
8506  #[cfg(test)]
8507  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8508    self.dispatch_with_completion(dispatch).result
8509  }
8510
8511  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8512    let mut state = self
8513      .state
8514      .lock()
8515      .unwrap_or_else(std::sync::PoisonError::into_inner);
8516    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8517      entry
8518        .delivery
8519        .as_ref()
8520        .is_some_and(|current| current.delivery == delivery)
8521    }) {
8522      state
8523        .entries
8524        .get_mut(&id)
8525        .expect("the matching CurrentThread task driver must remain registered")
8526        .delivery
8527        .as_mut()
8528        .expect("the matching delivery must remain installed")
8529        .accepted = true;
8530      true
8531    } else {
8532      // A callback may complete or unregister synchronously before
8533      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
8534      // proves acceptance; if no host serviced it, losing the reference is an
8535      // accepted delivery failure retained behind this publication barrier.
8536      false
8537    };
8538    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8539      dispatch_state.accepted = true;
8540      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8541    }
8542  }
8543
8544  fn reject_delivery(
8545    &self,
8546    id: CurrentThreadTaskDriverId,
8547    delivery: CurrentThreadTaskDelivery,
8548  ) -> (
8549    Option<CurrentThreadTaskDriverEntry>,
8550    Vec<CurrentThreadTaskDispatchFailure>,
8551  ) {
8552    let mut state = self
8553      .state
8554      .lock()
8555      .unwrap_or_else(std::sync::PoisonError::into_inner);
8556    if !state.entries.get(&id).is_some_and(|entry| {
8557      entry
8558        .delivery
8559        .as_ref()
8560        .is_some_and(|current| current.delivery == delivery)
8561    }) {
8562      return (None, Vec::new());
8563    }
8564    let entry = state
8565      .remove_driver(id)
8566      .expect("the matching CurrentThread task driver must remain registered");
8567    let mut failures = Vec::new();
8568    Self::finish_entry_references(&mut state, &entry, &mut failures);
8569    (Some(entry), failures)
8570  }
8571
8572  fn finish_delivery(
8573    &self,
8574    delivery: CurrentThreadTaskDelivery,
8575    failed: bool,
8576  ) -> CurrentThreadTaskDeliveryCompletion {
8577    let mut state = self
8578      .state
8579      .lock()
8580      .unwrap_or_else(std::sync::PoisonError::into_inner);
8581    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8582      entry
8583        .delivery
8584        .as_ref()
8585        .is_some_and(|current| current.delivery == delivery)
8586    }) {
8587      return CurrentThreadTaskDeliveryCompletion::default();
8588    }
8589    let current = state
8590      .entries
8591      .get_mut(&delivery.driver)
8592      .expect("the matching CurrentThread task driver must remain registered")
8593      .delivery
8594      .take()
8595      .expect("the matching delivery must remain installed");
8596    let indexed = state.delivery_drivers.remove(&delivery.capability);
8597    debug_assert_eq!(
8598      indexed,
8599      Some(delivery.driver),
8600      "delivery capability index must match its driver"
8601    );
8602    let redispatch = state
8603      .entries
8604      .get(&delivery.driver)
8605      .expect("the matching CurrentThread task driver must remain registered")
8606      .pending
8607      .is_some_and(|dispatch| {
8608        state
8609          .dispatches
8610          .get(&dispatch)
8611          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8612      });
8613    let mut failed_dispatches = Vec::new();
8614    // Callback completion proves only that JavaScript ran. A success return is
8615    // authoritative only after `drive_current_thread_tasks` claimed this exact
8616    // host attempt; otherwise a no-op or malicious callback could retire the
8617    // registry reference while leaving the executor dispatch pending forever.
8618    Self::finish_dispatch_reference(
8619      &mut state,
8620      delivery.dispatch,
8621      true,
8622      failed || !current.claimed,
8623      &mut failed_dispatches,
8624    );
8625    CurrentThreadTaskDeliveryCompletion {
8626      failed_dispatches,
8627      redispatch,
8628    }
8629  }
8630
8631  fn claim_delivery(&self, capability: u64) -> Option<u64> {
8632    let mut state = self
8633      .state
8634      .lock()
8635      .unwrap_or_else(std::sync::PoisonError::into_inner);
8636    let driver = *state.delivery_drivers.get(&capability)?;
8637    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8638    if delivery.capability != capability {
8639      return None;
8640    }
8641    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8642    if dispatch_state.is_serviced()
8643      || dispatch_state.is_cancelling()
8644      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8645    {
8646      return None;
8647    }
8648    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8649    current.claimed = true;
8650    current.accepted = true;
8651    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8652    Some(delivery.dispatch)
8653  }
8654
8655  fn mark_serviced(&self, dispatch: u64) {
8656    let mut state = self
8657      .state
8658      .lock()
8659      .unwrap_or_else(std::sync::PoisonError::into_inner);
8660    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8661      return;
8662    };
8663    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8664    let mut removed_references = 0usize;
8665    for entry in state.entries.values_mut() {
8666      if entry.pending == Some(dispatch) {
8667        entry.pending = None;
8668        removed_references = removed_references
8669          .checked_add(1)
8670          .expect("CurrentThread dispatch reference count exhausted");
8671      }
8672    }
8673    let dispatch_state = state
8674      .dispatches
8675      .get_mut(&dispatch)
8676      .expect("the serviced CurrentThread dispatch must remain registered");
8677    dispatch_state.references = dispatch_state
8678      .references
8679      .checked_sub(removed_references)
8680      .expect("CurrentThread dispatch reference count underflow");
8681    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8682  }
8683
8684  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8685    let delivery = {
8686      let state = self
8687        .state
8688        .lock()
8689        .unwrap_or_else(std::sync::PoisonError::into_inner);
8690      state
8691        .delivery_drivers
8692        .get(&capability)
8693        .and_then(|driver| state.entries.get(driver))
8694        .and_then(|entry| entry.delivery.as_ref())
8695        .map(|delivery| delivery.delivery)
8696        .filter(|delivery| delivery.capability == capability)
8697    };
8698    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8699      self.finish_delivery(delivery, true)
8700    })
8701  }
8702
8703  fn finish_dispatch_reference(
8704    state: &mut CurrentThreadTaskDriverRegistryState,
8705    dispatch: u64,
8706    accepted: bool,
8707    failed: bool,
8708    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8709  ) {
8710    let (publications, references) = {
8711      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8712        return;
8713      };
8714      dispatch_state.references = dispatch_state
8715        .references
8716        .checked_sub(1)
8717        .expect("CurrentThread dispatch reference count underflow");
8718      dispatch_state.accepted |= accepted;
8719      dispatch_state.accepted_failure |= accepted && failed;
8720      (dispatch_state.publications, dispatch_state.references)
8721    };
8722    if references != 0 || publications != 0 {
8723      return;
8724    }
8725    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8726      return;
8727    };
8728    if dispatch_state.accepted_failure
8729      && !dispatch_state.is_serviced()
8730      && !dispatch_state.is_cancelling()
8731    {
8732      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8733      failures.push(dispatch_state.failure(dispatch));
8734    } else if !dispatch_state.is_cancelling() {
8735      state.dispatches.remove(&dispatch);
8736    }
8737  }
8738
8739  fn finish_entry_references(
8740    state: &mut CurrentThreadTaskDriverRegistryState,
8741    entry: &CurrentThreadTaskDriverEntry,
8742    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8743  ) {
8744    if let Some(delivery) = &entry.delivery {
8745      Self::finish_dispatch_reference(
8746        state,
8747        delivery.delivery.dispatch,
8748        delivery.accepted,
8749        delivery.accepted,
8750        failures,
8751      );
8752    }
8753    if let Some(pending) = entry.pending {
8754      Self::finish_dispatch_reference(state, pending, true, true, failures);
8755    }
8756  }
8757
8758  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8759    let mut state = self
8760      .state
8761      .lock()
8762      .unwrap_or_else(std::sync::PoisonError::into_inner);
8763    let publications = {
8764      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8765        return CurrentThreadHostDispatchResult::Unavailable;
8766      };
8767      dispatch_state.publications = dispatch_state
8768        .publications
8769        .checked_sub(1)
8770        .expect("CurrentThread dispatch publication count underflow");
8771      dispatch_state.publications
8772    };
8773    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8774      return CurrentThreadHostDispatchResult::Accepted;
8775    }
8776    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8777      return CurrentThreadHostDispatchResult::Unavailable;
8778    };
8779    if dispatch_state.is_cancelling() {
8780      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8781    }
8782    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8783      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8784      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8785    }
8786    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8787    state.dispatches.remove(&dispatch);
8788    if accepted {
8789      CurrentThreadHostDispatchResult::Accepted
8790    } else {
8791      CurrentThreadHostDispatchResult::Unavailable
8792    }
8793  }
8794
8795  fn abort_publication(&self, dispatch: u64) {
8796    let mut state = self
8797      .state
8798      .lock()
8799      .unwrap_or_else(std::sync::PoisonError::into_inner);
8800    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8801      return;
8802    };
8803    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8804      return;
8805    };
8806    dispatch_state.publications = publications;
8807    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8808  }
8809
8810  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8811    let mut state = self
8812      .state
8813      .lock()
8814      .unwrap_or_else(std::sync::PoisonError::into_inner);
8815    let can_rearm = state
8816      .dispatches
8817      .get(&failure.dispatch)
8818      .is_some_and(|dispatch_state| {
8819        dispatch_state.is_cancelling()
8820          && dispatch_state.epoch == failure.epoch
8821          && dispatch_state.publications == 0
8822      })
8823      && !Self::has_dispatch_reference(&state, failure.dispatch);
8824    if !can_rearm {
8825      return false;
8826    }
8827    let dispatch_state = state
8828      .dispatches
8829      .get_mut(&failure.dispatch)
8830      .expect("the exact failed CurrentThread dispatch must remain registered");
8831    let epoch = dispatch_state
8832      .epoch
8833      .checked_add(1)
8834      .expect("CurrentThread dispatch failure epoch space exhausted");
8835    *dispatch_state = CurrentThreadDispatchState {
8836      epoch,
8837      ..CurrentThreadDispatchState::default()
8838    };
8839    true
8840  }
8841
8842  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8843    self
8844      .state
8845      .lock()
8846      .unwrap_or_else(std::sync::PoisonError::into_inner)
8847      .dispatches
8848      .get(&failure.dispatch)
8849      .is_some_and(|dispatch_state| {
8850        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8851      })
8852  }
8853
8854  fn retire_unreferenced_dispatch(
8855    state: &mut CurrentThreadTaskDriverRegistryState,
8856    dispatch: Option<u64>,
8857  ) {
8858    let Some(dispatch) = dispatch else {
8859      return;
8860    };
8861    if Self::has_dispatch_reference(state, dispatch) {
8862      return;
8863    }
8864    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8865      return;
8866    };
8867    if dispatch_state.publications == 0
8868      && !dispatch_state.is_cancelling()
8869      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8870    {
8871      state.dispatches.remove(&dispatch);
8872    }
8873  }
8874
8875  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8876    state
8877      .dispatches
8878      .get(&dispatch)
8879      .is_some_and(|dispatch_state| dispatch_state.references != 0)
8880  }
8881
8882  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8883    let mut state = self
8884      .state
8885      .lock()
8886      .unwrap_or_else(std::sync::PoisonError::into_inner);
8887    if state
8888      .dispatches
8889      .get(&failure.dispatch)
8890      .is_some_and(|dispatch_state| {
8891        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8892      })
8893    {
8894      state.dispatches.remove(&failure.dispatch);
8895    }
8896  }
8897
8898  fn retire_for_shutdown(&self) {
8899    let mut state = self
8900      .state
8901      .lock()
8902      .unwrap_or_else(std::sync::PoisonError::into_inner);
8903    state.dispatches.clear();
8904    for entry in state.entries.values_mut() {
8905      entry.pending = None;
8906    }
8907  }
8908}
8909
8910static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8911  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8912
8913pub fn register_current_thread_task_driver(
8914  driver: Arc<dyn CurrentThreadTaskDriver>,
8915) -> CurrentThreadTaskDriverId {
8916  CURRENT_THREAD_TASK_DRIVERS.register(driver)
8917}
8918
8919/// Request service for work that may have accumulated while no live host
8920/// dispatcher was registered. A new host receives the existing internal
8921/// dispatch capability through a fresh registration-scoped delivery token;
8922/// already accepting hosts keep their attempts.
8923pub fn request_current_thread_task_drain() {
8924  RUNTIME.request_current_thread_drain();
8925}
8926
8927pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8928  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8929}
8930
8931fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8932  let CurrentThreadTaskDispatchCompletion {
8933    result,
8934    failed_dispatches,
8935  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8936  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8937    failed_dispatches,
8938    redispatch: false,
8939  });
8940  result
8941}
8942
8943pub type TimerId = u64;
8944
8945/// Process-global id source for [`Sleep`] futures (both drivers).
8946static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8947
8948/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
8949/// helper thread on a threadless build, so it delegates
8950/// `sleep_until` to the host event loop through this trait. The Node binding
8951/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
8952/// at import -- one per importing napi env (main thread AND workers).
8953pub trait TimerDriver: Send + Sync + 'static {
8954  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
8955  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
8956  /// waker refresh, not a second timer.
8957  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8958  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
8959  /// raced ahead is acceptable (the woken task observes the sleep completed).
8960  fn cancel(&self, id: TimerId);
8961  /// Whether this driver can still deliver wakes. `false` once the driver's
8962  /// host is gone (the Node binding's driver dies with its owning napi env:
8963  /// worker exit aborts the weak threadsafe function). Selection skips and
8964  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
8965  fn is_live(&self) -> bool {
8966    true
8967  }
8968  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
8969  /// entries (its `is_live` turned false), with NO registry lock held.
8970  /// Implementations with pending-waker bookkeeping must wake everything
8971  /// armed on them here, so those sleeps re-poll onto the next live
8972  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
8973  /// host (before its env-cleanup hook or any call failure runs), and a
8974  /// silent `retain` would strand sleeps whose only wake source was the swept
8975  /// driver. Idempotent with the owner's other eviction paths. Default no-op
8976  /// for drivers without waker state.
8977  fn on_swept(&self) {}
8978}
8979
8980/// Handle to one registration in a [`TimerDriverRegistry`], returned by
8981/// [`TimerDriverRegistry::register`] and consumed by
8982/// [`TimerDriverRegistry::unregister`].
8983#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8984pub struct TimerDriverId(u64);
8985
8986type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8987
8988struct TimerDriverEntry {
8989  driver: Arc<dyn TimerDriver>,
8990  previous: Option<TimerDriverId>,
8991  next: Option<TimerDriverId>,
8992}
8993
8994#[derive(Default)]
8995struct TimerDriverRegistryState {
8996  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8997  oldest: Option<TimerDriverId>,
8998  newest: Option<TimerDriverId>,
8999}
9000
9001impl TimerDriverRegistryState {
9002  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
9003    let previous = self.newest;
9004    let replaced = self.entries.insert(
9005      id,
9006      TimerDriverEntry {
9007        driver,
9008        previous,
9009        next: None,
9010      },
9011    );
9012    assert!(replaced.is_none(), "timer driver id must be unique");
9013    if let Some(previous) = previous {
9014      self
9015        .entries
9016        .get_mut(&previous)
9017        .expect("the newest timer driver must remain registered")
9018        .next = Some(id);
9019    } else {
9020      self.oldest = Some(id);
9021    }
9022    self.newest = Some(id);
9023  }
9024
9025  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
9026    let entry = self.entries.remove(&id)?;
9027    if let Some(previous) = entry.previous {
9028      self
9029        .entries
9030        .get_mut(&previous)
9031        .expect("the previous timer driver must remain registered")
9032        .next = entry.next;
9033    } else {
9034      self.oldest = entry.next;
9035    }
9036    if let Some(next) = entry.next {
9037      self
9038        .entries
9039        .get_mut(&next)
9040        .expect("the next timer driver must remain registered")
9041        .previous = entry.previous;
9042    } else {
9043      self.newest = entry.previous;
9044    }
9045    Some(entry)
9046  }
9047
9048  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
9049    let mut snapshot = Vec::with_capacity(self.entries.len());
9050    let mut current = self.oldest;
9051    while let Some(id) = current {
9052      let entry = self
9053        .entries
9054        .get(&id)
9055        .expect("timer driver order must reference a live entry");
9056      snapshot.push((id, Arc::clone(&entry.driver)));
9057      current = entry.next;
9058    }
9059    debug_assert_eq!(
9060      snapshot.len(),
9061      self.entries.len(),
9062      "timer driver order must contain every entry exactly once"
9063    );
9064    snapshot
9065  }
9066
9067  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
9068    if self.entries.len() != expected.len() {
9069      return false;
9070    }
9071    let mut current = self.oldest;
9072    for expected in expected {
9073      if current != Some(*expected) {
9074        return false;
9075      }
9076      current = self
9077        .entries
9078        .get(expected)
9079        .expect("timer driver order must reference a live entry")
9080        .next;
9081    }
9082    current.is_none()
9083  }
9084}
9085
9086/// Host timer drivers keyed by registration order. A driver is owned by a host
9087/// context that can die independently
9088/// of the process -- the Node binding registers one driver per importing napi
9089/// env, and a worker's env teardown kills its driver's threadsafe function. A
9090/// single first-wins slot is therefore unsound: a worker that imports the
9091/// binding first and then exits would permanently shadow the slot with a dead
9092/// driver, and every CT timer armed afterwards would busy-fail against it.
9093/// Selecting only the newest live driver is also
9094/// insufficient: a live worker can be CPU-blocked indefinitely while another
9095/// environment remains responsive. The registry therefore keeps all
9096/// registrants and returns a stable snapshot of every live driver at each
9097/// [`Sleep`] poll, sweeping dead entries out as it goes.
9098///
9099/// Instance-based (not a hidden global) so tests exercise
9100/// registration/eviction/selection against local registries; production uses
9101/// the process-global one behind
9102/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
9103#[derive(Default)]
9104pub struct TimerDriverRegistry {
9105  /// Keyed lookup with a linked registration order. Exact unregister and a
9106  /// swept binding host's idempotent unregister are expected O(1), while
9107  /// snapshots keep deterministic oldest-to-newest fallback order.
9108  state: Mutex<TimerDriverRegistryState>,
9109  /// Stable repoll signals for every currently polled host sleep. A newly
9110  /// registered driver snapshots these after publishing itself, then wakes
9111  /// them outside both registry locks so each sleep re-arms the same timer id
9112  /// on the expanded all-live driver set.
9113  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
9114  next_id: AtomicU64,
9115}
9116
9117impl Drop for TimerDriverRegistry {
9118  fn drop(&mut self) {
9119    let state = self
9120      .state
9121      .get_mut()
9122      .unwrap_or_else(std::sync::PoisonError::into_inner);
9123    state.oldest = None;
9124    state.newest = None;
9125    let entries = std::mem::take(&mut state.entries);
9126    for (_, entry) in entries {
9127      let _ = catch_unwind_contained(|| drop(entry));
9128    }
9129  }
9130}
9131
9132impl TimerDriverRegistry {
9133  /// Add `driver` as the newest registrant and return its handle.
9134  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9135    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9136    let id = TimerDriverId(id);
9137    self
9138      .state
9139      .lock()
9140      .unwrap_or_else(std::sync::PoisonError::into_inner)
9141      .insert_driver(id, driver);
9142    // Publish the driver before snapshotting pending sleeps. A sleep publishes
9143    // its repoll signal before selecting live drivers. Therefore either this
9144    // snapshot sees the sleep, or that sleep's subsequent selection sees this
9145    // driver; no registration/insertion interleaving can miss both sides.
9146    let pending = {
9147      let mut entries = self
9148        .pending
9149        .lock()
9150        .unwrap_or_else(std::sync::PoisonError::into_inner);
9151      let mut pending = Vec::with_capacity(entries.len());
9152      entries.retain(|_, repoll| {
9153        if let Some(repoll) = repoll.upgrade() {
9154          pending.push(repoll);
9155          true
9156        } else {
9157          false
9158        }
9159      });
9160      pending
9161    };
9162    for repoll in pending {
9163      repoll.wake();
9164    }
9165    id
9166  }
9167
9168  /// Drop the registration behind `id` (no-op when already swept/removed).
9169  pub fn unregister(&self, id: TimerDriverId) {
9170    let removed = self
9171      .state
9172      .lock()
9173      .unwrap_or_else(std::sync::PoisonError::into_inner)
9174      .remove_driver(id);
9175    // A driver's destructor may re-enter this registry. Keep it outside the
9176    // entries lock just like is_live/on_swept callbacks.
9177    let _ = catch_unwind_contained(|| drop(removed));
9178  }
9179
9180  /// Every LIVE driver in registration order, sweeping dead entries out as a
9181  /// side effect.
9182  ///
9183  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
9184  /// lock held. `is_live` is externally implemented and may re-enter this
9185  /// registry; `on_swept` does so in the binding by calling `unregister` and
9186  /// waking pending sleeps whose re-polls call `current()` again. Selection
9187  /// therefore probes a snapshot, removes dead entries under the lock, then
9188  /// runs hooks after release. A concurrent registration/unregistration can
9189  /// stale the snapshot, in which case selection retries.
9190  fn live(&self) -> HostTimerDriverSnapshot {
9191    loop {
9192      let snapshot = HostTimerDriverSnapshot::new({
9193        let state = self
9194          .state
9195          .lock()
9196          .unwrap_or_else(std::sync::PoisonError::into_inner);
9197        state.ordered_snapshot()
9198      });
9199      let mut dead_ids = FxHashSet::default();
9200      let mut live_ids = Vec::new();
9201      for (id, driver) in snapshot.as_slice() {
9202        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9203          live_ids.push(*id);
9204        } else {
9205          dead_ids.insert(*id);
9206        }
9207      }
9208
9209      let (live, swept, retry) = {
9210        let mut state = self
9211          .state
9212          .lock()
9213          .unwrap_or_else(std::sync::PoisonError::into_inner);
9214        let mut swept = Vec::new();
9215        let mut current = state.oldest;
9216        while let Some(id) = current {
9217          let next = state
9218            .entries
9219            .get(&id)
9220            .expect("timer driver order must reference a live entry")
9221            .next;
9222          if dead_ids.contains(&id)
9223            && let Some(entry) = state.remove_driver(id)
9224          {
9225            swept.push(entry);
9226          }
9227          current = next;
9228        }
9229        let retry = !state.ordered_ids_match(&live_ids);
9230        let live = if retry {
9231          Vec::new()
9232        } else {
9233          state.ordered_snapshot()
9234        };
9235        (HostTimerDriverSnapshot::new(live), swept, retry)
9236      };
9237
9238      // A reentrant probe can unregister multiple snapshot-only drivers.
9239      // See the implementation notes in this crate's README.
9240      drop(snapshot);
9241      for entry in swept {
9242        let _ = catch_unwind_contained(|| entry.driver.on_swept());
9243        let _ = catch_unwind_contained(|| drop(entry));
9244      }
9245      if !retry {
9246        return live;
9247      }
9248    }
9249  }
9250
9251  #[cfg(test)]
9252  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9253    let mut live = self.live();
9254    live.pop().map(|(id, driver)| (id, driver.into_inner()))
9255  }
9256
9257  /// Whether a LIVE driver is currently registered (dead-only counts as no).
9258  pub fn has_live_driver(&self) -> bool {
9259    !self.live().is_empty()
9260  }
9261
9262  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9263    let replaced = self
9264      .pending
9265      .lock()
9266      .unwrap_or_else(std::sync::PoisonError::into_inner)
9267      .insert(id, Arc::downgrade(repoll));
9268    assert!(
9269      replaced.is_none(),
9270      "host timer {id} registered its repoll signal twice"
9271    );
9272    // Shutdown can close and try to remove the signal immediately before this
9273    // insertion. The post-insert check is the other side of that race.
9274    if repoll.is_closed() {
9275      self.remove_pending(id);
9276    }
9277  }
9278
9279  fn remove_pending(&self, id: TimerId) {
9280    let removed = self
9281      .pending
9282      .lock()
9283      .unwrap_or_else(std::sync::PoisonError::into_inner)
9284      .remove(&id);
9285    let _ = catch_unwind_contained(|| drop(removed));
9286  }
9287
9288  #[cfg(test)]
9289  fn pending_len(&self) -> usize {
9290    self
9291      .pending
9292      .lock()
9293      .unwrap_or_else(std::sync::PoisonError::into_inner)
9294      .len()
9295  }
9296}
9297
9298/// The process-global registry behind [`sleep_until`] (see
9299/// [`TimerDriverRegistry`] for the lifetime story).
9300static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9301  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9302
9303/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
9304/// Every host context that can serve timers registers its own driver (the
9305/// Node binding: one per importing napi env); every LIVE registrant receives
9306/// each timer so a starved environment cannot own its only wake source.
9307/// Returns the handle for [`unregister_timer_driver`].
9308pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9309  TIMER_DRIVERS.register(driver)
9310}
9311
9312/// Remove a driver registered via [`register_timer_driver`] -- called when
9313/// its host context dies (the Node binding evicts on env teardown and on
9314/// callback failure). Idempotent.
9315pub fn unregister_timer_driver(id: TimerDriverId) {
9316  TIMER_DRIVERS.unregister(id);
9317}
9318
9319/// Whether a LIVE host timer driver is currently registered. Lets the
9320/// embedder report CurrentThread timer availability honestly (e.g. through a
9321/// capabilities export) instead of guessing: with no
9322/// live driver a CurrentThread `sleep_until` would panic, so the capability
9323/// must read `false` -- including when the only registrants are DEAD (their
9324/// owning envs torn down), not merely when none ever registered.
9325pub fn has_live_timer_driver() -> bool {
9326  TIMER_DRIVERS.has_live_driver()
9327}
9328
9329/// Coalescing wake signal used only to make an already-pending [`Sleep`]
9330/// re-select host drivers after a new registration. The task's arbitrary
9331/// waker is never cloned, invoked, or dropped while this mutex is held.
9332#[derive(Default)]
9333struct HostTimerRepoll {
9334  state: Mutex<HostTimerRepollState>,
9335}
9336
9337#[derive(Default)]
9338struct HostTimerRepollState {
9339  closed: bool,
9340  notified: bool,
9341  waker: Option<Waker>,
9342}
9343
9344impl HostTimerRepoll {
9345  fn register(&self, waker: Waker) {
9346    let (replaced, wake) = {
9347      let mut state = self
9348        .state
9349        .lock()
9350        .unwrap_or_else(std::sync::PoisonError::into_inner);
9351      if state.closed {
9352        (Some(waker), None)
9353      } else if state.notified {
9354        state.notified = false;
9355        (state.waker.take(), Some(waker))
9356      } else {
9357        (state.waker.replace(waker), None)
9358      }
9359    };
9360    let _ = catch_unwind_contained(|| drop(replaced));
9361    if let Some(waker) = wake {
9362      wake_waker_contained(waker);
9363    }
9364  }
9365
9366  fn wake(&self) {
9367    let waker = {
9368      let mut state = self
9369        .state
9370        .lock()
9371        .unwrap_or_else(std::sync::PoisonError::into_inner);
9372      if state.closed {
9373        return;
9374      }
9375      if let Some(waker) = state.waker.take() {
9376        Some(waker)
9377      } else {
9378        state.notified = true;
9379        None
9380      }
9381    };
9382    if let Some(waker) = waker {
9383      wake_waker_contained(waker);
9384    }
9385  }
9386
9387  fn close(&self) {
9388    let waker = {
9389      let mut state = self
9390        .state
9391        .lock()
9392        .unwrap_or_else(std::sync::PoisonError::into_inner);
9393      state.closed = true;
9394      state.notified = false;
9395      state.waker.take()
9396    };
9397    let _ = catch_unwind_contained(|| drop(waker));
9398  }
9399
9400  fn is_closed(&self) -> bool {
9401    self
9402      .state
9403      .lock()
9404      .unwrap_or_else(std::sync::PoisonError::into_inner)
9405      .closed
9406  }
9407}
9408
9409/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
9410/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
9411/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
9412/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
9413/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
9414/// detection -- see the interaction notes at the top of this section.
9415#[derive(Default)]
9416struct HostTimerRegistry {
9417  state: Mutex<HostTimerRegistryState>,
9418}
9419
9420#[derive(Default)]
9421struct HostTimerRegistryState {
9422  closed: bool,
9423  timers: FxHashMap<TimerId, HostTimerEntry>,
9424}
9425
9426struct HostTimerEntry {
9427  deadline: Instant,
9428  waker: Waker,
9429  fired: Arc<AtomicBool>,
9430  drivers: Weak<TimerDriverRegistry>,
9431  repoll: Arc<HostTimerRepoll>,
9432}
9433
9434impl HostTimerRegistry {
9435  fn register(
9436    &self,
9437    id: TimerId,
9438    deadline: Instant,
9439    waker: Waker,
9440    fired: &Arc<AtomicBool>,
9441    drivers: &Arc<TimerDriverRegistry>,
9442    repoll: &Arc<HostTimerRepoll>,
9443  ) -> bool {
9444    let replaced = {
9445      let mut state = self
9446        .state
9447        .lock()
9448        .unwrap_or_else(std::sync::PoisonError::into_inner);
9449      if state.closed || fired.load(Ordering::SeqCst) {
9450        fired.store(true, Ordering::SeqCst);
9451        Err(waker)
9452      } else {
9453        Ok(state.timers.insert(
9454          id,
9455          HostTimerEntry {
9456            deadline,
9457            waker,
9458            fired: Arc::clone(fired),
9459            drivers: Arc::downgrade(drivers),
9460            repoll: Arc::clone(repoll),
9461          },
9462        ))
9463      }
9464    };
9465    match replaced {
9466      Ok(Some(entry)) => {
9467        let _ = catch_unwind_contained(|| drop(entry));
9468        true
9469      }
9470      Ok(None) => true,
9471      Err(waker) => {
9472        let _ = catch_unwind_contained(|| drop(waker));
9473        false
9474      }
9475    }
9476  }
9477
9478  fn remove(&self, id: TimerId) {
9479    let removed = self
9480      .state
9481      .lock()
9482      .unwrap_or_else(std::sync::PoisonError::into_inner)
9483      .timers
9484      .remove(&id);
9485    if let Some(entry) = removed {
9486      let _ = catch_unwind_contained(|| drop(entry));
9487    }
9488  }
9489
9490  fn shutdown(&self) {
9491    let timers = {
9492      let mut state = self
9493        .state
9494        .lock()
9495        .unwrap_or_else(std::sync::PoisonError::into_inner);
9496      state.closed = true;
9497      state
9498        .timers
9499        .drain()
9500        .map(|(id, entry)| {
9501          entry.fired.store(true, Ordering::SeqCst);
9502          (id, entry)
9503        })
9504        .collect::<Vec<_>>()
9505    };
9506    for (id, entry) in timers {
9507      entry.repoll.close();
9508      if let Some(drivers) = entry.drivers.upgrade() {
9509        drivers.remove_pending(id);
9510      }
9511      wake_waker_contained(entry.waker);
9512    }
9513  }
9514
9515  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
9516  /// only to pick the diagnostic variant: on a threadless build the park is
9517  /// provably dead either way (the host relay shares the parked thread), but
9518  /// a pending timer means the timer-backed message applies.
9519  fn has_pending(&self) -> bool {
9520    !self
9521      .state
9522      .lock()
9523      .unwrap_or_else(std::sync::PoisonError::into_inner)
9524      .timers
9525      .is_empty()
9526  }
9527
9528  /// Any host timer whose deadline (plus `grace` for host firing lag) is
9529  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
9530  /// timer with zero progress is legitimate. A timer past due by more than
9531  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
9532  /// signature the deadline detection exists to catch, so it must not keep
9533  /// suppressing the panic. Callers pass the armed park deadline as `grace`
9534  /// (the detector's own granularity: firing lag within one window is
9535  /// indistinguishable from an expiry racing the fire).
9536  fn has_live_wait(&self, grace: Duration) -> bool {
9537    let now = Instant::now();
9538    self
9539      .state
9540      .lock()
9541      .unwrap_or_else(std::sync::PoisonError::into_inner)
9542      .timers
9543      .values()
9544      .any(|entry| {
9545        entry
9546          .deadline
9547          .checked_add(grace)
9548          .is_none_or(|deadline| deadline > now)
9549      })
9550  }
9551}
9552
9553/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
9554/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
9555/// arm) both retire the wake-token exactly once.
9556struct HostTimerPendingGuard {
9557  registry: Arc<HostTimerRegistry>,
9558  drivers: Arc<TimerDriverRegistry>,
9559  repoll: Arc<HostTimerRepoll>,
9560  id: TimerId,
9561}
9562
9563impl Drop for HostTimerPendingGuard {
9564  fn drop(&mut self) {
9565    self.repoll.close();
9566    self.registry.remove(self.id);
9567    self.drivers.remove_pending(self.id);
9568  }
9569}
9570
9571/// Runtime-owned timer heap for the MultiThread executor.
9572///
9573/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
9574/// Rayon and therefore cannot consume one of the configured CPU workers while
9575/// a long timer is armed.
9576#[cfg(napi_runtime_os_threads)]
9577struct TimerHeap {
9578  inner: Arc<Mutex<TimerHeapInner>>,
9579  parker: Arc<DriverParker>,
9580  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9581  deadlock_state: Arc<MultiThreadDeadlockState>,
9582  #[cfg(test)]
9583  fail_next_start: AtomicBool,
9584  generation: u64,
9585  executor_id: u64,
9586  thread_name: String,
9587}
9588
9589#[cfg(napi_runtime_os_threads)]
9590#[derive(Default)]
9591struct TimerHeapInner {
9592  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
9593  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
9594  /// one later unbounded cleanup pass.
9595  queue: TimerDeadlineQueue,
9596  entries: FxHashMap<TimerId, HeapTimerEntry>,
9597  /// The parker of the dedicated timekeeper while it is waiting on a timer.
9598  /// `register_timer` unparks it when the earliest deadline moves up, so the
9599  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
9600  timekeeper_parker: Option<Arc<DriverParker>>,
9601  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
9602  /// later registrations must fire immediately (a `Sleep` polled after
9603  /// shutdown resolves early instead of parking a closing runtime forever).
9604  closed: bool,
9605}
9606
9607#[cfg(napi_runtime_os_threads)]
9608struct HeapTimerEntry {
9609  waker: Waker,
9610  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
9611  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
9612  /// even when the wall clock has not reached `deadline` (shutdown
9613  /// drain-fire).
9614  fired: Arc<AtomicBool>,
9615}
9616
9617#[cfg(napi_runtime_os_threads)]
9618#[derive(Default)]
9619struct TimerDeadlineQueue {
9620  nodes: Vec<(Instant, TimerId)>,
9621  positions: FxHashMap<TimerId, usize>,
9622}
9623
9624#[cfg(napi_runtime_os_threads)]
9625impl TimerDeadlineQueue {
9626  #[cfg(test)]
9627  fn len(&self) -> usize {
9628    self.nodes.len()
9629  }
9630
9631  #[cfg(test)]
9632  fn is_empty(&self) -> bool {
9633    self.nodes.is_empty()
9634  }
9635
9636  fn clear(&mut self) {
9637    self.nodes.clear();
9638    self.positions.clear();
9639  }
9640
9641  fn peek(&self) -> Option<(Instant, TimerId)> {
9642    self.nodes.first().copied()
9643  }
9644
9645  fn push(&mut self, deadline: Instant, id: TimerId) {
9646    let index = self.nodes.len();
9647    assert!(
9648      self.positions.insert(id, index).is_none(),
9649      "timer deadline queue received duplicate id {id}"
9650    );
9651    self.nodes.push((deadline, id));
9652    self.sift_up(index);
9653  }
9654
9655  fn pop(&mut self) -> Option<(Instant, TimerId)> {
9656    let first = self.nodes.first().copied()?;
9657    self.positions.remove(&first.1);
9658    let last = self
9659      .nodes
9660      .pop()
9661      .expect("the non-empty timer heap must have a final node");
9662    if !self.nodes.is_empty() {
9663      self.nodes[0] = last;
9664      self.positions.insert(last.1, 0);
9665      self.sift_down(0);
9666    }
9667    Some(first)
9668  }
9669
9670  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9671    let index = self.positions.remove(&id)?;
9672    let removed = self.nodes[index];
9673    let last = self.nodes.pop().expect("an indexed timer node must exist");
9674    if index < self.nodes.len() {
9675      self.nodes[index] = last;
9676      self.positions.insert(last.1, index);
9677      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9678        self.sift_up(index);
9679      } else {
9680        self.sift_down(index);
9681      }
9682    }
9683    Some(removed)
9684  }
9685
9686  fn sift_up(&mut self, mut index: usize) {
9687    while index > 0 {
9688      let parent = (index - 1) / 2;
9689      if self.nodes[parent] <= self.nodes[index] {
9690        break;
9691      }
9692      self.swap(parent, index);
9693      index = parent;
9694    }
9695  }
9696
9697  fn sift_down(&mut self, mut index: usize) {
9698    loop {
9699      let left = index * 2 + 1;
9700      if left >= self.nodes.len() {
9701        return;
9702      }
9703      let right = left + 1;
9704      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9705        right
9706      } else {
9707        left
9708      };
9709      if self.nodes[index] <= self.nodes[child] {
9710        return;
9711      }
9712      self.swap(index, child);
9713      index = child;
9714    }
9715  }
9716
9717  fn swap(&mut self, left: usize, right: usize) {
9718    self.nodes.swap(left, right);
9719    self.positions.insert(self.nodes[left].1, left);
9720    self.positions.insert(self.nodes[right].1, right);
9721  }
9722}
9723
9724#[cfg(napi_runtime_os_threads)]
9725impl TimerHeapInner {
9726  fn next_deadline(&self) -> Option<Instant> {
9727    self.queue.peek().map(|(deadline, _)| deadline)
9728  }
9729}
9730
9731#[cfg(napi_runtime_os_threads)]
9732impl TimerHeap {
9733  fn new(
9734    generation: u64,
9735    executor_id: u64,
9736    thread_name: String,
9737    deadlock_state: Arc<MultiThreadDeadlockState>,
9738  ) -> Self {
9739    Self {
9740      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9741      parker: Arc::new(DriverParker::default()),
9742      thread: Mutex::new(None),
9743      deadlock_state,
9744      #[cfg(test)]
9745      fail_next_start: AtomicBool::new(false),
9746      generation,
9747      executor_id,
9748      thread_name,
9749    }
9750  }
9751
9752  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9753    if self
9754      .inner
9755      .lock()
9756      .unwrap_or_else(std::sync::PoisonError::into_inner)
9757      .closed
9758    {
9759      return Ok(());
9760    }
9761    let current_thread = std::thread::current().id();
9762    loop {
9763      let finished = {
9764        let mut thread = self
9765          .thread
9766          .lock()
9767          .unwrap_or_else(std::sync::PoisonError::into_inner);
9768        match thread.as_ref() {
9769          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9770          Some(handle) if !handle.is_finished() => return Ok(()),
9771          Some(_) => thread.take(),
9772          None => {
9773            // Shutdown can close the heap after the optimistic check above.
9774            // Recheck before publishing another service thread.
9775            if self
9776              .inner
9777              .lock()
9778              .unwrap_or_else(std::sync::PoisonError::into_inner)
9779              .closed
9780            {
9781              return Ok(());
9782            }
9783            #[cfg(test)]
9784            if self.fail_next_start.swap(false, Ordering::SeqCst) {
9785              return Err(RuntimeConfigError(
9786                "injected runtime timer thread creation failure".to_string(),
9787              ));
9788            }
9789
9790            let inner = Arc::clone(&self.inner);
9791            let parker = Arc::clone(&self.parker);
9792            let deadlock_state = Arc::clone(&self.deadlock_state);
9793            let generation = self.generation;
9794            let executor_id = self.executor_id;
9795            let handle = std::thread::Builder::new()
9796              .name(self.thread_name.clone())
9797              .spawn(move || {
9798                // Keep the identity through OS-thread TLS teardown. Unlike
9799                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
9800                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9801                let _generation = RuntimeGenerationGuard::enter(generation);
9802                timer_timekeeper_main(&inner, &parker, &deadlock_state);
9803              })
9804              .map_err(|error| {
9805                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9806              })?;
9807            *thread = Some(handle);
9808            return Ok(());
9809          }
9810        }
9811      };
9812
9813      // A finished timer thread may still be running TLS destructors that
9814      // re-enter timer code. Never hold the handle mutex while waiting for
9815      // those destructors, and never attempt to join this thread from itself.
9816      if let Some(finished) = finished
9817        && finished.thread().id() != current_thread
9818        && let Err(payload) = finished.join()
9819      {
9820        discard_panic_payload(payload);
9821      }
9822      if self
9823        .inner
9824        .lock()
9825        .unwrap_or_else(std::sync::PoisonError::into_inner)
9826        .closed
9827      {
9828        return Ok(());
9829      }
9830    }
9831  }
9832
9833  fn close(&self) -> Vec<Waker> {
9834    let mut inner = self
9835      .inner
9836      .lock()
9837      .unwrap_or_else(std::sync::PoisonError::into_inner);
9838    inner.closed = true;
9839    inner.queue.clear();
9840    inner.timekeeper_parker = None;
9841    inner
9842      .entries
9843      .drain()
9844      .map(|(_, entry)| {
9845        entry.fired.store(true, Ordering::SeqCst);
9846        entry.waker
9847      })
9848      .collect()
9849  }
9850
9851  fn join(&self) {
9852    let handle = self
9853      .thread
9854      .lock()
9855      .unwrap_or_else(std::sync::PoisonError::into_inner)
9856      .take();
9857    if let Some(handle) = handle {
9858      if handle.thread().id() == std::thread::current().id() {
9859        // A custom RawWaker may own the final direct executor Arc and release
9860        // it from this thread. Closing the heap makes the loop exit after the
9861        // callback returns; joining self would deadlock that exit.
9862        drop(handle);
9863        return;
9864      }
9865      if let Err(payload) = crate::sync::join(handle) {
9866        discard_panic_payload(payload);
9867      }
9868    }
9869  }
9870}
9871
9872#[cfg(napi_runtime_os_threads)]
9873impl Drop for TimerHeap {
9874  fn drop(&mut self) {
9875    let mut admission = self.deadlock_state.begin_admission();
9876    let wakers = self.close();
9877    if !wakers.is_empty()
9878      && let Some(admission) = admission.as_mut()
9879    {
9880      admission.mark_published();
9881    }
9882    self.parker.unpark();
9883    for waker in wakers {
9884      wake_waker_contained(waker);
9885    }
9886    drop(admission);
9887    self.join();
9888  }
9889}
9890
9891#[cfg(napi_runtime_os_threads)]
9892fn timer_timekeeper_main(
9893  inner: &Mutex<TimerHeapInner>,
9894  parker: &Arc<DriverParker>,
9895  deadlock_state: &Arc<MultiThreadDeadlockState>,
9896) {
9897  loop {
9898    // Acquire before touching the heap. If a verdict has already closed the
9899    // gate, a due timer remains visible until the verdict's final timer recheck
9900    // causes that guard to reopen.
9901    let mut admission = deadlock_state.begin_admission();
9902    let (due, deadline) = {
9903      let mut inner = inner
9904        .lock()
9905        .unwrap_or_else(std::sync::PoisonError::into_inner);
9906      if inner.closed {
9907        inner.timekeeper_parker = None;
9908        return;
9909      }
9910      let now = Instant::now();
9911      let mut due = Vec::new();
9912      while let Some((deadline, id)) = inner.queue.peek() {
9913        if deadline > now {
9914          break;
9915        }
9916        inner.queue.pop();
9917        if let Some(entry) = inner.entries.remove(&id) {
9918          entry.fired.store(true, Ordering::SeqCst);
9919          due.push(entry.waker);
9920        }
9921      }
9922      let deadline = inner.next_deadline();
9923      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9924      (due, deadline)
9925    };
9926
9927    if !due.is_empty() {
9928      if let Some(admission) = admission.as_mut() {
9929        admission.mark_published();
9930      }
9931      for waker in due {
9932        wake_waker_contained(waker);
9933      }
9934      continue;
9935    }
9936    // Never retain an admission while sleeping: it protects only the
9937    // remove-then-wake publication window.
9938    drop(admission);
9939
9940    match deadline {
9941      Some(deadline) => {
9942        let now = Instant::now();
9943        if deadline > now {
9944          parker.park_timeout(deadline - now);
9945        }
9946      }
9947      None => parker.park(),
9948    }
9949  }
9950}
9951
9952#[cfg(napi_runtime_os_threads)]
9953impl MultiThreadExecutor {
9954  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
9955  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
9956  /// shutdown, and set here immediately when the heap is already closed (the
9957  /// caller re-checks it after registering, so a post-shutdown poll resolves
9958  /// instead of parking forever).
9959  fn register_timer(
9960    self: &Arc<Self>,
9961    id: TimerId,
9962    deadline: Instant,
9963    waker: Waker,
9964    fired: &Arc<AtomicBool>,
9965  ) {
9966    let mut admission = self.begin_deadlock_admission();
9967    #[cfg(test)]
9968    run_timer_registration_admission_test_hook();
9969    // Establish the service thread before publishing the entry. If thread
9970    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
9971    // heap state exists for Drop to miss.
9972    if let Err(error) = self.timers.ensure_started() {
9973      let _ = catch_unwind_contained(|| drop(waker));
9974      panic!("{error}");
9975    }
9976    let (became_earliest, published, discarded_waker) = {
9977      let mut inner = self
9978        .timers
9979        .inner
9980        .lock()
9981        .unwrap_or_else(std::sync::PoisonError::into_inner);
9982      if inner.closed || fired.load(Ordering::SeqCst) {
9983        fired.store(true, Ordering::SeqCst);
9984        (false, false, Some(waker))
9985      } else if let Some(entry) = inner.entries.get_mut(&id) {
9986        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
9987        (
9988          false,
9989          false,
9990          Some(std::mem::replace(&mut entry.waker, waker)),
9991        )
9992      } else {
9993        let previous = inner.next_deadline();
9994        inner.entries.insert(
9995          id,
9996          HeapTimerEntry {
9997            waker,
9998            fired: Arc::clone(fired),
9999          },
10000        );
10001        inner.queue.push(deadline, id);
10002        (
10003          previous.is_none_or(|previous| deadline < previous),
10004          true,
10005          None,
10006        )
10007      }
10008    };
10009    if published && let Some(admission) = admission.as_mut() {
10010      admission.mark_published();
10011    }
10012    if let Some(waker) = discarded_waker {
10013      // RawWaker destruction is user code and may re-enter timer cancellation.
10014      // Keep it outside the heap mutex and contain hostile destructors.
10015      let _ = catch_unwind_contained(|| drop(waker));
10016    }
10017    if became_earliest {
10018      // Re-arm the lazy service thread and any cooperative timer wait.
10019      self.note_earlier_timer();
10020    }
10021  }
10022
10023  /// Best-effort cancel: remove the entry and its indexed heap node.
10024  /// If cancellation changes the earliest deadline, wake every timer-bounded
10025  /// driver so no pool worker remains parked on the stale deadline.
10026  fn cancel_timer(&self, id: TimerId) {
10027    let (removed, earliest_changed) = {
10028      let mut inner = self
10029        .timers
10030        .inner
10031        .lock()
10032        .unwrap_or_else(std::sync::PoisonError::into_inner);
10033      let previous = inner.next_deadline();
10034      let removed = inner.entries.remove(&id);
10035      if removed.is_some() {
10036        let queued = inner.queue.remove(id);
10037        debug_assert!(
10038          queued.is_some(),
10039          "a live timer entry must have one indexed deadline node"
10040        );
10041      }
10042      let current = inner.next_deadline();
10043      (removed, previous != current)
10044    };
10045    if let Some(entry) = removed {
10046      // A RawWaker destructor may cancel another timer. Never run it while
10047      // holding the heap lock.
10048      let _ = catch_unwind_contained(|| drop(entry));
10049    }
10050    if earliest_changed {
10051      self.timers.parker.unpark();
10052      self.parked_drivers.wake_all();
10053    }
10054  }
10055
10056  /// Earliest pending deadline (None when the heap is empty or closed).
10057  fn next_timer_deadline(&self) -> Option<Instant> {
10058    self
10059      .timers
10060      .inner
10061      .lock()
10062      .unwrap_or_else(std::sync::PoisonError::into_inner)
10063      .next_deadline()
10064  }
10065
10066  /// Pop and wake everything due at/before now. Wakers are collected under
10067  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
10068  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
10069  /// the lock, before the wake, so the woken poll observes it.
10070  fn fire_due_timers(&self) {
10071    let mut admission = self.begin_deadlock_admission();
10072    let due: Vec<Waker> = {
10073      let mut inner = self
10074        .timers
10075        .inner
10076        .lock()
10077        .unwrap_or_else(std::sync::PoisonError::into_inner);
10078      let now = Instant::now();
10079      let mut due = Vec::new();
10080      while let Some((deadline, id)) = inner.queue.peek() {
10081        if deadline > now {
10082          break;
10083        }
10084        inner.queue.pop();
10085        if let Some(entry) = inner.entries.remove(&id) {
10086          entry.fired.store(true, Ordering::SeqCst);
10087          due.push(entry.waker);
10088        }
10089      }
10090      due
10091    };
10092    if !due.is_empty()
10093      && let Some(admission) = admission.as_mut()
10094    {
10095      admission.mark_published();
10096    }
10097    for waker in due {
10098      // Wakers may originate outside this executor. Isolate user RawWaker
10099      // implementations so a panic cannot unwind the dedicated timekeeper.
10100      wake_waker_contained(waker);
10101    }
10102  }
10103
10104  /// The earliest pending deadline moved up (or the heap went non-empty).
10105  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
10106  /// whose current park may still reflect the previous deadline.
10107  fn note_earlier_timer(self: &Arc<Self>) {
10108    self.timers.parker.unpark();
10109    self.parked_drivers.wake_one();
10110  }
10111
10112  /// Drain-fire every pending heap timer and poison later registrations.
10113  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
10114  /// sleeps resolve on their next poll; the woken tasks are
10115  /// scheduled through the normal path, so a coordinator awaiting a debounce
10116  /// completes its close sequence instead of parking forever.
10117  fn shutdown_timers(&self) {
10118    let mut admission = self.begin_deadlock_admission();
10119    let wakers = self.timers.close();
10120    if !wakers.is_empty()
10121      && let Some(admission) = admission.as_mut()
10122    {
10123      admission.mark_published();
10124    }
10125    for waker in wakers {
10126      // A user-provided RawWaker may panic. Shutdown is a lifecycle
10127      // transition, so one hostile wake must not leave the controller stuck
10128      // in `Stopping` and prevent every later restart.
10129      wake_waker_contained(waker);
10130    }
10131    self.timers.parker.unpark();
10132  }
10133
10134  fn begin_shutdown(&self) {
10135    let _generation = RuntimeGenerationGuard::enter(self.generation);
10136    // Publish stop before taking the blocking queue. The final deadlock verdict
10137    // holds stop publication while rechecking that queue, so queue -> stop
10138    // would invert its lock order. A concurrent taker either claims while stop
10139    // is still false or observes stop after this publication.
10140    self.stop.begin_shutdown();
10141    let queued = {
10142      let mut queue = self
10143        .blocking_queue
10144        .lock()
10145        .unwrap_or_else(std::sync::PoisonError::into_inner);
10146      // The blocking FIFO mutex closes queue admission. `take_blocking` either
10147      // claimed a job before stop publication or observes stop/closure.
10148      queue.closed = true;
10149      queue.take_all()
10150    };
10151    // Captured values belong to user code and may panic in Drop. Retire each
10152    // rejected closure independently so one destructor cannot strand the
10153    // runtime in `Stopping` or prevent the remaining queued jobs from being
10154    // cancelled.
10155    for queued in queued {
10156      let _ = catch_unwind_contained(|| drop(queued));
10157    }
10158    self.shutdown_timers();
10159  }
10160
10161  /// Non-blocking read of the predicate `wait_until_scheduler_idle` sleeps on.
10162  /// The timer join is deliberately excluded: it is a thread lifetime, not
10163  /// work a host can let finish by yielding.
10164  fn scheduler_work_pending(&self) -> bool {
10165    self.active_drainers.load(Ordering::Acquire) != 0
10166  }
10167
10168  fn wait_until_scheduler_idle(&self) {
10169    self.timers.join();
10170    let mut idle = self
10171      .scheduler_idle_lock
10172      .lock()
10173      .unwrap_or_else(std::sync::PoisonError::into_inner);
10174    // Register BEFORE the first predicate evaluation and stay registered for
10175    // the whole wait. `finish_draining` reads this count under the same mutex,
10176    // so any drainer that retires after this increment is published either
10177    // sees `> 0` and wakes us, or had already decremented `active_drainers`
10178    // before we read the predicate -- in which case we never sleep.
10179    *idle += 1;
10180    while self.active_drainers.load(Ordering::Acquire) != 0 {
10181      idle = self
10182        .scheduler_idle
10183        .wait(idle)
10184        .unwrap_or_else(std::sync::PoisonError::into_inner);
10185    }
10186    *idle -= 1;
10187  }
10188}
10189
10190/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
10191/// (or early on runtime shutdown drain-fire). Dropping it cancels the
10192/// underlying registration -- the `tokio::select!` losing-arm semantics the
10193/// watch coordinator's debounce loop relies on.
10194pub struct Sleep {
10195  deadline: Instant,
10196  inner: SleepInner,
10197}
10198
10199enum SleepInner {
10200  #[cfg(napi_runtime_os_threads)]
10201  Heap(HeapSleep),
10202  Host(HostSleep),
10203}
10204
10205#[cfg(napi_runtime_os_threads)]
10206struct HeapSleep {
10207  id: TimerId,
10208  executor: Weak<MultiThreadExecutor>,
10209  fired: Arc<AtomicBool>,
10210  registered: bool,
10211}
10212
10213struct HostSleep {
10214  id: TimerId,
10215  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
10216  /// registrant. Arming redundant host timers prevents a live but starved
10217  /// environment from owning the sleep's only wake source.
10218  drivers: Arc<TimerDriverRegistry>,
10219  /// Drivers this sleep last armed on. The first responsive fire wakes the
10220  /// sleep; completion cancels every losing arm, including pre-delivery arms
10221  /// whose binding relay retains a cancellation tombstone.
10222  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10223  /// The executor's registry, held so the FIRST poll can arm the wake-token
10224  /// at the same moment `driver.register` arms the host timer.
10225  registry: Arc<HostTimerRegistry>,
10226  fired: Arc<AtomicBool>,
10227  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
10228  /// request a re-poll without retaining or invoking a task waker under the
10229  /// driver-registry locks.
10230  repoll: Arc<HostTimerRepoll>,
10231  /// Retires this sleep's wake-token in the executor's registry exactly once
10232  /// (created at first poll alongside the host registration, taken on
10233  /// completion, dropped with the Sleep on cancellation). `None` before the
10234  /// first poll: an unpolled Sleep holds no wake-token.
10235  pending: Option<HostTimerPendingGuard>,
10236}
10237
10238fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10239  let _ = catch_unwind_contained(|| driver.cancel(id));
10240  let _ = catch_unwind_contained(|| drop(driver));
10241}
10242
10243fn cancel_host_timer_drivers(
10244  id: TimerId,
10245  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10246) {
10247  for (_, driver) in armed.drain() {
10248    cancel_host_timer_driver(id, driver);
10249  }
10250}
10251
10252impl Future for Sleep {
10253  type Output = ();
10254
10255  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10256    let deadline = self.deadline;
10257    let this = self.get_mut();
10258    match &mut this.inner {
10259      #[cfg(napi_runtime_os_threads)]
10260      SleepInner::Heap(heap) => {
10261        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10262          if heap.registered {
10263            // Wall-clock completion may beat the (busy) timekeeper's fire:
10264            // retire the registration so it cannot wake spuriously later.
10265            if let Some(executor) = heap.executor.upgrade() {
10266              executor.cancel_timer(heap.id);
10267            }
10268            heap.registered = false;
10269          }
10270          return Poll::Ready(());
10271        }
10272        let Some(executor) = heap.executor.upgrade() else {
10273          heap.fired.store(true, Ordering::SeqCst);
10274          heap.registered = false;
10275          return Poll::Ready(());
10276        };
10277        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10278        heap.registered = true;
10279        // Registration on a closed (shut-down) heap stores nothing and sets
10280        // `fired` instead: re-check so this poll resolves rather than parking
10281        // a closing runtime forever.
10282        if heap.fired.load(Ordering::SeqCst) {
10283          heap.registered = false;
10284          return Poll::Ready(());
10285        }
10286        Poll::Pending
10287      }
10288      SleepInner::Host(host) => {
10289        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10290          cancel_host_timer_drivers(host.id, &mut host.armed);
10291          host.pending.take();
10292          return Poll::Ready(());
10293        }
10294        // Clone both wakers before publishing either one. If the second clone
10295        // unwinds, destroy the first clone behind its own panic boundary before
10296        // resuming the original panic, so a hostile RawWaker destructor cannot
10297        // turn clone failure into a process-aborting double panic.
10298        let repoll_waker = cx.waker().clone();
10299        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10300          Ok(waker) => waker,
10301          Err(payload) => {
10302            let _ = catch_unwind_contained(|| drop(repoll_waker));
10303            resume_unwind(payload);
10304          }
10305        };
10306        host.repoll.register(repoll_waker);
10307        if !host.registry.register(
10308          host.id,
10309          deadline,
10310          registry_waker,
10311          &host.fired,
10312          &host.drivers,
10313          &host.repoll,
10314        ) {
10315          cancel_host_timer_drivers(host.id, &mut host.armed);
10316          if host.pending.take().is_none() {
10317            host.repoll.close();
10318          }
10319          return Poll::Ready(());
10320        }
10321        if host.pending.is_none() {
10322          // FIRST poll: the registry entry appears at the same moment the
10323          // host timer is armed below -- and only then. An unpolled Sleep has
10324          // no host callback behind it, so counting it at creation would
10325          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
10326          // mislabel the threadless certain diagnostic as timer-backed. The
10327          // certain panic itself no longer consults pending timers for its
10328          // verdict.
10329          host.drivers.register_pending(host.id, &host.repoll);
10330          host.pending = Some(HostTimerPendingGuard {
10331            registry: Arc::clone(&host.registry),
10332            drivers: Arc::clone(&host.drivers),
10333            repoll: Arc::clone(&host.repoll),
10334            id: host.id,
10335          });
10336        }
10337        // Re-select every LIVE driver on every poll. A live but starved
10338        // environment may accept a TSFN call and then stop servicing its
10339        // event loop, so no single host may own the only timer arm.
10340        let current = host.drivers.live();
10341        assert!(
10342          !current.as_slice().is_empty(),
10343          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10344           that registered timer drivers (via \
10345           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10346           one per importing env through `registerTimerHost`) have all been torn down, so this \
10347           `sleep_until` can never be woken."
10348        );
10349        // Publish cancellation ownership for the complete live snapshot before
10350        // invoking any arbitrary callback. An earlier driver may unregister a
10351        // later one and then unwind; both the Sleep and the contained snapshot
10352        // must still own that later driver while the original panic propagates.
10353        // See the implementation notes in this crate's README.
10354        let missing = current
10355          .as_slice()
10356          .iter()
10357          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10358          .count();
10359        host.armed.reserve(missing);
10360        for (current_id, current_driver) in current.as_slice() {
10361          host
10362            .armed
10363            .entry(*current_id)
10364            .or_insert_with(|| Arc::clone(current_driver));
10365        }
10366        for (current_id, _) in current.as_slice() {
10367          let armed_driver = host
10368            .armed
10369            .get(current_id)
10370            .expect("every live timer driver must have cancellation ownership before callbacks");
10371          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10372        }
10373        let current_ids = current
10374          .as_slice()
10375          .iter()
10376          .map(|(current_id, _)| *current_id)
10377          .collect::<FxHashSet<_>>();
10378        let retired = host
10379          .armed
10380          .keys()
10381          .filter(|armed_id| !current_ids.contains(armed_id))
10382          .copied()
10383          .collect::<Vec<_>>();
10384        for retired in retired {
10385          let old_driver = host
10386            .armed
10387            .remove(&retired)
10388            .expect("the retired timer driver must remain armed");
10389          cancel_host_timer_driver(host.id, old_driver);
10390        }
10391        Poll::Pending
10392      }
10393    }
10394  }
10395}
10396
10397impl Drop for Sleep {
10398  fn drop(&mut self) {
10399    match &mut self.inner {
10400      #[cfg(napi_runtime_os_threads)]
10401      SleepInner::Heap(heap) => {
10402        if heap.registered
10403          && let Some(executor) = heap.executor.upgrade()
10404        {
10405          executor.cancel_timer(heap.id);
10406        }
10407      }
10408      SleepInner::Host(host) => {
10409        // Close directly as an unwind backstop even when first-poll setup
10410        // failed before `pending` could install its normal cleanup guard.
10411        host.repoll.close();
10412        cancel_host_timer_drivers(host.id, &mut host.armed);
10413        // `pending` (if still held) drops here and retires the wake-token.
10414      }
10415    }
10416  }
10417}
10418
10419/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
10420/// the executor-owned heap; CurrentThread requires a LIVE host driver
10421/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
10422/// missing driver must never become a silent never-firing debounce).
10423pub fn sleep_until(deadline: Instant) -> Sleep {
10424  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10425}
10426
10427/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
10428/// build executors and driver registries locally and must not touch the
10429/// process-global ones).
10430fn make_sleep(
10431  backend: &RuntimeBackend,
10432  host_drivers: &Arc<TimerDriverRegistry>,
10433  deadline: Instant,
10434) -> Sleep {
10435  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10436  match &backend.executor {
10437    #[cfg(napi_runtime_os_threads)]
10438    RuntimeExecutor::MultiThread(executor) => Sleep {
10439      deadline,
10440      inner: SleepInner::Heap(HeapSleep {
10441        id,
10442        executor: Arc::downgrade(executor),
10443        fired: Arc::new(AtomicBool::new(false)),
10444        registered: false,
10445      }),
10446    },
10447    RuntimeExecutor::CurrentThread(executor) => {
10448      // Fail loud at CREATION when no live driver exists (the
10449      // never-registered case gets its diagnostic at the sleep_until call
10450      // site, not on some later poll). The driver is NOT pinned here: each
10451      // poll re-selects every live registrant, so a driver dying while this
10452      // sleep is in flight is cancelled while the remaining hosts stay armed
10453      // -- and if every driver dies mid-flight, the poll path has its own loud
10454      // panic.
10455      assert!(
10456        host_drivers.has_live_driver(),
10457        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10458         single-thread flavor delegates timers to the host event loop. Install one via \
10459         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10460         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10461      );
10462      // The registry entry is created at FIRST POLL (when `driver.register`
10463      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
10464      // -- nothing was handed to the host loop -- so counting it at creation
10465      // would wrongly veto the threaded deadline verdict and mislabel the
10466      // threadless certain diagnostic as timer-backed. First-poll accounting
10467      // has no false-panic side.
10468      Sleep {
10469        deadline,
10470        inner: SleepInner::Host(HostSleep {
10471          id,
10472          drivers: Arc::clone(host_drivers),
10473          armed: FxHashMap::default(),
10474          registry: Arc::clone(&executor.host_timers),
10475          fired: Arc::new(AtomicBool::new(false)),
10476          repoll: Arc::new(HostTimerRepoll::default()),
10477          pending: None,
10478        }),
10479      }
10480    }
10481  }
10482}
10483
10484#[derive(Clone)]
10485enum RuntimeExecutor {
10486  CurrentThread(Arc<CurrentThreadExecutor>),
10487  #[cfg(napi_runtime_os_threads)]
10488  MultiThread(Arc<MultiThreadExecutor>),
10489}
10490
10491enum WeakRuntimeExecutor {
10492  CurrentThread(Weak<CurrentThreadExecutor>),
10493  #[cfg(napi_runtime_os_threads)]
10494  MultiThread(Weak<MultiThreadExecutor>),
10495}
10496
10497impl WeakRuntimeExecutor {
10498  fn schedule(&self, runnable: Runnable) {
10499    match self {
10500      Self::CurrentThread(executor) => {
10501        if let Some(executor) = executor.upgrade() {
10502          executor.schedule(runnable);
10503        }
10504      }
10505      #[cfg(napi_runtime_os_threads)]
10506      Self::MultiThread(executor) => {
10507        if let Some(executor) = executor.upgrade() {
10508          executor.schedule(runnable);
10509        }
10510      }
10511    }
10512  }
10513}
10514
10515#[derive(Clone)]
10516struct RuntimeBackend {
10517  work: Arc<GenerationWork>,
10518  executor: RuntimeExecutor,
10519}
10520
10521impl RuntimeBackend {
10522  fn new(
10523    options: &RuntimeOptions,
10524    metrics: Arc<RuntimeMetrics>,
10525  ) -> Result<Self, RuntimeConfigError> {
10526    #[cfg(test)]
10527    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10528      return Err(RuntimeConfigError(
10529        "injected async runtime backend creation failure".to_string(),
10530      ));
10531    }
10532
10533    let work = GenerationWork::new();
10534    let stop = Arc::new(GenerationStop::default());
10535    let executor = match options.flavor {
10536      RuntimeFlavor::CurrentThread => {
10537        let executor = CurrentThreadExecutor::with_generation(
10538          metrics,
10539          THREADLESS_BUILD,
10540          options.park_deadline,
10541          work.id,
10542          Arc::clone(&stop),
10543        );
10544        executor.attach_generation_work(&work);
10545        RuntimeExecutor::CurrentThread(Arc::new(executor))
10546      }
10547      RuntimeFlavor::MultiThread => {
10548        #[cfg(napi_runtime_os_threads)]
10549        {
10550          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10551            options, metrics, work.id, stop,
10552          )?))
10553        }
10554        #[cfg(not(napi_runtime_os_threads))]
10555        {
10556          let _ = metrics;
10557          return Err(RuntimeConfigError(
10558            "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10559             (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10560              .to_string(),
10561          ));
10562        }
10563      }
10564    };
10565    Ok(Self { work, executor })
10566  }
10567
10568  #[cfg(test)]
10569  fn from_executor(executor: RuntimeExecutor) -> Self {
10570    let work = GenerationWork::new();
10571    if let RuntimeExecutor::CurrentThread(executor) = &executor {
10572      executor.attach_generation_work(&work);
10573    }
10574    Self { work, executor }
10575  }
10576
10577  fn generation(&self) -> u64 {
10578    self.work.id
10579  }
10580
10581  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10582    match &self.executor {
10583      RuntimeExecutor::CurrentThread(executor) => {
10584        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10585      }
10586      #[cfg(napi_runtime_os_threads)]
10587      RuntimeExecutor::MultiThread(executor) => {
10588        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10589      }
10590    }
10591  }
10592
10593  /// First schedule of a freshly submitted task. This runs on the stack of the
10594  /// caller of `spawn`, never on a waker's; see
10595  /// `CurrentThreadExecutor::schedule_submission`.
10596  fn schedule_submission(&self, runnable: Runnable) {
10597    match &self.executor {
10598      RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10599      #[cfg(napi_runtime_os_threads)]
10600      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10601    }
10602  }
10603
10604  fn spawn_registered_blocking<F, T>(
10605    &self,
10606    function: F,
10607    registration: GenerationWorkGuard,
10608    metrics: &Arc<RuntimeMetrics>,
10609  ) -> JoinHandle<T>
10610  where
10611    F: FnOnce() -> T + Send + 'static,
10612    T: Send + 'static,
10613  {
10614    match &self.executor {
10615      RuntimeExecutor::CurrentThread(executor) => {
10616        let _ = metrics;
10617        executor.schedule_blocking(function, registration)
10618      }
10619      #[cfg(napi_runtime_os_threads)]
10620      RuntimeExecutor::MultiThread(executor) => {
10621        let generation = self.generation();
10622        let _ = metrics;
10623        let function = RegisteredBlockingFunction::new(function, registration, generation);
10624        executor.schedule_blocking_result(move || function.run())
10625      }
10626    }
10627  }
10628
10629  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10630    match &self.executor {
10631      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10632      #[cfg(napi_runtime_os_threads)]
10633      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10634    }
10635  }
10636
10637  fn generation_stop(&self) -> Arc<GenerationStop> {
10638    match &self.executor {
10639      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10640      #[cfg(napi_runtime_os_threads)]
10641      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10642    }
10643  }
10644
10645  fn begin_shutdown(&self) {
10646    let _generation = RuntimeGenerationGuard::enter(self.generation());
10647    self.work.close_and_abort();
10648    match &self.executor {
10649      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10650      #[cfg(napi_runtime_os_threads)]
10651      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10652    }
10653  }
10654
10655  fn wait_until_idle(&self) {
10656    match &self.executor {
10657      RuntimeExecutor::CurrentThread(executor) => {
10658        // A wake in flight across `begin_shutdown` is rejected by the closed
10659        // queue and parked in `queue.rejected` with its guard still held, and
10660        // a failure transition racing `begin_shutdown` from a waker's stack
10661        // moves the queued guards there too; retire them here, on this stack,
10662        // until the generation is idle.
10663        self.work.wait_until_idle_retiring(
10664          || executor.has_rejected_work(),
10665          || executor.retire_rejected_work(),
10666        );
10667        executor.wait_until_scheduler_idle();
10668      }
10669      #[cfg(napi_runtime_os_threads)]
10670      RuntimeExecutor::MultiThread(executor) => {
10671        self.work.wait_until_idle();
10672        executor.wait_until_scheduler_idle();
10673      }
10674    }
10675  }
10676
10677  /// Non-blocking counterpart of `wait_until_idle`: whether that call would
10678  /// have to wait for anything. Same sources, read without sleeping --
10679  /// registered async/blocking work, work the executor still owes a
10680  /// cancellation, and an in-flight drain or host turn.
10681  ///
10682  /// Joining the worker threads (phase 2's `wait_for_all_workers`) is NOT a
10683  /// source here: a Rayon worker exits when the last backend handle is
10684  /// dropped, which only phase 2 does, so `WorkerLifecycle::remaining` is
10685  /// non-zero for the whole window between the phases by construction and
10686  /// would make this poll never answer `false`. That join waits on thread
10687  /// teardown, never on user work, so yielding to the host cannot help it.
10688  ///
10689  /// `read` decides whether the sources that live under a lock may wait for
10690  /// it; the MultiThread scheduler source is an atomic either way.
10691  fn work_pending(&self, read: PendingRead) -> bool {
10692    if self.work.has_active_work(read) {
10693      return true;
10694    }
10695    match &self.executor {
10696      RuntimeExecutor::CurrentThread(executor) => {
10697        executor.rejected_work_pending(read) || executor.scheduler_work_pending(read)
10698      }
10699      #[cfg(napi_runtime_os_threads)]
10700      RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10701    }
10702  }
10703
10704  #[cfg(napi_runtime_os_threads)]
10705  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10706    match &self.executor {
10707      RuntimeExecutor::CurrentThread(_) => None,
10708      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10709    }
10710  }
10711
10712  fn stop_identity(&self) -> RuntimeStopIdentity {
10713    RuntimeStopIdentity {
10714      generation: self.generation(),
10715      #[cfg(napi_runtime_os_threads)]
10716      executor_id: match &self.executor {
10717        RuntimeExecutor::CurrentThread(_) => None,
10718        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10719      },
10720    }
10721  }
10722
10723  fn is_current(&self) -> bool {
10724    self.stop_identity().is_current()
10725  }
10726}
10727
10728#[derive(Clone, Copy)]
10729struct RuntimeStopIdentity {
10730  generation: u64,
10731  #[cfg(napi_runtime_os_threads)]
10732  executor_id: Option<u64>,
10733}
10734
10735impl RuntimeStopIdentity {
10736  fn is_current(self) -> bool {
10737    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10738      return true;
10739    }
10740    #[cfg(napi_runtime_os_threads)]
10741    if let Some(executor_id) = self.executor_id
10742      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10743        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10744    {
10745      return true;
10746    }
10747    false
10748  }
10749}
10750
10751enum RuntimeLifecycle {
10752  Initial,
10753  StoppedBeforeFirstUse,
10754  Running(RuntimeBackend),
10755  Stopping(RuntimeStopIdentity),
10756  StoppingWithoutBackend(ZeroBackendShutdownTarget),
10757  Stopped,
10758}
10759
10760#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10761enum ZeroBackendShutdownTarget {
10762  StoppedBeforeFirstUse,
10763  Stopped,
10764}
10765
10766impl ZeroBackendShutdownTarget {
10767  fn into_lifecycle(self) -> RuntimeLifecycle {
10768    match self {
10769      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10770      Self::Stopped => RuntimeLifecycle::Stopped,
10771    }
10772  }
10773}
10774
10775struct RuntimeState {
10776  options: RuntimeOptions,
10777  lifecycle: RuntimeLifecycle,
10778  rejected_drops: usize,
10779  /// Handoff between the two shutdown phases, `None` unless a
10780  /// `begin_shutdown` is outstanding. `RuntimeLifecycle::Stopping` carries no
10781  /// backend, so the single-call `shutdown` keeps it on its own stack; the
10782  /// split needs somewhere to park it across the host's yield window.
10783  ///
10784  /// Scoped to the generation the phase 1 was opened against: a successful
10785  /// `start` publishes a fresh one and drops it
10786  /// (`forget_shutdown_handoff`).
10787  ///
10788  /// A `ShutdownDrain::Backend` handoff is taken: whoever finds it takes it,
10789  /// drains it and publishes the stop; everybody else answers off the
10790  /// lifecycle. A `ShutdownDrain::ZeroBackend` handoff is not -- there is no
10791  /// backend to own. Nobody takes it. Every `finish_shutdown` that finds it
10792  /// waits on `rejected_drops`, and the one that observes zero clears the
10793  /// slot together with the publication, under one lock hold. Either way the
10794  /// handoff carries no owner and no credit -- see `phase_ever_opened`.
10795  ///
10796  /// The slot never goes empty while a stop is still owed. For `Backend` the
10797  /// taker swaps the handoff for `ShutdownDrain::Joining`, which stays until
10798  /// `Stopped` is published under this same lock. For `ZeroBackend` the
10799  /// handoff itself stays, untouched, until the target lifecycle is published
10800  /// under that lock. That keeps the non-blocking surfaces
10801  /// (`begin_shutdown`'s re-entry arm, `runtime_work_pending`) truthful for
10802  /// the whole wait instead of reading like a runtime with nothing pending.
10803  draining: Option<ShutdownDrain>,
10804  /// Sticky: `true` once any `begin_shutdown` has OPENED a phase (any arm that
10805  /// parks a handoff, `Backend` or `Settled`). Never cleared -- not by a
10806  /// finish, not by a `start`, not by a restart.
10807  ///
10808  /// It answers the one question `finish_shutdown` cannot answer from
10809  /// `draining` and the lifecycle alone. An empty handoff slot on a runtime
10810  /// that has never announced a stop is a host bug (phase 2 called on its
10811  /// own); an empty slot on a runtime that has announced one only means
10812  /// somebody else already completed, dropped or superseded it, which phase 2
10813  /// reports as `Ok(())`.
10814  ///
10815  /// A counter was tried here and is wrong by construction: the credit is
10816  /// spent by whichever thread performs the join, NOT by the thread that
10817  /// opened the phase. A concurrent in-protocol `finish_shutdown` therefore
10818  /// pairs off a single-call `shutdown`'s own phase, and that `shutdown` is
10819  /// then told its stop was never started -- 8.2% of the time on
10820  /// CurrentThread, after it had already published the stop. `(begin, start)`
10821  /// pairs leak credits on top of that, turning later orphan finishes into
10822  /// `Ok`. A bool can neither race nor leak.
10823  phase_ever_opened: bool,
10824}
10825
10826impl RuntimeState {
10827  /// Park an accepted phase 1's handoff and record that a stop has been
10828  /// announced. Both halves are set under the one lock every opening arm
10829  /// already holds, so no phase can be open without the flag.
10830  fn open_phase_one(&mut self, drain: ShutdownDrain) {
10831    self.phase_ever_opened = true;
10832    self.draining = Some(drain);
10833  }
10834
10835  /// Drop any parked handoff. Called by every path that publishes a
10836  /// generation (`start`, and `backend_locked`'s lazy first backend): a fresh
10837  /// generation owes nothing to a handoff opened against an earlier one.
10838  ///
10839  /// Dropping `draining` here is what keeps a stale handoff from answering
10840  /// for the new generation -- a surviving `Settled` would make the next
10841  /// `begin_shutdown` return from its outstanding-handoff arm without
10842  /// stopping anything, let `finish_shutdown` consume it and report `Ok(())`,
10843  /// and pin `runtime_work_pending` to `false`, so a `shutdown` would claim
10844  /// success while this generation kept running, kept accepting work and kept
10845  /// its MultiThread workers unjoined.
10846  ///
10847  /// It does NOT clear `phase_ever_opened`. Dropping the handoff answers the
10848  /// stale-handoff question completely; whether phase 2 may be called at all
10849  /// is a different question, and a `start` does not turn an outstanding phase
10850  /// 2 into a host bug -- it turns it into a no-op.
10851  fn forget_shutdown_handoff(&mut self) {
10852    // Only a settled handoff can ever reach here: `ShutdownDrain::Backend` is
10853    // parked together with the published `Stopping` and `Joining` replaces it
10854    // until `Stopped` is published, and every `start` arm waits out
10855    // `Stopping` instead of publishing over it. `ZeroBackend` is the same
10856    // argument one lifecycle over: it is parked together with the published
10857    // `StoppingWithoutBackend`, every `start` arm waits that out, and
10858    // `backend_locked` refuses to build a backend under it.
10859    debug_assert!(
10860      !matches!(
10861        self.draining,
10862        Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10863      ),
10864      "a start must never publish over an outstanding backend drain"
10865    );
10866    self.draining = None;
10867  }
10868}
10869
10870/// What `RuntimeController::finish_shutdown` still owes an outstanding
10871/// `begin_shutdown`.
10872enum ShutdownDrain {
10873  /// `Stopping(identity)` is published and this backend is waiting to be
10874  /// drained and joined. Nobody owns it yet, so it is still up for grabs.
10875  Backend(RuntimeBackend),
10876  /// A `finish_shutdown` has TAKEN the handoff above and is inside the join.
10877  /// The marker stays until that thread publishes `Stopped`: the lifecycle
10878  /// reads `Stopping` for the whole window, so an empty slot would make a
10879  /// re-entering phase 1 park on `lifecycle_changed` and
10880  /// `runtime_work_pending` answer `false` about work the finisher is
10881  /// demonstrably still waiting for.
10882  ///
10883  /// The backend rides along only for the FIRST half of that window
10884  /// (`wait_until_idle`), which is the half that can still report work. The
10885  /// drainer clears it to `None` before releasing its own last reference,
10886  /// because that release is what drops the executor and lets the workers
10887  /// exit -- a marker that kept a clone alive would wedge the worker join
10888  /// forever (observed: `wait_for_all_workers` never returning). By then
10889  /// `wait_until_idle` has returned, so `None` is the truthful verdict for
10890  /// the remaining half.
10891  ///
10892  /// It is NOT up for grabs -- a second `finish_shutdown` that finds it waits
10893  /// for the publication and reports it, exactly as it does for an empty slot
10894  /// on a runtime that has announced a stop.
10895  Joining(Option<RuntimeBackend>),
10896  /// Phase 1 completed a zero-backend transition (`Initial`, or an already
10897  /// stopped runtime): phase 2 has nothing left to drain.
10898  Settled,
10899  /// Phase 1 announced a zero-backend stop (`Initial`, or an already stopped
10900  /// runtime) that still owes the retirement of one or more
10901  /// rejected-submission destructors before `target` may be published.
10902  ///
10903  /// Those destructors are arbitrary user `Drop` code running on the
10904  /// REJECTING thread, so waiting for them is exactly what phase 1 must not
10905  /// do: on the threaded WASI artifact that thread can be waiting for a
10906  /// JavaScript turn the phase-1 caller can only give by returning. The
10907  /// lifecycle reads `StoppingWithoutBackend(target)` for the whole window,
10908  /// exactly as `Stopping` + `Backend` do for a generation with a backend, and
10909  /// phase 2 does the wait and publishes the target.
10910  ///
10911  /// It is NOT taken out of the slot the way `Backend` is -- there is nothing
10912  /// to own -- so every `finish_shutdown` that finds it re-reads the slot
10913  /// after each wake and only the one that observes `rejected_drops == 0`
10914  /// under the published target publishes it.
10915  ZeroBackend(ZeroBackendShutdownTarget),
10916}
10917
10918#[cfg(test)]
10919thread_local! {
10920  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10921    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10922  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10923    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10924  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10925    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10926  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10927    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10928  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10929    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10930  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10931    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10932  static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10933    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10934  static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10935    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10936  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10937    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10938}
10939
10940#[cfg(test)]
10941fn run_before_runtime_submission_lock_test_hook() {
10942  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10943  {
10944    hook();
10945  }
10946}
10947
10948#[cfg(test)]
10949fn run_after_async_work_registration_test_hook() {
10950  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10951  {
10952    hook();
10953  }
10954}
10955
10956#[cfg(test)]
10957fn run_after_blocking_work_registration_test_hook() {
10958  if let Some(hook) =
10959    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10960  {
10961    hook();
10962  }
10963}
10964
10965#[cfg(test)]
10966fn run_after_current_thread_host_admission_test_hook() {
10967  if let Some(hook) =
10968    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10969  {
10970    hook();
10971  }
10972}
10973
10974#[cfg(test)]
10975fn run_before_initial_start_wait_test_hook() {
10976  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10977    hook();
10978  }
10979}
10980
10981#[cfg(test)]
10982fn run_after_generation_stop_publication_test_hook() {
10983  if let Some(hook) =
10984    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10985  {
10986    hook();
10987  }
10988}
10989
10990/// Runs on the thread that TOOK the outstanding phase 1, after the handoff is
10991/// out of the state and before it waits: anything a test starts from here
10992/// provably arrives at the handshake second.
10993#[cfg(test)]
10994fn run_after_shutdown_drain_taken_test_hook() {
10995  if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10996    hook();
10997  }
10998}
10999
11000/// Runs inside the single-call `shutdown`, in the window between its two
11001/// halves where it holds no lock: this is exactly where a racing `start` can
11002/// land and drop the handoff phase 1 parked.
11003#[cfg(test)]
11004fn run_between_shutdown_phases_test_hook() {
11005  if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
11006    hook();
11007  }
11008}
11009
11010thread_local! {
11011  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
11012    const { std::cell::Cell::new(0) };
11013}
11014
11015struct RejectedSubmissionDropContext;
11016
11017impl RejectedSubmissionDropContext {
11018  fn enter() -> Self {
11019    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
11020    Self
11021  }
11022
11023  fn is_current() -> bool {
11024    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
11025  }
11026}
11027
11028impl Drop for RejectedSubmissionDropContext {
11029  fn drop(&mut self) {
11030    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
11031      depth.set(
11032        depth
11033          .get()
11034          .checked_sub(1)
11035          .expect("rejected submission drop depth underflow"),
11036      );
11037    });
11038  }
11039}
11040
11041struct RuntimeController {
11042  state: Mutex<RuntimeState>,
11043  lifecycle_changed: Condvar,
11044  metrics: Arc<RuntimeMetrics>,
11045  // Lock-free mirror of `state.lifecycle`'s backend, read by `fast_register`
11046  // without the `state` lock. Invariant: store `Some(b)` on every transition
11047  // INTO `Running(b)` and `None` on every transition OUT -- exactly 3 sites
11048  // today (lazy-create, start, shutdown). Any new Running-boundary write MUST
11049  // mirror here, or `fast_register` routes work onto a superseded backend.
11050  current_backend: ArcSwapOption<RuntimeBackend>,
11051  // Lock-free count of the pool threads the runtime will still spawn, read by
11052  // `napi_wasm_runtime_pool_workers` without the `state` lock. Invariant:
11053  // under that lock, `pool_workers_for(&state.options)` stored right after
11054  // every `state.options` write -- 3 sites (`new`, `configure`,
11055  // `configure_partial_inner`); a configure that fails writes neither -- and
11056  // 0 stored right after every backend build -- 2 sites (`backend_locked`,
11057  // `start` from `Stopped`). A built backend already spawned all its pool
11058  // threads (Rayon's `build()` spawns `num_threads` at once and never grows),
11059  // so the threaded WASI loader must not preload Workers for them again. Only
11060  // that export and the tests read it.
11061  #[cfg_attr(not(any(napi_runtime_wasi_threads, test)), allow(dead_code))]
11062  pool_workers: AtomicU32,
11063}
11064
11065/// How many pool threads the options run the scheduler on: `worker_threads`
11066/// under [`RuntimeFlavor::MultiThread`], 0 under
11067/// [`RuntimeFlavor::CurrentThread`], which runs on the host's thread. Expects
11068/// validated options, so MultiThread is never below 2.
11069fn pool_workers_for(options: &RuntimeOptions) -> u32 {
11070  match options.flavor {
11071    RuntimeFlavor::MultiThread => u32::try_from(options.worker_threads).unwrap_or(u32::MAX),
11072    RuntimeFlavor::CurrentThread => 0,
11073  }
11074}
11075
11076struct RejectedSubmissionGuard<'a> {
11077  controller: &'a RuntimeController,
11078}
11079
11080impl Drop for RejectedSubmissionGuard<'_> {
11081  fn drop(&mut self) {
11082    let mut state = self
11083      .controller
11084      .state
11085      .lock()
11086      .unwrap_or_else(std::sync::PoisonError::into_inner);
11087    state.rejected_drops = state
11088      .rejected_drops
11089      .checked_sub(1)
11090      .expect("rejected submission drop count underflow");
11091    self.controller.lifecycle_changed.notify_all();
11092  }
11093}
11094
11095impl RuntimeController {
11096  fn new() -> Self {
11097    let options = RuntimeOptions::default()
11098      .validate()
11099      .expect("default async runtime options must be valid");
11100    let pool_workers = AtomicU32::new(pool_workers_for(&options));
11101    Self {
11102      state: Mutex::new(RuntimeState {
11103        options,
11104        lifecycle: RuntimeLifecycle::Initial,
11105        rejected_drops: 0,
11106        draining: None,
11107        phase_ever_opened: false,
11108      }),
11109      lifecycle_changed: Condvar::new(),
11110      metrics: Arc::new(RuntimeMetrics::default()),
11111      current_backend: ArcSwapOption::empty(),
11112      pool_workers,
11113    }
11114  }
11115
11116  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11117    let options = options.validate()?;
11118    let mut state = self
11119      .state
11120      .lock()
11121      .unwrap_or_else(std::sync::PoisonError::into_inner);
11122    Self::ensure_configuration_mutable(&state)?;
11123    self
11124      .pool_workers
11125      .store(pool_workers_for(&options), Ordering::Release);
11126    state.options = options;
11127    Ok(())
11128  }
11129
11130  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11131    self.configure_partial_inner(patch, |_| {})
11132  }
11133
11134  fn configure_partial_inner(
11135    &self,
11136    patch: RuntimeOptionsPatch,
11137    after_merge: impl FnOnce(&RuntimeOptions),
11138  ) -> Result<(), RuntimeConfigError> {
11139    let mut state = self
11140      .state
11141      .lock()
11142      .unwrap_or_else(std::sync::PoisonError::into_inner);
11143    Self::ensure_configuration_mutable(&state)?;
11144
11145    let mut options = state.options.clone();
11146    patch.apply_to(&mut options);
11147    after_merge(&options);
11148    let options = options.validate()?;
11149    self
11150      .pool_workers
11151      .store(pool_workers_for(&options), Ordering::Release);
11152    state.options = options;
11153    Ok(())
11154  }
11155
11156  #[cfg(any(napi_runtime_wasi_threads, test))]
11157  fn pool_workers(&self) -> u32 {
11158    self.pool_workers.load(Ordering::Acquire)
11159  }
11160
11161  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11162    if matches!(
11163      &state.lifecycle,
11164      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11165    ) {
11166      return Ok(());
11167    }
11168    Err(RuntimeConfigError(
11169      "the async runtime configuration is frozen; configure it before the first async call"
11170        .to_string(),
11171    ))
11172  }
11173
11174  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11175    let Some(active_generation) = active_runtime_generation() else {
11176      return Ok(());
11177    };
11178    let lifecycle_generation = match &state.lifecycle {
11179      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11180      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11181      RuntimeLifecycle::Initial
11182      | RuntimeLifecycle::StoppedBeforeFirstUse
11183      | RuntimeLifecycle::StoppingWithoutBackend(_)
11184      | RuntimeLifecycle::Stopped => None,
11185    };
11186    if lifecycle_generation == Some(active_generation) {
11187      return Ok(());
11188    }
11189    Err(RuntimeConfigError(
11190      "cannot access or control the async runtime from work owned by a retired generation"
11191        .to_string(),
11192    ))
11193  }
11194
11195  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11196    Self::ensure_active_generation_current(state)?;
11197    match &state.lifecycle {
11198      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11199      RuntimeLifecycle::StoppedBeforeFirstUse
11200      | RuntimeLifecycle::Stopping(_)
11201      | RuntimeLifecycle::StoppingWithoutBackend(_)
11202      | RuntimeLifecycle::Stopped => {
11203        return Err(RuntimeConfigError(
11204          "the async runtime is stopped; call start before submitting work".to_string(),
11205        ));
11206      }
11207      RuntimeLifecycle::Initial => {}
11208    }
11209    if state.rejected_drops != 0 {
11210      return Err(RuntimeConfigError(
11211        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11212      ));
11213    }
11214    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11215    // Its pool threads exist now; see the `pool_workers` field.
11216    self.pool_workers.store(0, Ordering::Release);
11217    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11218    state.forget_shutdown_handoff();
11219    self.current_backend.store(Some(Arc::new(backend.clone())));
11220    Ok(backend)
11221  }
11222
11223  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11224    let mut state = self
11225      .state
11226      .lock()
11227      .unwrap_or_else(std::sync::PoisonError::into_inner);
11228    self.backend_locked(&mut state)
11229  }
11230
11231  fn backend(&self) -> RuntimeBackend {
11232    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11233  }
11234
11235  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11236    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11237      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11238      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11239      RuntimeLifecycle::Initial
11240      | RuntimeLifecycle::StoppedBeforeFirstUse
11241      | RuntimeLifecycle::StoppingWithoutBackend(_)
11242      | RuntimeLifecycle::Stopped => None,
11243    });
11244    state.rejected_drops += 1;
11245    generation
11246  }
11247
11248  /// Lock-free spawn fast path. Returns Some((backend, registration)) when the
11249  /// runtime is Running, the caller's generation is current, and registration
11250  /// succeeds -- all without taking the outer `state` lock. Returns None (fall
11251  /// to the locked slow path) for any miss: no current backend, retired
11252  /// generation, or a closed generation. Staleness is safe: see the safety
11253  /// invariant -- try_register_* re-checks `closed`, and wait_until_idle already
11254  /// gates on any registration that slips in during shutdown.
11255  #[inline]
11256  fn fast_register<R>(
11257    &self,
11258    register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11259  ) -> Option<(Arc<RuntimeBackend>, R)> {
11260    let guard = self.current_backend.load();
11261    let backend = guard.as_ref()?; // None => Initial/Stopping/Stopped => slow path
11262    if let Some(active) = active_runtime_generation()
11263      && active != backend.generation()
11264    {
11265      return None; // retired generation => slow path yields exact error
11266    }
11267    let registration = register(&backend.work)?; // closed => None => slow path rejects
11268    // Hand back the already-loaded Arc (one refcount bump) rather than deep-
11269    // cloning `RuntimeBackend`'s two inner Arcs; both callers only borrow it.
11270    Some((Arc::clone(backend), registration))
11271  }
11272
11273  fn try_spawn_tracked<F, T>(
11274    &self,
11275    future: F,
11276  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11277  where
11278    F: Future<Output = T> + Send + 'static,
11279    T: Send + 'static,
11280  {
11281    #[cfg(test)]
11282    run_before_runtime_submission_lock_test_hook();
11283
11284    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11285      #[cfg(test)]
11286      run_after_async_work_registration_test_hook();
11287      return Ok(spawn_registered(
11288        &backend,
11289        Arc::clone(&self.metrics),
11290        future,
11291        registration,
11292      ));
11293    }
11294
11295    let (backend, registration) = {
11296      let mut state = self
11297        .state
11298        .lock()
11299        .unwrap_or_else(std::sync::PoisonError::into_inner);
11300      let backend = match self.backend_locked(&mut state) {
11301        Ok(backend) => backend,
11302        Err(error) => {
11303          let generation = Self::begin_rejected_drop(&mut state);
11304          return Err((error, future, generation));
11305        }
11306      };
11307      let Some(registration) = backend.work.try_register_async() else {
11308        let generation = Self::begin_rejected_drop(&mut state);
11309        return Err((
11310          RuntimeConfigError(
11311            "the async runtime is stopped; call start before submitting work".to_string(),
11312          ),
11313          future,
11314          generation,
11315        ));
11316      };
11317      (backend, registration)
11318    };
11319    #[cfg(test)]
11320    run_after_async_work_registration_test_hook();
11321    Ok(spawn_registered(
11322      &backend,
11323      Arc::clone(&self.metrics),
11324      future,
11325      registration,
11326    ))
11327  }
11328
11329  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11330  where
11331    F: Future<Output = T> + Send + 'static,
11332    T: Send + 'static,
11333  {
11334    match self.try_spawn_tracked(future) {
11335      Ok(handle) => Ok(handle),
11336      Err((error, future, _generation)) => {
11337        drop(RejectedSubmissionGuard { controller: self });
11338        Err((error, future))
11339      }
11340    }
11341  }
11342
11343  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11344  where
11345    F: Future<Output = T> + Send + 'static,
11346    T: Send + 'static,
11347  {
11348    match self.try_spawn_tracked(future) {
11349      Ok(handle) => handle,
11350      Err((error, future, generation)) => {
11351        let _rejection = RejectedSubmissionGuard { controller: self };
11352        let _drop_context = RejectedSubmissionDropContext::enter();
11353        let _generation = generation.map(RuntimeGenerationGuard::enter);
11354        let _ = catch_unwind_contained(|| drop(future));
11355        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11356          message: error.to_string(),
11357        }))))
11358      }
11359    }
11360  }
11361
11362  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11363  where
11364    F: Future<Output = ()> + Send + 'static,
11365  {
11366    match self.try_spawn(future) {
11367      Ok(handle) => {
11368        handle.detach();
11369        Ok(())
11370      }
11371      Err((_, future)) => Err(future),
11372    }
11373  }
11374
11375  fn try_spawn_blocking_tracked<F, T>(
11376    &self,
11377    function: F,
11378  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11379  where
11380    F: FnOnce() -> T + Send + 'static,
11381    T: Send + 'static,
11382  {
11383    #[cfg(test)]
11384    run_before_runtime_submission_lock_test_hook();
11385
11386    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11387      #[cfg(test)]
11388      run_after_blocking_work_registration_test_hook();
11389      return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11390    }
11391
11392    let (backend, registration) = {
11393      let mut state = self
11394        .state
11395        .lock()
11396        .unwrap_or_else(std::sync::PoisonError::into_inner);
11397      let backend = match self.backend_locked(&mut state) {
11398        Ok(backend) => backend,
11399        Err(error) => {
11400          let generation = Self::begin_rejected_drop(&mut state);
11401          return Err((error, function, generation));
11402        }
11403      };
11404      let Some(registration) = backend.work.try_register_work() else {
11405        let generation = Self::begin_rejected_drop(&mut state);
11406        return Err((
11407          RuntimeConfigError(
11408            "the async runtime is stopped; call start before submitting work".to_string(),
11409          ),
11410          function,
11411          generation,
11412        ));
11413      };
11414      (backend, registration)
11415    };
11416    #[cfg(test)]
11417    run_after_blocking_work_registration_test_hook();
11418    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11419  }
11420
11421  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11422  where
11423    F: FnOnce() -> T + Send + 'static,
11424    T: Send + 'static,
11425  {
11426    match self.try_spawn_blocking_tracked(function) {
11427      Ok(handle) => Ok(handle),
11428      Err((error, function, _generation)) => {
11429        drop(RejectedSubmissionGuard { controller: self });
11430        Err((error, function))
11431      }
11432    }
11433  }
11434
11435  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11436  where
11437    F: FnOnce() -> T + Send + 'static,
11438    T: Send + 'static,
11439  {
11440    match self.try_spawn_blocking_tracked(function) {
11441      Ok(handle) => handle,
11442      Err((error, function, generation)) => {
11443        let _rejection = RejectedSubmissionGuard { controller: self };
11444        let _drop_context = RejectedSubmissionDropContext::enter();
11445        let _generation = generation.map(RuntimeGenerationGuard::enter);
11446        let _ = catch_unwind_contained(|| drop(function));
11447        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11448          message: error.to_string(),
11449        }))))
11450      }
11451    }
11452  }
11453
11454  #[cfg(test)]
11455  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11456    self
11457      .try_block_on(future)
11458      .unwrap_or_else(|error| panic!("{error}"))
11459  }
11460
11461  fn try_block_on_scope(
11462    &self,
11463  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11464    let mut state = self
11465      .state
11466      .lock()
11467      .unwrap_or_else(std::sync::PoisonError::into_inner);
11468    let backend = match self.backend_locked(&mut state) {
11469      Ok(backend) => backend,
11470      Err(error) => {
11471        let generation = Self::begin_rejected_drop(&mut state);
11472        return Err((error, generation));
11473      }
11474    };
11475    let Some(registration) = backend.work.try_register_work() else {
11476      let generation = Self::begin_rejected_drop(&mut state);
11477      return Err((
11478        RuntimeConfigError(
11479          "the async runtime is stopped; call start before submitting work".to_string(),
11480        ),
11481        generation,
11482      ));
11483    };
11484    Ok((backend, registration))
11485  }
11486
11487  fn try_block_on(
11488    &self,
11489    future: Pin<&mut dyn Future<Output = ()>>,
11490  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11491    let (backend, registration) = match self.try_block_on_scope() {
11492      Ok(scope) => scope,
11493      Err((error, _generation)) => {
11494        drop(RejectedSubmissionGuard { controller: self });
11495        return Err(error);
11496      }
11497    };
11498    let _registration = registration;
11499    let _generation = RuntimeGenerationGuard::enter(backend.generation());
11500    Ok(backend.block_on(future))
11501  }
11502
11503  fn start(&self) -> Result<(), RuntimeConfigError> {
11504    let mut state = self
11505      .state
11506      .lock()
11507      .unwrap_or_else(std::sync::PoisonError::into_inner);
11508    loop {
11509      Self::ensure_active_generation_current(&state)?;
11510      match &state.lifecycle {
11511        // napi starts the runtime while the addon is still being registered.
11512        // Keep the first backend lazy so configureAsyncRuntime remains usable
11513        // until the first async binding call.
11514        RuntimeLifecycle::Initial => {
11515          if state.rejected_drops == 0 {
11516            // A started runtime owes nothing to a handoff parked against an
11517            // earlier generation. The stop that handoff announced still
11518            // happened, so `phase_ever_opened` stays set and a phase 2 that
11519            // arrives afterwards is a no-op instead of an error.
11520            state.forget_shutdown_handoff();
11521            return Ok(());
11522          }
11523          if RejectedSubmissionDropContext::is_current() {
11524            return Err(RuntimeConfigError(
11525              "cannot start the async runtime while a rejected submission is being destroyed"
11526                .to_string(),
11527            ));
11528          }
11529          #[cfg(test)]
11530          run_before_initial_start_wait_test_hook();
11531          state = self
11532            .lifecycle_changed
11533            .wait(state)
11534            .unwrap_or_else(std::sync::PoisonError::into_inner);
11535        }
11536        RuntimeLifecycle::Running(_) => {
11537          state.forget_shutdown_handoff();
11538          return Ok(());
11539        }
11540        RuntimeLifecycle::Stopping(identity) => {
11541          if identity.is_current() {
11542            return Err(RuntimeConfigError(
11543              "cannot restart the async runtime from work in the generation being stopped"
11544                .to_string(),
11545            ));
11546          }
11547          state = self
11548            .lifecycle_changed
11549            .wait(state)
11550            .unwrap_or_else(std::sync::PoisonError::into_inner);
11551        }
11552        RuntimeLifecycle::StoppingWithoutBackend(_) => {
11553          if RejectedSubmissionDropContext::is_current() {
11554            return Err(RuntimeConfigError(
11555              "cannot restart the async runtime while a rejected submission is being destroyed"
11556                .to_string(),
11557            ));
11558          }
11559          state = self
11560            .lifecycle_changed
11561            .wait(state)
11562            .unwrap_or_else(std::sync::PoisonError::into_inner);
11563        }
11564        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11565          if state.rejected_drops == 0 {
11566            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11567              state.lifecycle = RuntimeLifecycle::Initial;
11568              state.forget_shutdown_handoff();
11569              return Ok(());
11570            }
11571            break;
11572          }
11573          if RejectedSubmissionDropContext::is_current() {
11574            return Err(RuntimeConfigError(
11575              "cannot restart the async runtime while a rejected submission is being destroyed"
11576                .to_string(),
11577            ));
11578          }
11579          state = self
11580            .lifecycle_changed
11581            .wait(state)
11582            .unwrap_or_else(std::sync::PoisonError::into_inner);
11583        }
11584      }
11585    }
11586    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11587    // Its pool threads exist now; see the `pool_workers` field.
11588    self.pool_workers.store(0, Ordering::Release);
11589    self.current_backend.store(Some(Arc::new(backend.clone())));
11590    state.lifecycle = RuntimeLifecycle::Running(backend);
11591    state.forget_shutdown_handoff();
11592    Ok(())
11593  }
11594
11595  fn options(&self) -> RuntimeOptions {
11596    self
11597      .state
11598      .lock()
11599      .unwrap_or_else(std::sync::PoisonError::into_inner)
11600      .options
11601      .clone()
11602  }
11603
11604  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11605    let state = self
11606      .state
11607      .lock()
11608      .unwrap_or_else(std::sync::PoisonError::into_inner);
11609    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11610      return None;
11611    };
11612    match &backend.executor {
11613      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11614      #[cfg(napi_runtime_os_threads)]
11615      RuntimeExecutor::MultiThread(_) => None,
11616    }
11617  }
11618
11619  fn request_current_thread_drain(&self) {
11620    if let Some(executor) = self.running_current_thread_executor() {
11621      executor.request_drain_if_queued();
11622    }
11623  }
11624
11625  fn retire_rejected_current_thread_work(&self) {
11626    if let Some(executor) = self.running_current_thread_executor() {
11627      executor.retire_rejected_work_off_waker_stack();
11628    }
11629  }
11630
11631  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11632    let executor = {
11633      let state = self
11634        .state
11635        .lock()
11636        .unwrap_or_else(std::sync::PoisonError::into_inner);
11637      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11638        return;
11639      };
11640      match &backend.executor {
11641        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11642        #[cfg(napi_runtime_os_threads)]
11643        RuntimeExecutor::MultiThread(_) => return,
11644      }
11645    };
11646    executor.cancel_host_dispatch(failure);
11647  }
11648
11649  #[cfg(test)]
11650  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11651    let executor = {
11652      let state = self
11653        .state
11654        .lock()
11655        .unwrap_or_else(std::sync::PoisonError::into_inner);
11656      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11657        return;
11658      };
11659      match &backend.executor {
11660        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11661        #[cfg(napi_runtime_os_threads)]
11662        RuntimeExecutor::MultiThread(_) => return,
11663      }
11664    };
11665    executor.cancel_host_dispatch_for_test(dispatch);
11666  }
11667
11668  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11669    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11670    self.drive_current_thread_dispatch_with_lease(dispatch, true)
11671  }
11672
11673  fn drive_current_thread_dispatch_with_lease(
11674    &self,
11675    dispatch: u64,
11676    mark_serviced: bool,
11677  ) -> Option<CurrentThreadTaskCallbackLease> {
11678    let mut host_turn = {
11679      let state = self
11680        .state
11681        .lock()
11682        .unwrap_or_else(std::sync::PoisonError::into_inner);
11683      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11684        return None;
11685      };
11686      let executor = match &backend.executor {
11687        RuntimeExecutor::CurrentThread(executor) => executor,
11688        #[cfg(napi_runtime_os_threads)]
11689        RuntimeExecutor::MultiThread(_) => return None,
11690      };
11691      let host_turn = executor.try_admit_host_turn(dispatch)?;
11692      if mark_serviced {
11693        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11694      }
11695      host_turn
11696    };
11697    #[cfg(test)]
11698    run_after_current_thread_host_admission_test_hook();
11699    host_turn.drive();
11700    Some(CurrentThreadTaskCallbackLease::new(host_turn))
11701  }
11702
11703  #[cfg(test)]
11704  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11705    self
11706      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11707      .is_some()
11708  }
11709
11710  /// Phase 1 of the two-phase shutdown: publish `Stopping`, close admission,
11711  /// abort registered tasks, drop queued work, drain-fire timers and wake
11712  /// parkers -- then return WITHOUT waiting for work that is already running.
11713  ///
11714  /// `Ok(true)` = the announced stop still owes a wait, so the caller should
11715  /// let its host loop turn and poll [`Self::runtime_work_pending`] before
11716  /// phase 2. That is registered work of a generation with a backend, and --
11717  /// on a zero-backend stop -- a rejected submission still being destroyed on
11718  /// another thread, which phase 1 parks as `ShutdownDrain::ZeroBackend`
11719  /// rather than wait out. `Ok(false)` = there is nothing left and
11720  /// `finish_shutdown` will not block.
11721  ///
11722  /// This exists for a host that cannot yield inside a single-call
11723  /// `shutdown`: the threaded WASI artifact enters shutdown from the JS
11724  /// thread, so a blocking closure that still needs a JS turn could never get
11725  /// one while that thread sat in `wait_until_idle`.
11726  ///
11727  /// A `begin_shutdown` while one is outstanding opens no second phase and
11728  /// never waits: it answers with the current work-pending verdict, so the
11729  /// thread that owns the shutdown may re-enter it from a host turn. That
11730  /// holds for the whole of an outstanding stop, including the window in
11731  /// which another thread is already inside the join -- the handoff slot
11732  /// carries `ShutdownDrain::Joining` there rather than going empty, so this
11733  /// call answers from it instead of parking on the published `Stopping`.
11734  /// Phase 1s and phase 2s do not have to balance -- `finish_shutdown`
11735  /// completes the most recently announced stop and is idempotent.
11736  ///
11737  /// `start()` between the phases is NOT allowed -- it waits in `Stopping`
11738  /// until phase 2 publishes `Stopped`, and is rejected outright when it comes
11739  /// from work in the generation being stopped. Admission needs no guard of
11740  /// its own: `backend_locked` already rejects every submission under
11741  /// `Stopping`. The same holds for a zero-backend stop that still owes a
11742  /// rejected destruction: it publishes `StoppingWithoutBackend` for the whole
11743  /// window, and `start` waits there until phase 2 publishes the target. Only
11744  /// a zero-backend phase 1 that settled outright (`ShutdownDrain::Settled`,
11745  /// nothing published to wait in) lets a `start` through -- it drops the
11746  /// handoff, and the `finish_shutdown` that follows finds the fresh
11747  /// generation running and is a no-op `Ok(())`.
11748  fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11749    // See `finish_shutdown`: the same waits, the same reason.
11750    let _bounded_waits = BoundedWaits::enter();
11751    let backend = {
11752      let mut state = self
11753        .state
11754        .lock()
11755        .unwrap_or_else(std::sync::PoisonError::into_inner);
11756      // Before the outstanding-handoff arm, not only inside the loop below:
11757      // that arm returns early, and work of a RETIRED generation must be told
11758      // so rather than be handed the current stop's verdict. The window it
11759      // covers is the whole of an outstanding stop, join included, so the
11760      // guard cannot sit behind it.
11761      Self::ensure_active_generation_current(&state)?;
11762      if let Some(drain) = &state.draining {
11763        let outstanding = match drain {
11764          // `Joining` answers exactly like `Backend`: the stop it carries has
11765          // been announced and is being completed right now, so this call has
11766          // nothing to open and nothing to wait for -- only a verdict to give.
11767          ShutdownDrain::Backend(backend) => Some(backend.clone()),
11768          ShutdownDrain::Joining(backend) => backend.clone(),
11769          // A zero-backend stop that still owes a rejected destruction. The
11770          // verdict lives in `state`, not in a backend, so it is answered
11771          // here -- under this lock, before the `drop(state)` below -- and,
11772          // like every other arm of this re-entry, it never waits. The
11773          // drop-context error keeps the surface identical to the
11774          // `StoppingWithoutBackend` and `Stopped` arms below: a destructor
11775          // can never drive the shutdown of the state it is blocking.
11776          ShutdownDrain::ZeroBackend(_) => {
11777            if RejectedSubmissionDropContext::is_current() {
11778              return Err(RuntimeConfigError(
11779                "cannot shut down the async runtime while a rejected submission is being destroyed"
11780                  .to_string(),
11781              ));
11782            }
11783            return Ok(state.rejected_drops != 0);
11784          }
11785          ShutdownDrain::Settled => None,
11786        };
11787        // A second phase 1 opens nothing: the stop this handoff carries has
11788        // already been announced, and one `finish_shutdown` completes it for
11789        // every caller. Answering from the handoff can never skip stopping a
11790        // live generation: the handoff belongs to the generation it was opened
11791        // against, and `forget_shutdown_handoff` drops it at every `start`.
11792        //
11793        // Answer off the lifecycle lock: probing a backend takes the
11794        // generation and executor locks, and the same re-entry must not
11795        // nest them under this one.
11796        drop(state);
11797        return Ok(outstanding.is_some_and(|backend| backend.work_pending(PendingRead::Locked)));
11798      }
11799      loop {
11800        Self::ensure_active_generation_current(&state)?;
11801        match &state.lifecycle {
11802          RuntimeLifecycle::Initial => {
11803            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11804              return Err(RuntimeConfigError(
11805                "cannot shut down the async runtime while a rejected submission is being destroyed"
11806                  .to_string(),
11807              ));
11808            }
11809            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11810            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11811            if state.rejected_drops != 0 {
11812              // Phase 1 NEVER waits. The outstanding retirement is another
11813              // thread's user `Drop`, and that thread may be waiting for a
11814              // host turn this one can only give by returning. Park the
11815              // announced stop for phase 2 and report it as pending work, so
11816              // the host keeps turning its loop -- the turn that destructor
11817              // may be waiting for.
11818              state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11819              self.lifecycle_changed.notify_all();
11820              return Ok(true);
11821            }
11822            state.lifecycle = target.into_lifecycle();
11823            state.open_phase_one(ShutdownDrain::Settled);
11824            self.lifecycle_changed.notify_all();
11825            return Ok(false);
11826          }
11827          RuntimeLifecycle::Running(backend) => {
11828            if backend.is_current() {
11829              return Err(RuntimeConfigError(
11830                "cannot shut down the async runtime from work running on that runtime".to_string(),
11831              ));
11832            }
11833            let identity = backend.stop_identity();
11834            let stop = backend.generation_stop();
11835            // Lock order is lifecycle -> stop publication. Publish before
11836            // mutating the lifecycle so a gated verdict that acquired the
11837            // publication mutex first wins while this state remains Running;
11838            // otherwise the verdict must observe the published stop.
11839            let stop_publication = stop.publication_guard();
11840            stop_publication.publish_stopping();
11841            let RuntimeLifecycle::Running(backend) =
11842              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11843            else {
11844              unreachable!();
11845            };
11846            self.current_backend.store(None);
11847            // Park the backend for phase 2. `Stopping` carries only an
11848            // identity, so without this slot the backend would live on this
11849            // stack and phase 1 could not return.
11850            state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11851            // Release before aborting tasks or running any user destruction.
11852            // Neither path may retain the publication mutex across scheduler
11853            // admission, lifecycle reentry, or a panic boundary.
11854            drop(stop_publication);
11855            break backend;
11856          }
11857          RuntimeLifecycle::Stopping(identity) => {
11858            if identity.is_current() {
11859              return Err(RuntimeConfigError(
11860                "cannot wait for async runtime shutdown from work in the generation being stopped"
11861                  .to_string(),
11862              ));
11863            }
11864            state = self
11865              .lifecycle_changed
11866              .wait(state)
11867              .unwrap_or_else(std::sync::PoisonError::into_inner);
11868          }
11869          RuntimeLifecycle::StoppingWithoutBackend(_) => {
11870            if RejectedSubmissionDropContext::is_current() {
11871              return Err(RuntimeConfigError(
11872                "cannot shut down the async runtime while a rejected submission is being destroyed"
11873                  .to_string(),
11874              ));
11875            }
11876            state = self
11877              .lifecycle_changed
11878              .wait(state)
11879              .unwrap_or_else(std::sync::PoisonError::into_inner);
11880          }
11881          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11882            if state.rejected_drops == 0 {
11883              state.open_phase_one(ShutdownDrain::Settled);
11884              return Ok(false);
11885            }
11886            if RejectedSubmissionDropContext::is_current() {
11887              return Err(RuntimeConfigError(
11888                "cannot shut down the async runtime while a rejected submission is being destroyed"
11889                  .to_string(),
11890              ));
11891            }
11892            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11893              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11894            } else {
11895              ZeroBackendShutdownTarget::Stopped
11896            };
11897            // Same rule as the `Initial` arm: announce the stop, hand the
11898            // wait to phase 2, and return without waiting on another thread's
11899            // destructor.
11900            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11901            state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11902            self.lifecycle_changed.notify_all();
11903            return Ok(true);
11904          }
11905        }
11906      }
11907    };
11908
11909    #[cfg(test)]
11910    run_after_generation_stop_publication_test_hook();
11911    backend.begin_shutdown();
11912    Ok(backend.work_pending(PendingRead::Locked))
11913  }
11914
11915  /// Non-blocking poll for the window between the phases: `false` means
11916  /// `finish_shutdown` will not have to wait for user work. Reads the running
11917  /// generation when no phase 1 is outstanding, so a host may also use it to
11918  /// decide whether a shutdown would block at all.
11919  ///
11920  /// It stays truthful while a `finish_shutdown` is inside the join: that
11921  /// thread leaves `ShutdownDrain::Joining` in the slot, so this poll reports
11922  /// the very work the join is waiting for instead of `false`.
11923  ///
11924  /// Never blocks, not even on a lock: every lock it reads under is only
11925  /// tried ([`PendingRead::NonBlocking`]), and one held by another thread
11926  /// answers `true`, so the host takes one more turn and asks again. A held
11927  /// lock is not always about to come back: on `wasm32-wasip1-threads` a pool
11928  /// thread that traps while holding one unwinds nothing, and waiting for it
11929  /// here would park the JavaScript thread in `memory.atomic.wait32` for good
11930  /// -- before the loader can see that thread's crash.
11931  fn runtime_work_pending(&self) -> bool {
11932    let backend = {
11933      let state = match self.state.try_lock() {
11934        Ok(state) => state,
11935        Err(std::sync::TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
11936        Err(std::sync::TryLockError::WouldBlock) => return true,
11937      };
11938      match (&state.draining, &state.lifecycle) {
11939        // `Joining` is the in-flight drain: while it still carries the
11940        // backend, report the work the finisher is waiting for, not `false`.
11941        (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11942          backend.clone()
11943        }
11944        // A zero-backend stop that still owes a rejected destruction: that
11945        // destruction IS the pending work, and reporting it is what makes the
11946        // host keep turning its event loop -- the turn the destructor may be
11947        // waiting for. `false` here would send the loader straight into the
11948        // phase-2 join in the same synchronous turn.
11949        (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11950        (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11951        // `Settled` is a handoff for a generation that had no backend, and
11952        // `forget_shutdown_handoff` drops it at every `start`, so it never
11953        // shadows a running generation. A backend-less `Joining` is a drain
11954        // past `wait_until_idle`: no user work can be left to report.
11955        (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11956          return false;
11957        }
11958      }
11959    };
11960    backend.work_pending(PendingRead::NonBlocking)
11961  }
11962
11963  /// Phase 2 of the two-phase shutdown: wait for the generation to go idle,
11964  /// join the workers and publish `Stopped`. Everything after
11965  /// `begin_shutdown`, in the same order the single call runs it.
11966  ///
11967  /// It completes the most recently announced stop, and it is IDEMPOTENT. It
11968  /// owns no phase and counts none: whoever finds a `ShutdownDrain::Backend`
11969  /// handoff takes it, swaps it for `ShutdownDrain::Joining`, drains it and
11970  /// publishes the stop while retiring that marker, and every other caller
11971  /// answers off the lifecycle -- waiting out a join that is still in flight,
11972  /// then reporting `Ok(())` for the stop that was delivered. A
11973  /// `ShutdownDrain::ZeroBackend` handoff is never taken and never swapped:
11974  /// every caller that finds it waits on the outstanding rejected
11975  /// destruction, the one that observes it retired publishes the target and
11976  /// clears the slot, and the rest wake to that published stop and report it
11977  /// the same way. Phase 1s and phase 2s therefore do not have to balance,
11978  /// and two threads completing the same shutdown both get `Ok(())` instead
11979  /// of one of them being told its own stop never happened.
11980  ///
11981  /// It never stops a generation it did not begin. A `start` between the
11982  /// phases -- or any later restart -- drops a handoff that published nothing
11983  /// to wait in (`ShutdownDrain::Settled`) and publishes a fresh generation; a
11984  /// `finish_shutdown` that arrives afterwards with nothing outstanding is a
11985  /// no-op `Ok(())` and does NOT adopt that generation. A `ZeroBackend`
11986  /// handoff is never dropped that way: it publishes `StoppingWithoutBackend`
11987  /// for its whole window, so a `start` waits there until phase 2 publishes
11988  /// the target.
11989  ///
11990  /// The only missing-phase error left is phase 2 on a runtime that has never
11991  /// begun a shutdown at all (`phase_ever_opened == false`): the host called
11992  /// the second half without ever calling the first. Like `shutdown`, it also
11993  /// rejects a caller that is work of the generation being stopped, which could
11994  /// only wait for itself.
11995  ///
11996  /// On `wasm32-wasip1-threads` every lock, condvar wait and join this thread
11997  /// makes in here (and in `begin_shutdown`) stops once the host reports a
11998  /// crashed thread, with a trap: a pool thread that trapped holds its locks
11999  /// and its work for good, and waiting for either would park the host's
12000  /// JavaScript thread before the loader could see the crash. See
12001  /// `crate::sync`.
12002  fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
12003    let _bounded_waits = BoundedWaits::enter();
12004    const STOPPING_WAIT_ERROR: &str =
12005      "cannot wait for async runtime shutdown from work in the generation being stopped";
12006    const REJECTED_DROP_ERROR: &str =
12007      "cannot shut down the async runtime while a rejected submission is being destroyed";
12008    const NO_PHASE_ERROR: &str =
12009      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
12010
12011    let backend = {
12012      let mut state = self
12013        .state
12014        .lock()
12015        .unwrap_or_else(std::sync::PoisonError::into_inner);
12016      // Every arm but `Backend` leaves this function; only that one falls
12017      // through to the take below.
12018      match &state.draining {
12019        Some(ShutdownDrain::Settled) => {
12020          // Phase 1 completed a zero-backend transition; nothing to drain.
12021          state.draining = None;
12022          return Ok(());
12023        }
12024        Some(ShutdownDrain::Backend(backend)) => {
12025          if backend.is_current() {
12026            return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12027          }
12028        }
12029        // Phase 1 announced a zero-backend stop that still owed the
12030        // retirement of a rejected submission's destructor. This is the wait
12031        // phase 1 refused to do. The marker is NOT taken -- there is nothing
12032        // to own -- so the slot is re-read after every wake: only the caller
12033        // that observes `rejected_drops == 0` while the slot still carries
12034        // this target publishes it, and a second finisher that wakes to an
12035        // already-published stop reports it like any other completed phase 2.
12036        Some(ShutdownDrain::ZeroBackend(_)) => loop {
12037          let target = match (&state.draining, &state.lifecycle) {
12038            (
12039              Some(ShutdownDrain::ZeroBackend(parked)),
12040              RuntimeLifecycle::StoppingWithoutBackend(published),
12041            ) if published == parked => *parked,
12042            // Another finisher published it, or a `start` superseded it.
12043            // Either way the stop this call was asked to complete has been
12044            // delivered.
12045            _ => return Ok(()),
12046          };
12047          if state.rejected_drops == 0 {
12048            // Retire the marker and publish the stop under the ONE lock
12049            // acquisition, exactly as the backend path does below: no
12050            // observer may ever see `StoppingWithoutBackend` without a drain,
12051            // or the terminal lifecycle with one.
12052            state.draining = None;
12053            state.lifecycle = target.into_lifecycle();
12054            self.lifecycle_changed.notify_all();
12055            return Ok(());
12056          }
12057          if RejectedSubmissionDropContext::is_current() {
12058            return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12059          }
12060          // `rejected_drops` can flicker 1 -> 0 -> 1 across the host's turns:
12061          // a fresh submission under `StoppingWithoutBackend` is rejected and
12062          // increments it again. Re-observe, exactly as the backend path's
12063          // tail wait does.
12064          state = self
12065            .lifecycle_changed
12066            .wait(state)
12067            .unwrap_or_else(std::sync::PoisonError::into_inner);
12068        },
12069        // `Joining`: another caller took the handoff and is inside the join.
12070        // Same answer as an empty slot on a runtime that announced a stop --
12071        // wait out the publication and report it. `phase_ever_opened` is
12072        // necessarily set here, so the orphan check below never fires.
12073        Some(ShutdownDrain::Joining(_)) | None => {
12074          if !state.phase_ever_opened {
12075            // No shutdown has ever been announced on this runtime, so there is
12076            // nothing for this call to complete: the host called the second
12077            // half without the first.
12078            return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
12079          }
12080          // A stop WAS announced. The slot holds an in-flight drain, or is
12081          // empty because a `start` dropped the handoff or the joining thread
12082          // has already published and retired its marker. Wait out a
12083          // join still in flight and report its publication -- a concurrent
12084          // `shutdown` waits in exactly this place today. Waiting inside this
12085          // arm, instead of re-entering the match, also keeps this caller from
12086          // adopting a phase 1 that was opened after it woke.
12087          loop {
12088            match &state.lifecycle {
12089              RuntimeLifecycle::Stopping(identity) => {
12090                if identity.is_current() {
12091                  return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12092                }
12093              }
12094              RuntimeLifecycle::StoppingWithoutBackend(_) => {
12095                if RejectedSubmissionDropContext::is_current() {
12096                  return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12097                }
12098              }
12099              // Stopped/StoppedBeforeFirstUse: the publication an announced
12100              // stop was owed has happened, whichever thread performed the
12101              // join. Initial/Running: a `start` superseded that generation --
12102              // this call must report the stop, not stop the new generation.
12103              RuntimeLifecycle::StoppedBeforeFirstUse
12104              | RuntimeLifecycle::Stopped
12105              | RuntimeLifecycle::Initial
12106              | RuntimeLifecycle::Running(_) => return Ok(()),
12107            }
12108            state = self
12109              .lifecycle_changed
12110              .wait(state)
12111              .unwrap_or_else(std::sync::PoisonError::into_inner);
12112          }
12113        }
12114      }
12115      // Take the backend so this thread owns the drain and the slot can
12116      // never be taken twice -- but leave an explicit in-flight marker behind
12117      // instead of emptying the slot, so the non-blocking surfaces keep
12118      // telling the truth until `Stopped` is published below. Nothing can
12119      // overwrite it in between: `open_phase_one` is only reached with an
12120      // empty slot, and every `start` arm waits out the `Stopping` this
12121      // marker is published alongside.
12122      let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
12123        unreachable!();
12124      };
12125      state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
12126      backend
12127    };
12128
12129    #[cfg(test)]
12130    run_after_shutdown_drain_taken_test_hook();
12131    backend.wait_until_idle();
12132
12133    #[cfg(napi_runtime_os_threads)]
12134    let worker_lifecycle = backend.worker_lifecycle();
12135    // Hand the backend back out of the marker BEFORE dropping this thread's
12136    // own reference: `drop(backend)` below has to be the LAST one, because
12137    // releasing the executor is what lets the workers exit and what
12138    // `wait_for_all_workers` then joins. The marker itself stays -- phase 1
12139    // and the poll must keep answering without waiting for the rest of the
12140    // join -- it just stops carrying a backend, which `wait_until_idle`
12141    // having returned already makes the truthful answer.
12142    {
12143      let mut state = self
12144        .state
12145        .lock()
12146        .unwrap_or_else(std::sync::PoisonError::into_inner);
12147      debug_assert!(
12148        matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
12149        "nothing may disturb an in-flight drain marker before its publication"
12150      );
12151      state.draining = Some(ShutdownDrain::Joining(None));
12152    }
12153    drop(backend);
12154
12155    #[cfg(napi_runtime_os_threads)]
12156    if let Some(worker_lifecycle) = worker_lifecycle {
12157      worker_lifecycle.wait_for_all_workers();
12158    }
12159
12160    let mut state = self
12161      .state
12162      .lock()
12163      .unwrap_or_else(std::sync::PoisonError::into_inner);
12164    while state.rejected_drops != 0 {
12165      state = self
12166        .lifecycle_changed
12167        .wait(state)
12168        .unwrap_or_else(std::sync::PoisonError::into_inner);
12169    }
12170    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
12171    debug_assert!(
12172      matches!(state.draining, Some(ShutdownDrain::Joining(None))),
12173      "nothing may retire an in-flight drain marker but its own publication"
12174    );
12175    // Retire the marker and publish the stop under the ONE lock acquisition:
12176    // no observer may ever see `Stopping` without a drain, or `Stopped` with
12177    // one.
12178    state.draining = None;
12179    state.lifecycle = RuntimeLifecycle::Stopped;
12180    self.lifecycle_changed.notify_all();
12181    Ok(())
12182  }
12183
12184  /// Single-call shutdown: the two phases back to back. Ordering and
12185  /// semantics are exactly what this function has always done -- phase 1 ends
12186  /// at `RuntimeBackend::begin_shutdown` and phase 2 resumes at
12187  /// `wait_until_idle`, with nothing in between.
12188  ///
12189  /// It takes the lifecycle lock twice, so a `start` (or another shutdown) can
12190  /// land between the halves. Phase 2 being idempotent is what makes that
12191  /// window free: whether the stop this call announced is published by this
12192  /// thread, by a concurrent `finish_shutdown` that took the handoff first, or
12193  /// superseded by a racing `start`, this call reports `Ok(())` for the stop it
12194  /// performed -- it never has to be told that its own shutdown was never
12195  /// started.
12196  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12197    self.begin_shutdown()?;
12198    #[cfg(test)]
12199    run_between_shutdown_phases_test_hook();
12200    self.finish_shutdown()
12201  }
12202}
12203
12204static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12205
12206pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12207  RUNTIME.configure(options)
12208}
12209
12210pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12211  RUNTIME.configure_partial(patch)
12212}
12213
12214pub fn configured_options() -> RuntimeOptions {
12215  RUNTIME.options()
12216}
12217
12218/// How many pool threads the runtime will still spawn, for the generated
12219/// threaded WASI loader: the configured `worker_threads` under MultiThread, 0
12220/// under CurrentThread (also the wasm default before any configure), and 0
12221/// once a backend has started; its pool threads already exist. A configure
12222/// that fails, including one refused because the configuration is frozen,
12223/// leaves it unchanged.
12224///
12225/// The loader reads it after the module registered (and, with
12226/// `napi.wasm.asyncRuntime`, after every successful `configureAsyncRuntime`)
12227/// and keeps that many loaded Workers idle in emnapi's reuse pool, so the pool
12228/// threads the first async call spawns take a Worker that is already booting
12229/// instead of creating one. It reads an atomic and never takes the runtime
12230/// state lock: like the other `napi_wasm_*` polls it must not wait on a lock a
12231/// crashed pool thread may still hold. See the cli's `docs/wasi.md`, "Thread
12232/// pool preload".
12233#[cfg(napi_runtime_wasi_threads)]
12234#[unsafe(no_mangle)]
12235pub extern "C" fn napi_wasm_runtime_pool_workers() -> u32 {
12236  RUNTIME.pool_workers()
12237}
12238
12239pub fn is_multi_threaded() -> bool {
12240  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12241}
12242
12243fn spawn_registered<F, T>(
12244  backend: &RuntimeBackend,
12245  metrics: Arc<RuntimeMetrics>,
12246  future: F,
12247  registration: (AbortRegistration, GenerationWorkGuard),
12248) -> JoinHandle<T>
12249where
12250  F: Future<Output = T> + Send + 'static,
12251  T: Send + 'static,
12252{
12253  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12254  let generation = backend.generation();
12255  let (abort_registration, work_registration) = registration;
12256  #[cfg(napi_runtime_os_threads)]
12257  let dependency = Arc::new(TaskDependency::new_task(generation));
12258  #[cfg(napi_runtime_os_threads)]
12259  let task_dependency = Arc::clone(&dependency);
12260  let wrapped = async move {
12261    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12262    let mut abortable = std::pin::pin!(abortable);
12263    let polled = futures::future::poll_fn(|cx| {
12264      #[cfg(napi_runtime_os_threads)]
12265      task_dependency.begin_poll();
12266      #[cfg(napi_runtime_os_threads)]
12267      let _dependency = TaskDependencyGuard::enter(&task_dependency);
12268      abortable.as_mut().poll(cx)
12269    });
12270    match AssertUnwindSafe(polled).catch_unwind().await {
12271      Ok(Ok(output)) => {
12272        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12273        Ok(ContainedTaskOutput::new(output, generation))
12274      }
12275      Ok(Err(_)) => Err(JoinError {
12276        message: "async runtime stopped before the task completed".to_string(),
12277      }),
12278      Err(panic) => {
12279        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12280        Err(panic_payload_to_join_error(panic))
12281      }
12282    }
12283  };
12284  let scheduler = backend.downgrade_executor();
12285  // `async-task` aborts the process if its future's destructor unwinds. Keep
12286  // the entire task generator behind our manually-dropped containment wrapper,
12287  // including the never-polled case where `future` is still a generator field.
12288  let (runnable, task) = async_task::spawn(
12289    RegisteredTaskFuture::new(wrapped, work_registration, generation),
12290    move |runnable| {
12291      scheduler.schedule(runnable);
12292    },
12293  );
12294  backend.schedule_submission(runnable);
12295  JoinHandle(JoinHandleInner::Task {
12296    task: task.fallible(),
12297    awaiter: None,
12298    #[cfg(napi_runtime_os_threads)]
12299    dependency,
12300  })
12301}
12302
12303pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12304where
12305  F: Future<Output = T> + Send + 'static,
12306  T: Send + 'static,
12307{
12308  RUNTIME.spawn(future)
12309}
12310
12311/// Submit a future without consuming it when the runtime is not accepting work.
12312///
12313/// Lifecycle owners can retain the returned future and retry it after
12314/// [`start`] completes a runtime restart.
12315///
12316/// **On a wasm target, prefer this over [`spawn`].** Building the MultiThread
12317/// pool is lazy and fail-loud: the first async call is what calls
12318/// `wasi.thread-spawn`, and whether that works is a JavaScript fact the crate
12319/// cannot observe up front. Here the failure is an `Err` you can act on; the
12320/// infallible helpers turn it into a panic, which `panic = "abort"` then turns
12321/// into a dead instance.
12322pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12323where
12324  F: Future<Output = T> + Send + 'static,
12325  T: Send + 'static,
12326{
12327  RUNTIME.try_spawn(future)
12328}
12329
12330pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12331where
12332  F: Future<Output = ()> + Send + 'static,
12333{
12334  RUNTIME.try_spawn_detached(future)
12335}
12336
12337pub fn spawn_detached<F>(future: F)
12338where
12339  F: Future<Output = ()> + Send + 'static,
12340{
12341  spawn(future).detach();
12342}
12343
12344pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12345where
12346  F: FnOnce() -> T + Send + 'static,
12347  T: Send + 'static,
12348{
12349  RUNTIME.spawn_blocking(function)
12350}
12351
12352/// Submit blocking work without consuming it when the runtime is not accepting work.
12353///
12354/// Lifecycle owners can retain the returned closure and retry it after [`start`]
12355/// completes a runtime restart.
12356///
12357/// **On a wasm target, prefer this over [`spawn_blocking`]** -- see
12358/// [`try_spawn`] for why.
12359pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12360where
12361  F: FnOnce() -> T + Send + 'static,
12362  T: Send + 'static,
12363{
12364  RUNTIME.try_spawn_blocking(function)
12365}
12366
12367fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12368  let (backend, registration) = match controller.try_block_on_scope() {
12369    Ok(scope) => scope,
12370    Err((error, generation)) => {
12371      let _rejection = RejectedSubmissionGuard { controller };
12372      let _drop_context = RejectedSubmissionDropContext::enter();
12373      let _generation = generation.map(RuntimeGenerationGuard::enter);
12374      let _ = catch_unwind_contained(|| drop(future));
12375      panic!("{error}");
12376    }
12377  };
12378  let generation = backend.generation();
12379  let _registration = registration;
12380  let _generation = RuntimeGenerationGuard::enter(generation);
12381  let mut output = None;
12382  {
12383    let future = ContainedFuture::new(future, generation);
12384    let mut erased = std::pin::pin!(async {
12385      output = Some(future.await);
12386    });
12387    let _ = backend.block_on(erased.as_mut());
12388    // `erased` is destroyed before this scope exits, while the controller's
12389    // generation work and identity guards are live. `ContainedFuture` keeps a
12390    // poll panic from becoming a process-aborting double panic if the owned
12391    // future's destructor also unwinds.
12392  }
12393  output.expect("async runtime returned before the future completed")
12394}
12395
12396pub fn block_on<F: Future>(future: F) -> F::Output {
12397  block_on_with_controller(&RUNTIME, future)
12398}
12399
12400pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12401  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12402}
12403
12404/// Drive a borrowed future without consuming it when admission or shutdown fails.
12405///
12406/// **On a wasm target, prefer this over [`block_on`] / [`block_on_dyn`]** --
12407/// see [`try_spawn`] for why.
12408///
12409/// Under [`RuntimeFlavor::MultiThread`] this parks the CALLING thread on
12410/// `memory.atomic.wait32`, which a browser main thread is forbidden to do
12411/// (emnapi answers `napi_would_deadlock` there). The threaded wasm artifact is
12412/// a Node / WebContainer target today; inside a Worker it is fine.
12413pub fn try_block_on_dyn(
12414  future: Pin<&mut dyn Future<Output = ()>>,
12415) -> Result<(), RuntimeConfigError> {
12416  match RUNTIME.try_block_on(future)? {
12417    BlockOnOutcome::Completed => Ok(()),
12418    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12419      "the async runtime stopped before block_on completed its future".to_string(),
12420    )),
12421  }
12422}
12423
12424pub fn start() -> Result<(), RuntimeConfigError> {
12425  RUNTIME.start()
12426}
12427
12428/// Shut the async runtime down: stop accepting work, cancel what is queued,
12429/// wait for what is running, join the workers.
12430///
12431/// Equivalent to [`begin_shutdown`] followed by [`finish_shutdown`], and
12432/// unchanged in ordering and semantics. It reports the shutdown it performed
12433/// even when another thread completes that shutdown, or restarts the runtime,
12434/// between the two halves.
12435pub fn shutdown() -> Result<(), RuntimeConfigError> {
12436  RUNTIME.shutdown()
12437}
12438
12439/// Phase 1 of a two-phase shutdown. Publishes the stop, closes admission,
12440/// aborts registered tasks, drops queued work, drain-fires timers and wakes
12441/// parkers -- and never waits. Never, including for a rejected submission
12442/// whose destructor is still running on another thread: that wait belongs to
12443/// [`finish_shutdown`] too.
12444///
12445/// `Ok(true)` means the announced stop still owes a wait -- registered work
12446/// that is still live, or a rejected submission still being destroyed: let
12447/// the host loop turn, poll [`runtime_work_pending`] until it answers `false`
12448/// (or a bound expires), then call [`finish_shutdown`]. `Ok(false)` means
12449/// [`finish_shutdown`] has nothing to wait for.
12450///
12451/// For a host that cannot yield inside [`shutdown`] -- on the threaded WASI
12452/// artifact the call comes from the JS thread, which is also the only thread
12453/// that can give a running blocking closure the turn it is waiting for.
12454///
12455/// Submissions are rejected between the phases, and [`start`] must not be
12456/// called there: it waits for phase 2 to publish the stop. A second
12457/// `begin_shutdown` before [`finish_shutdown`] neither waits nor starts a
12458/// second shutdown; it just re-reports [`runtime_work_pending`]. Calls of the
12459/// two phases do not have to balance: [`finish_shutdown`] completes the most
12460/// recently announced stop and is idempotent.
12461pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12462  RUNTIME.begin_shutdown()
12463}
12464
12465/// Non-blocking poll for the window between [`begin_shutdown`] and
12466/// [`finish_shutdown`]: `false` means phase 2 will not wait for user work.
12467///
12468/// Answers for the running generation when no shutdown is outstanding.
12469///
12470/// It never waits for a lock either: when another thread holds one it reads
12471/// under, it answers `true` and the host polls again on its next turn. That
12472/// keeps a pool thread that died holding such a lock (a trap on
12473/// `wasm32-wasip1-threads` unwinds nothing) from parking the polling thread
12474/// for good.
12475pub fn runtime_work_pending() -> bool {
12476  RUNTIME.runtime_work_pending()
12477}
12478
12479/// Phase 2 of a two-phase shutdown: wait for the generation to go idle, join
12480/// the workers and publish the stop.
12481///
12482/// Completes the most recently announced stop, whichever thread performs the
12483/// join, and is idempotent: extra calls, and calls that race another thread
12484/// completing the same shutdown, answer `Ok(())`. A call that arrives after a
12485/// [`start`] finds nothing outstanding and is a no-op -- it never stops the
12486/// generation that restart published. It errors only when no [`begin_shutdown`]
12487/// has EVER been called on this runtime, and when called from work of the
12488/// generation being stopped. Calling it while [`runtime_work_pending`] still
12489/// answers `true` is allowed -- it then blocks, which is what [`shutdown`]
12490/// does.
12491pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12492  RUNTIME.finish_shutdown()
12493}
12494
12495fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12496  for failure in completion.failed_dispatches {
12497    RUNTIME.cancel_current_thread_dispatch(failure);
12498  }
12499  if completion.redispatch {
12500    RUNTIME.request_current_thread_drain();
12501  }
12502  // Every host entry point lands here (delivery acknowledgement or failure,
12503  // driver unregistration, explicit dispatch cancellation). Work a
12504  // waker-stack failure transition moved aside is cancelled on this clean
12505  // stack even when the completion itself carried nothing, e.g. an env
12506  // cleanup hook unregistering a driver whose deliveries all failed already.
12507  RUNTIME.retire_rejected_current_thread_work();
12508}
12509
12510/// Acknowledge that one exact host delivery callback returned the successful
12511/// claim result from [`drive_current_thread_tasks`].
12512///
12513/// This releases the host's single-flight slot and submits at most its latest
12514/// still-needed pending delivery. The registry verifies that this exact
12515/// delivery was claimed; a forged success for an unclaimed delivery is treated
12516/// as failure. A stale acknowledgement from an unregistered host or retired
12517/// attempt is a no-op.
12518pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12519  complete_current_thread_task_delivery(
12520    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12521  );
12522}
12523
12524/// Report that one exact host delivery callback failed.
12525///
12526/// Recovery begins only after no other live host attempt can still service the
12527/// same executor dispatch. The registration and delivery identities prevent a
12528/// stale failure from affecting another host or a re-registered generation.
12529pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12530  complete_current_thread_task_delivery(
12531    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12532  );
12533}
12534
12535/// Cancel an accepted CurrentThread host delivery that failed before it could
12536/// schedule a fresh turn.
12537///
12538/// `delivery` is the opaque registration-scoped driver capability,
12539/// not the executor's shared dispatch capability. Repeated or stale failures
12540/// are no-ops.
12541pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12542  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12543}
12544
12545/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
12546///
12547/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
12548/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
12549/// Returns a callback lease only when this exact delivery was claimed, admitted,
12550/// and driven. It returns `None` for stale/unregistered deliveries, before
12551/// backend creation, and on MultiThread. The host must retain the lease through
12552/// delivery acknowledgement/failure and callback payload destruction.
12553pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12554  RUNTIME.drive_current_thread_tasks(delivery)
12555}
12556
12557pub fn reset_metrics() {
12558  RUNTIME.metrics.reset();
12559}
12560
12561pub fn metrics() -> RuntimeMetricsSnapshot {
12562  let options = RUNTIME.options();
12563  RUNTIME.metrics.snapshot(&options)
12564}
12565
12566#[cfg(test)]
12567// The wasm lanes gate out whole classes of tests (see the three rules below),
12568// which strands their helper fns, statics and driver registries. That is the
12569// point of the gates, not a defect, and 100+ dead-code warnings would bury a
12570// real one in the `cargo check --tests` guard CI runs on both WASI triples.
12571#[cfg_attr(target_family = "wasm", allow(dead_code))]
12572mod tests {
12573  use super::*;
12574
12575  // Three gates in here do NOT mean "can this build create OS threads?", so
12576  // the `napi_runtime_os_threads` rewrite deliberately does not reach them.
12577  // Each was verified by running the suite on `wasm32-wasip1-threads`:
12578  //
12579  // * `#[cfg(not(target_family = "wasm"))]` on a test that re-execs itself
12580  //   through `std::process::Command`: `std::env::current_exe()` is
12581  //   `Unsupported` on WASI (22 tests).
12582  // * `#[cfg(not(target_family = "wasm"))]` on a test that asserts the
12583  //   DEFAULT flavor is MultiThread: every wasm target keeps CurrentThread by
12584  //   default, which `RuntimeOptions::default` still decides with
12585  //   `cfg!(target_family = "wasm")` (2 tests).
12586  // * `panic = "unwind"` on a test that drives a panic through
12587  //   `catch_unwind`: both WASI targets are `panic = "abort"`, so every
12588  //   containment path this crate has is inert there and the panic takes the
12589  //   process down instead of becoming a `JoinError` (43 tests). The
12590  //   containment code itself still compiles and runs; only the tests that
12591  //   PROVE containment need an unwinding target.
12592  //
12593  // `napi_runtime_os_threads` itself is also the right gate for a test whose
12594  // HARNESS spawns a `std::thread` even though the code under test does not:
12595  // threadless WASI answers `thread_spawn` with `Os { code: 58, Unsupported }`
12596  // (13 tests).
12597
12598  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12599  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12600    LazyLock::new(Mutex::default);
12601  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12602  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12603  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12604  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12605    LazyLock::new(Mutex::default);
12606  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12607  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12608    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12609  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12610    LazyLock::new(Mutex::default);
12611  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12612  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12613    CurrentThreadTaskDriverRegistry,
12614  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12615  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12616  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12617  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12618    LazyLock::new(Mutex::default);
12619  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12620    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12621  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12622    LazyLock::new(Mutex::default);
12623  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12624  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12625    LazyLock::new(Mutex::default);
12626  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12627    LazyLock::new(Mutex::default);
12628  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12629    CurrentThreadTaskDriverRegistry,
12630  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12631  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12632    LazyLock::new(Mutex::default);
12633  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12634  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12635    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12636  static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12637    CurrentThreadTaskDriverRegistry,
12638  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12639  static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12640    CurrentThreadTaskDriverRegistry,
12641  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12642  static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12643    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12644  static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12645    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12646  static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12647    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12648  static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12649    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12650  static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12651    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12652  static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12653    CurrentThreadTaskDriverRegistry,
12654  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12655  static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12656    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12657  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12658
12659  thread_local! {
12660    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12661      const { std::cell::Cell::new(0) };
12662    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12663      const { std::cell::Cell::new(0) };
12664  }
12665
12666  #[cfg(napi_runtime_os_threads)]
12667  struct StopPollProbe {
12668    polls: Arc<AtomicUsize>,
12669    first_poll: Option<std::sync::mpsc::Sender<()>>,
12670  }
12671
12672  #[cfg(napi_runtime_os_threads)]
12673  impl Future for StopPollProbe {
12674    type Output = ();
12675
12676    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12677      self.polls.fetch_add(1, Ordering::SeqCst);
12678      if let Some(first_poll) = self.first_poll.take() {
12679        first_poll.send(()).unwrap();
12680      }
12681      Poll::Pending
12682    }
12683  }
12684
12685  #[cfg(napi_runtime_os_threads)]
12686  struct PanickingJoinAwaiter {
12687    wakes: Arc<AtomicUsize>,
12688    drops: Arc<AtomicUsize>,
12689  }
12690
12691  #[cfg(napi_runtime_os_threads)]
12692  impl std::task::Wake for PanickingJoinAwaiter {
12693    fn wake(self: Arc<Self>) {
12694      self.wakes.fetch_add(1, Ordering::SeqCst);
12695      panic!("intentional JoinHandle awaiter wake panic");
12696    }
12697
12698    fn wake_by_ref(self: &Arc<Self>) {
12699      self.wakes.fetch_add(1, Ordering::SeqCst);
12700      panic!("intentional JoinHandle awaiter wake-by-ref panic");
12701    }
12702  }
12703
12704  #[cfg(napi_runtime_os_threads)]
12705  impl Drop for PanickingJoinAwaiter {
12706    fn drop(&mut self) {
12707      self.drops.fetch_add(1, Ordering::SeqCst);
12708      panic!("intentional JoinHandle awaiter destructor panic");
12709    }
12710  }
12711
12712  #[cfg(napi_runtime_os_threads)]
12713  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12714    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12715  }
12716
12717  #[cfg(napi_runtime_os_threads)]
12718  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12719  enum JoinAwaiterWakerEventKind {
12720    Clone,
12721    Wake,
12722    WakeByRef,
12723    Drop,
12724  }
12725
12726  #[cfg(napi_runtime_os_threads)]
12727  struct JoinAwaiterWakerState {
12728    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12729  }
12730
12731  #[cfg(napi_runtime_os_threads)]
12732  impl JoinAwaiterWakerState {
12733    fn record(&self, kind: JoinAwaiterWakerEventKind) {
12734      self
12735        .events
12736        .lock()
12737        .unwrap_or_else(std::sync::PoisonError::into_inner)
12738        .push((kind, active_runtime_generation()));
12739    }
12740
12741    fn clear(&self) {
12742      self
12743        .events
12744        .lock()
12745        .unwrap_or_else(std::sync::PoisonError::into_inner)
12746        .clear();
12747    }
12748
12749    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12750      self
12751        .events
12752        .lock()
12753        .unwrap_or_else(std::sync::PoisonError::into_inner)
12754        .clone()
12755    }
12756  }
12757
12758  #[cfg(napi_runtime_os_threads)]
12759  struct JoinAwaiterWakerToken {
12760    state: Arc<JoinAwaiterWakerState>,
12761  }
12762
12763  #[cfg(napi_runtime_os_threads)]
12764  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12765    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12766    token.state.record(JoinAwaiterWakerEventKind::Clone);
12767    let clone = Box::new(JoinAwaiterWakerToken {
12768      state: Arc::clone(&token.state),
12769    });
12770    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12771  }
12772
12773  #[cfg(napi_runtime_os_threads)]
12774  unsafe fn wake_join_awaiter_waker(data: *const ()) {
12775    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12776    token.state.record(JoinAwaiterWakerEventKind::Wake);
12777  }
12778
12779  #[cfg(napi_runtime_os_threads)]
12780  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12781    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12782    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12783  }
12784
12785  #[cfg(napi_runtime_os_threads)]
12786  unsafe fn drop_join_awaiter_waker(data: *const ()) {
12787    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12788    token.state.record(JoinAwaiterWakerEventKind::Drop);
12789  }
12790
12791  #[cfg(napi_runtime_os_threads)]
12792  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12793    clone_join_awaiter_waker,
12794    wake_join_awaiter_waker,
12795    wake_join_awaiter_waker_by_ref,
12796    drop_join_awaiter_waker,
12797  );
12798
12799  #[cfg(napi_runtime_os_threads)]
12800  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12801    let token = Box::new(JoinAwaiterWakerToken { state });
12802    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12803    unsafe { Waker::from_raw(raw) }
12804  }
12805
12806  #[cfg(napi_runtime_os_threads)]
12807  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12808    wait_until("the block_on driver to sleep", || {
12809      stop
12810        .parkers
12811        .lock()
12812        .unwrap_or_else(std::sync::PoisonError::into_inner)
12813        .iter()
12814        .filter_map(Weak::upgrade)
12815        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12816    });
12817  }
12818
12819  #[cfg(panic = "unwind")]
12820  #[test]
12821  fn unique_ids_fail_before_wraparound() {
12822    let counter = AtomicU64::new(u64::MAX - 1);
12823    assert_eq!(
12824      next_unique_id(&counter, "test id space exhausted"),
12825      u64::MAX - 1
12826    );
12827
12828    let exhausted = catch_unwind(AssertUnwindSafe(|| {
12829      next_unique_id(&counter, "test id space exhausted");
12830    }));
12831    assert!(exhausted.is_err());
12832    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12833  }
12834
12835  #[cfg(panic = "unwind")]
12836  #[test]
12837  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12838    let work = GenerationWork::new();
12839    {
12840      let mut state = work.state.lock().unwrap();
12841      state.next_task_id = u64::MAX;
12842    }
12843
12844    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12845    assert!(exhausted.is_err());
12846
12847    let state = work.state.lock().unwrap();
12848    assert_eq!(state.next_task_id, u64::MAX);
12849    assert_eq!(state.active, 0);
12850    assert!(state.abort_handles.is_empty());
12851  }
12852
12853  /// A waker that records, when its last reference drops, whether `work`'s
12854  /// state lock was free at that moment. `Abortable` parks the waker it is
12855  /// polled with in the abort state its handles share, so the waker drops
12856  /// exactly where the last `AbortHandle` of that task does.
12857  struct GenerationStateLockProbe {
12858    work: Weak<GenerationWork>,
12859    dropped_with_lock_free: Arc<Mutex<Option<bool>>>,
12860  }
12861
12862  #[expect(
12863    clippy::manual_noop_waker,
12864    reason = "the test observes the waker payload's Drop"
12865  )]
12866  impl std::task::Wake for GenerationStateLockProbe {
12867    fn wake(self: Arc<Self>) {}
12868  }
12869
12870  impl Drop for GenerationStateLockProbe {
12871    fn drop(&mut self) {
12872      let lock_free = self
12873        .work
12874        .upgrade()
12875        .is_some_and(|work| work.state.try_lock().is_ok());
12876      *self.dropped_with_lock_free.lock().unwrap() = Some(lock_free);
12877    }
12878  }
12879
12880  /// Register one async task on `work` and leave the handle `work` keeps as
12881  /// the only owner of its abort state, with a [`GenerationStateLockProbe`]
12882  /// parked in it.
12883  fn register_task_with_state_lock_probe(
12884    work: &Arc<GenerationWork>,
12885  ) -> (GenerationWorkGuard, Arc<Mutex<Option<bool>>>) {
12886    let (registration, guard) = work
12887      .try_register_async()
12888      .expect("an open generation must accept the task");
12889    let dropped_with_lock_free = Arc::new(Mutex::new(None));
12890    let waker = Waker::from(Arc::new(GenerationStateLockProbe {
12891      work: Arc::downgrade(work),
12892      dropped_with_lock_free: Arc::clone(&dropped_with_lock_free),
12893    }));
12894    let mut task = std::pin::pin!(Abortable::new(std::future::pending::<()>(), registration));
12895    assert!(
12896      task
12897        .as_mut()
12898        .poll(&mut Context::from_waker(&waker))
12899        .is_pending()
12900    );
12901    (guard, dropped_with_lock_free)
12902  }
12903
12904  #[test]
12905  fn generation_work_guard_drops_its_abort_handle_outside_the_state_lock() {
12906    // The last `AbortHandle` of a task frees its abort state and the waker
12907    // parked there, and a waker's drop is foreign code. Under the state lock,
12908    // anything that goes wrong in there -- on `wasm32-wasip1-threads`, a trap
12909    // in the allocator unwinds nothing -- leaves the lock held for good.
12910    let work = GenerationWork::new();
12911    let (guard, dropped_with_lock_free) = register_task_with_state_lock_probe(&work);
12912    assert_eq!(
12913      *dropped_with_lock_free.lock().unwrap(),
12914      None,
12915      "the handle the generation keeps must still own the abort state"
12916    );
12917
12918    drop(guard);
12919    assert_eq!(
12920      *dropped_with_lock_free.lock().unwrap(),
12921      Some(true),
12922      "the guard must drop the handle it removed after releasing the state lock"
12923    );
12924  }
12925
12926  #[test]
12927  fn close_and_abort_takes_the_abort_handles_out_of_the_state() {
12928    // `abort` consumes the parked waker, so what is left to free is the abort
12929    // state itself. Taking the handles out at close frees it after the lock
12930    // is released, and a guard retiring later finds nothing to free under it.
12931    let work = GenerationWork::new();
12932    let (registration, guard) = work
12933      .try_register_async()
12934      .expect("an open generation must accept the task");
12935    let task = Abortable::new(std::future::pending::<()>(), registration);
12936
12937    work.close_and_abort();
12938    assert!(task.is_aborted(), "close must abort the registered task");
12939    assert!(
12940      work.state.lock().unwrap().abort_handles.is_empty(),
12941      "close must take the handles out of the state, not leave them for the guards"
12942    );
12943    drop(task);
12944    drop(guard);
12945    assert_eq!(work.state.lock().unwrap().active, 0);
12946  }
12947
12948  #[cfg(panic = "unwind")]
12949  #[test]
12950  fn caught_panic_payload_destructor_runs() {
12951    struct DropSignal(Arc<AtomicBool>);
12952
12953    impl Drop for DropSignal {
12954      fn drop(&mut self) {
12955        self.0.store(true, Ordering::SeqCst);
12956      }
12957    }
12958
12959    let dropped = Arc::new(AtomicBool::new(false));
12960    let payload = DropSignal(Arc::clone(&dropped));
12961
12962    assert!(
12963      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12964      "the original panic must remain contained"
12965    );
12966    assert!(
12967      dropped.load(Ordering::SeqCst),
12968      "an ordinary caught panic payload must be destroyed"
12969    );
12970  }
12971
12972  #[cfg(panic = "unwind")]
12973  #[test]
12974  fn hostile_caught_panic_payload_destructor_remains_contained() {
12975    struct PanicOnDrop(Arc<AtomicBool>);
12976
12977    impl Drop for PanicOnDrop {
12978      fn drop(&mut self) {
12979        self.0.store(true, Ordering::SeqCst);
12980        panic!("caught panic payload destructor escaped");
12981      }
12982    }
12983
12984    let dropped = Arc::new(AtomicBool::new(false));
12985    let payload = PanicOnDrop(Arc::clone(&dropped));
12986    let result = catch_unwind(AssertUnwindSafe(|| {
12987      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12988    }));
12989
12990    assert!(
12991      result.is_ok(),
12992      "the payload destructor's nested panic must remain contained"
12993    );
12994    assert!(
12995      result.unwrap().is_none(),
12996      "the original panic must remain contained"
12997    );
12998    assert!(
12999      dropped.load(Ordering::SeqCst),
13000      "the hostile payload destructor must be attempted"
13001    );
13002  }
13003
13004  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13005    CurrentThreadHostDispatchResult::Accepted
13006  }
13007
13008  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13009    CurrentThreadHostDispatchResult::Unavailable
13010  }
13011
13012  fn count_current_thread_recovery_host_dispatch(
13013    _dispatch: u64,
13014  ) -> CurrentThreadHostDispatchResult {
13015    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
13016    CurrentThreadHostDispatchResult::Accepted
13017  }
13018
13019  fn reset_current_thread_recovery_host_dispatches() {
13020    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
13021  }
13022
13023  fn current_thread_recovery_host_dispatches() -> usize {
13024    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
13025  }
13026
13027  fn accept_only_first_current_thread_host_dispatch(
13028    _dispatch: u64,
13029  ) -> CurrentThreadHostDispatchResult {
13030    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
13031      let previous = dispatches.get();
13032      dispatches.set(previous + 1);
13033      previous == 0
13034    }) {
13035      CurrentThreadHostDispatchResult::Accepted
13036    } else {
13037      CurrentThreadHostDispatchResult::Unavailable
13038    }
13039  }
13040
13041  fn reset_current_thread_one_shot_host_dispatches() {
13042    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
13043  }
13044
13045  fn current_thread_one_shot_host_dispatches() -> usize {
13046    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
13047  }
13048
13049  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13050    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13051    CurrentThreadHostDispatchResult::Accepted
13052  }
13053
13054  fn count_current_thread_blocking_budget_host_dispatch(
13055    _dispatch: u64,
13056  ) -> CurrentThreadHostDispatchResult {
13057    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13058    CurrentThreadHostDispatchResult::Accepted
13059  }
13060
13061  fn count_current_thread_admission_host_dispatch(
13062    _dispatch: u64,
13063  ) -> CurrentThreadHostDispatchResult {
13064    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13065    CurrentThreadHostDispatchResult::Accepted
13066  }
13067
13068  fn count_current_thread_interleaved_recovery_host_dispatch(
13069    _dispatch: u64,
13070  ) -> CurrentThreadHostDispatchResult {
13071    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13072    CurrentThreadHostDispatchResult::Accepted
13073  }
13074
13075  fn dispatch_late_registration_current_thread_tasks(
13076    dispatch: u64,
13077  ) -> CurrentThreadHostDispatchResult {
13078    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
13079  }
13080
13081  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13082    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13083      .lock()
13084      .unwrap()
13085      .push(dispatch);
13086    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
13087  }
13088
13089  fn dispatch_authoritative_panic_current_thread_tasks(
13090    dispatch: u64,
13091  ) -> CurrentThreadHostDispatchResult {
13092    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13093      .lock()
13094      .unwrap()
13095      .push(dispatch);
13096    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
13097      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
13098      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
13099      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
13100    }
13101  }
13102
13103  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13104    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13105  }
13106
13107  fn dispatch_waker_stack_failure_current_thread_tasks(
13108    dispatch: u64,
13109  ) -> CurrentThreadHostDispatchResult {
13110    CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13111  }
13112
13113  fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
13114    dispatch: u64,
13115  ) -> CurrentThreadHostDispatchResult {
13116    CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13117  }
13118
13119  fn dispatch_submission_failure_current_thread_tasks(
13120    dispatch: u64,
13121  ) -> CurrentThreadHostDispatchResult {
13122    CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13123  }
13124
13125  fn dispatch_retirement_turn_current_thread_tasks(
13126    dispatch: u64,
13127  ) -> CurrentThreadHostDispatchResult {
13128    CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
13129  }
13130
13131  fn dispatch_nested_retirement_current_thread_tasks(
13132    dispatch: u64,
13133  ) -> CurrentThreadHostDispatchResult {
13134    CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
13135  }
13136
13137  fn dispatch_sustained_producer_current_thread_tasks(
13138    dispatch: u64,
13139  ) -> CurrentThreadHostDispatchResult {
13140    CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
13141  }
13142
13143  fn dispatch_retirement_retry_current_thread_tasks(
13144    dispatch: u64,
13145  ) -> CurrentThreadHostDispatchResult {
13146    CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
13147  }
13148
13149  fn dispatch_final_retirement_offer_current_thread_tasks(
13150    dispatch: u64,
13151  ) -> CurrentThreadHostDispatchResult {
13152    CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
13153  }
13154
13155  fn dispatch_inline_final_offer_current_thread_tasks(
13156    dispatch: u64,
13157  ) -> CurrentThreadHostDispatchResult {
13158    CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
13159  }
13160
13161  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
13162    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13163      .lock()
13164      .unwrap()
13165      .push(dispatch);
13166    assert!(
13167      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13168      "injected CurrentThread host dispatch panic"
13169    );
13170    CurrentThreadHostDispatchResult::Accepted
13171  }
13172
13173  fn panic_once_current_thread_armed_republish_host_dispatch(
13174    dispatch: u64,
13175  ) -> CurrentThreadHostDispatchResult {
13176    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13177      .lock()
13178      .unwrap()
13179      .push(dispatch);
13180    assert!(
13181      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13182      "injected armed CurrentThread host dispatch panic"
13183    );
13184    CurrentThreadHostDispatchResult::Accepted
13185  }
13186
13187  fn record_current_thread_republish_rearm_dispatch(
13188    dispatch: u64,
13189  ) -> CurrentThreadHostDispatchResult {
13190    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13191      .lock()
13192      .unwrap()
13193      .push(dispatch);
13194    CurrentThreadHostDispatchResult::Accepted
13195  }
13196
13197  fn reject_initial_current_thread_publication_once(
13198    dispatch: u64,
13199  ) -> CurrentThreadHostDispatchResult {
13200    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13201      .lock()
13202      .unwrap();
13203    dispatches.push(dispatch);
13204    if dispatches.len() == 1 {
13205      CurrentThreadHostDispatchResult::Unavailable
13206    } else {
13207      CurrentThreadHostDispatchResult::Accepted
13208    }
13209  }
13210
13211  fn reject_current_thread_replacement_publication_once(
13212    dispatch: u64,
13213  ) -> CurrentThreadHostDispatchResult {
13214    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13215      .lock()
13216      .unwrap();
13217    dispatches.push(dispatch);
13218    if dispatches.len() == 2 {
13219      CurrentThreadHostDispatchResult::Unavailable
13220    } else {
13221      CurrentThreadHostDispatchResult::Accepted
13222    }
13223  }
13224
13225  fn fail_current_thread_replacement_publication_once(
13226    dispatch: u64,
13227  ) -> CurrentThreadHostDispatchResult {
13228    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13229      .lock()
13230      .unwrap()
13231      .push(dispatch);
13232    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
13233  }
13234
13235  fn count_current_thread_shutdown_republish_host_dispatch(
13236    _dispatch: u64,
13237  ) -> CurrentThreadHostDispatchResult {
13238    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13239    CurrentThreadHostDispatchResult::Accepted
13240  }
13241
13242  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13243    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13244    CurrentThreadHostDispatchResult::Accepted
13245  }
13246
13247  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
13248    let pending = executor.dispatch_pending.load(Ordering::Acquire);
13249    if pending != 0 {
13250      return pending;
13251    }
13252    let dispatch = next_current_thread_dispatch_id();
13253    match executor.dispatch_pending.compare_exchange(
13254      0,
13255      dispatch,
13256      Ordering::AcqRel,
13257      Ordering::Acquire,
13258    ) {
13259      Ok(_) => dispatch,
13260      Err(pending) => pending,
13261    }
13262  }
13263
13264  fn current_thread_dispatch_failure(
13265    dispatch: u64,
13266    epoch: u64,
13267  ) -> CurrentThreadTaskDispatchFailure {
13268    CurrentThreadTaskDispatchFailure { dispatch, epoch }
13269  }
13270
13271  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
13272    let executor = controller
13273      .running_current_thread_executor()
13274      .expect("the test controller must have a running CurrentThread executor");
13275    current_thread_dispatch(&executor)
13276  }
13277
13278  #[test]
13279  fn current_thread_executor_drives_spawned_tasks() {
13280    let metrics = Arc::new(RuntimeMetrics::default());
13281    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
13282    let scheduler = Arc::clone(&executor);
13283    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
13284      scheduler.schedule(runnable);
13285    });
13286
13287    executor.schedule(runnable);
13288
13289    let mut output = None;
13290    {
13291      let mut future = std::pin::pin!(async {
13292        output = Some(task.await);
13293      });
13294      executor.block_on(future.as_mut());
13295    }
13296    assert_eq!(output, Some(42));
13297    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
13298    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
13299  }
13300
13301  #[cfg(napi_runtime_os_threads)]
13302  #[test]
13303  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13304    use std::sync::mpsc;
13305
13306    let executor = Arc::new(CurrentThreadExecutor::with_detection(
13307      Arc::new(RuntimeMetrics::default()),
13308      false,
13309      None,
13310    ));
13311    let polls = Arc::new(AtomicUsize::new(0));
13312    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13313    let (outcome_tx, outcome_rx) = mpsc::channel();
13314    let driver_executor = Arc::clone(&executor);
13315    let driver_polls = Arc::clone(&polls);
13316    let driver = std::thread::spawn(move || {
13317      let mut future = std::pin::pin!(StopPollProbe {
13318        polls: driver_polls,
13319        first_poll: Some(first_poll_tx)
13320      });
13321      outcome_tx
13322        .send(driver_executor.block_on(future.as_mut()))
13323        .unwrap();
13324    });
13325
13326    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13327    wait_for_generation_parker_to_sleep(&executor.stop);
13328    executor.stop.begin_shutdown();
13329
13330    assert_eq!(
13331      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13332      BlockOnOutcome::Stopped
13333    );
13334    driver.join().unwrap();
13335    assert_eq!(
13336      polls.load(Ordering::SeqCst),
13337      1,
13338      "a shutdown wake must be checked before the next CurrentThread user poll"
13339    );
13340  }
13341
13342  #[cfg(napi_runtime_os_threads)]
13343  #[test]
13344  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13345    use std::sync::mpsc;
13346
13347    let metrics = Arc::new(RuntimeMetrics::default());
13348    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13349      Arc::clone(&metrics),
13350      accept_current_thread_host_dispatch,
13351    ));
13352    let polls = Arc::new(AtomicUsize::new(0));
13353    let task_polls = Arc::clone(&polls);
13354    let (runnable, task) = async_task::spawn(
13355      async move {
13356        task_polls.fetch_add(1, Ordering::SeqCst);
13357      },
13358      |_| {},
13359    );
13360    executor.schedule(runnable);
13361    let dispatch = current_thread_dispatch(&executor);
13362
13363    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13364    let (release_claim_tx, release_claim_rx) = mpsc::channel();
13365    let driver_executor = Arc::clone(&executor);
13366    let driver = std::thread::spawn(move || {
13367      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13368        *slot.borrow_mut() = Some(Box::new(move || {
13369          claim_locked_tx.send(()).unwrap();
13370          release_claim_rx.recv().unwrap();
13371        }));
13372      });
13373      driver_executor.drive_host_turn_for_dispatch(dispatch);
13374    });
13375    claim_locked_rx
13376      .recv_timeout(Duration::from_secs(2))
13377      .expect("the admitted host turn must pause before its runnable claim");
13378
13379    executor.stop.publish_stopping();
13380    release_claim_tx.send(()).unwrap();
13381    driver.join().unwrap();
13382
13383    assert_eq!(
13384      polls.load(Ordering::SeqCst),
13385      0,
13386      "stop publication must outrank a later runnable claim by an already-admitted host turn"
13387    );
13388    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13389
13390    executor.begin_shutdown();
13391    assert!(
13392      futures::executor::block_on(task.fallible()).is_none(),
13393      "shutdown must cancel the runnable rejected by the stopped claim"
13394    );
13395    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13396  }
13397
13398  #[cfg(napi_runtime_os_threads)]
13399  #[test]
13400  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13401    use std::sync::mpsc;
13402
13403    let metrics = Arc::new(RuntimeMetrics::default());
13404    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13405      Arc::clone(&metrics),
13406      accept_current_thread_host_dispatch,
13407    ));
13408    let polls = Arc::new(AtomicUsize::new(0));
13409    let task_polls = Arc::clone(&polls);
13410    let (runnable, task) = async_task::spawn(
13411      async move {
13412        task_polls.fetch_add(1, Ordering::SeqCst);
13413      },
13414      |_| {},
13415    );
13416    executor.schedule(runnable);
13417
13418    let queue = executor
13419      .queue
13420      .lock()
13421      .unwrap_or_else(std::sync::PoisonError::into_inner);
13422    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13423    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13424    let cancelling_executor = Arc::clone(&executor);
13425    let cancellation = std::thread::spawn(move || {
13426      let scheduler = cancelling_executor
13427        .scheduler_idle_lock
13428        .lock()
13429        .unwrap_or_else(std::sync::PoisonError::into_inner);
13430      let cancelling = cancelling_executor
13431        .begin_failed_dispatch_cancellation()
13432        .expect("the test must publish terminal dispatch cancellation");
13433      drop(scheduler);
13434      cancellation_published_tx.send(()).unwrap();
13435      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13436      cancellation_done_tx.send(()).unwrap();
13437    });
13438    cancellation_published_rx
13439      .recv_timeout(Duration::from_secs(2))
13440      .expect("terminal dispatch cancellation must publish before queue draining");
13441
13442    let claimant_executor = Arc::clone(&executor);
13443    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13444    assert!(
13445      !claimant.join().unwrap(),
13446      "a runnable claim after terminal cancellation publication must be rejected"
13447    );
13448    assert_eq!(polls.load(Ordering::SeqCst), 0);
13449    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13450    assert!(
13451      matches!(
13452        cancellation_done_rx.try_recv(),
13453        Err(mpsc::TryRecvError::Empty)
13454      ),
13455      "terminal cancellation must still be waiting to drain the queued runnable"
13456    );
13457
13458    drop(queue);
13459    cancellation_done_rx
13460      .recv_timeout(Duration::from_secs(2))
13461      .expect("terminal cancellation must drain the rejected runnable");
13462    cancellation.join().unwrap();
13463    assert!(
13464      futures::executor::block_on(task.fallible()).is_none(),
13465      "terminal cancellation must cancel the runnable it prevented from starting"
13466    );
13467    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13468  }
13469
13470  #[cfg(napi_runtime_os_threads)]
13471  #[test]
13472  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13473    use std::sync::mpsc;
13474
13475    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13476    let polls = Arc::new(AtomicUsize::new(0));
13477    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13478    let (outcome_tx, outcome_rx) = mpsc::channel();
13479    let driver_executor = Arc::clone(&executor);
13480    let driver_polls = Arc::clone(&polls);
13481    let driver = std::thread::spawn(move || {
13482      let mut future = std::pin::pin!(StopPollProbe {
13483        polls: driver_polls,
13484        first_poll: Some(first_poll_tx)
13485      });
13486      outcome_tx
13487        .send(driver_executor.block_on(future.as_mut()))
13488        .unwrap();
13489    });
13490
13491    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13492    wait_for_generation_parker_to_sleep(&executor.stop);
13493    executor.stop.begin_shutdown();
13494
13495    assert_eq!(
13496      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13497      BlockOnOutcome::Stopped
13498    );
13499    driver.join().unwrap();
13500    assert_eq!(
13501      polls.load(Ordering::SeqCst),
13502      1,
13503      "a shutdown wake must be checked before the next parking block_on user poll"
13504    );
13505  }
13506
13507  #[cfg(napi_runtime_os_threads)]
13508  #[test]
13509  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13510    use std::sync::mpsc;
13511
13512    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13513    let polls = Arc::new(AtomicUsize::new(0));
13514    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13515    let (outcome_tx, outcome_rx) = mpsc::channel();
13516    let driver_executor = Arc::clone(&executor);
13517    let driver_polls = Arc::clone(&polls);
13518    let driver = std::thread::spawn(move || {
13519      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13520      let mut future = std::pin::pin!(StopPollProbe {
13521        polls: driver_polls,
13522        first_poll: Some(first_poll_tx)
13523      });
13524      outcome_tx
13525        .send(driver_executor.block_on(future.as_mut()))
13526        .unwrap();
13527    });
13528
13529    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13530    wait_for_generation_parker_to_sleep(&executor.stop);
13531    executor.stop.begin_shutdown();
13532
13533    assert_eq!(
13534      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13535      BlockOnOutcome::Stopped
13536    );
13537    driver.join().unwrap();
13538    assert_eq!(
13539      polls.load(Ordering::SeqCst),
13540      1,
13541      "a shutdown wake must be checked before the next cooperative user poll"
13542    );
13543  }
13544
13545  #[test]
13546  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13547    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13548      Arc::new(RuntimeMetrics::default()),
13549      accept_current_thread_host_dispatch,
13550    ));
13551    let pending = u64::MAX - 1;
13552    executor.dispatch_pending.store(pending, Ordering::Release);
13553    let mut allocations = 0;
13554
13555    executor.request_drain_with(|| {
13556      allocations += 1;
13557      u64::MAX
13558    });
13559
13560    assert_eq!(allocations, 0);
13561    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13562  }
13563
13564  #[cfg(napi_runtime_os_threads)]
13565  #[test]
13566  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13567    use std::sync::mpsc;
13568
13569    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13570      Arc::new(RuntimeMetrics::default()),
13571      accept_current_thread_host_dispatch,
13572    ));
13573    let dispatch = current_thread_dispatch(&executor);
13574    let scheduler = executor
13575      .scheduler_idle_lock
13576      .lock()
13577      .unwrap_or_else(std::sync::PoisonError::into_inner);
13578    let (claiming_tx, claiming_rx) = mpsc::channel();
13579    let (claimed_tx, claimed_rx) = mpsc::channel();
13580    let claiming_executor = Arc::clone(&executor);
13581    let claimant = std::thread::spawn(move || {
13582      claiming_tx.send(()).unwrap();
13583      claimed_tx
13584        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13585        .unwrap();
13586    });
13587    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13588
13589    executor.stop.begin_shutdown();
13590    drop(scheduler);
13591
13592    assert!(
13593      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13594      "an old-generation dispatch must not claim a host turn after stop becomes visible"
13595    );
13596    claimant.join().unwrap();
13597    assert_eq!(
13598      executor.dispatch_pending.load(Ordering::Acquire),
13599      dispatch,
13600      "rejecting the stopped claim must not consume its stale capability"
13601    );
13602  }
13603
13604  #[cfg(napi_runtime_os_threads)]
13605  #[test]
13606  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13607    use std::sync::mpsc;
13608
13609    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13610    let metrics = Arc::new(RuntimeMetrics::default());
13611    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13612      Arc::clone(&metrics),
13613      count_current_thread_stopped_host_dispatch,
13614    ));
13615    let scheduler = executor
13616      .scheduler_idle_lock
13617      .lock()
13618      .unwrap_or_else(std::sync::PoisonError::into_inner);
13619    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13620    let scheduling_executor = Arc::clone(&executor);
13621    let scheduling = std::thread::spawn(move || {
13622      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13623        *slot.borrow_mut() = Some(Box::new(move || {
13624          publication_blocked_tx.send(()).unwrap();
13625        }));
13626      });
13627      let scheduler = Arc::clone(&scheduling_executor);
13628      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13629        scheduler.schedule(runnable);
13630      });
13631      task.detach();
13632      scheduling_executor.schedule(runnable);
13633    });
13634
13635    publication_blocked_rx
13636      .recv_timeout(Duration::from_secs(2))
13637      .expect("dispatch publication must block behind the scheduler-idle mutex");
13638    executor.begin_shutdown();
13639    drop(scheduler);
13640    scheduling.join().unwrap();
13641    executor.wait_until_scheduler_idle();
13642
13643    assert_eq!(
13644      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13645      0,
13646      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13647    );
13648    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13649    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13650  }
13651
13652  #[cfg(napi_runtime_os_threads)]
13653  #[test]
13654  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13655    use std::{sync::mpsc, time::Duration};
13656
13657    let metrics = Arc::new(RuntimeMetrics::default());
13658    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13659      Arc::clone(&metrics),
13660      accept_current_thread_host_dispatch,
13661    ));
13662    let scheduler = Arc::clone(&executor);
13663    let completed = Arc::new(AtomicBool::new(false));
13664    let completed_task = Arc::clone(&completed);
13665    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13666    let shared = async move {
13667      wake_rx.await.expect("wake sender dropped");
13668    }
13669    .boxed()
13670    .shared();
13671    let (runnable, task) = async_task::spawn(
13672      async move {
13673        shared.await;
13674        completed_task.store(true, Ordering::SeqCst);
13675      },
13676      move |runnable| scheduler.schedule(runnable),
13677    );
13678
13679    executor.schedule(runnable);
13680    assert!(
13681      !completed.load(Ordering::SeqCst),
13682      "accepted host dispatch must defer the first poll"
13683    );
13684    executor.drive_host_turn();
13685
13686    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13687    std::thread::spawn(move || {
13688      wake_tx.send(()).expect("shared wake receiver dropped");
13689      send_returned_tx.send(()).unwrap();
13690    });
13691    send_returned_rx
13692      .recv_timeout(Duration::from_secs(1))
13693      .expect("Shared wake re-entered its own waker mutex through inline polling");
13694    assert!(
13695      !completed.load(Ordering::SeqCst),
13696      "the wake must remain queued until a fresh host turn"
13697    );
13698
13699    executor.drive_host_turn();
13700    futures::executor::block_on(task);
13701    assert!(completed.load(Ordering::SeqCst));
13702    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13703  }
13704
13705  #[cfg(napi_runtime_os_threads)]
13706  #[test]
13707  fn current_thread_hostless_wake_is_enqueue_only() {
13708    use std::{sync::mpsc, time::Duration};
13709
13710    struct LockOnSecondPoll {
13711      gate: Arc<Mutex<()>>,
13712      first: bool,
13713      waker: Option<mpsc::Sender<Waker>>,
13714      completed: Arc<AtomicBool>,
13715    }
13716
13717    impl Future for LockOnSecondPoll {
13718      type Output = ();
13719
13720      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13721        if self.first {
13722          self.first = false;
13723          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13724          return Poll::Pending;
13725        }
13726        drop(self.gate.lock().unwrap());
13727        self.completed.store(true, Ordering::SeqCst);
13728        Poll::Ready(())
13729      }
13730    }
13731
13732    let metrics = Arc::new(RuntimeMetrics::default());
13733    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13734    let gate = Arc::new(Mutex::new(()));
13735    let completed = Arc::new(AtomicBool::new(false));
13736    let (waker_tx, waker_rx) = mpsc::channel();
13737    let scheduler = Arc::clone(&executor);
13738    let (runnable, task) = async_task::spawn(
13739      LockOnSecondPoll {
13740        gate: Arc::clone(&gate),
13741        first: true,
13742        waker: Some(waker_tx),
13743        completed: Arc::clone(&completed),
13744      },
13745      move |runnable| scheduler.schedule(runnable),
13746    );
13747    executor.schedule(runnable);
13748    executor.drive_host_turn();
13749    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13750
13751    let (returned_tx, returned_rx) = mpsc::channel();
13752    let wake_gate = Arc::clone(&gate);
13753    let waking = std::thread::spawn(move || {
13754      let _held = wake_gate.lock().unwrap();
13755      waker.wake();
13756      returned_tx.send(()).unwrap();
13757    });
13758    returned_rx
13759      .recv_timeout(Duration::from_secs(1))
13760      .expect("hostless wake must enqueue without polling under the caller's lock");
13761    waking.join().unwrap();
13762    assert!(!completed.load(Ordering::SeqCst));
13763
13764    executor.drive_host_turn();
13765    futures::executor::block_on(task);
13766    assert!(completed.load(Ordering::SeqCst));
13767  }
13768
13769  #[cfg(napi_runtime_os_threads)]
13770  #[test]
13771  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13772    use std::sync::mpsc;
13773
13774    let controller = Arc::new(current_thread_controller(
13775      "current-thread-hostless-late-work",
13776    ));
13777    let backend = controller.backend();
13778    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13779      panic!("configured CurrentThread backend must create a CurrentThread executor");
13780    };
13781    let executor = Arc::clone(executor);
13782    drop(backend);
13783
13784    let (value_tx, value_rx) = oneshot::channel();
13785    let (done_tx, done_rx) = mpsc::channel();
13786    let block_on_controller = Arc::clone(&controller);
13787    let runner = std::thread::spawn(move || {
13788      let mut output = None;
13789      let outcome = {
13790        let mut future = std::pin::pin!(async {
13791          output = Some(value_rx.await.expect("scheduled sender must deliver"));
13792        });
13793        block_on_controller.block_on(future.as_mut())
13794      };
13795      done_tx.send((outcome, output)).unwrap();
13796    });
13797
13798    wait_until(
13799      "the hostless CurrentThread block_on driver to sleep",
13800      || {
13801        executor
13802          .stop
13803          .parkers
13804          .lock()
13805          .unwrap_or_else(std::sync::PoisonError::into_inner)
13806          .iter()
13807          .filter_map(Weak::upgrade)
13808          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13809      },
13810    );
13811
13812    controller
13813      .try_spawn_detached(async move {
13814        value_tx
13815          .send(37usize)
13816          .expect("block_on receiver must remain alive");
13817      })
13818      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13819
13820    let (outcome, output) = done_rx
13821      .recv_timeout(Duration::from_secs(2))
13822      .expect("queue publication must wake the sleeping explicit driver");
13823    assert_eq!(outcome, BlockOnOutcome::Completed);
13824    assert_eq!(output, Some(37));
13825    runner.join().unwrap();
13826    controller.shutdown().unwrap();
13827  }
13828
13829  #[cfg(napi_runtime_os_threads)]
13830  #[test]
13831  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13832    use std::sync::mpsc;
13833
13834    struct BlockingPoll {
13835      entered: Option<mpsc::Sender<()>>,
13836      release: mpsc::Receiver<()>,
13837    }
13838
13839    impl Future for BlockingPoll {
13840      type Output = ();
13841
13842      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13843        self.entered.take().unwrap().send(()).unwrap();
13844        self.release.recv().unwrap();
13845        Poll::Ready(())
13846      }
13847    }
13848
13849    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13850      Arc::new(RuntimeMetrics::default()),
13851      reject_current_thread_host_dispatch,
13852    ));
13853    let (value_tx, value_rx) = oneshot::channel();
13854    let (older_done_tx, older_done_rx) = mpsc::channel();
13855    let older_executor = Arc::clone(&executor);
13856    let older = std::thread::spawn(move || {
13857      let mut value = None;
13858      {
13859        let mut future = std::pin::pin!(async {
13860          value = Some(value_rx.await.unwrap());
13861        });
13862        assert_eq!(
13863          older_executor.block_on(future.as_mut()),
13864          BlockOnOutcome::Completed
13865        );
13866      }
13867      older_done_tx.send(value.unwrap()).unwrap();
13868    });
13869    wait_until("the older CurrentThread driver to sleep", || {
13870      executor
13871        .stop
13872        .parkers
13873        .lock()
13874        .unwrap_or_else(std::sync::PoisonError::into_inner)
13875        .iter()
13876        .filter_map(Weak::upgrade)
13877        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13878    });
13879
13880    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13881    let (release_newer_tx, release_newer_rx) = mpsc::channel();
13882    let newer_executor = Arc::clone(&executor);
13883    let newer = std::thread::spawn(move || {
13884      let mut future = std::pin::pin!(BlockingPoll {
13885        entered: Some(newer_entered_tx),
13886        release: release_newer_rx
13887      });
13888      assert_eq!(
13889        newer_executor.block_on(future.as_mut()),
13890        BlockOnOutcome::Completed
13891      );
13892    });
13893    newer_entered_rx
13894      .recv_timeout(Duration::from_secs(1))
13895      .expect("the newer driver must be blocked inside its poll");
13896
13897    let scheduler = Arc::clone(&executor);
13898    let (runnable, task) = async_task::spawn(
13899      async move {
13900        value_tx.send(41usize).unwrap();
13901      },
13902      move |runnable| scheduler.schedule(runnable),
13903    );
13904    task.detach();
13905    executor.schedule(runnable);
13906
13907    assert_eq!(
13908      older_done_rx
13909        .recv_timeout(Duration::from_secs(2))
13910        .expect("queue publication must also wake the older sleeping driver"),
13911      41
13912    );
13913    release_newer_tx.send(()).unwrap();
13914    older.join().unwrap();
13915    newer.join().unwrap();
13916  }
13917
13918  #[test]
13919  fn current_thread_shutdown_cancels_host_queued_task() {
13920    use std::sync::mpsc;
13921
13922    struct DropSignal(mpsc::Sender<()>);
13923
13924    impl Drop for DropSignal {
13925      fn drop(&mut self) {
13926        self.0.send(()).unwrap();
13927      }
13928    }
13929
13930    let metrics = Arc::new(RuntimeMetrics::default());
13931    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13932      Arc::clone(&metrics),
13933      accept_current_thread_host_dispatch,
13934    ));
13935    let scheduler = Arc::clone(&executor);
13936    let (dropped_tx, dropped_rx) = mpsc::channel();
13937    let signal = DropSignal(dropped_tx);
13938    let (runnable, task) = async_task::spawn(
13939      async move {
13940        let _signal = signal;
13941        futures::future::pending::<()>().await;
13942      },
13943      move |runnable| scheduler.schedule(runnable),
13944    );
13945
13946    executor.schedule(runnable);
13947    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13948    executor.begin_shutdown();
13949
13950    dropped_rx
13951      .recv_timeout(Duration::from_secs(1))
13952      .expect("shutdown must drop a task queued behind a host turn");
13953    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13954    assert!(
13955      futures::executor::block_on(task.fallible()).is_none(),
13956      "the queued task handle must resolve as cancelled"
13957    );
13958  }
13959
13960  #[test]
13961  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13962    reset_current_thread_recovery_host_dispatches();
13963    let metrics = Arc::new(RuntimeMetrics::default());
13964    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13965      Arc::clone(&metrics),
13966      count_current_thread_recovery_host_dispatch,
13967    ));
13968    let scheduler = Arc::clone(&executor);
13969    let completed = Arc::new(AtomicBool::new(false));
13970    let completed_task = Arc::clone(&completed);
13971    let (runnable, task) = async_task::spawn(
13972      async move {
13973        completed_task.store(true, Ordering::SeqCst);
13974      },
13975      move |runnable| scheduler.schedule(runnable),
13976    );
13977
13978    executor.schedule(runnable);
13979    assert_eq!(current_thread_recovery_host_dispatches(), 1);
13980    let lost_dispatch = current_thread_dispatch(&executor);
13981
13982    // Model another host registering while the first host still owns an
13983    // accepted delivery. The executor capability stays stable; the host
13984    // registry gives the new registration a distinct delivery token.
13985    executor.request_drain_if_queued();
13986    assert_eq!(
13987      current_thread_recovery_host_dispatches(),
13988      2,
13989      "the newly registered host must receive the pending executor dispatch"
13990    );
13991    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13992    executor.drive_host_turn_for_dispatch(lost_dispatch);
13993    futures::executor::block_on(task);
13994    assert!(completed.load(Ordering::SeqCst));
13995    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13996
13997    let second_completed = Arc::new(AtomicBool::new(false));
13998    let second_completed_task = Arc::clone(&second_completed);
13999    let second_scheduler = Arc::clone(&executor);
14000    let (second_runnable, second_task) = async_task::spawn(
14001      async move {
14002        second_completed_task.store(true, Ordering::SeqCst);
14003      },
14004      move |runnable| second_scheduler.schedule(runnable),
14005    );
14006    executor.schedule(second_runnable);
14007    assert_eq!(
14008      current_thread_recovery_host_dispatches(),
14009      3,
14010      "work queued after the stale callback must receive a new dispatch"
14011    );
14012    let third_dispatch = current_thread_dispatch(&executor);
14013
14014    // The already-consumed capability remains stale relative to dispatch 2 and
14015    // cannot service its work.
14016    executor.drive_host_turn_for_dispatch(lost_dispatch);
14017    assert!(!second_completed.load(Ordering::SeqCst));
14018    executor.drive_host_turn_for_dispatch(third_dispatch);
14019    futures::executor::block_on(second_task);
14020    assert!(second_completed.load(Ordering::SeqCst));
14021
14022    let third_scheduler = Arc::clone(&executor);
14023    let (third_runnable, third_task) =
14024      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
14025    executor.schedule(third_runnable);
14026    assert_eq!(
14027      current_thread_recovery_host_dispatches(),
14028      4,
14029      "a stale callback must not leave the dispatch latch stuck"
14030    );
14031    executor.drive_host_turn();
14032    futures::executor::block_on(third_task);
14033  }
14034
14035  #[test]
14036  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
14037    let metrics = Arc::new(RuntimeMetrics::default());
14038    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14039      Arc::clone(&metrics),
14040      accept_current_thread_host_dispatch,
14041    ));
14042    let scheduler = Arc::clone(&executor);
14043    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14044      scheduler.schedule(runnable)
14045    });
14046
14047    executor.schedule(runnable);
14048    let failed_dispatch = current_thread_dispatch(&executor);
14049    executor.cancel_host_dispatch_for_test(failed_dispatch);
14050    let recovery_dispatch = current_thread_dispatch(&executor);
14051    assert_eq!(
14052      executor.recovery_dispatch.load(Ordering::Acquire),
14053      recovery_dispatch
14054    );
14055
14056    executor.request_drain_if_queued();
14057    let new_host_dispatch = current_thread_dispatch(&executor);
14058    assert_eq!(new_host_dispatch, recovery_dispatch);
14059    assert_eq!(
14060      executor.recovery_dispatch.load(Ordering::Acquire),
14061      recovery_dispatch
14062    );
14063    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14064
14065    executor.drive_host_turn_for_dispatch(new_host_dispatch);
14066    assert_eq!(futures::executor::block_on(task), 17);
14067  }
14068
14069  #[cfg(napi_runtime_os_threads)]
14070  #[test]
14071  fn current_thread_host_admission_serializes_dispatch_publication() {
14072    use std::sync::mpsc;
14073
14074    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14075    let metrics = Arc::new(RuntimeMetrics::default());
14076    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14077      Arc::clone(&metrics),
14078      count_current_thread_admission_host_dispatch,
14079    ));
14080    let first_scheduler = Arc::clone(&executor);
14081    let (first_runnable, first_task) =
14082      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
14083    first_task.detach();
14084    executor.schedule(first_runnable);
14085    let dispatch = current_thread_dispatch(&executor);
14086    assert_eq!(
14087      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14088      1
14089    );
14090
14091    let (claimed_tx, claimed_rx) = mpsc::channel();
14092    let (release_tx, release_rx) = mpsc::channel();
14093    let driver_executor = Arc::clone(&executor);
14094    let driver = std::thread::spawn(move || {
14095      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
14096        *slot.borrow_mut() = Some(Box::new(move || {
14097          claimed_tx.send(()).unwrap();
14098          release_rx.recv().unwrap();
14099        }));
14100      });
14101      driver_executor.drive_host_turn_for_dispatch(dispatch);
14102    });
14103    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14104
14105    let second_ran = Arc::new(AtomicBool::new(false));
14106    let second_ran_task = Arc::clone(&second_ran);
14107    let scheduling_executor = Arc::clone(&executor);
14108    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14109    let (scheduled_tx, scheduled_rx) = mpsc::channel();
14110    let scheduling = std::thread::spawn(move || {
14111      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14112        *slot.borrow_mut() = Some(Box::new(move || {
14113          publication_blocked_tx.send(()).unwrap();
14114        }));
14115      });
14116      let scheduler = Arc::clone(&scheduling_executor);
14117      let (runnable, task) = async_task::spawn(
14118        async move {
14119          second_ran_task.store(true, Ordering::SeqCst);
14120        },
14121        move |runnable| scheduler.schedule(runnable),
14122      );
14123      task.detach();
14124      scheduling_executor.schedule(runnable);
14125      scheduled_tx.send(()).unwrap();
14126    });
14127
14128    publication_blocked_rx
14129      .recv_timeout(Duration::from_secs(2))
14130      .unwrap();
14131    assert!(
14132      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
14133      "dispatch publication must wait until host admission publishes its draining role"
14134    );
14135    assert_eq!(
14136      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14137      1,
14138      "the admission gap must not publish a duplicate host dispatch"
14139    );
14140
14141    release_tx.send(()).unwrap();
14142    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14143    scheduling.join().unwrap();
14144    driver.join().unwrap();
14145    if !second_ran.load(Ordering::SeqCst) {
14146      executor.drive_host_turn();
14147    }
14148    assert!(second_ran.load(Ordering::SeqCst));
14149    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14150  }
14151
14152  #[test]
14153  fn current_thread_host_turn_yields_after_runnable_budget() {
14154    struct SelfWakingFuture {
14155      remaining: usize,
14156    }
14157
14158    impl Future for SelfWakingFuture {
14159      type Output = ();
14160
14161      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14162        if self.remaining == 0 {
14163          return Poll::Ready(());
14164        }
14165        self.remaining -= 1;
14166        cx.waker().wake_by_ref();
14167        Poll::Pending
14168      }
14169    }
14170
14171    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14172    let metrics = Arc::new(RuntimeMetrics::default());
14173    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14174      Arc::clone(&metrics),
14175      count_current_thread_budget_host_dispatch,
14176    ));
14177    let scheduler = Arc::clone(&executor);
14178    let (runnable, task) = async_task::spawn(
14179      SelfWakingFuture {
14180        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14181      },
14182      move |runnable| scheduler.schedule(runnable),
14183    );
14184
14185    executor.schedule(runnable);
14186    executor.drive_host_turn();
14187
14188    assert_eq!(
14189      metrics.runnable_polls.load(Ordering::Relaxed),
14190      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
14191    );
14192    assert_eq!(
14193      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14194      2,
14195      "remaining hot work must continue through a fresh host turn"
14196    );
14197
14198    executor.drive_host_turn();
14199    futures::executor::block_on(task);
14200    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14201  }
14202
14203  #[cfg(napi_runtime_os_threads)]
14204  #[test]
14205  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
14206    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
14207
14208    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14209    let metrics = Arc::new(RuntimeMetrics::default());
14210    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14211      Arc::clone(&metrics),
14212      count_current_thread_blocking_budget_host_dispatch,
14213    ));
14214    let work = GenerationWork::new();
14215    let observed = Arc::new(Mutex::new(Vec::new()));
14216
14217    let task_executor = Arc::clone(&executor);
14218    let wake_executor = Arc::clone(&executor);
14219    let producer_executor = Arc::clone(&executor);
14220    let producer_work = Arc::clone(&work);
14221    let producer_observed = Arc::clone(&observed);
14222    let outer_registration = work
14223      .try_register_work()
14224      .expect("the outer blocking job must be accepted");
14225    let (runnable, task) = async_task::spawn(
14226      async move {
14227        let queued = task_executor
14228          .schedule_blocking(
14229            move || {
14230              std::thread::spawn(move || {
14231                (0..QUEUED_BLOCKING_JOBS)
14232                  .map(|index| {
14233                    let registration = producer_work
14234                      .try_register_work()
14235                      .expect("queued blocking work must be accepted");
14236                    let observed = Arc::clone(&producer_observed);
14237                    producer_executor.schedule_blocking(
14238                      move || {
14239                        observed
14240                          .lock()
14241                          .unwrap_or_else(std::sync::PoisonError::into_inner)
14242                          .push(index);
14243                      },
14244                      registration,
14245                    )
14246                  })
14247                  .collect::<Vec<_>>()
14248              })
14249              .join()
14250              .expect("the blocking producer must finish")
14251            },
14252            outer_registration,
14253          )
14254          .await
14255          .expect("the inline blocking job must finish");
14256        for handle in queued {
14257          handle.await.expect("queued blocking work must finish");
14258        }
14259      },
14260      move |runnable| wake_executor.schedule(runnable),
14261    );
14262
14263    executor.schedule(runnable);
14264    executor.drive_host_turn();
14265
14266    let first_turn = observed
14267      .lock()
14268      .unwrap_or_else(std::sync::PoisonError::into_inner)
14269      .clone();
14270    assert!(
14271      !first_turn.is_empty(),
14272      "the first host turn must service queued blocking work"
14273    );
14274    assert!(
14275      first_turn.len() < QUEUED_BLOCKING_JOBS,
14276      "inline lane release must not drain the entire blocking FIFO in one host turn"
14277    );
14278    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
14279    assert_eq!(
14280      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14281      2,
14282      "remaining blocking work must continue through a fresh host turn"
14283    );
14284
14285    let mut continuation_turns = 0;
14286    while observed
14287      .lock()
14288      .unwrap_or_else(std::sync::PoisonError::into_inner)
14289      .len()
14290      < QUEUED_BLOCKING_JOBS
14291    {
14292      continuation_turns += 1;
14293      assert!(
14294        continuation_turns <= QUEUED_BLOCKING_JOBS,
14295        "queued blocking work failed to make bounded host-turn progress"
14296      );
14297      executor.drive_host_turn();
14298    }
14299    executor.drive_host_turn();
14300    futures::executor::block_on(task);
14301
14302    assert_eq!(
14303      *observed
14304        .lock()
14305        .unwrap_or_else(std::sync::PoisonError::into_inner),
14306      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14307      "blocking jobs must preserve FIFO order across host turns"
14308    );
14309    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14310  }
14311
14312  #[cfg(napi_runtime_os_threads)]
14313  #[test]
14314  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14315    use std::sync::mpsc;
14316
14317    struct SelfWakingFuture {
14318      remaining: usize,
14319    }
14320
14321    impl Future for SelfWakingFuture {
14322      type Output = ();
14323
14324      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14325        if self.remaining == 0 {
14326          return Poll::Ready(());
14327        }
14328        self.remaining -= 1;
14329        cx.waker().wake_by_ref();
14330        Poll::Pending
14331      }
14332    }
14333
14334    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14335    let old_generation = controller.backend().generation();
14336    controller
14337      .try_spawn_detached(SelfWakingFuture {
14338        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14339      })
14340      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14341    let dispatch = controller_current_thread_dispatch(&controller);
14342
14343    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14344    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14345    let driver_controller = Arc::clone(&controller);
14346    let driver = std::thread::spawn(move || {
14347      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14348        *slot.borrow_mut() = Some(Box::new(move || {
14349          dispatch_entered_tx.send(()).unwrap();
14350          release_dispatch_rx.recv().unwrap();
14351        }));
14352      });
14353      driver_controller.drive_current_thread_dispatch(dispatch, false);
14354    });
14355    dispatch_entered_rx
14356      .recv_timeout(Duration::from_secs(2))
14357      .expect("the bounded host turn must begin publishing its continuation");
14358
14359    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14360    let shutdown_controller = Arc::clone(&controller);
14361    let shutdown = std::thread::spawn(move || {
14362      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14363    });
14364    wait_until("CurrentThread shutdown to leave Running", || {
14365      let state = controller
14366        .state
14367        .lock()
14368        .unwrap_or_else(std::sync::PoisonError::into_inner);
14369      !matches!(
14370        &state.lifecycle,
14371        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14372      )
14373    });
14374
14375    let (restart_tx, restart_rx) = mpsc::channel();
14376    let restart_controller = Arc::clone(&controller);
14377    let restart = std::thread::spawn(move || {
14378      let result = restart_controller
14379        .start()
14380        .map(|()| restart_controller.backend().generation());
14381      restart_tx.send(result).unwrap();
14382    });
14383
14384    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14385    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14386    let shutdown_overlapped = early_shutdown.is_some();
14387    let restart_overlapped = early_restart.is_some();
14388    release_dispatch_tx.send(()).unwrap();
14389    driver.join().unwrap();
14390
14391    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14392      shutdown_rx
14393        .recv_timeout(Duration::from_secs(2))
14394        .expect("shutdown must finish after continuation publication retires")
14395    });
14396    shutdown_result.unwrap();
14397    let new_generation = early_restart.unwrap_or_else(|| {
14398      restart_rx
14399        .recv_timeout(Duration::from_secs(2))
14400        .expect("restart must follow continuation publication and shutdown")
14401    });
14402    let new_generation = new_generation.unwrap();
14403    shutdown.join().unwrap();
14404    restart.join().unwrap();
14405
14406    assert!(
14407      !shutdown_overlapped,
14408      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14409    );
14410    assert!(
14411      !restart_overlapped,
14412      "restart must not overlap an old-generation continuation dispatch"
14413    );
14414    assert_ne!(new_generation, old_generation);
14415    controller.shutdown().unwrap();
14416  }
14417
14418  #[cfg(napi_runtime_os_threads)]
14419  #[test]
14420  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14421    use std::sync::mpsc;
14422
14423    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14424    let old_generation = controller.backend().generation();
14425    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14426    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14427    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14428    let submit_controller = Arc::clone(&controller);
14429    let reentrant_controller = Arc::clone(&controller);
14430    let submitter = std::thread::spawn(move || {
14431      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14432        *slot.borrow_mut() = Some(Box::new(move || {
14433          reentrant_shutdown_tx
14434            .send(reentrant_controller.shutdown())
14435            .unwrap();
14436          dispatch_entered_tx.send(()).unwrap();
14437          release_dispatch_rx.recv().unwrap();
14438        }));
14439      });
14440      submit_controller
14441        .try_spawn_detached(std::future::pending::<()>())
14442        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14443    });
14444
14445    let reentrant_shutdown = reentrant_shutdown_rx
14446      .recv_timeout(Duration::from_secs(2))
14447      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14448    dispatch_entered_rx
14449      .recv_timeout(Duration::from_secs(2))
14450      .expect("the initial host dispatch publication must reach the injected pause");
14451
14452    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14453    let shutdown_controller = Arc::clone(&controller);
14454    let shutdown = std::thread::spawn(move || {
14455      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14456    });
14457    wait_until("CurrentThread shutdown to leave Running", || {
14458      let state = controller
14459        .state
14460        .lock()
14461        .unwrap_or_else(std::sync::PoisonError::into_inner);
14462      !matches!(
14463        &state.lifecycle,
14464        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14465      )
14466    });
14467
14468    let (restart_tx, restart_rx) = mpsc::channel();
14469    let restart_controller = Arc::clone(&controller);
14470    let restart = std::thread::spawn(move || {
14471      let result = restart_controller
14472        .start()
14473        .map(|()| restart_controller.backend().generation());
14474      restart_tx.send(result).unwrap();
14475    });
14476
14477    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14478    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14479    let shutdown_overlapped = early_shutdown.is_some();
14480    let restart_overlapped = early_restart.is_some();
14481    release_dispatch_tx.send(()).unwrap();
14482    submitter.join().unwrap();
14483
14484    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14485      shutdown_rx
14486        .recv_timeout(Duration::from_secs(2))
14487        .expect("shutdown must finish after initial dispatch publication retires")
14488    });
14489    shutdown_result.unwrap();
14490    let new_generation = early_restart.unwrap_or_else(|| {
14491      restart_rx
14492        .recv_timeout(Duration::from_secs(2))
14493        .expect("restart must follow initial dispatch publication and shutdown")
14494    });
14495    let new_generation = new_generation.unwrap();
14496    shutdown.join().unwrap();
14497    restart.join().unwrap();
14498
14499    let reentrant_error = reentrant_shutdown
14500      .expect_err("dispatch publication must be generation-scoped scheduler work");
14501    assert!(
14502      reentrant_error
14503        .to_string()
14504        .contains("work running on that runtime")
14505    );
14506    assert!(
14507      !shutdown_overlapped,
14508      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14509    );
14510    assert!(
14511      !restart_overlapped,
14512      "restart must not overlap an old-generation host dispatch call"
14513    );
14514    assert_ne!(new_generation, old_generation);
14515    controller.shutdown().unwrap();
14516  }
14517
14518  #[cfg(panic = "unwind")]
14519  #[test]
14520  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14521    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14522      .lock()
14523      .unwrap()
14524      .clear();
14525    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14526    let metrics = Arc::new(RuntimeMetrics::default());
14527    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14528      Arc::clone(&metrics),
14529      panic_once_current_thread_host_dispatch,
14530    ));
14531    let scheduler = Arc::clone(&executor);
14532    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14533      scheduler.schedule(runnable)
14534    });
14535
14536    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14537    assert!(
14538      publication.is_err(),
14539      "the injected host dispatch panic must unwind"
14540    );
14541    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14542    {
14543      let scheduler = executor
14544        .scheduler_idle_lock
14545        .lock()
14546        .unwrap_or_else(std::sync::PoisonError::into_inner);
14547      assert_eq!(scheduler.active_dispatch_calls, 0);
14548      assert!(scheduler.dispatch_publications.is_empty());
14549    }
14550
14551    executor.request_drain_if_queued();
14552
14553    assert_ne!(dispatch, 0);
14554    assert_eq!(
14555      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14556        .lock()
14557        .unwrap(),
14558      vec![dispatch, dispatch],
14559      "a later host registration must retry the same pending capability"
14560    );
14561    {
14562      let scheduler = executor
14563        .scheduler_idle_lock
14564        .lock()
14565        .unwrap_or_else(std::sync::PoisonError::into_inner);
14566      assert_eq!(scheduler.active_dispatch_calls, 0);
14567      assert!(scheduler.dispatch_publications.is_empty());
14568    }
14569    executor.drive_host_turn_for_dispatch(dispatch);
14570    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14571    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14572  }
14573
14574  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14575  #[test]
14576  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14577    use std::sync::mpsc;
14578
14579    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14580      .lock()
14581      .unwrap()
14582      .clear();
14583    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14584    let metrics = Arc::new(RuntimeMetrics::default());
14585    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14586      Arc::clone(&metrics),
14587      panic_once_current_thread_armed_republish_host_dispatch,
14588    ));
14589    let scheduler = Arc::clone(&executor);
14590    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14591      scheduler.schedule(runnable)
14592    });
14593
14594    let (called_tx, called_rx) = mpsc::channel();
14595    let (release_tx, release_rx) = mpsc::channel();
14596    let publishing_executor = Arc::clone(&executor);
14597    let publication = std::thread::spawn(move || {
14598      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14599        *slot.borrow_mut() = Some(Box::new(move || {
14600          called_tx.send(()).unwrap();
14601          release_rx.recv().unwrap();
14602        }));
14603      });
14604      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14605    });
14606    called_rx
14607      .recv_timeout(Duration::from_secs(2))
14608      .expect("the first publication must pause before calling the host dispatcher");
14609
14610    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14611    executor.request_drain_if_queued();
14612    let publication_state = {
14613      let scheduler = executor
14614        .scheduler_idle_lock
14615        .lock()
14616        .unwrap_or_else(std::sync::PoisonError::into_inner);
14617      (
14618        scheduler.active_dispatch_calls,
14619        scheduler.dispatch_publications.len(),
14620        scheduler.dispatch_publications[0].dispatch,
14621        scheduler.dispatch_publications[0].republish_requested,
14622      )
14623    };
14624    release_tx.send(()).unwrap();
14625
14626    let publication = publication.join().unwrap();
14627    assert!(
14628      publication.is_err(),
14629      "the original host-dispatch panic must resume"
14630    );
14631    assert_ne!(dispatch, 0);
14632    assert_eq!(publication_state, (1, 1, dispatch, true));
14633    assert_eq!(
14634      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14635        .lock()
14636        .unwrap(),
14637      vec![dispatch, dispatch],
14638      "the armed request must retry automatically before the original panic resumes"
14639    );
14640    {
14641      let scheduler = executor
14642        .scheduler_idle_lock
14643        .lock()
14644        .unwrap_or_else(std::sync::PoisonError::into_inner);
14645      assert_eq!(scheduler.active_dispatch_calls, 0);
14646      assert!(scheduler.dispatch_publications.is_empty());
14647    }
14648    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14649    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14650
14651    executor.drive_host_turn_for_dispatch(dispatch);
14652    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14653    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14654  }
14655
14656  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14657  #[test]
14658  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14659    use std::sync::mpsc;
14660
14661    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14662      .lock()
14663      .unwrap()
14664      .clear();
14665    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14666    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14667    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14668    {
14669      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14670        .state
14671        .lock()
14672        .unwrap_or_else(std::sync::PoisonError::into_inner);
14673      assert!(state.entries.is_empty());
14674      assert!(state.dispatches.is_empty());
14675    }
14676    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14677      .register(Arc::new(FailDuringAuthoritativePanicPublication));
14678    let metrics = Arc::new(RuntimeMetrics::default());
14679    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14680      Arc::clone(&metrics),
14681      dispatch_authoritative_panic_current_thread_tasks,
14682      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14683    ));
14684    let scheduler = Arc::clone(&executor);
14685    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14686      scheduler.schedule(runnable)
14687    });
14688
14689    let (called_tx, called_rx) = mpsc::channel();
14690    let (release_tx, release_rx) = mpsc::channel();
14691    let publishing_executor = Arc::clone(&executor);
14692    let publication = std::thread::spawn(move || {
14693      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14694        *slot.borrow_mut() = Some(Box::new(move || {
14695          called_tx.send(()).unwrap();
14696          release_rx.recv().unwrap();
14697        }));
14698      });
14699      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14700    });
14701    called_rx
14702      .recv_timeout(Duration::from_secs(2))
14703      .expect("the first publication must pause before raising its panic");
14704
14705    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14706    executor.request_drain_if_queued();
14707    release_tx.send(()).unwrap();
14708
14709    let payload = publication
14710      .join()
14711      .unwrap()
14712      .expect_err("the first host-dispatch panic must resume");
14713    assert!(
14714      payload.is::<FirstHostileCurrentThreadPanic>(),
14715      "the first panic must remain authoritative over nested recovery"
14716    );
14717    assert_eq!(
14718      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14719      1,
14720      "the later hostile payload must be discarded under containment"
14721    );
14722    assert_eq!(
14723      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14724      0,
14725      "the authoritative payload must remain owned by the resumed unwind"
14726    );
14727    discard_panic_payload(payload);
14728    assert_eq!(
14729      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14730      1
14731    );
14732
14733    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14734      .lock()
14735      .unwrap()
14736      .clone();
14737    assert_eq!(dispatches.len(), 3);
14738    let replacement_dispatch = dispatches[2];
14739    assert_eq!(dispatches[0], failed_dispatch);
14740    assert_eq!(dispatches[1], failed_dispatch);
14741    assert_ne!(replacement_dispatch, failed_dispatch);
14742    assert_eq!(
14743      executor.dispatch_pending.load(Ordering::Acquire),
14744      replacement_dispatch
14745    );
14746    assert_eq!(
14747      executor.recovery_dispatch.load(Ordering::Acquire),
14748      replacement_dispatch
14749    );
14750    {
14751      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14752        .state
14753        .lock()
14754        .unwrap_or_else(std::sync::PoisonError::into_inner);
14755      assert!(
14756        !state.dispatches.contains_key(&failed_dispatch),
14757        "nested recovery unwind must retire the exact failed epoch"
14758      );
14759      assert!(!state.dispatches.contains_key(&replacement_dispatch));
14760    }
14761
14762    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14763    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14764    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14765      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14766    executor.request_drain_if_queued();
14767
14768    assert_eq!(
14769      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14770        .lock()
14771        .unwrap(),
14772      vec![
14773        failed_dispatch,
14774        failed_dispatch,
14775        replacement_dispatch,
14776        replacement_dispatch
14777      ],
14778      "retry must retain the replacement reserved before the nested panic"
14779    );
14780    let delivery = healthy.deliveries()[0];
14781    assert_eq!(
14782      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14783      Some(replacement_dispatch)
14784    );
14785    let mut host_turn = executor
14786      .try_admit_host_turn(replacement_dispatch)
14787      .expect("the retried replacement delivery must be admitted");
14788    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14789    host_turn.drive();
14790    assert!(
14791      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14792        .finish_delivery(delivery, false)
14793        .failed_dispatches
14794        .is_empty()
14795    );
14796    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14797    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14798
14799    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14800    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14801      .state
14802      .lock()
14803      .unwrap_or_else(std::sync::PoisonError::into_inner);
14804    assert!(state.entries.is_empty());
14805    assert!(state.dispatches.is_empty());
14806  }
14807
14808  #[cfg(napi_runtime_os_threads)]
14809  #[test]
14810  fn current_thread_shutdown_suppresses_queued_republish() {
14811    use std::sync::mpsc;
14812
14813    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14814    let metrics = Arc::new(RuntimeMetrics::default());
14815    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14816      Arc::clone(&metrics),
14817      count_current_thread_shutdown_republish_host_dispatch,
14818    ));
14819    let scheduler = Arc::clone(&executor);
14820    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14821      scheduler.schedule(runnable)
14822    });
14823
14824    let (returned_tx, returned_rx) = mpsc::channel();
14825    let (release_tx, release_rx) = mpsc::channel();
14826    let publishing_executor = Arc::clone(&executor);
14827    let publication = std::thread::spawn(move || {
14828      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14829        *slot.borrow_mut() = Some(Box::new(move || {
14830          returned_tx.send(()).unwrap();
14831          release_rx.recv().unwrap();
14832        }));
14833      });
14834      publishing_executor.schedule(runnable);
14835    });
14836    returned_rx
14837      .recv_timeout(Duration::from_secs(2))
14838      .expect("the initial publication must reach its completion barrier");
14839
14840    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14841    executor.request_drain_if_queued();
14842    let publication_state = {
14843      let scheduler = executor
14844        .scheduler_idle_lock
14845        .lock()
14846        .unwrap_or_else(std::sync::PoisonError::into_inner);
14847      (
14848        scheduler.active_dispatch_calls,
14849        scheduler.dispatch_publications.len(),
14850        scheduler.dispatch_publications[0].dispatch,
14851        scheduler.dispatch_publications[0].republish_requested,
14852      )
14853    };
14854    executor.begin_shutdown();
14855    release_tx.send(()).unwrap();
14856    publication.join().unwrap();
14857    executor.wait_until_scheduler_idle();
14858
14859    assert_ne!(dispatch, 0);
14860    assert_eq!(publication_state, (1, 1, dispatch, true));
14861    assert_eq!(
14862      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14863      1
14864    );
14865    {
14866      let scheduler = executor
14867        .scheduler_idle_lock
14868        .lock()
14869        .unwrap_or_else(std::sync::PoisonError::into_inner);
14870      assert_eq!(scheduler.active_dispatch_calls, 0);
14871      assert!(scheduler.dispatch_publications.is_empty());
14872    }
14873    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14874    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14875    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14876    assert_eq!(futures::executor::block_on(task.fallible()), None);
14877  }
14878
14879  #[cfg(napi_runtime_os_threads)]
14880  #[test]
14881  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14882    use std::sync::mpsc;
14883
14884    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14885      .lock()
14886      .unwrap()
14887      .clear();
14888    let metrics = Arc::new(RuntimeMetrics::default());
14889    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14890      Arc::clone(&metrics),
14891      record_current_thread_republish_rearm_dispatch,
14892    ));
14893    let scheduler = Arc::clone(&executor);
14894    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14895      scheduler.schedule(runnable)
14896    });
14897
14898    let (first_returned_tx, first_returned_rx) = mpsc::channel();
14899    let (release_first_tx, release_first_rx) = mpsc::channel();
14900    let (second_returned_tx, second_returned_rx) = mpsc::channel();
14901    let (release_second_tx, release_second_rx) = mpsc::channel();
14902    let publishing_executor = Arc::clone(&executor);
14903    let publication = std::thread::spawn(move || {
14904      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14905        *slot.borrow_mut() = Some(Box::new(move || {
14906          first_returned_tx.send(()).unwrap();
14907          release_first_rx.recv().unwrap();
14908          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14909            *slot.borrow_mut() = Some(Box::new(move || {
14910              second_returned_tx.send(()).unwrap();
14911              release_second_rx.recv().unwrap();
14912            }));
14913          });
14914        }));
14915      });
14916      publishing_executor.schedule(runnable);
14917    });
14918    first_returned_rx
14919      .recv_timeout(Duration::from_secs(2))
14920      .expect("the first publication must reach its completion barrier");
14921
14922    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14923    executor.request_drain_if_queued();
14924    executor.request_drain_if_queued();
14925    let first_publication_state = {
14926      let scheduler = executor
14927        .scheduler_idle_lock
14928        .lock()
14929        .unwrap_or_else(std::sync::PoisonError::into_inner);
14930      (
14931        scheduler.active_dispatch_calls,
14932        scheduler.dispatch_publications.len(),
14933        scheduler.dispatch_publications[0].dispatch,
14934        scheduler.dispatch_publications[0].republish_requested,
14935      )
14936    };
14937    release_first_tx.send(()).unwrap();
14938    second_returned_rx
14939      .recv_timeout(Duration::from_secs(2))
14940      .expect("the second publication must reach its completion barrier");
14941
14942    executor.request_drain_if_queued();
14943    executor.request_drain_if_queued();
14944    let second_publication_state = {
14945      let scheduler = executor
14946        .scheduler_idle_lock
14947        .lock()
14948        .unwrap_or_else(std::sync::PoisonError::into_inner);
14949      (
14950        scheduler.active_dispatch_calls,
14951        scheduler.dispatch_publications.len(),
14952        scheduler.dispatch_publications[0].dispatch,
14953        scheduler.dispatch_publications[0].republish_requested,
14954      )
14955    };
14956    release_second_tx.send(()).unwrap();
14957    publication.join().unwrap();
14958
14959    assert_ne!(dispatch, 0);
14960    assert_eq!(first_publication_state, (1, 1, dispatch, true));
14961    assert_eq!(second_publication_state, (1, 1, dispatch, true));
14962    assert_eq!(
14963      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14964      vec![dispatch, dispatch, dispatch],
14965      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14966    );
14967    {
14968      let scheduler = executor
14969        .scheduler_idle_lock
14970        .lock()
14971        .unwrap_or_else(std::sync::PoisonError::into_inner);
14972      assert_eq!(scheduler.active_dispatch_calls, 0);
14973      assert!(scheduler.dispatch_publications.is_empty());
14974    }
14975    executor.drive_host_turn_for_dispatch(dispatch);
14976    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14977    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14978  }
14979
14980  #[cfg(napi_runtime_os_threads)]
14981  #[test]
14982  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14983    use std::sync::mpsc;
14984
14985    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14986      .lock()
14987      .unwrap()
14988      .clear();
14989    let metrics = Arc::new(RuntimeMetrics::default());
14990    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14991      Arc::clone(&metrics),
14992      reject_initial_current_thread_publication_once,
14993    ));
14994    let scheduler = Arc::clone(&executor);
14995    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14996      scheduler.schedule(runnable)
14997    });
14998
14999    let (returned_tx, returned_rx) = mpsc::channel();
15000    let (release_tx, release_rx) = mpsc::channel();
15001    let publishing_executor = Arc::clone(&executor);
15002    let publication = std::thread::spawn(move || {
15003      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15004        *slot.borrow_mut() = Some(Box::new(move || {
15005          returned_tx.send(()).unwrap();
15006          release_rx.recv().unwrap();
15007        }));
15008      });
15009      publishing_executor.schedule(runnable);
15010    });
15011    returned_rx
15012      .recv_timeout(Duration::from_secs(2))
15013      .expect("the unavailable initial publication must reach its completion barrier");
15014
15015    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15016    executor.request_drain_if_queued();
15017    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
15018      .lock()
15019      .unwrap()
15020      .len();
15021
15022    release_tx.send(()).unwrap();
15023    publication.join().unwrap();
15024
15025    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
15026      .lock()
15027      .unwrap()
15028      .clone();
15029    assert_ne!(dispatch, 0);
15030    assert_eq!(
15031      attempts_while_unavailable_is_paused, 1,
15032      "a racing host request must join the in-flight publication instead of retiring it independently"
15033    );
15034    assert_eq!(
15035      dispatches,
15036      vec![dispatch, dispatch],
15037      "the publication owner must retry the same internal capability after the new host joins"
15038    );
15039    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
15040    executor.drive_host_turn_for_dispatch(dispatch);
15041    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
15042    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15043  }
15044
15045  #[cfg(napi_runtime_os_threads)]
15046  #[test]
15047  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
15048    use std::sync::mpsc;
15049
15050    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15051      .lock()
15052      .unwrap()
15053      .clear();
15054    let metrics = Arc::new(RuntimeMetrics::default());
15055    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15056      Arc::clone(&metrics),
15057      reject_current_thread_replacement_publication_once,
15058    ));
15059    let scheduler = Arc::clone(&executor);
15060    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
15061      scheduler.schedule(runnable)
15062    });
15063    executor.schedule(runnable);
15064    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15065    assert_ne!(failed_dispatch, 0);
15066
15067    let (returned_tx, returned_rx) = mpsc::channel();
15068    let (release_tx, release_rx) = mpsc::channel();
15069    let cancelling_executor = Arc::clone(&executor);
15070    let cancellation = std::thread::spawn(move || {
15071      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15072        *slot.borrow_mut() = Some(Box::new(move || {
15073          returned_tx.send(()).unwrap();
15074          release_rx.recv().unwrap();
15075        }));
15076      });
15077      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
15078    });
15079    returned_rx
15080      .recv_timeout(Duration::from_secs(2))
15081      .expect("the unavailable replacement publication must reach its completion barrier");
15082
15083    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15084    executor.request_drain_if_queued();
15085    let attempts_while_unavailable_is_paused =
15086      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15087        .lock()
15088        .unwrap()
15089        .len();
15090
15091    release_tx.send(()).unwrap();
15092    cancellation.join().unwrap();
15093
15094    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15095      .lock()
15096      .unwrap()
15097      .clone();
15098    assert_ne!(replacement_dispatch, 0);
15099    assert_ne!(replacement_dispatch, failed_dispatch);
15100    assert_eq!(
15101      attempts_while_unavailable_is_paused, 2,
15102      "a racing host request must join the in-flight replacement publication"
15103    );
15104    assert_eq!(
15105      dispatches,
15106      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15107      "republication must reuse the sole replacement capability instead of allocating another"
15108    );
15109    assert_eq!(
15110      executor.dispatch_pending.load(Ordering::Acquire),
15111      replacement_dispatch
15112    );
15113    assert_eq!(
15114      executor.recovery_dispatch.load(Ordering::Acquire),
15115      replacement_dispatch
15116    );
15117    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15118
15119    executor.drive_host_turn_for_dispatch(replacement_dispatch);
15120    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
15121    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15122    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15123  }
15124
15125  #[cfg(napi_runtime_os_threads)]
15126  #[test]
15127  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
15128    use std::sync::mpsc;
15129
15130    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15131      .lock()
15132      .unwrap()
15133      .clear();
15134    {
15135      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15136        .state
15137        .lock()
15138        .unwrap_or_else(std::sync::PoisonError::into_inner);
15139      assert!(state.entries.is_empty());
15140      assert!(state.dispatches.is_empty());
15141    }
15142    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15143    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15144      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15145    let metrics = Arc::new(RuntimeMetrics::default());
15146    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15147      Arc::clone(&metrics),
15148      fail_current_thread_replacement_publication_once,
15149      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
15150    ));
15151    let scheduler = Arc::clone(&executor);
15152    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
15153      scheduler.schedule(runnable)
15154    });
15155    executor.schedule(runnable);
15156    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15157    assert_ne!(failed_dispatch, 0);
15158    let initial_delivery = initial.deliveries()[0];
15159    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15160      .finish_delivery(initial_delivery, true);
15161    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
15162      panic!("the initial accepted delivery must report one exact failure");
15163    };
15164    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
15165    let failed_dispatch_failure = *failed_dispatch_failure;
15166    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
15167    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15168      .register(Arc::new(FailDuringReplacementRepublishPublication));
15169
15170    let (returned_tx, returned_rx) = mpsc::channel();
15171    let (release_tx, release_rx) = mpsc::channel();
15172    let cancelling_executor = Arc::clone(&executor);
15173    let cancellation = std::thread::spawn(move || {
15174      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15175        *slot.borrow_mut() = Some(Box::new(move || {
15176          returned_tx.send(()).unwrap();
15177          release_rx.recv().unwrap();
15178        }));
15179      });
15180      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
15181    });
15182    returned_rx
15183      .recv_timeout(Duration::from_secs(2))
15184      .expect("the failed replacement publication must reach its completion barrier");
15185
15186    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15187    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15188    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15189      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15190    executor.request_drain_if_queued();
15191    let publication_state = {
15192      let scheduler = executor
15193        .scheduler_idle_lock
15194        .lock()
15195        .unwrap_or_else(std::sync::PoisonError::into_inner);
15196      (
15197        scheduler.active_dispatch_calls,
15198        scheduler.dispatch_publications.len(),
15199        scheduler.dispatch_publications[0].dispatch,
15200        scheduler.dispatch_publications[0].republish_requested,
15201      )
15202    };
15203    release_tx.send(()).unwrap();
15204    cancellation.join().unwrap();
15205
15206    assert_ne!(replacement_dispatch, 0);
15207    assert_ne!(replacement_dispatch, failed_dispatch);
15208    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
15209    assert_eq!(
15210      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15211        .lock()
15212        .unwrap(),
15213      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15214      "the newly armed host request must receive the exact replacement before cancellation"
15215    );
15216    assert_eq!(
15217      executor.dispatch_pending.load(Ordering::Acquire),
15218      replacement_dispatch
15219    );
15220    assert_eq!(
15221      executor.recovery_dispatch.load(Ordering::Acquire),
15222      replacement_dispatch
15223    );
15224    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15225
15226    let delivery = healthy.deliveries()[0];
15227    assert_eq!(delivery.dispatch, replacement_dispatch);
15228    assert_eq!(
15229      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15230        .claim_delivery(delivery.capability()),
15231      Some(replacement_dispatch)
15232    );
15233    let mut host_turn = executor
15234      .try_admit_host_turn(replacement_dispatch)
15235      .expect("the rearmed replacement delivery must be admitted");
15236    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15237    host_turn.drive();
15238    assert!(
15239      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15240        .finish_delivery(delivery, false)
15241        .failed_dispatches
15242        .is_empty()
15243    );
15244    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
15245    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15246    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15247
15248    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
15249    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
15250    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15251      .state
15252      .lock()
15253      .unwrap_or_else(std::sync::PoisonError::into_inner);
15254    assert!(state.entries.is_empty());
15255    assert!(state.dispatches.is_empty());
15256  }
15257
15258  #[cfg(napi_runtime_os_threads)]
15259  #[test]
15260  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
15261    use std::sync::mpsc;
15262
15263    {
15264      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15265        .state
15266        .lock()
15267        .unwrap_or_else(std::sync::PoisonError::into_inner);
15268      assert!(state.entries.is_empty());
15269      assert!(state.dispatches.is_empty());
15270    }
15271    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15272    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15273      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15274    let metrics = Arc::new(RuntimeMetrics::default());
15275    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15276      Arc::clone(&metrics),
15277      dispatch_stale_failure_current_thread_tasks,
15278      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
15279    ));
15280    let scheduler = Arc::clone(&executor);
15281    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
15282      scheduler.schedule(runnable)
15283    });
15284    executor.schedule(runnable);
15285
15286    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15287    let initial_delivery = initial.deliveries()[0];
15288    let delayed_completion =
15289      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
15290    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
15291      panic!("the initial accepted delivery must report one exact failure");
15292    };
15293    assert_eq!(
15294      *delayed_failure,
15295      current_thread_dispatch_failure(dispatch, 0)
15296    );
15297    let delayed_failure = *delayed_failure;
15298
15299    let (returned_tx, returned_rx) = mpsc::channel();
15300    let (release_tx, release_rx) = mpsc::channel();
15301    let publishing_executor = Arc::clone(&executor);
15302    let publication = std::thread::spawn(move || {
15303      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15304        *slot.borrow_mut() = Some(Box::new(move || {
15305          returned_tx.send(()).unwrap();
15306          release_rx.recv().unwrap();
15307        }));
15308      });
15309      publishing_executor.request_drain_if_queued();
15310    });
15311    returned_rx
15312      .recv_timeout(Duration::from_secs(2))
15313      .expect("the old failed epoch must pause before executor recovery");
15314
15315    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15316    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15317      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15318    executor.request_drain_if_queued();
15319    release_tx.send(()).unwrap();
15320    publication.join().unwrap();
15321
15322    let healthy_delivery = healthy.deliveries()[0];
15323    let delayed_initial_delivery = initial.deliveries()[1];
15324    {
15325      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15326        .state
15327        .lock()
15328        .unwrap_or_else(std::sync::PoisonError::into_inner);
15329      let dispatch_state = state
15330        .dispatches
15331        .get(&dispatch)
15332        .expect("the rearmed dispatch must remain registered");
15333      assert_eq!(dispatch_state.epoch, 1);
15334      assert!(!dispatch_state.is_cancelling());
15335    }
15336
15337    executor.cancel_host_dispatch(delayed_failure);
15338
15339    assert_eq!(
15340      executor.dispatch_pending.load(Ordering::Acquire),
15341      dispatch,
15342      "the delayed old-epoch failure must not consume the rearmed executor capability"
15343    );
15344    assert_eq!(
15345      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15346        .state
15347        .lock()
15348        .unwrap_or_else(std::sync::PoisonError::into_inner)
15349        .dispatches
15350        .get(&dispatch)
15351        .map(|state| (state.epoch, state.phase)),
15352      Some((1, CurrentThreadDispatchPhase::Pending)),
15353      "the delayed old-epoch cancellation must not remove the accepted rearm"
15354    );
15355
15356    assert_eq!(
15357      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15358      Some(dispatch)
15359    );
15360    let mut host_turn = executor
15361      .try_admit_host_turn(dispatch)
15362      .expect("the current-epoch delivery must be admitted");
15363    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15364    host_turn.drive();
15365    assert!(
15366      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15367        .finish_delivery(healthy_delivery, false)
15368        .failed_dispatches
15369        .is_empty()
15370    );
15371    assert!(
15372      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15373        .finish_delivery(delayed_initial_delivery, false)
15374        .failed_dispatches
15375        .is_empty()
15376    );
15377    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15378    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15379
15380    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15381    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15382    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15383      .state
15384      .lock()
15385      .unwrap_or_else(std::sync::PoisonError::into_inner);
15386    assert!(state.entries.is_empty());
15387    assert!(state.dispatches.is_empty());
15388  }
15389
15390  #[cfg(napi_runtime_os_threads)]
15391  #[test]
15392  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15393    use std::sync::mpsc;
15394
15395    {
15396      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15397        .state
15398        .lock()
15399        .unwrap_or_else(std::sync::PoisonError::into_inner);
15400      assert!(state.entries.is_empty());
15401      assert!(state.dispatches.is_empty());
15402    }
15403    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15404      .register(Arc::new(FailDuringLateRegistrationPublication));
15405
15406    let metrics = Arc::new(RuntimeMetrics::default());
15407    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15408      Arc::clone(&metrics),
15409      dispatch_late_registration_current_thread_tasks,
15410      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15411    ));
15412    let scheduler = Arc::clone(&executor);
15413    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15414      scheduler.schedule(runnable)
15415    });
15416    metrics.runnable_scheduled();
15417    executor
15418      .queue
15419      .lock()
15420      .unwrap_or_else(std::sync::PoisonError::into_inner)
15421      .runnables
15422      .push_back(runnable);
15423    let replacement_dispatch = next_current_thread_dispatch_id();
15424    executor
15425      .recovery_dispatch
15426      .store(replacement_dispatch, Ordering::Release);
15427    executor
15428      .dispatch_pending
15429      .store(replacement_dispatch, Ordering::Release);
15430
15431    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15432    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15433    let publishing_executor = Arc::clone(&executor);
15434    let publication = std::thread::spawn(move || {
15435      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15436        *slot.borrow_mut() = Some(Box::new(move || {
15437          owner_retired_tx.send(()).unwrap();
15438          request_blocked_rx
15439            .recv_timeout(Duration::from_secs(2))
15440            .expect("the binding-like drain request must wait for the failure transition");
15441        }));
15442      });
15443      publishing_executor.request_drain_if_queued();
15444    });
15445    owner_retired_rx
15446      .recv_timeout(Duration::from_secs(2))
15447      .expect("the failed replacement publication must retire its owner");
15448
15449    // Match the binding sequence: publish the host registration first, then
15450    // request service for work that may have accumulated before registration.
15451    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15452    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15453      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15454    let requesting_executor = Arc::clone(&executor);
15455    let request = std::thread::spawn(move || {
15456      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15457        *slot.borrow_mut() = Some(Box::new(move || {
15458          request_blocked_tx.send(()).unwrap();
15459        }));
15460      });
15461      requesting_executor.request_drain_if_queued();
15462    });
15463
15464    publication.join().unwrap();
15465    request.join().unwrap();
15466
15467    assert!(
15468      healthy.deliveries().is_empty(),
15469      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15470    );
15471    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15472    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15473    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15474    assert_eq!(futures::executor::block_on(task.fallible()), None);
15475    {
15476      let scheduler = executor
15477        .scheduler_idle_lock
15478        .lock()
15479        .unwrap_or_else(std::sync::PoisonError::into_inner);
15480      assert_eq!(scheduler.active_dispatch_calls, 0);
15481      assert!(scheduler.dispatch_publications.is_empty());
15482    }
15483
15484    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15485
15486    let scheduler = Arc::clone(&executor);
15487    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15488      scheduler.schedule(runnable)
15489    });
15490    executor.schedule(later_runnable);
15491    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15492    let later_delivery = healthy.deliveries()[0];
15493    assert_eq!(
15494      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15495      Some(later_dispatch)
15496    );
15497    let mut host_turn = executor
15498      .try_admit_host_turn(later_dispatch)
15499      .expect("the later exact delivery must be admitted");
15500    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15501    host_turn.drive();
15502    assert!(
15503      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15504        .finish_delivery(later_delivery, false)
15505        .failed_dispatches
15506        .is_empty()
15507    );
15508    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15509    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15510
15511    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15512    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15513      .state
15514      .lock()
15515      .unwrap_or_else(std::sync::PoisonError::into_inner);
15516    assert!(state.entries.is_empty());
15517    assert!(state.dispatches.is_empty());
15518  }
15519
15520  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15521  #[test]
15522  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15523  {
15524    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15525      .lock()
15526      .unwrap()
15527      .clear();
15528    {
15529      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15530        .state
15531        .lock()
15532        .unwrap_or_else(std::sync::PoisonError::into_inner);
15533      assert!(state.entries.is_empty());
15534      assert!(state.dispatches.is_empty());
15535    }
15536    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15537      .next_delivery
15538      .store(0, Ordering::Relaxed);
15539    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15540      .register(Arc::new(FailDuringNestedUnwindPublication));
15541
15542    let metrics = Arc::new(RuntimeMetrics::default());
15543    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15544      Arc::clone(&metrics),
15545      dispatch_nested_unwind_current_thread_tasks,
15546      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15547    ));
15548    let scheduler = Arc::clone(&executor);
15549    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15550      scheduler.schedule(runnable)
15551    });
15552
15553    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15554      *slot.borrow_mut() = Some(Box::new(|| {
15555        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15556          .next_delivery
15557          .store(u64::MAX, Ordering::Relaxed);
15558      }));
15559    });
15560    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15561    assert!(
15562      publication.is_err(),
15563      "replacement delivery-id exhaustion must unwind through the outer failed publication"
15564    );
15565
15566    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15567      .lock()
15568      .unwrap()
15569      .clone();
15570    assert_eq!(dispatches.len(), 2);
15571    let failed_dispatch = dispatches[0];
15572    let replacement_dispatch = dispatches[1];
15573    assert_ne!(failed_dispatch, replacement_dispatch);
15574    assert_eq!(
15575      executor.dispatch_pending.load(Ordering::Acquire),
15576      replacement_dispatch
15577    );
15578    assert_eq!(
15579      executor.recovery_dispatch.load(Ordering::Acquire),
15580      replacement_dispatch
15581    );
15582    {
15583      let scheduler = executor
15584        .scheduler_idle_lock
15585        .lock()
15586        .unwrap_or_else(std::sync::PoisonError::into_inner);
15587      assert_eq!(scheduler.active_dispatch_calls, 0);
15588      assert!(scheduler.dispatch_publications.is_empty());
15589    }
15590    {
15591      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15592        .state
15593        .lock()
15594        .unwrap_or_else(std::sync::PoisonError::into_inner);
15595      assert!(
15596        !state.dispatches.contains_key(&failed_dispatch),
15597        "outer unwind cleanup must retire the exact old cancelling dispatch"
15598      );
15599      assert!(
15600        !state.dispatches.contains_key(&replacement_dispatch),
15601        "the replacement publication guard must release its aborted registry state"
15602      );
15603    }
15604
15605    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15606      .next_delivery
15607      .store(1, Ordering::Relaxed);
15608    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15609    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15610    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15611      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15612
15613    executor.request_drain_if_queued();
15614
15615    assert_eq!(
15616      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15617      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15618      "retry must reuse the replacement reserved before the nested unwind"
15619    );
15620    let delivery = healthy.deliveries()[0];
15621    assert_eq!(delivery.dispatch, replacement_dispatch);
15622    assert_eq!(
15623      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15624      Some(replacement_dispatch)
15625    );
15626    let mut host_turn = executor
15627      .try_admit_host_turn(replacement_dispatch)
15628      .expect("the retried replacement delivery must be admitted");
15629    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15630    host_turn.drive();
15631    assert!(
15632      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15633        .finish_delivery(delivery, false)
15634        .failed_dispatches
15635        .is_empty()
15636    );
15637    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15638    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15639
15640    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15641    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15642      .state
15643      .lock()
15644      .unwrap_or_else(std::sync::PoisonError::into_inner);
15645    assert!(state.entries.is_empty());
15646    assert!(state.dispatches.is_empty());
15647  }
15648
15649  #[test]
15650  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15651    reset_current_thread_recovery_host_dispatches();
15652    let metrics = Arc::new(RuntimeMetrics::default());
15653    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15654      Arc::clone(&metrics),
15655      count_current_thread_recovery_host_dispatch,
15656    ));
15657
15658    let scheduler = Arc::clone(&executor);
15659    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15660      scheduler.schedule(runnable)
15661    });
15662    executor.schedule(runnable);
15663    assert_eq!(current_thread_recovery_host_dispatches(), 1);
15664    let failed_dispatch = current_thread_dispatch(&executor);
15665
15666    // Model the host scheduler throwing after Rust accepted the callback but
15667    // before JavaScript could queue a fresh turn.
15668    executor.cancel_host_dispatch_for_test(failed_dispatch);
15669    assert_eq!(
15670      current_thread_recovery_host_dispatches(),
15671      2,
15672      "cancellation must request one replacement without another submission"
15673    );
15674    let replacement_dispatch = current_thread_dispatch(&executor);
15675    assert_ne!(replacement_dispatch, failed_dispatch);
15676    assert_eq!(
15677      executor.recovery_dispatch.load(Ordering::Acquire),
15678      replacement_dispatch
15679    );
15680
15681    executor.drive_host_turn_for_dispatch(replacement_dispatch);
15682    assert_eq!(futures::executor::block_on(task), 1);
15683    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15684    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15685  }
15686
15687  #[test]
15688  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15689    reset_current_thread_recovery_host_dispatches();
15690    let metrics = Arc::new(RuntimeMetrics::default());
15691    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15692      Arc::clone(&metrics),
15693      count_current_thread_recovery_host_dispatch,
15694    ));
15695    let backend =
15696      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15697
15698    let registration = backend
15699      .work
15700      .try_register_async()
15701      .expect("the backend must accept work");
15702    let task = spawn_registered(
15703      &backend,
15704      Arc::clone(&metrics),
15705      std::future::pending::<()>(),
15706      registration,
15707    );
15708    let failed_dispatch = current_thread_dispatch(&executor);
15709    executor.cancel_host_dispatch_for_test(failed_dispatch);
15710    let replacement_dispatch = current_thread_dispatch(&executor);
15711
15712    executor.cancel_host_dispatch_for_test(replacement_dispatch);
15713
15714    assert_eq!(
15715      current_thread_recovery_host_dispatches(),
15716      2,
15717      "a second scheduler failure must not create an unbounded retry loop"
15718    );
15719    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15720    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15721    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15722    assert_eq!(
15723      futures::executor::block_on(task).unwrap_err().to_string(),
15724      "async runtime stopped before the task completed",
15725      "terminal host failure must settle the original operation as cancelled"
15726    );
15727    assert_eq!(backend.work.state.lock().unwrap().active, 0);
15728
15729    let later_registration = backend
15730      .work
15731      .try_register_async()
15732      .expect("terminal cancellation must reopen acceptance");
15733    let later_task = spawn_registered(
15734      &backend,
15735      Arc::clone(&metrics),
15736      async { 2usize },
15737      later_registration,
15738    );
15739    assert_eq!(
15740      current_thread_recovery_host_dispatches(),
15741      3,
15742      "terminal cancellation must not permanently close the executor"
15743    );
15744    executor.drive_host_turn();
15745    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15746  }
15747
15748  /// Regression test for the hazard shape rolldown hit when a `worker_threads`
15749  /// Worker running watch mode is torn down on the CurrentThread flavor: the
15750  /// cleanup-hook dispatch failure cancels the queued coordinator runnable,
15751  /// whose JoinHandle is wrapped in `futures::Shared`. `Shared`'s notifier
15752  /// wakes its awaiters while holding its `wakers` mutex; the parked awaiter's
15753  /// schedule is rejected by the cancellation sweep, and dropping that
15754  /// runnable inline destroys the awaiter's `Shared` clone, whose destructor
15755  /// re-locks `wakers` on the same thread.
15756  #[cfg(napi_runtime_os_threads)]
15757  #[test]
15758  fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15759    use std::{sync::mpsc, time::Duration};
15760
15761    let (done_tx, done_rx) = mpsc::channel();
15762    std::thread::spawn(move || {
15763      let metrics = Arc::new(RuntimeMetrics::default());
15764      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15765        Arc::clone(&metrics),
15766        accept_current_thread_host_dispatch,
15767      ));
15768
15769      // Task A: its JoinHandle is shared, exactly like a watcher close handle.
15770      let scheduler_a = Arc::clone(&executor);
15771      let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15772        scheduler_a.schedule(runnable)
15773      });
15774      let shared = async move { task_a.fallible().await }.boxed().shared();
15775      let shared_for_b = shared.clone();
15776
15777      // Task B: awaits a clone of that Shared and parks after one poll.
15778      let scheduler_b = Arc::clone(&executor);
15779      let (runnable_b, task_b) = async_task::spawn(
15780        async move {
15781          shared_for_b.await;
15782        },
15783        move |runnable| scheduler_b.schedule(runnable),
15784      );
15785      executor.schedule(runnable_b);
15786      executor.drive_host_turn();
15787      assert_eq!(
15788        metrics.queued_runnables.load(Ordering::Relaxed),
15789        0,
15790        "B must be parked and unscheduled after its first poll"
15791      );
15792
15793      // A's runnable is queued (woken, not yet polled) when the host dispatch
15794      // fails twice, so the terminal cancellation sweep drops it.
15795      executor.schedule(runnable_a);
15796      let failed_dispatch = current_thread_dispatch(&executor);
15797      executor.cancel_host_dispatch_for_test(failed_dispatch);
15798      let replacement_dispatch = current_thread_dispatch(&executor);
15799      executor.cancel_host_dispatch_for_test(replacement_dispatch);
15800
15801      assert!(
15802        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15803        "the cancellation sweep must reopen the executor"
15804      );
15805      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15806      assert!(
15807        futures::executor::block_on(task_b.fallible()).is_none(),
15808        "B's rejected wake must still cancel B"
15809      );
15810      drop(shared);
15811      done_tx.send(()).unwrap();
15812    });
15813    done_rx
15814      .recv_timeout(Duration::from_secs(5))
15815      .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15816  }
15817
15818  /// Regression test for the closed-queue sibling of the hazard above. A wake
15819  /// can be in flight on any thread when shutdown starts: `async-task` marks
15820  /// the task `SCHEDULED` before it invokes the schedule closure, so the abort
15821  /// that `close_and_abort` issues for that task coalesces with the in-flight
15822  /// wake and returns, `begin_shutdown` closes the queue without waiting for
15823  /// the closure to land, and the resumed closure is rejected by the closed
15824  /// queue. That rejection must not drop the runnable inline either: the
15825  /// waker here is still inside `futures::Shared`'s notifier, holding its
15826  /// `wakers` mutex, and the rejected future owns a clone of the same `Shared`.
15827  #[cfg(napi_runtime_os_threads)]
15828  #[test]
15829  fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15830    use std::{
15831      sync::{Barrier, mpsc},
15832      time::Duration,
15833    };
15834
15835    let (done_tx, done_rx) = mpsc::channel();
15836    std::thread::spawn(move || {
15837      let metrics = Arc::new(RuntimeMetrics::default());
15838      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15839        Arc::clone(&metrics),
15840        accept_current_thread_host_dispatch,
15841      ));
15842      let backend =
15843        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15844      let generation = backend.generation();
15845
15846      // Task A: registered like production work; its JoinHandle is shared.
15847      let (abort_a, guard_a) = backend
15848        .work
15849        .try_register_async()
15850        .expect("the backend must accept work");
15851      let scheduler_a = Arc::clone(&executor);
15852      let (runnable_a, task_a) = async_task::spawn(
15853        RegisteredTaskFuture::new(
15854          Abortable::new(std::future::pending::<()>(), abort_a),
15855          guard_a,
15856          generation,
15857        ),
15858        move |runnable| scheduler_a.schedule(runnable),
15859      );
15860      let shared = async move { task_a.fallible().await.is_some() }
15861        .boxed()
15862        .shared();
15863      let shared_for_b = shared.clone();
15864
15865      // Task B: registered and abortable too, awaiting a clone of that Shared.
15866      // Its schedule closure pauses before it reaches the executor, exactly
15867      // where a waker thread may be descheduled while shutdown runs.
15868      let (abort_b, guard_b) = backend
15869        .work
15870        .try_register_async()
15871        .expect("the backend must accept work");
15872      let wake_in_flight = Arc::new(Barrier::new(2));
15873      let wake_release = Arc::new(Barrier::new(2));
15874      let scheduler_b = Arc::clone(&executor);
15875      let (runnable_b, task_b) = async_task::spawn(
15876        RegisteredTaskFuture::new(
15877          Abortable::new(
15878            async move {
15879              shared_for_b.await;
15880            },
15881            abort_b,
15882          ),
15883          guard_b,
15884          generation,
15885        ),
15886        {
15887          let wake_in_flight = Arc::clone(&wake_in_flight);
15888          let wake_release = Arc::clone(&wake_release);
15889          move |runnable| {
15890            wake_in_flight.wait();
15891            wake_release.wait();
15892            scheduler_b.schedule(runnable);
15893          }
15894        },
15895      );
15896      executor.schedule(runnable_b);
15897      executor.drive_host_turn();
15898      assert_eq!(
15899        metrics.queued_runnables.load(Ordering::Relaxed),
15900        0,
15901        "B must be parked and unscheduled after its first poll"
15902      );
15903
15904      // Cancel A on another thread: closing A notifies its awaiter, and the
15905      // Shared notifier wakes B while holding `wakers`. B's schedule closure
15906      // then parks at the barrier with that lock still held.
15907      let waker_thread = std::thread::spawn(move || drop(runnable_a));
15908      wake_in_flight.wait();
15909
15910      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
15911      // the abort wake is a no-op; the queue then closes.
15912      backend.begin_shutdown();
15913      wake_release.wait();
15914      backend.wait_until_idle();
15915
15916      waker_thread
15917        .join()
15918        .expect("the waker thread must not panic");
15919      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15920      assert!(
15921        futures::executor::block_on(task_b.fallible()).is_none(),
15922        "B's rejected wake must still cancel B"
15923      );
15924      assert_eq!(backend.work.state.lock().unwrap().active, 0);
15925      drop(shared);
15926      done_tx.send(()).unwrap();
15927    });
15928    done_rx
15929      .recv_timeout(Duration::from_secs(5))
15930      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15931  }
15932
15933  /// Regression test for the terminal-failure sibling of the two hazards
15934  /// above. Task B awaits a `futures::Shared` and parks. Completing the Shared
15935  /// wakes B from inside its notifier, which holds `wakers`, and `schedule`
15936  /// publishes a host dispatch on that same stack. The host fails the
15937  /// delivery before `dispatch` returns, and fails the one replacement the
15938  /// same way, so the terminal failure transition runs while the waker's
15939  /// stack is still under `wakers`. Nothing may be dropped there: B's future
15940  /// owns a clone of the same `Shared`, whose destructor re-locks `wakers`.
15941  /// The transition moves B aside and reopens; the next host turn cancels B.
15942  #[cfg(napi_runtime_os_threads)]
15943  #[test]
15944  fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15945    use std::{sync::mpsc, time::Duration};
15946
15947    let (done_tx, done_rx) = mpsc::channel();
15948    std::thread::spawn(move || {
15949      {
15950        let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15951          .state
15952          .lock()
15953          .unwrap_or_else(std::sync::PoisonError::into_inner);
15954        assert!(state.entries.is_empty());
15955        assert!(state.dispatches.is_empty());
15956      }
15957      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15958        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15959      ));
15960      let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15961        .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15962      let metrics = Arc::new(RuntimeMetrics::default());
15963      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15964        Arc::clone(&metrics),
15965        dispatch_waker_stack_failure_current_thread_tasks,
15966        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15967      ));
15968
15969      // Task B awaits a clone of a Shared and parks after its first poll.
15970      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15971      let shared = rx.map(|_| ()).boxed().shared();
15972      let shared_for_b = shared.clone();
15973      let scheduler_b = Arc::clone(&executor);
15974      let (runnable_b, task_b) = async_task::spawn(
15975        async move {
15976          shared_for_b.await;
15977        },
15978        move |runnable| scheduler_b.schedule(runnable),
15979      );
15980      executor.schedule(runnable_b);
15981
15982      // Service the accepted delivery the way `drive_current_thread_tasks`
15983      // and `acknowledge_current_thread_task_delivery` do.
15984      let delivery = driver.deliveries()[0];
15985      let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15986        .claim_delivery(delivery.capability())
15987        .expect("the accepted delivery must be claimable");
15988      let mut host_turn = executor
15989        .try_admit_host_turn(dispatch)
15990        .expect("the claimed delivery must be admitted");
15991      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15992      host_turn.drive();
15993      drop(host_turn);
15994      let completion =
15995        CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15996      assert!(completion.failed_dispatches.is_empty());
15997      assert!(!completion.redispatch);
15998      assert_eq!(
15999        metrics.queued_runnables.load(Ordering::Relaxed),
16000        0,
16001        "B must be parked and unscheduled after its first poll"
16002      );
16003
16004      // From now on every accepted delivery fails before `dispatch` returns.
16005      driver.fail.store(true, Ordering::SeqCst);
16006      // Completing the Shared wakes B from inside its notifier, under
16007      // `wakers`; B's schedule publishes the dispatch and its replacement on
16008      // this stack, both fail, and the terminal transition runs here.
16009      tx.send(()).expect("B must still hold its Shared clone");
16010      assert_eq!(
16011        driver.deliveries().len(),
16012        // The initial service, the wake's dispatch and its one replacement,
16013        // then the retirement turns and the final offer -- this host refuses
16014        // every one of them.
16015        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
16016        "the wake must publish the dispatch, its one replacement, and the \
16017         retirement turns the terminal transition owes itself"
16018      );
16019      assert!(
16020        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16021        "the terminal failure must reopen the executor before the waker returns"
16022      );
16023      assert!(
16024        executor.has_rejected_work(),
16025        "B must be moved aside, not dropped on the waker's stack"
16026      );
16027      assert_eq!(
16028        metrics.queued_runnables.load(Ordering::Relaxed),
16029        0,
16030        "the moved runnable carries its queued debit"
16031      );
16032      let mut task_b = task_b.fallible();
16033      assert!(
16034        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16035        "B must still be pending: nothing may be dropped on the waker's stack"
16036      );
16037
16038      // The next host turn (what `drive_current_thread_tasks` runs for the
16039      // delivery a later submission publishes) cancels B on its own stack.
16040      executor.drive_host_turn();
16041      assert!(!executor.has_rejected_work());
16042      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16043      assert!(
16044        poll_ready_within(&mut task_b, Duration::from_secs(5))
16045          .expect("the host turn must cancel B")
16046          .is_none(),
16047        "the terminal failure must still cancel B"
16048      );
16049      drop(shared);
16050
16051      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
16052      let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
16053        .state
16054        .lock()
16055        .unwrap_or_else(std::sync::PoisonError::into_inner);
16056      assert!(state.entries.is_empty());
16057      assert!(state.dispatches.is_empty());
16058      done_tx.send(()).unwrap();
16059    });
16060    done_rx
16061      .recv_timeout(Duration::from_secs(5))
16062      .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
16063  }
16064
16065  /// Poll `future` with a no-op waker until it is ready or `timeout` passes.
16066  /// The tests below drive the executor by hand, so readiness is produced on
16067  /// this thread and a wait can only fail by design, never by scheduling.
16068  #[cfg(napi_runtime_os_threads)]
16069  fn poll_ready_within<F: Future + Unpin>(
16070    future: &mut F,
16071    timeout: std::time::Duration,
16072  ) -> Option<F::Output> {
16073    let deadline = std::time::Instant::now() + timeout;
16074    let mut cx = Context::from_waker(futures::task::noop_waker_ref());
16075    loop {
16076      if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
16077        return Some(output);
16078      }
16079      if std::time::Instant::now() >= deadline {
16080        return None;
16081      }
16082      std::thread::sleep(std::time::Duration::from_millis(1));
16083    }
16084  }
16085
16086  /// Liveness regression test for the transition above. After the waker-stack
16087  /// terminal failure the driver stays registered and becomes healthy again.
16088  /// Work submitted from then on is new work: its schedule must publish a
16089  /// fresh delivery, and the host turn that services it cancels the work that
16090  /// was queued when the failure happened (B) before it runs the new task.
16091  /// Nothing else prompts the executor: no drain request, no re-registration,
16092  /// no shutdown.
16093  #[cfg(napi_runtime_os_threads)]
16094  #[test]
16095  fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
16096    use std::{sync::mpsc, time::Duration};
16097
16098    const WAIT: Duration = Duration::from_secs(5);
16099    let (done_tx, done_rx) = mpsc::channel();
16100    std::thread::spawn(move || {
16101      let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
16102      {
16103        let state = registry
16104          .state
16105          .lock()
16106          .unwrap_or_else(std::sync::PoisonError::into_inner);
16107        assert!(state.entries.is_empty());
16108        assert!(state.dispatches.is_empty());
16109      }
16110      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16111      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16112      let metrics = Arc::new(RuntimeMetrics::default());
16113      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16114        Arc::clone(&metrics),
16115        dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
16116        registry,
16117      ));
16118
16119      // Service one accepted delivery the way the host does.
16120      let service = |index: usize| {
16121        let delivery = driver.deliveries()[index];
16122        let dispatch = registry
16123          .claim_delivery(delivery.capability())
16124          .expect("the accepted delivery must be claimable");
16125        let mut host_turn = executor
16126          .try_admit_host_turn(dispatch)
16127          .expect("the claimed delivery must be admitted");
16128        registry.mark_serviced(dispatch);
16129        host_turn.drive();
16130        drop(host_turn);
16131        let completion = registry.finish_delivery(delivery, false);
16132        assert!(completion.failed_dispatches.is_empty());
16133        assert!(!completion.redispatch);
16134      };
16135
16136      // Task B awaits a clone of a Shared and parks after its first poll.
16137      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16138      let shared = rx.map(|_| ()).boxed().shared();
16139      let shared_for_b = shared.clone();
16140      let scheduler_b = Arc::clone(&executor);
16141      let (runnable_b, task_b) = async_task::spawn(
16142        async move {
16143          shared_for_b.await;
16144        },
16145        move |runnable| scheduler_b.schedule(runnable),
16146      );
16147      executor.schedule(runnable_b);
16148      service(0);
16149      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16150
16151      // The waker-stack terminal failure: the wake's dispatch and its one
16152      // replacement both fail before `dispatch` returns.
16153      driver.fail.store(true, Ordering::SeqCst);
16154      tx.send(()).expect("B must still hold its Shared clone");
16155      // The dispatch, its one replacement, and the retirement turns the
16156      // transition publishes for itself -- which this still-failing host
16157      // loses too, down to the final offer.
16158      const AFTER_WAKE: usize =
16159        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16160      assert_eq!(driver.deliveries().len(), AFTER_WAKE);
16161      let mut task_b = task_b.fallible();
16162      assert!(
16163        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16164        "B must still be pending: nothing may be dropped on the waker's stack"
16165      );
16166
16167      // The host is healthy again and still registered. A later submission
16168      // must publish a delivery on its own.
16169      driver.fail.store(false, Ordering::SeqCst);
16170      let scheduler_later = Arc::clone(&executor);
16171      let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
16172        scheduler_later.schedule(runnable)
16173      });
16174      executor.schedule(runnable_later);
16175      assert_eq!(
16176        driver.deliveries().len(),
16177        AFTER_WAKE + 1,
16178        "a submission after the terminal failure must publish a fresh delivery"
16179      );
16180      assert!(
16181        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16182        "the executor must be open for the healthy host"
16183      );
16184
16185      // The host services that delivery: the pre-failure work is cancelled
16186      // and the new task runs.
16187      service(AFTER_WAKE);
16188      let mut task_later = task_later.fallible();
16189      assert_eq!(
16190        poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
16191        Some(42),
16192        "the later task must complete on the delivered host turn"
16193      );
16194      assert!(
16195        poll_ready_within(&mut task_b, WAIT)
16196          .expect("B must have been cancelled by the host turn")
16197          .is_none(),
16198        "the terminal failure must still cancel the work queued when it happened"
16199      );
16200      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16201      drop(shared);
16202
16203      registry.unregister(registration);
16204      let state = registry
16205        .state
16206        .lock()
16207        .unwrap_or_else(std::sync::PoisonError::into_inner);
16208      assert!(state.entries.is_empty());
16209      assert!(state.dispatches.is_empty());
16210      done_tx.send(()).unwrap();
16211    });
16212    done_rx
16213      .recv_timeout(WAIT)
16214      .expect("the healthy host never got a delivery for the later submission");
16215  }
16216
16217  /// Regression test for the continuation a WAKE owes itself.
16218  ///
16219  /// `6d452174` gave an ordinary submission its own continuation, but a
16220  /// genuine wake still had none. B parks on a `futures::Shared`; completing
16221  /// the Shared wakes B from inside its notifier, which holds `wakers`, so
16222  /// `schedule` publishes on a stack where nothing may be destroyed. The host
16223  /// loses that publication and the one replacement before `dispatch`
16224  /// returns, and the terminal transition moves B into `queue.rejected` and
16225  /// reopens -- correctly dropping nothing.
16226  ///
16227  /// What was missing is what happens next. The transition consumed the
16228  /// executor's one outstanding host request and left the queue empty, so
16229  /// `has_serviceable_work` is false and nothing published again: during
16230  /// normal operation there is no generation idle waiter, no host turn
16231  /// follows the failed publications, and B's `JoinHandle` and
16232  /// `GenerationWorkGuard` stayed live forever. Nothing in this test supplies
16233  /// an unrelated entry -- no later submission, no drain request, no
16234  /// unregistration, no shutdown. The host only services the delivery the
16235  /// transition published for ITSELF.
16236  #[cfg(napi_runtime_os_threads)]
16237  #[test]
16238  fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
16239    use std::{sync::mpsc, time::Duration};
16240
16241    const WAIT: Duration = Duration::from_secs(5);
16242    let (done_tx, done_rx) = mpsc::channel();
16243    std::thread::spawn(move || {
16244      let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
16245      {
16246        let state = registry
16247          .state
16248          .lock()
16249          .unwrap_or_else(std::sync::PoisonError::into_inner);
16250        assert!(state.entries.is_empty());
16251        assert!(state.dispatches.is_empty());
16252      }
16253      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16254      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16255      let metrics = Arc::new(RuntimeMetrics::default());
16256      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16257        Arc::clone(&metrics),
16258        dispatch_retirement_turn_current_thread_tasks,
16259        registry,
16260      ));
16261
16262      // Service one accepted delivery the way the host does.
16263      let service = |index: usize| {
16264        let delivery = driver.deliveries()[index];
16265        let dispatch = registry
16266          .claim_delivery(delivery.capability())
16267          .expect("the accepted delivery must be claimable");
16268        let mut host_turn = executor
16269          .try_admit_host_turn(dispatch)
16270          .expect("the claimed delivery must be admitted");
16271        registry.mark_serviced(dispatch);
16272        host_turn.drive();
16273        drop(host_turn);
16274        let completion = registry.finish_delivery(delivery, false);
16275        assert!(completion.failed_dispatches.is_empty());
16276        assert!(!completion.redispatch);
16277      };
16278
16279      // Task B awaits a clone of a Shared and parks after its first poll.
16280      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16281      let shared = rx.map(|_| ()).boxed().shared();
16282      let shared_for_b = shared.clone();
16283      let scheduler_b = Arc::clone(&executor);
16284      let (runnable_b, task_b) = async_task::spawn(
16285        async move {
16286          shared_for_b.await;
16287        },
16288        move |runnable| scheduler_b.schedule(runnable),
16289      );
16290      executor.schedule(runnable_b);
16291      service(0);
16292      assert_eq!(
16293        metrics.queued_runnables.load(Ordering::Relaxed),
16294        0,
16295        "B must be parked and unscheduled after its first poll"
16296      );
16297
16298      // Exactly the wake's publication and its one replacement are lost; the
16299      // host is healthy again from the third delivery on.
16300      driver.fail_budget.store(2, Ordering::SeqCst);
16301      tx.send(()).expect("B must still hold its Shared clone");
16302
16303      assert_eq!(
16304        driver.deliveries().len(),
16305        4,
16306        "the wake must publish the dispatch, its one replacement, and the \
16307         retirement turn the terminal transition owes itself; without that \
16308         third publication no host turn ever follows and B stays pending \
16309         forever"
16310      );
16311      assert!(
16312        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16313        "the terminal failure must reopen the executor before the waker returns"
16314      );
16315      assert!(
16316        executor.has_rejected_work(),
16317        "B must be moved aside, not dropped on the waker's stack"
16318      );
16319      assert_eq!(
16320        metrics.queued_runnables.load(Ordering::Relaxed),
16321        0,
16322        "the moved runnable carries its queued debit"
16323      );
16324      let mut task_b = task_b.fallible();
16325      assert!(
16326        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16327        "B must still be pending: nothing may be dropped on the waker's stack"
16328      );
16329
16330      // The ONLY continuation: the host serves the turn the transition asked
16331      // for. No submission, no drain request, no unregistration, no shutdown.
16332      service(3);
16333      assert!(
16334        !executor.has_rejected_work(),
16335        "the retirement turn must cancel the moved work on its own stack"
16336      );
16337      assert!(
16338        poll_ready_within(&mut task_b, WAIT)
16339          .expect("the retirement turn must cancel B")
16340          .is_none(),
16341        "the terminal failure must still cancel the work queued when it happened"
16342      );
16343      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16344      drop(shared);
16345
16346      registry.unregister(registration);
16347      let state = registry
16348        .state
16349        .lock()
16350        .unwrap_or_else(std::sync::PoisonError::into_inner);
16351      assert!(state.entries.is_empty());
16352      assert!(state.dispatches.is_empty());
16353      done_tx.send(()).unwrap();
16354    });
16355    done_rx.recv_timeout(WAIT).expect(
16356      "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16357       `queue.rejected` and its JoinHandle never settled",
16358    );
16359  }
16360
16361  /// Regression test for the continuation a NESTED terminal failure owes.
16362  ///
16363  /// `fc4d8e3b` gave a waker-stack terminal failure a continuation of its own:
16364  /// it publishes one fresh host dispatch, and the host turn that services it
16365  /// is the clean stack that retires the moved work. That publication calls
16366  /// the host driver again, so a one-shot bounds it -- and the one-shot used
16367  /// to DISCARD the nested request instead of handing it back:
16368  ///
16369  ///   thread W (waker)                     | thread T (producer)
16370  ///   -------------------------------------+---------------------------
16371  ///   Shared notifier wakes B -> schedule  |
16372  ///   publish D1 .. host fails             |
16373  ///   publish D2 (replacement) .. fails    |
16374  ///   terminal: B -> queue.rejected, reopen|
16375  ///   retirement scope ENTER, publish D3 --+--> schedule(C) -> queue.runnables
16376  ///                                        |    request_drain -> Coalesced
16377  ///   host fails D3, sees C serviceable    |
16378  ///   publish D4 (replacement) .. fails    |
16379  ///   terminal: C -> queue.rejected, reopen|
16380  ///   retirement scope already ACTIVE      |
16381  ///     -> returned, publishing NOTHING    |
16382  ///
16383  /// Four deliveries during the wake, every one of them lost, no host turn at
16384  /// all, and both B and C sat in `queue.rejected` with their `JoinHandle`s
16385  /// pending and their `GenerationWorkGuard`s live -- even though the host is
16386  /// healthy for its very next delivery. The nested request now goes back to
16387  /// the frame that owns the publication, which publishes a fifth delivery for
16388  /// it once D3 has unwound. Nothing in this test supplies an unrelated entry:
16389  /// no later submission, no drain request, no unregistration, no shutdown.
16390  #[cfg(napi_runtime_os_threads)]
16391  #[test]
16392  fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16393    use std::{sync::mpsc, time::Duration};
16394
16395    const WAIT: Duration = Duration::from_secs(5);
16396    let (done_tx, done_rx) = mpsc::channel();
16397    std::thread::spawn(move || {
16398      let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16399      {
16400        let state = registry
16401          .state
16402          .lock()
16403          .unwrap_or_else(std::sync::PoisonError::into_inner);
16404        assert!(state.entries.is_empty());
16405        assert!(state.dispatches.is_empty());
16406      }
16407      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16408      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16409      let metrics = Arc::new(RuntimeMetrics::default());
16410      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16411        Arc::clone(&metrics),
16412        dispatch_nested_retirement_current_thread_tasks,
16413        registry,
16414      ));
16415
16416      // Service one accepted delivery the way the host does.
16417      let service = |index: usize| {
16418        let delivery = driver.deliveries()[index];
16419        let dispatch = registry
16420          .claim_delivery(delivery.capability())
16421          .expect("the accepted delivery must be claimable");
16422        let mut host_turn = executor
16423          .try_admit_host_turn(dispatch)
16424          .expect("the claimed delivery must be admitted");
16425        registry.mark_serviced(dispatch);
16426        host_turn.drive();
16427        drop(host_turn);
16428        let completion = registry.finish_delivery(delivery, false);
16429        assert!(completion.failed_dispatches.is_empty());
16430        assert!(!completion.redispatch);
16431      };
16432
16433      // Task B awaits a clone of a Shared and parks after its first poll, so
16434      // its wake arrives from inside the notifier's `wakers` lock.
16435      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16436      let shared = rx.map(|_| ()).boxed().shared();
16437      let shared_for_b = shared.clone();
16438      let scheduler_b = Arc::clone(&executor);
16439      let (runnable_b, task_b) = async_task::spawn(
16440        async move {
16441          shared_for_b.await;
16442        },
16443        move |runnable| scheduler_b.schedule(runnable),
16444      );
16445      executor.schedule(runnable_b);
16446      service(0);
16447      assert_eq!(
16448        metrics.queued_runnables.load(Ordering::Relaxed),
16449        0,
16450        "B must be parked and unscheduled after its first poll"
16451      );
16452
16453      // Task C is queued by ANOTHER thread, from inside the retirement turn's
16454      // publication. The executor is reopened by then, so C lands in
16455      // `queue.runnables`; the publication already owns `dispatch_pending`, so
16456      // C's own drain request coalesces and adds no delivery of its own.
16457      let scheduler_c = Arc::clone(&executor);
16458      let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16459        scheduler_c.schedule(runnable);
16460      });
16461      let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16462      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16463      let producer_executor = Arc::clone(&executor);
16464      let producer = std::thread::spawn(move || {
16465        let runnable = gate_rx
16466          .recv()
16467          .expect("the retirement turn must open the window");
16468        producer_executor.schedule(runnable);
16469        queued_tx.send(()).expect("W must still be waiting");
16470      });
16471      driver.during_delivery(3, move || {
16472        gate_tx.send(runnable_c).expect("the producer must be live");
16473        queued_rx
16474          .recv_timeout(WAIT)
16475          .expect("the producer must queue C before the retirement turn fails");
16476      });
16477
16478      // Every delivery of the interleaving is lost: the wake's publication,
16479      // its one replacement, the retirement turn the terminal transition
16480      // publishes for itself, and the replacement that turn's failure reserves
16481      // once it sees C queued. The host is healthy again from the sixth.
16482      driver.fail_budget.store(4, Ordering::SeqCst);
16483      tx.send(()).expect("B must still hold its Shared clone");
16484      producer.join().expect("the producer must not panic");
16485
16486      assert_eq!(
16487        driver.deliveries().len(),
16488        6,
16489        "the nested terminal transition may not publish on this stack, but the \
16490         retirement request it hands back must be serviced after the outer \
16491         publication unwinds; without that sixth delivery no host turn ever \
16492         follows and both B and C stay pending forever"
16493      );
16494      assert!(
16495        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16496        "both terminal failures must reopen the executor before the waker returns"
16497      );
16498      assert!(
16499        executor.has_rejected_work(),
16500        "B and C must be moved aside, not dropped on the waker's stack"
16501      );
16502      assert_eq!(
16503        metrics.queued_runnables.load(Ordering::Relaxed),
16504        0,
16505        "the moved runnables carry their queued debit"
16506      );
16507      let mut task_b = task_b.fallible();
16508      let mut task_c = task_c.fallible();
16509      assert!(
16510        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16511        "B must still be pending: nothing may be dropped on the waker's stack"
16512      );
16513      assert!(
16514        poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16515        "C must still be pending: nothing may be dropped on the waker's stack"
16516      );
16517
16518      // The ONLY continuation: the host serves the delivery the retirement
16519      // request produced. No submission, no drain request, no unregistration,
16520      // no shutdown.
16521      service(5);
16522      assert!(
16523        !executor.has_rejected_work(),
16524        "the retirement turn must cancel both moved runnables on its own stack"
16525      );
16526      assert!(
16527        poll_ready_within(&mut task_b, WAIT)
16528          .expect("the retirement turn must cancel B")
16529          .is_none(),
16530        "the terminal failure must still cancel the work queued when it happened"
16531      );
16532      assert!(
16533        poll_ready_within(&mut task_c, WAIT)
16534          .expect("the retirement turn must cancel C")
16535          .is_none(),
16536        "the nested terminal failure must cancel the work it moved aside too"
16537      );
16538      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16539      drop(shared);
16540
16541      registry.unregister(registration);
16542      let state = registry
16543        .state
16544        .lock()
16545        .unwrap_or_else(std::sync::PoisonError::into_inner);
16546      assert!(state.entries.is_empty());
16547      assert!(state.dispatches.is_empty());
16548      done_tx.send(()).unwrap();
16549    });
16550    done_rx.recv_timeout(WAIT).expect(
16551      "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16552       runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16553    );
16554  }
16555
16556  /// Regression test for the BOUND on the retirement turns `e2a8ed9c` opened.
16557  ///
16558  /// That commit made the frame owning a waker-stack retirement publication
16559  /// loop while nested terminal transitions kept handing work back. Its
16560  /// termination argument -- every turn consumes one distinct live task --
16561  /// bounds the turns by the live tasks, which is no bound at all in wall
16562  /// clock: a producer that keeps submitting keeps supplying the runnable each
16563  /// nested transition needs.
16564  ///
16565  ///   thread W (waker)                     | thread T (producer)
16566  ///   -------------------------------------+---------------------------
16567  ///   Shared notifier wakes B -> schedule  |
16568  ///   publish D1 --------------------------+--> schedule(C1) -> runnables
16569  ///   host fails D1 synchronously          |    request_drain -> Coalesced
16570  ///   publish D2 (replacement) ------------+--> schedule(C2)
16571  ///   host fails D2 synchronously          |
16572  ///   terminal: C1,C2 -> rejected, reopen  |
16573  ///   retirement turn 1: publish D3 -------+--> schedule(C3)   ... and so on
16574  ///
16575  /// Every turn is two more host callbacks and two more producer submissions,
16576  /// so the turns ran for exactly as long as the producer did: measured on
16577  /// `e2a8ed9c`, 32768 submissions produced 32769 deliveries inside one wake,
16578  /// and a producer left running for 400ms held this single `schedule`
16579  /// callback -- and `futures::Shared`'s `wakers` mutex under it -- for 432ms.
16580  ///
16581  /// The owner frame now takes at most `REJECTED_WORK_RETIREMENT_ATTEMPTS`
16582  /// turns a producer can interleave with, and then makes the one offer a
16583  /// producer cannot inflate (`publish_final_rejected_work_retirement`). This
16584  /// host refuses that one too, so what is left is the honest end state for a
16585  /// host that never accepts: the work waits, undropped, for a depth-0 entry.
16586  /// Nothing here supplies one until `wait_until_idle` -- no submission, no
16587  /// drain request, no unregistration -- and that is what
16588  /// `current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget`
16589  /// pins from the other side, where the host accepts the final offer.
16590  #[cfg(napi_runtime_os_threads)]
16591  #[test]
16592  fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16593    use std::{sync::mpsc, time::Duration};
16594
16595    const WAIT: Duration = Duration::from_secs(5);
16596    /// Far more submissions than the turns may ever spend, so what stops them
16597    /// is the executor's budget and not the producer running dry.
16598    const PRODUCER_BUDGET: usize = 4096;
16599    /// One publication and its one replacement for the wake itself, two more
16600    /// for each retirement turn, and the single final offer.
16601    const BOUNDED_DELIVERIES: usize =
16602      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16603
16604    let (done_tx, done_rx) = mpsc::channel();
16605    std::thread::spawn(move || {
16606      let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16607      {
16608        let state = registry
16609          .state
16610          .lock()
16611          .unwrap_or_else(std::sync::PoisonError::into_inner);
16612        assert!(state.entries.is_empty());
16613        assert!(state.dispatches.is_empty());
16614      }
16615      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16616      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16617      let metrics = Arc::new(RuntimeMetrics::default());
16618      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16619        Arc::clone(&metrics),
16620        dispatch_sustained_producer_current_thread_tasks,
16621        registry,
16622      ));
16623      let backend =
16624        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16625
16626      // Service one accepted delivery the way the host does.
16627      let service = |index: usize| {
16628        let delivery = driver.deliveries()[index];
16629        let dispatch = registry
16630          .claim_delivery(delivery.capability())
16631          .expect("the accepted delivery must be claimable");
16632        let mut host_turn = executor
16633          .try_admit_host_turn(dispatch)
16634          .expect("the claimed delivery must be admitted");
16635        registry.mark_serviced(dispatch);
16636        host_turn.drive();
16637        drop(host_turn);
16638        let completion = registry.finish_delivery(delivery, false);
16639        assert!(completion.failed_dispatches.is_empty());
16640        assert!(!completion.redispatch);
16641      };
16642
16643      // B awaits a clone of a Shared and parks after its first poll, so its
16644      // wake arrives from inside the notifier's `wakers` lock. It is a
16645      // registered task, so its `GenerationWorkGuard` is observable.
16646      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16647      let shared = rx.map(|_| ()).boxed().shared();
16648      let shared_for_b = shared.clone();
16649      let work_registration = backend
16650        .work
16651        .try_register_async()
16652        .expect("the backend must accept work");
16653      let task_b = spawn_registered(
16654        &backend,
16655        Arc::clone(&metrics),
16656        async move {
16657          shared_for_b.await;
16658        },
16659        work_registration,
16660      );
16661      service(0);
16662      assert_eq!(
16663        metrics.queued_runnables.load(Ordering::Relaxed),
16664        0,
16665        "B must be parked and unscheduled after its first poll"
16666      );
16667      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16668
16669      // The producer queues one runnable from inside every publication. The
16670      // publication already owns `dispatch_pending`, so each of those drain
16671      // requests coalesces and adds no delivery of its own -- every delivery
16672      // below is one the waker's stack published.
16673      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16674      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16675      let producer_executor = Arc::clone(&executor);
16676      let producer = std::thread::spawn(move || {
16677        let mut submissions = 0usize;
16678        while queue_one_rx.recv().is_ok() {
16679          let scheduler = Arc::clone(&producer_executor);
16680          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16681            scheduler.schedule(runnable);
16682          });
16683          task.detach();
16684          producer_executor.schedule(runnable);
16685          submissions += 1;
16686          if queued_tx.send(()).is_err() {
16687            break;
16688          }
16689        }
16690        submissions
16691      });
16692      driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16693
16694      // From here the host fails every delivery synchronously, on the calling
16695      // thread, and never recovers.
16696      driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16697      let before = driver.deliveries().len();
16698      tx.send(()).expect("B must still hold its Shared clone");
16699      let during_wake = driver.deliveries().len() - before;
16700      let unspent = driver.disarm_producer();
16701      let submissions = producer.join().expect("the producer must not panic");
16702
16703      assert_eq!(
16704        during_wake, BOUNDED_DELIVERIES,
16705        "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16706         on the waker's stack plus its one final offer; without a budget it \
16707         services the producer's stream instead and this wake never returns"
16708      );
16709      assert_eq!(
16710        submissions, during_wake,
16711        "the producer must have queued a runnable during every publication"
16712      );
16713      assert!(
16714        unspent > 0,
16715        "the producer must still have been willing to submit when the turns stopped"
16716      );
16717      assert!(
16718        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16719        "every terminal failure, and the final offer, must reopen the executor \
16720         before the waker returns"
16721      );
16722      assert!(
16723        executor.has_rejected_work(),
16724        "the moved work must be parked, not dropped on the waker's stack"
16725      );
16726      assert_eq!(
16727        executor.dispatch_pending.load(Ordering::Acquire),
16728        0,
16729        "a host that refused every offer including the final one leaves none \
16730         outstanding: this is the documented end state for a host that never accepts"
16731      );
16732      assert_eq!(
16733        metrics.queued_runnables.load(Ordering::Relaxed),
16734        0,
16735        "the moved runnables carry their queued debit"
16736      );
16737      let mut task_b = Box::pin(task_b);
16738      assert!(
16739        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16740        "B must still be pending: nothing may be dropped on the waker's stack"
16741      );
16742      assert_eq!(
16743        backend.work.state.lock().unwrap().active,
16744        1,
16745        "B's generation guard rides with its parked runnable"
16746      );
16747
16748      // The first depth-0 entry retires it, and no `GenerationWorkGuard`
16749      // outlives that wait. Nothing above supplied this entry: no submission,
16750      // no drain request, no unregistration.
16751      backend.wait_until_idle();
16752      assert!(!executor.has_rejected_work());
16753      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16754      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16755      assert!(
16756        poll_ready_within(&mut task_b, WAIT)
16757          .expect("the idle wait must cancel B")
16758          .is_err(),
16759        "the terminal failure must still cancel the work queued when it happened"
16760      );
16761      drop(shared);
16762
16763      registry.unregister(registration);
16764      let state = registry
16765        .state
16766        .lock()
16767        .unwrap_or_else(std::sync::PoisonError::into_inner);
16768      assert!(state.entries.is_empty());
16769      assert!(state.dispatches.is_empty());
16770      done_tx.send(()).unwrap();
16771    });
16772    done_rx.recv_timeout(WAIT).expect(
16773      "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16774       the wake never returned",
16775    );
16776  }
16777
16778  /// Regression test for the offer the attempt budget may not swallow -- the
16779  /// reviewer's interleaving, with a FINITE producer.
16780  ///
16781  /// `fd471429` capped the retirement turns at
16782  /// `REJECTED_WORK_RETIREMENT_ATTEMPTS` and argued that exhausting the budget
16783  /// strands nothing, because the producer that drove it to exhaustion
16784  /// supplies the next continuation itself with its next ordinary submission.
16785  /// It does not, if it has just made its last one:
16786  ///
16787  ///   thread W (waker)                     | thread T (producer, budget 18)
16788  ///   -------------------------------------+---------------------------
16789  ///   ... turns 1..7 ...                   | ... C3..C16 ...
16790  ///   turn 8: publish D17 -----------------+--> schedule(C17)
16791  ///   host fails D17, sees C17 serviceable |
16792  ///   publish D18 (replacement) -----------+--> schedule(C18)  <- last one
16793  ///   host fails D18                       |    (producer now quiescent)
16794  ///   terminal #9: C18 -> rejected, reopen |
16795  ///   budget spent -> turns stop           |
16796  ///
16797  /// The host is healthy from its next delivery and stays registered, but no
16798  /// dispatch is outstanding, `queue.rejected` holds B and every C, and
16799  /// nothing publishes again. Measured on `fd471429`: 18 deliveries during the
16800  /// wake, `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle`
16801  /// pending, its future never dropped, its `GenerationWorkGuard` still
16802  /// active, and no delivery afterwards.
16803  ///
16804  /// The budget bounds what a producer can inflate; it may not swallow the
16805  /// continuation itself. After it, one final offer is made with the
16806  /// terminal-cancellation claim held, so nothing can interleave with it and
16807  /// it needs no budget of its own -- and this host accepts it. That host turn
16808  /// is the depth-0 stack that retires everything the wake moved aside.
16809  /// Nothing here supplies any other entry: no submission after the wake, no
16810  /// drain request, no unregistration, no shutdown.
16811  #[cfg(napi_runtime_os_threads)]
16812  #[test]
16813  fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16814    use std::{sync::mpsc, time::Duration};
16815
16816    const WAIT: Duration = Duration::from_secs(5);
16817    /// Every delivery the budget can pay for, and not one more: the producer's
16818    /// last runnable is queued inside the last of them and it then goes
16819    /// quiescent.
16820    const BUDGETED_DELIVERIES: usize =
16821      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16822
16823    let (done_tx, done_rx) = mpsc::channel();
16824    std::thread::spawn(move || {
16825      struct DropFlag(Arc<AtomicBool>);
16826      impl Drop for DropFlag {
16827        fn drop(&mut self) {
16828          self.0.store(true, Ordering::SeqCst);
16829        }
16830      }
16831
16832      let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16833      {
16834        let state = registry
16835          .state
16836          .lock()
16837          .unwrap_or_else(std::sync::PoisonError::into_inner);
16838        assert!(state.entries.is_empty());
16839        assert!(state.dispatches.is_empty());
16840      }
16841      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16842      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16843      let metrics = Arc::new(RuntimeMetrics::default());
16844      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16845        Arc::clone(&metrics),
16846        dispatch_final_retirement_offer_current_thread_tasks,
16847        registry,
16848      ));
16849      let backend =
16850        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16851
16852      let service = |index: usize| {
16853        let delivery = driver.deliveries()[index];
16854        let dispatch = registry
16855          .claim_delivery(delivery.capability())
16856          .expect("the accepted delivery must be claimable");
16857        let mut host_turn = executor
16858          .try_admit_host_turn(dispatch)
16859          .expect("the claimed delivery must be admitted");
16860        registry.mark_serviced(dispatch);
16861        host_turn.drive();
16862        drop(host_turn);
16863        let completion = registry.finish_delivery(delivery, false);
16864        assert!(completion.failed_dispatches.is_empty());
16865        assert!(!completion.redispatch);
16866      };
16867
16868      let dropped = Arc::new(AtomicBool::new(false));
16869      let flag = DropFlag(Arc::clone(&dropped));
16870      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16871      let shared = rx.map(|_| ()).boxed().shared();
16872      let shared_for_b = shared.clone();
16873      let work_registration = backend
16874        .work
16875        .try_register_async()
16876        .expect("the backend must accept work");
16877      let task_b = spawn_registered(
16878        &backend,
16879        Arc::clone(&metrics),
16880        async move {
16881          let _flag = flag;
16882          shared_for_b.await;
16883        },
16884        work_registration,
16885      );
16886      service(0);
16887      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16888      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16889
16890      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16891      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16892      let producer_executor = Arc::clone(&executor);
16893      let producer = std::thread::spawn(move || {
16894        let mut submissions = 0usize;
16895        while queue_one_rx.recv().is_ok() {
16896          let scheduler = Arc::clone(&producer_executor);
16897          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16898            scheduler.schedule(runnable);
16899          });
16900          task.detach();
16901          producer_executor.schedule(runnable);
16902          submissions += 1;
16903          if queued_tx.send(()).is_err() {
16904            break;
16905          }
16906        }
16907        submissions
16908      });
16909      // The producer feeds exactly the deliveries the budget can pay for, and
16910      // the host refuses exactly those: it is healthy from the next one.
16911      driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16912      driver
16913        .fail_budget
16914        .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16915
16916      let before = driver.deliveries().len();
16917      tx.send(()).expect("B must still hold its Shared clone");
16918      let during_wake = driver.deliveries().len() - before;
16919      let unspent = driver.disarm_producer();
16920      let submissions = producer.join().expect("the producer must not panic");
16921
16922      assert_eq!(
16923        during_wake,
16924        BUDGETED_DELIVERIES + 1,
16925        "the budget must be followed by the one offer a producer cannot \
16926         inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16927         deliveries and never asks the host that would have accepted"
16928      );
16929      assert_eq!(
16930        submissions, BUDGETED_DELIVERIES,
16931        "the producer must have queued a runnable during every budgeted \
16932         publication and gone quiescent before the final offer"
16933      );
16934      assert_eq!(
16935        unspent, 0,
16936        "the producer must have run dry exactly as the budget ran out"
16937      );
16938      assert_eq!(
16939        driver.fail_budget.load(Ordering::SeqCst),
16940        0,
16941        "the host must have refused every budgeted delivery and no more"
16942      );
16943      assert!(
16944        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16945        "the final offer must reopen the executor before the waker returns"
16946      );
16947      assert!(
16948        executor.has_rejected_work(),
16949        "nothing may be dropped on the waker's stack"
16950      );
16951      assert_ne!(
16952        executor.dispatch_pending.load(Ordering::Acquire),
16953        0,
16954        "the wake must leave a dispatch outstanding: that accepted delivery is \
16955         the continuation the parked work has"
16956      );
16957      let mut task_b = Box::pin(task_b);
16958      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16959      assert!(!dropped.load(Ordering::SeqCst));
16960      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16961
16962      // The ONLY continuation: the host serves the final offer. No submission,
16963      // no drain request, no unregistration, no shutdown.
16964      service(driver.deliveries().len() - 1);
16965      assert!(
16966        !executor.has_rejected_work(),
16967        "that host turn is a depth-0 stack and must retire everything the wake \
16968         moved aside"
16969      );
16970      assert!(
16971        poll_ready_within(&mut task_b, WAIT)
16972          .expect("the final offer's host turn must cancel B")
16973          .is_err()
16974      );
16975      assert!(
16976        dropped.load(Ordering::SeqCst),
16977        "B's future must be destroyed, on the host's stack and not the waker's"
16978      );
16979      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16980      assert_eq!(
16981        backend.work.state.lock().unwrap().active,
16982        0,
16983        "no GenerationWorkGuard may outlive the retirement"
16984      );
16985      drop(shared);
16986
16987      registry.unregister(registration);
16988      let state = registry
16989        .state
16990        .lock()
16991        .unwrap_or_else(std::sync::PoisonError::into_inner);
16992      assert!(state.entries.is_empty());
16993      assert!(state.dispatches.is_empty());
16994      done_tx.send(()).unwrap();
16995    });
16996    done_rx.recv_timeout(WAIT).expect(
16997      "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16998    );
16999  }
17000
17001  /// Regression test for the turn a REFUSED offer owes, with no producer at
17002  /// all.
17003  ///
17004  /// Until this commit the turns were driven by a thread-local flag that only
17005  /// a NESTED terminal transition set. An offer the host simply refused with
17006  /// an empty queue reserves no replacement, so it reached no transition and
17007  /// set no flag, and the owner frame returned after a single offer with seven
17008  /// attempts unspent -- the same stranded state the budget's exhaustion
17009  /// leaves, reached with nothing racing at all. Measured before: 3 deliveries
17010  /// during the wake, `dispatch_pending` 0, B parked in `queue.rejected` with
17011  /// its guard held, and a host that was healthy for its very next delivery
17012  /// never asked for one.
17013  ///
17014  /// What a turn owes is executor state, not a message: work parked and no
17015  /// dispatch outstanding. Reading that instead of a flag makes both routes
17016  /// one route, and this host -- which refuses the wake's publication, its
17017  /// replacement and the first offer, and accepts the second -- is served.
17018  #[cfg(napi_runtime_os_threads)]
17019  #[test]
17020  fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
17021    use std::{sync::mpsc, time::Duration};
17022
17023    const WAIT: Duration = Duration::from_secs(5);
17024    let (done_tx, done_rx) = mpsc::channel();
17025    std::thread::spawn(move || {
17026      struct DropFlag(Arc<AtomicBool>);
17027      impl Drop for DropFlag {
17028        fn drop(&mut self) {
17029          self.0.store(true, Ordering::SeqCst);
17030        }
17031      }
17032
17033      let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
17034      {
17035        let state = registry
17036          .state
17037          .lock()
17038          .unwrap_or_else(std::sync::PoisonError::into_inner);
17039        assert!(state.entries.is_empty());
17040        assert!(state.dispatches.is_empty());
17041      }
17042      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17043      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17044      let metrics = Arc::new(RuntimeMetrics::default());
17045      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17046        Arc::clone(&metrics),
17047        dispatch_retirement_retry_current_thread_tasks,
17048        registry,
17049      ));
17050      let backend =
17051        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17052
17053      let service = |index: usize| {
17054        let delivery = driver.deliveries()[index];
17055        let dispatch = registry
17056          .claim_delivery(delivery.capability())
17057          .expect("the accepted delivery must be claimable");
17058        let mut host_turn = executor
17059          .try_admit_host_turn(dispatch)
17060          .expect("the claimed delivery must be admitted");
17061        registry.mark_serviced(dispatch);
17062        host_turn.drive();
17063        drop(host_turn);
17064        let completion = registry.finish_delivery(delivery, false);
17065        assert!(completion.failed_dispatches.is_empty());
17066        assert!(!completion.redispatch);
17067      };
17068
17069      let dropped = Arc::new(AtomicBool::new(false));
17070      let flag = DropFlag(Arc::clone(&dropped));
17071      let (tx, rx) = futures::channel::oneshot::channel::<()>();
17072      let shared = rx.map(|_| ()).boxed().shared();
17073      let shared_for_b = shared.clone();
17074      let work_registration = backend
17075        .work
17076        .try_register_async()
17077        .expect("the backend must accept work");
17078      let task_b = spawn_registered(
17079        &backend,
17080        Arc::clone(&metrics),
17081        async move {
17082          let _flag = flag;
17083          shared_for_b.await;
17084        },
17085        work_registration,
17086      );
17087      service(0);
17088      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17089      assert_eq!(backend.work.state.lock().unwrap().active, 1);
17090
17091      // The wake's publication, its one replacement, and the first retirement
17092      // offer are lost. The host is healthy from the second offer on, and no
17093      // producer ever runs: every offer after the first is refused with an
17094      // empty queue, which reserves no replacement and reaches no transition.
17095      let before = driver.deliveries().len();
17096      driver.fail_budget.store(3, Ordering::SeqCst);
17097      tx.send(()).expect("B must still hold its Shared clone");
17098      let during_wake = driver.deliveries().len() - before;
17099
17100      assert_eq!(
17101        during_wake, 4,
17102        "a refused offer owes another turn just as a nested transition does: \
17103         work is parked and no dispatch is outstanding"
17104      );
17105      assert_ne!(
17106        executor.dispatch_pending.load(Ordering::Acquire),
17107        0,
17108        "the accepted second offer is the continuation the parked work has"
17109      );
17110      assert!(executor.has_rejected_work());
17111      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17112      let mut task_b = Box::pin(task_b);
17113      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17114      assert!(!dropped.load(Ordering::SeqCst));
17115
17116      // The ONLY continuation: the host serves the offer it accepted.
17117      service(driver.deliveries().len() - 1);
17118      assert!(!executor.has_rejected_work());
17119      assert!(
17120        poll_ready_within(&mut task_b, WAIT)
17121          .expect("the accepted offer's host turn must cancel B")
17122          .is_err()
17123      );
17124      assert!(dropped.load(Ordering::SeqCst));
17125      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17126      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17127      drop(shared);
17128
17129      registry.unregister(registration);
17130      let state = registry
17131        .state
17132        .lock()
17133        .unwrap_or_else(std::sync::PoisonError::into_inner);
17134      assert!(state.entries.is_empty());
17135      assert!(state.dispatches.is_empty());
17136      done_tx.send(()).unwrap();
17137    });
17138    done_rx
17139      .recv_timeout(WAIT)
17140      .expect("a refused retirement offer left B stranded with a healthy host");
17141  }
17142
17143  /// Regression test for the continuation the FINAL offer owes when the host
17144  /// services it from inside `dispatch`.
17145  ///
17146  /// `6cfe2002` makes one last offer after the attempt budget with the
17147  /// terminal-cancellation claim held, so nothing can inflate it, and then
17148  /// reopens and returns. A driver may claim, drive and acknowledge a delivery
17149  /// inside `dispatch` -- the registry supports that synchronous completion,
17150  /// and `RacingTaskDriver` in this module drives one inline -- and such a turn
17151  /// on THIS offer can do nothing at all:
17152  /// `retire_rejected_work_off_waker_stack` returns at nonzero schedule depth,
17153  /// and `take_runnable` returns `None` while `cancelling_failed_dispatch` is
17154  /// held. It only consumes `dispatch_pending`. The offer then returned with
17155  /// the executor reopened, no dispatch outstanding and `queue.rejected` still
17156  /// holding B. Measured before this commit: 11 deliveries during the wake,
17157  /// `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle` pending, its
17158  /// future never dropped, its `GenerationWorkGuard` still active -- and not
17159  /// one delivery afterwards, with the driver registered and healthy.
17160  ///
17161  /// An offer the host ACCEPTED but could not act on leaves exactly the state
17162  /// a refused one leaves, and the invariant is the one this branch already
17163  /// reads off the executor: while work is parked in `queue.rejected` and no
17164  /// dispatch is outstanding, this stack still owes a continuation. So the
17165  /// offer reports whether the host accepted it, and an accepted offer whose
17166  /// dispatch was consumed is followed by ONE ordinary publication -- the
17167  /// executor is open again, so that is the path `5857092f` reopened it for.
17168  /// It is one publication and not a loop, and a nested terminal transition
17169  /// inside it finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held and
17170  /// publishes nothing.
17171  ///
17172  /// Nothing here supplies any other entry: no submission after the wake, no
17173  /// drain request, no unregistration, no shutdown.
17174  #[cfg(napi_runtime_os_threads)]
17175  #[test]
17176  fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
17177    use std::{sync::mpsc, time::Duration};
17178
17179    const WAIT: Duration = Duration::from_secs(5);
17180    /// The wake's publication, its one replacement, and every budgeted turn.
17181    const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
17182
17183    let (done_tx, done_rx) = mpsc::channel();
17184    std::thread::spawn(move || {
17185      struct DropFlag(Arc<AtomicBool>);
17186      impl Drop for DropFlag {
17187        fn drop(&mut self) {
17188          self.0.store(true, Ordering::SeqCst);
17189        }
17190      }
17191
17192      let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
17193      {
17194        let state = registry
17195          .state
17196          .lock()
17197          .unwrap_or_else(std::sync::PoisonError::into_inner);
17198        assert!(state.entries.is_empty());
17199        assert!(state.dispatches.is_empty());
17200      }
17201      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17202      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17203      let metrics = Arc::new(RuntimeMetrics::default());
17204      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17205        Arc::clone(&metrics),
17206        dispatch_inline_final_offer_current_thread_tasks,
17207        registry,
17208      ));
17209      let backend =
17210        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17211
17212      let service = |index: usize| {
17213        let delivery = driver.deliveries()[index];
17214        let dispatch = registry
17215          .claim_delivery(delivery.capability())
17216          .expect("the accepted delivery must be claimable");
17217        let mut host_turn = executor
17218          .try_admit_host_turn(dispatch)
17219          .expect("the claimed delivery must be admitted");
17220        registry.mark_serviced(dispatch);
17221        host_turn.drive();
17222        drop(host_turn);
17223        let completion = registry.finish_delivery(delivery, false);
17224        assert!(completion.failed_dispatches.is_empty());
17225        assert!(!completion.redispatch);
17226      };
17227
17228      let dropped = Arc::new(AtomicBool::new(false));
17229      let flag = DropFlag(Arc::clone(&dropped));
17230      let (tx, rx) = futures::channel::oneshot::channel::<()>();
17231      let shared = rx.map(|_| ()).boxed().shared();
17232      let shared_for_b = shared.clone();
17233      let work_registration = backend
17234        .work
17235        .try_register_async()
17236        .expect("the backend must accept work");
17237      let task_b = spawn_registered(
17238        &backend,
17239        Arc::clone(&metrics),
17240        async move {
17241          let _flag = flag;
17242          shared_for_b.await;
17243        },
17244        work_registration,
17245      );
17246      service(0);
17247      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17248      assert_eq!(backend.work.state.lock().unwrap().active, 1);
17249
17250      // The wake's publication, its one replacement and every budgeted turn
17251      // are refused. The delivery right after them is the final offer, and the
17252      // host claims, drives and acknowledges THAT one from inside `dispatch`:
17253      // on the waker's stack, with the terminal-cancellation claim held.
17254      let before = driver.deliveries().len();
17255      let serviced_inline = Arc::new(AtomicBool::new(false));
17256      {
17257        let executor = Arc::clone(&executor);
17258        let hook_driver = Arc::clone(&driver);
17259        let serviced_inline = Arc::clone(&serviced_inline);
17260        driver.during_delivery(before + REFUSED_DELIVERIES, move || {
17261          let delivery = *hook_driver
17262            .deliveries()
17263            .last()
17264            .expect("the final offer must be recorded before the hook runs");
17265          let dispatch = registry
17266            .claim_delivery(delivery.capability())
17267            .expect("the final offer must be claimable");
17268          let mut host_turn = executor
17269            .try_admit_host_turn(dispatch)
17270            .expect("the claimed final offer must be admitted");
17271          registry.mark_serviced(dispatch);
17272          host_turn.drive();
17273          drop(host_turn);
17274          let completion = registry.finish_delivery(delivery, false);
17275          assert!(completion.failed_dispatches.is_empty());
17276          serviced_inline.store(true, Ordering::SeqCst);
17277        });
17278      }
17279      driver
17280        .fail_budget
17281        .store(REFUSED_DELIVERIES, Ordering::SeqCst);
17282      tx.send(()).expect("B must still hold its Shared clone");
17283      let during_wake = driver.deliveries().len() - before;
17284
17285      assert!(
17286        serviced_inline.load(Ordering::SeqCst),
17287        "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
17288      );
17289      assert_eq!(
17290        during_wake,
17291        REFUSED_DELIVERIES + 2,
17292        "an accepted offer whose host turn could not act owes the same continuation a refused \
17293         one owes: work parked and no dispatch outstanding"
17294      );
17295      assert_ne!(
17296        executor.dispatch_pending.load(Ordering::Acquire),
17297        0,
17298        "the publication that follows the consumed offer is the continuation the parked work has"
17299      );
17300      assert!(executor.has_rejected_work());
17301      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17302      let mut task_b = Box::pin(task_b);
17303      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17304      assert!(!dropped.load(Ordering::SeqCst));
17305
17306      // The ONLY continuation: the host serves it on a clean stack.
17307      service(driver.deliveries().len() - 1);
17308      assert!(!executor.has_rejected_work());
17309      assert!(
17310        poll_ready_within(&mut task_b, WAIT)
17311          .expect("the host turn that follows the consumed offer must cancel B")
17312          .is_err()
17313      );
17314      assert!(dropped.load(Ordering::SeqCst));
17315      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17316      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17317      drop(shared);
17318
17319      registry.unregister(registration);
17320      let state = registry
17321        .state
17322        .lock()
17323        .unwrap_or_else(std::sync::PoisonError::into_inner);
17324      assert!(state.entries.is_empty());
17325      assert!(state.dispatches.is_empty());
17326      done_tx.send(()).unwrap();
17327    });
17328    done_rx.recv_timeout(WAIT).expect(
17329      "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17330    );
17331  }
17332
17333  /// Regression test for the continuation an ordinary `spawn` owes itself.
17334  /// `async-task` runs one scheduler callback for two different things: a
17335  /// wake, which any `Waker` can raise from any stack, and the first schedule
17336  /// of a freshly submitted task, which `spawn` makes on its caller's own
17337  /// stack. Only the wake can arrive under a foreign lock. When the host fails
17338  /// a submission's publication and its one replacement synchronously, the
17339  /// terminal transition is that submission's ONLY continuation: it produced
17340  /// no host turn, `complete_current_thread_task_delivery` has already run,
17341  /// and nothing else will reach `retire_rejected_work`. Treating the frame as
17342  /// a waker's parked the runnable in `queue.rejected` with the driver
17343  /// registered and healthy again, so the `JoinHandle` stayed pending forever
17344  /// and its `GenerationWorkGuard` stayed active. Nothing here supplies a
17345  /// later entry: no drain request, no host turn, no unregistration, no
17346  /// shutdown.
17347  #[cfg(napi_runtime_os_threads)]
17348  #[test]
17349  fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17350    use std::time::Duration;
17351
17352    let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17353    {
17354      let state = registry
17355        .state
17356        .lock()
17357        .unwrap_or_else(std::sync::PoisonError::into_inner);
17358      assert!(state.entries.is_empty());
17359      assert!(state.dispatches.is_empty());
17360    }
17361    let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17362    let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17363    let metrics = Arc::new(RuntimeMetrics::default());
17364    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17365      Arc::clone(&metrics),
17366      dispatch_submission_failure_current_thread_tasks,
17367      registry,
17368    ));
17369    let backend =
17370      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17371
17372    // Every accepted delivery fails before `dispatch` returns, the way a
17373    // closing threadsafe function does.
17374    driver.fail.store(true, Ordering::SeqCst);
17375
17376    let work_registration = backend
17377      .work
17378      .try_register_async()
17379      .expect("the backend must accept work");
17380    let task = spawn_registered(
17381      &backend,
17382      Arc::clone(&metrics),
17383      std::future::pending::<()>(),
17384      work_registration,
17385    );
17386
17387    assert_eq!(
17388      driver.deliveries().len(),
17389      2,
17390      "the submission must publish the dispatch and exactly one replacement"
17391    );
17392    assert!(
17393      !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17394      "the terminal failure must reopen the executor"
17395    );
17396    assert!(
17397      !executor.has_rejected_work(),
17398      "a submission's own stack is not a waker's: the rejected work must be        cancelled here, because nothing else will"
17399    );
17400    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17401    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17402
17403    let mut task = Box::pin(task);
17404    assert_eq!(
17405      poll_ready_within(&mut task, Duration::ZERO)
17406        .expect("the submission must settle without any later entry")
17407        .unwrap_err()
17408        .to_string(),
17409      "async runtime stopped before the task completed",
17410      "terminal host failure must settle the submitted operation as cancelled"
17411    );
17412
17413    registry.unregister(registration);
17414    let state = registry
17415      .state
17416      .lock()
17417      .unwrap_or_else(std::sync::PoisonError::into_inner);
17418    assert!(state.entries.is_empty());
17419    assert!(state.dispatches.is_empty());
17420  }
17421
17422  /// MultiThread twin of
17423  /// `current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline`.
17424  /// The same wake is in flight when shutdown starts: B's schedule closure
17425  /// runs inside `futures::Shared`'s notifier, under its `wakers` mutex, and
17426  /// B's future owns a clone of that `Shared`. `MultiThreadExecutor::schedule`
17427  /// has no closed-queue branch: the late runnable is pushed to the injector
17428  /// (or the worker's LIFO slot) exactly like any other, and the drainer that
17429  /// claims it under the published stop cancels it on its own stack
17430  /// (`claim_runnable` -> `RunnableClaim::Cancel` -> `cancel_runnable`). That
17431  /// stack never holds `wakers`, so the drop only waits for the waker thread
17432  /// to leave the notifier. This pins that shape: a closed-queue rejection
17433  /// added to `schedule` that dropped the runnable inline would deadlock
17434  /// here, exactly as the CurrentThread executor did.
17435  #[cfg(napi_runtime_os_threads)]
17436  #[test]
17437  fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17438    use std::{
17439      sync::{Barrier, mpsc},
17440      time::Duration,
17441    };
17442
17443    let (done_tx, done_rx) = mpsc::channel();
17444    std::thread::spawn(move || {
17445      let metrics = Arc::new(RuntimeMetrics::default());
17446      let options = RuntimeOptions {
17447        flavor: RuntimeFlavor::MultiThread,
17448        worker_threads: 2,
17449        max_blocking_tasks: 1,
17450        thread_name_prefix: "mt-inline-drop".to_string(),
17451        park_deadline: None,
17452        drain_linger: DEFAULT_DRAIN_LINGER,
17453      };
17454      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17455      let backend =
17456        RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17457      let generation = backend.generation();
17458
17459      // Task A: registered like production work; its JoinHandle is shared.
17460      let (abort_a, guard_a) = backend
17461        .work
17462        .try_register_async()
17463        .expect("the backend must accept work");
17464      let scheduler_a = Arc::clone(&executor);
17465      let (runnable_a, task_a) = async_task::spawn(
17466        RegisteredTaskFuture::new(
17467          Abortable::new(std::future::pending::<()>(), abort_a),
17468          guard_a,
17469          generation,
17470        ),
17471        move |runnable| scheduler_a.schedule(runnable),
17472      );
17473      let shared = async move { task_a.fallible().await.is_some() }
17474        .boxed()
17475        .shared();
17476      let shared_for_b = shared.clone();
17477
17478      // Task B: registered and abortable too, awaiting a clone of that Shared.
17479      // Its schedule closure pauses before it reaches the executor, exactly
17480      // where a waker thread may be descheduled while shutdown runs.
17481      let (abort_b, guard_b) = backend
17482        .work
17483        .try_register_async()
17484        .expect("the backend must accept work");
17485      let wake_in_flight = Arc::new(Barrier::new(2));
17486      let wake_release = Arc::new(Barrier::new(2));
17487      let scheduler_b = Arc::clone(&executor);
17488      let (runnable_b, task_b) = async_task::spawn(
17489        RegisteredTaskFuture::new(
17490          Abortable::new(
17491            async move {
17492              shared_for_b.await;
17493            },
17494            abort_b,
17495          ),
17496          guard_b,
17497          generation,
17498        ),
17499        {
17500          let wake_in_flight = Arc::clone(&wake_in_flight);
17501          let wake_release = Arc::clone(&wake_release);
17502          move |runnable| {
17503            wake_in_flight.wait();
17504            wake_release.wait();
17505            scheduler_b.schedule(runnable);
17506          }
17507        },
17508      );
17509      // The first poll runs on a pool drainer; wait until B has parked on the
17510      // Shared and left the drainer, so the wake below is a real re-schedule
17511      // from the notifier rather than a wake-while-running that the drainer
17512      // would re-publish from its own stack.
17513      executor.schedule(runnable_b);
17514      wait_until("B parked after its first poll", || {
17515        metrics.runnable_polls.load(Ordering::SeqCst) == 1
17516          && metrics.active_runnables.load(Ordering::SeqCst) == 0
17517          && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17518      });
17519
17520      // Cancel A on another thread: closing A notifies its awaiter, and the
17521      // Shared notifier wakes B while holding `wakers`. B's schedule closure
17522      // then parks at the barrier with that lock still held.
17523      let waker_thread = std::thread::spawn(move || drop(runnable_a));
17524      wake_in_flight.wait();
17525
17526      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
17527      // the abort wake is a no-op; the stop is then published.
17528      backend.begin_shutdown();
17529      wake_release.wait();
17530      backend.wait_until_idle();
17531
17532      waker_thread
17533        .join()
17534        .expect("the waker thread must not panic");
17535      assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17536      assert!(
17537        futures::executor::block_on(task_b.fallible()).is_none(),
17538        "B's late wake must still cancel B"
17539      );
17540      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17541      drop(shared);
17542      done_tx.send(()).unwrap();
17543    });
17544    done_rx
17545      .recv_timeout(Duration::from_secs(5))
17546      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17547  }
17548
17549  #[cfg(napi_runtime_os_threads)]
17550  #[test]
17551  fn current_thread_recovery_reservation_blocks_new_host_publication() {
17552    use std::sync::mpsc;
17553
17554    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17555    let metrics = Arc::new(RuntimeMetrics::default());
17556    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17557      Arc::clone(&metrics),
17558      count_current_thread_interleaved_recovery_host_dispatch,
17559    ));
17560    let backend =
17561      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17562
17563    let registration = backend
17564      .work
17565      .try_register_async()
17566      .expect("the backend must accept work");
17567    let task = spawn_registered(
17568      &backend,
17569      Arc::clone(&metrics),
17570      std::future::pending::<()>(),
17571      registration,
17572    );
17573    let failed_dispatch = current_thread_dispatch(&executor);
17574    assert_eq!(
17575      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17576      1
17577    );
17578
17579    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17580    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17581    let cancelling_executor = Arc::clone(&executor);
17582    let cancellation = std::thread::spawn(move || {
17583      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17584        *slot.borrow_mut() = Some(Box::new(move || {
17585          recovery_cleared_tx.send(()).unwrap();
17586          release_recovery_rx.recv().unwrap();
17587        }));
17588      });
17589      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17590    });
17591    recovery_cleared_rx
17592      .recv_timeout(Duration::from_secs(2))
17593      .expect("failed dispatch cancellation must pause after consuming the old capability");
17594
17595    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17596    let (publication_done_tx, publication_done_rx) = mpsc::channel();
17597    let publishing_executor = Arc::clone(&executor);
17598    let publication = std::thread::spawn(move || {
17599      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17600        *slot.borrow_mut() = Some(Box::new(move || {
17601          publication_blocked_tx.send(()).unwrap();
17602        }));
17603      });
17604      publishing_executor.request_drain_if_queued();
17605      publication_done_tx.send(()).unwrap();
17606    });
17607
17608    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17609      release_recovery_tx.send(()).unwrap();
17610      cancellation.join().unwrap();
17611      publication.join().unwrap();
17612      panic!("a newly registered host must wait for recovery reservation: {error}");
17613    }
17614    assert!(
17615      matches!(
17616        publication_done_rx.try_recv(),
17617        Err(mpsc::TryRecvError::Empty)
17618      ),
17619      "the new host must not publish in the consumed-to-recovery gap"
17620    );
17621    release_recovery_tx.send(()).unwrap();
17622
17623    cancellation.join().unwrap();
17624    publication_done_rx
17625      .recv_timeout(Duration::from_secs(2))
17626      .unwrap();
17627    publication.join().unwrap();
17628
17629    let replacement_dispatch = current_thread_dispatch(&executor);
17630    assert_ne!(replacement_dispatch, failed_dispatch);
17631    assert_eq!(
17632      executor.recovery_dispatch.load(Ordering::Acquire),
17633      replacement_dispatch
17634    );
17635    assert_eq!(
17636      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17637      3,
17638      "the original publication and two host attempts for its one replacement must run"
17639    );
17640
17641    executor.cancel_host_dispatch_for_test(replacement_dispatch);
17642
17643    assert_eq!(
17644      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17645      3,
17646      "failure of the tagged replacement must cancel work without a second replacement"
17647    );
17648    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17649    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17650    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17651    assert_eq!(
17652      futures::executor::block_on(task).unwrap_err().to_string(),
17653      "async runtime stopped before the task completed"
17654    );
17655
17656    let later_registration = backend
17657      .work
17658      .try_register_async()
17659      .expect("terminal cancellation must reopen acceptance");
17660    let later_task = spawn_registered(
17661      &backend,
17662      Arc::clone(&metrics),
17663      async { 2usize },
17664      later_registration,
17665    );
17666    assert_eq!(
17667      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17668      4
17669    );
17670    executor.drive_host_turn();
17671    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17672  }
17673
17674  #[cfg(napi_runtime_os_threads)]
17675  #[test]
17676  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17677    use std::sync::mpsc;
17678
17679    struct BlockingDrop {
17680      entered: mpsc::Sender<()>,
17681      release: mpsc::Receiver<()>,
17682    }
17683
17684    impl Drop for BlockingDrop {
17685      fn drop(&mut self) {
17686        self.entered.send(()).unwrap();
17687        self.release.recv().unwrap();
17688      }
17689    }
17690
17691    reset_current_thread_recovery_host_dispatches();
17692    let metrics = Arc::new(RuntimeMetrics::default());
17693    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17694      Arc::clone(&metrics),
17695      count_current_thread_recovery_host_dispatch,
17696    ));
17697    let backend =
17698      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17699
17700    let holder_registration = backend
17701      .work
17702      .try_register_work()
17703      .expect("the backend must accept the blocking holder");
17704    let (holder_started_tx, holder_started_rx) = mpsc::channel();
17705    let (release_holder_tx, release_holder_rx) = mpsc::channel();
17706    let holder_executor = Arc::clone(&executor);
17707    let holder = std::thread::spawn(move || {
17708      let handle = holder_executor.schedule_blocking(
17709        move || {
17710          holder_started_tx.send(()).unwrap();
17711          release_holder_rx.recv().unwrap();
17712        },
17713        holder_registration,
17714      );
17715      futures::executor::block_on(handle).unwrap();
17716    });
17717    holder_started_rx
17718      .recv_timeout(Duration::from_secs(2))
17719      .expect("the first blocking call must hold the CurrentThread lane");
17720
17721    let cancelled_registration = backend
17722      .work
17723      .try_register_work()
17724      .expect("the backend must accept queued blocking work");
17725    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17726    let (release_drop_tx, release_drop_rx) = mpsc::channel();
17727    let blocking_drop = BlockingDrop {
17728      entered: drop_entered_tx,
17729      release: release_drop_rx,
17730    };
17731    let cancelled = executor.schedule_blocking(
17732      move || {
17733        let _blocking_drop = blocking_drop;
17734      },
17735      cancelled_registration,
17736    );
17737
17738    let async_registration = backend
17739      .work
17740      .try_register_async()
17741      .expect("the backend must accept queued async work");
17742    let pending = spawn_registered(
17743      &backend,
17744      Arc::clone(&metrics),
17745      std::future::pending::<()>(),
17746      async_registration,
17747    );
17748    let failed_dispatch = current_thread_dispatch(&executor);
17749    executor.cancel_host_dispatch_for_test(failed_dispatch);
17750    let replacement_dispatch = current_thread_dispatch(&executor);
17751
17752    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17753    let cancelling_executor = Arc::clone(&executor);
17754    let cancellation = std::thread::spawn(move || {
17755      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17756      cancel_done_tx.send(()).unwrap();
17757    });
17758    drop_entered_rx
17759      .recv_timeout(Duration::from_secs(2))
17760      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17761    let active_during_drop = backend.work.state.lock().unwrap().active;
17762    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17763
17764    release_drop_tx.send(()).unwrap();
17765    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17766    cancellation.join().unwrap();
17767
17768    assert_eq!(
17769      active_during_drop, 2,
17770      "the blocking capture's work registration must remain active until its destructor returns"
17771    );
17772    assert!(
17773      cancellation_blocked,
17774      "terminal cancellation must wait for capture destruction"
17775    );
17776    assert!(futures::executor::block_on(cancelled).is_err());
17777    assert!(futures::executor::block_on(pending).is_err());
17778    assert_eq!(backend.work.state.lock().unwrap().active, 1);
17779
17780    release_holder_tx.send(()).unwrap();
17781    holder.join().unwrap();
17782    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17783  }
17784
17785  #[test]
17786  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17787    reset_current_thread_one_shot_host_dispatches();
17788    let metrics = Arc::new(RuntimeMetrics::default());
17789    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17790      Arc::clone(&metrics),
17791      accept_only_first_current_thread_host_dispatch,
17792    ));
17793
17794    let scheduler = Arc::clone(&executor);
17795    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17796      scheduler.schedule(runnable)
17797    });
17798    executor.schedule(runnable);
17799    let failed_dispatch = current_thread_dispatch(&executor);
17800
17801    executor.cancel_host_dispatch_for_test(failed_dispatch);
17802
17803    assert_eq!(
17804      current_thread_one_shot_host_dispatches(),
17805      2,
17806      "the original and its one bounded replacement must be attempted"
17807    );
17808    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17809    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17810    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17811    assert!(
17812      futures::executor::block_on(task.fallible()).is_none(),
17813      "an unavailable replacement host must settle the original task as cancelled"
17814    );
17815  }
17816
17817  enum TestCurrentThreadTaskDriverBehavior {
17818    Healthy,
17819    PanicInLivenessAndSweep,
17820    PanicInDispatchAndSweep,
17821  }
17822
17823  struct TestCurrentThreadTaskDriver {
17824    behavior: TestCurrentThreadTaskDriverBehavior,
17825    dispatched: AtomicBool,
17826  }
17827
17828  #[derive(Default)]
17829  struct ManualCurrentThreadTaskDriver {
17830    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17831  }
17832
17833  struct FailDuringAuthoritativePanicPublication;
17834  struct FailDuringLateRegistrationPublication;
17835  struct FailDuringNestedUnwindPublication;
17836  struct FailDuringReplacementRepublishPublication;
17837  struct FirstHostileCurrentThreadPanic;
17838  struct LaterHostileCurrentThreadPanic;
17839
17840  impl Drop for FirstHostileCurrentThreadPanic {
17841    fn drop(&mut self) {
17842      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17843      panic!("the first retained CurrentThread panic payload was dropped");
17844    }
17845  }
17846
17847  impl Drop for LaterHostileCurrentThreadPanic {
17848    fn drop(&mut self) {
17849      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17850      panic!("a later CurrentThread panic payload escaped containment");
17851    }
17852  }
17853
17854  impl ManualCurrentThreadTaskDriver {
17855    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17856      self.deliveries.lock().unwrap().clone()
17857    }
17858  }
17859
17860  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17861    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17862      self.deliveries.lock().unwrap().push(delivery);
17863      true
17864    }
17865  }
17866
17867  /// Accepts every delivery; once `fail` is set it also fails the accepted
17868  /// delivery before `dispatch` returns, from a helper thread it joins, the
17869  /// way a host whose env is tearing down reports a threadsafe-function call
17870  /// that cannot land.
17871  struct SynchronouslyFailingCurrentThreadTaskDriver {
17872    registry: &'static CurrentThreadTaskDriverRegistry,
17873    fail: AtomicBool,
17874    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17875  }
17876
17877  impl SynchronouslyFailingCurrentThreadTaskDriver {
17878    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17879      Self {
17880        registry,
17881        fail: AtomicBool::new(false),
17882        deliveries: Mutex::default(),
17883      }
17884    }
17885
17886    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17887      self.deliveries.lock().unwrap().clone()
17888    }
17889  }
17890
17891  impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17892    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17893      self.deliveries.lock().unwrap().push(delivery);
17894      if self.fail.load(Ordering::SeqCst) {
17895        let registry = self.registry;
17896        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17897          .join()
17898          .expect("the failing helper must not panic");
17899        assert!(
17900          completion.failed_dispatches.is_empty(),
17901          "the failed delivery must remain provisional until its publication retires"
17902        );
17903      }
17904      true
17905    }
17906  }
17907
17908  /// Accepts every delivery, and fails the first `fail_budget` of them before
17909  /// `dispatch` returns -- from a helper thread it joins, exactly like
17910  /// `SynchronouslyFailingCurrentThreadTaskDriver`. Once the budget is spent
17911  /// the host is healthy again, so the retirement turn a waker-stack terminal
17912  /// failure publishes for itself is accepted instead of lost.
17913  /// A one-shot the test runs from inside a chosen delivery; see
17914  /// `BoundedFailureCurrentThreadTaskDriver::during_delivery`.
17915  type DeliveryHook = Box<dyn FnOnce() + Send>;
17916
17917  struct BoundedFailureCurrentThreadTaskDriver {
17918    registry: &'static CurrentThreadTaskDriverRegistry,
17919    fail_budget: AtomicUsize,
17920    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17921    during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17922  }
17923
17924  impl BoundedFailureCurrentThreadTaskDriver {
17925    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17926      Self {
17927        registry,
17928        fail_budget: AtomicUsize::new(0),
17929        deliveries: Mutex::default(),
17930        during_delivery: Mutex::default(),
17931      }
17932    }
17933
17934    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17935      self.deliveries.lock().unwrap().clone()
17936    }
17937
17938    /// Run `hook` from inside the `index`-th delivery (0-based over every
17939    /// delivery this driver receives), before that delivery is failed. This is
17940    /// the window another thread needs to queue work while a publication is in
17941    /// flight.
17942    fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17943      *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17944    }
17945  }
17946
17947  impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17948    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17949      let index = {
17950        let mut deliveries = self.deliveries.lock().unwrap();
17951        deliveries.push(delivery);
17952        deliveries.len() - 1
17953      };
17954      let hook = {
17955        let mut slot = self.during_delivery.lock().unwrap();
17956        if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17957          slot.take().map(|(_, hook)| hook)
17958        } else {
17959          None
17960        }
17961      };
17962      if let Some(hook) = hook {
17963        hook();
17964      }
17965      let budget = self.fail_budget.load(Ordering::SeqCst);
17966      if budget != 0 {
17967        self.fail_budget.store(budget - 1, Ordering::SeqCst);
17968        let registry = self.registry;
17969        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17970          .join()
17971          .expect("the failing helper must not panic");
17972        assert!(
17973          completion.failed_dispatches.is_empty(),
17974          "the failed delivery must remain provisional until its publication retires"
17975        );
17976      }
17977      true
17978    }
17979  }
17980
17981  /// Accepts every delivery and, while `fail` is set, fails it synchronously
17982  /// from inside `dispatch` on the calling thread -- the option
17983  /// [`CurrentThreadTaskDriver::dispatch`] documents for an SPI host. While a
17984  /// producer is armed it also rendezvouses with the producer thread from
17985  /// inside every delivery, so exactly one runnable is queued while each
17986  /// publication is in flight.
17987  struct SustainedProducerGate {
17988    remaining: usize,
17989    queue_one: std::sync::mpsc::Sender<()>,
17990    queued: std::sync::mpsc::Receiver<()>,
17991  }
17992
17993  struct SustainedProducerCurrentThreadTaskDriver {
17994    registry: &'static CurrentThreadTaskDriverRegistry,
17995    /// Deliveries still to be failed: `usize::MAX` fails every one of them,
17996    /// `n` fails the next `n` and is healthy after, `0` is healthy.
17997    fail_budget: AtomicUsize,
17998    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17999    producer: Mutex<Option<SustainedProducerGate>>,
18000  }
18001
18002  impl SustainedProducerCurrentThreadTaskDriver {
18003    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
18004      Self {
18005        registry,
18006        fail_budget: AtomicUsize::new(0),
18007        deliveries: Mutex::default(),
18008        producer: Mutex::default(),
18009      }
18010    }
18011
18012    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
18013      self.deliveries.lock().unwrap().clone()
18014    }
18015
18016    fn arm_producer(
18017      &self,
18018      remaining: usize,
18019      queue_one: std::sync::mpsc::Sender<()>,
18020      queued: std::sync::mpsc::Receiver<()>,
18021    ) {
18022      *self.producer.lock().unwrap() = Some(SustainedProducerGate {
18023        remaining,
18024        queue_one,
18025        queued,
18026      });
18027    }
18028
18029    /// Close the gate and report how many submissions the producer was still
18030    /// willing to make.
18031    fn disarm_producer(&self) -> usize {
18032      self
18033        .producer
18034        .lock()
18035        .unwrap()
18036        .take()
18037        .map_or(0, |gate| gate.remaining)
18038    }
18039  }
18040
18041  impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
18042    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18043      self.deliveries.lock().unwrap().push(delivery);
18044      {
18045        let mut slot = self.producer.lock().unwrap();
18046        if let Some(gate) = slot.as_mut()
18047          && gate.remaining != 0
18048        {
18049          gate.remaining -= 1;
18050          gate.queue_one.send(()).expect("the producer must be live");
18051          gate
18052            .queued
18053            .recv_timeout(std::time::Duration::from_secs(5))
18054            .expect("the producer must queue one runnable per publication");
18055        }
18056      }
18057      let budget = self.fail_budget.load(Ordering::SeqCst);
18058      if budget != 0 {
18059        if budget != usize::MAX {
18060          self.fail_budget.store(budget - 1, Ordering::SeqCst);
18061        }
18062        let completion = self.registry.finish_delivery(delivery, true);
18063        assert!(
18064          completion.failed_dispatches.is_empty(),
18065          "the failed delivery must remain provisional until its publication retires"
18066        );
18067      }
18068      true
18069    }
18070  }
18071
18072  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
18073    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18074      let completion =
18075        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
18076      assert!(
18077        completion.failed_dispatches.is_empty(),
18078        "the failed delivery must remain provisional until its publication retires"
18079      );
18080      true
18081    }
18082  }
18083
18084  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
18085    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18086      let completion =
18087        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
18088      assert!(
18089        completion.failed_dispatches.is_empty(),
18090        "the failed delivery must remain provisional until its publication retires"
18091      );
18092      true
18093    }
18094  }
18095
18096  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
18097    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18098      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
18099      assert!(
18100        completion.failed_dispatches.is_empty(),
18101        "the failed delivery must remain provisional until its publication retires"
18102      );
18103      true
18104    }
18105  }
18106
18107  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
18108    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18109      let completion =
18110        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
18111      assert!(
18112        completion.failed_dispatches.is_empty(),
18113        "the failed delivery must remain provisional until its publication retires"
18114      );
18115      true
18116    }
18117  }
18118
18119  struct ReentrantCurrentThreadTaskDriver {
18120    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18121    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
18122  }
18123
18124  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
18125    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18126      true
18127    }
18128
18129    fn is_live(&self) -> bool {
18130      let registration = *self.registration.lock().unwrap();
18131      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
18132        registry.unregister(id);
18133      }
18134      false
18135    }
18136  }
18137
18138  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
18139    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18140    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18141  }
18142
18143  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
18144    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18145      true
18146    }
18147
18148    fn is_live(&self) -> bool {
18149      if let Some(registry) = self.registry.upgrade() {
18150        for registration in self.registrations.lock().unwrap().iter().copied() {
18151          let completion = registry.unregister(registration);
18152          assert!(completion.failed_dispatches.is_empty());
18153          assert!(!completion.redispatch);
18154        }
18155      }
18156      false
18157    }
18158  }
18159
18160  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18161    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18162    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18163    drops: Arc<AtomicUsize>,
18164  }
18165
18166  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18167    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18168      if let Some(registry) = self.registry.upgrade() {
18169        for registration in self.registrations.lock().unwrap().iter().copied() {
18170          let completion = registry.unregister(registration);
18171          assert!(completion.failed_dispatches.is_empty());
18172          assert!(!completion.redispatch);
18173        }
18174      }
18175      true
18176    }
18177  }
18178
18179  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18180    fn drop(&mut self) {
18181      self.drops.fetch_add(1, Ordering::SeqCst);
18182      panic!("intentional reentrant CurrentThread task-driver destructor panic");
18183    }
18184  }
18185
18186  struct PanickingDropCurrentThreadTaskDriver {
18187    drops: Arc<AtomicUsize>,
18188  }
18189
18190  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
18191    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18192      true
18193    }
18194  }
18195
18196  impl Drop for PanickingDropCurrentThreadTaskDriver {
18197    fn drop(&mut self) {
18198      self.drops.fetch_add(1, Ordering::SeqCst);
18199      panic!("intentional CurrentThread task-driver destructor panic");
18200    }
18201  }
18202
18203  struct ReentrantCurrentThreadTaskDriverDrop {
18204    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18205    fallback: CurrentThreadTaskDriverId,
18206  }
18207
18208  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
18209    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18210      true
18211    }
18212  }
18213
18214  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
18215    fn drop(&mut self) {
18216      if let Some(registry) = self.registry.upgrade() {
18217        registry.unregister(self.fallback);
18218      }
18219    }
18220  }
18221
18222  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
18223    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18224      assert!(
18225        !matches!(
18226          self.behavior,
18227          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
18228        ),
18229        "intentional task-driver dispatch panic"
18230      );
18231      self.dispatched.store(true, Ordering::SeqCst);
18232      true
18233    }
18234
18235    fn is_live(&self) -> bool {
18236      assert!(
18237        !matches!(
18238          self.behavior,
18239          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
18240        ),
18241        "intentional task-driver liveness panic"
18242      );
18243      true
18244    }
18245
18246    fn on_swept(&self) {
18247      assert!(
18248        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
18249        "intentional task-driver sweep panic"
18250      );
18251    }
18252  }
18253
18254  #[cfg(panic = "unwind")]
18255  #[test]
18256  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
18257    let registry = CurrentThreadTaskDriverRegistry::default();
18258    let fallback = Arc::new(TestCurrentThreadTaskDriver {
18259      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18260      dispatched: AtomicBool::new(false),
18261    });
18262    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18263    registry.register(Arc::new(TestCurrentThreadTaskDriver {
18264      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
18265      dispatched: AtomicBool::new(false),
18266    }));
18267    registry.register(Arc::new(TestCurrentThreadTaskDriver {
18268      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
18269      dispatched: AtomicBool::new(false),
18270    }));
18271
18272    assert_eq!(
18273      registry.dispatch(37),
18274      CurrentThreadHostDispatchResult::Accepted,
18275      "a panicking newest host must fall back to a live host"
18276    );
18277    assert!(fallback.dispatched.load(Ordering::SeqCst));
18278  }
18279
18280  #[cfg(panic = "unwind")]
18281  #[test]
18282  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
18283    let registry = CurrentThreadTaskDriverRegistry::default();
18284    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
18285    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
18286    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18287    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18288    registry
18289      .next_delivery
18290      .store(u64::MAX - 1, Ordering::Relaxed);
18291
18292    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
18293    assert!(
18294      publication.is_err(),
18295      "delivery-id exhaustion on the second host must unwind the publication"
18296    );
18297    {
18298      let state = registry
18299        .state
18300        .lock()
18301        .unwrap_or_else(std::sync::PoisonError::into_inner);
18302      assert!(
18303        !state.dispatches.contains_key(&38),
18304        "unwind cleanup must release the exact registry publication barrier"
18305      );
18306      assert!(
18307        state
18308          .entries
18309          .values()
18310          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18311        "failed transactional reservation must leave every physical host slot clean"
18312      );
18313    }
18314    assert!(first.deliveries().is_empty());
18315    assert!(second.deliveries().is_empty());
18316
18317    registry.next_delivery.store(0, Ordering::Relaxed);
18318    assert_eq!(
18319      registry.dispatch(38),
18320      CurrentThreadHostDispatchResult::Accepted
18321    );
18322    let first_delivery = first.deliveries()[0];
18323    let second_delivery = second.deliveries()[0];
18324    assert_eq!(
18325      registry.claim_delivery(first_delivery.capability()),
18326      Some(38)
18327    );
18328    assert_eq!(
18329      registry.claim_delivery(second_delivery.capability()),
18330      Some(38)
18331    );
18332    registry.mark_serviced(38);
18333    assert!(
18334      registry
18335        .finish_delivery(first_delivery, false)
18336        .failed_dispatches
18337        .is_empty()
18338    );
18339    assert!(
18340      registry
18341        .finish_delivery(second_delivery, false)
18342        .failed_dispatches
18343        .is_empty()
18344    );
18345
18346    assert_eq!(
18347      registry.dispatch(39),
18348      CurrentThreadHostDispatchResult::Accepted
18349    );
18350    let first_later = first.deliveries()[1];
18351    let second_later = second.deliveries()[1];
18352    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18353    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18354    registry.mark_serviced(39);
18355    assert!(
18356      registry
18357        .finish_delivery(first_later, false)
18358        .failed_dispatches
18359        .is_empty()
18360    );
18361    assert!(
18362      registry
18363        .finish_delivery(second_later, false)
18364        .failed_dispatches
18365        .is_empty()
18366    );
18367    assert!(
18368      registry
18369        .state
18370        .lock()
18371        .unwrap_or_else(std::sync::PoisonError::into_inner)
18372        .dispatches
18373        .is_empty()
18374    );
18375    let state = registry
18376      .state
18377      .lock()
18378      .unwrap_or_else(std::sync::PoisonError::into_inner);
18379    assert!(
18380      state
18381        .entries
18382        .values()
18383        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18384    );
18385  }
18386
18387  #[test]
18388  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18389    let registry = CurrentThreadTaskDriverRegistry::default();
18390    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18391    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18392    let dispatch = 40;
18393
18394    assert_eq!(
18395      registry.dispatch(dispatch),
18396      CurrentThreadHostDispatchResult::Accepted
18397    );
18398    let failed_delivery = failed.deliveries()[0];
18399    let failure = current_thread_dispatch_failure(dispatch, 0);
18400    assert_eq!(
18401      registry
18402        .finish_delivery(failed_delivery, true)
18403        .failed_dispatches,
18404      vec![failure]
18405    );
18406    registry.unregister(failed_id);
18407
18408    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18409    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18410    assert!(
18411      registry.rearm_failed_publication(failure),
18412      "a closed unreferenced dispatch must reopen for its armed publication owner"
18413    );
18414    assert_eq!(
18415      registry.dispatch(dispatch),
18416      CurrentThreadHostDispatchResult::Accepted
18417    );
18418    let replacement_delivery = replacement.deliveries()[0];
18419    assert_eq!(replacement_delivery.dispatch, dispatch);
18420    assert_eq!(
18421      registry.claim_delivery(replacement_delivery.capability()),
18422      Some(dispatch)
18423    );
18424    registry.mark_serviced(dispatch);
18425    assert!(
18426      registry
18427        .finish_delivery(replacement_delivery, false)
18428        .failed_dispatches
18429        .is_empty()
18430    );
18431    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18432  }
18433
18434  #[test]
18435  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18436    struct RacingTaskDriver {
18437      executor: Arc<CurrentThreadExecutor>,
18438      drives: bool,
18439      dispatches: Mutex<Vec<u64>>,
18440    }
18441
18442    impl CurrentThreadTaskDriver for RacingTaskDriver {
18443      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18444        self.dispatches.lock().unwrap().push(delivery.dispatch);
18445        if self.drives {
18446          self
18447            .executor
18448            .drive_host_turn_for_dispatch(delivery.dispatch);
18449        }
18450        true
18451      }
18452    }
18453
18454    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18455      Arc::new(RuntimeMetrics::default()),
18456      accept_current_thread_host_dispatch,
18457    ));
18458    let polls = Arc::new(AtomicUsize::new(0));
18459    let task_polls = Arc::clone(&polls);
18460    let scheduler = Arc::clone(&executor);
18461    let (runnable, task) = async_task::spawn(
18462      async move {
18463        task_polls.fetch_add(1, Ordering::SeqCst);
18464      },
18465      move |runnable| scheduler.schedule(runnable),
18466    );
18467    executor.schedule(runnable);
18468    let dispatch = current_thread_dispatch(&executor);
18469
18470    let registry = CurrentThreadTaskDriverRegistry::default();
18471    let responsive = Arc::new(RacingTaskDriver {
18472      executor: Arc::clone(&executor),
18473      drives: true,
18474      dispatches: Mutex::new(Vec::new()),
18475    });
18476    let starved = Arc::new(RacingTaskDriver {
18477      executor: Arc::clone(&executor),
18478      drives: false,
18479      dispatches: Mutex::new(Vec::new()),
18480    });
18481    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18482    // The newest host is intentionally live but unresponsive.
18483    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18484
18485    assert_eq!(
18486      registry.dispatch(dispatch),
18487      CurrentThreadHostDispatchResult::Accepted
18488    );
18489    assert_eq!(
18490      responsive.dispatches.lock().unwrap().as_slice(),
18491      &[dispatch]
18492    );
18493    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18494    assert_eq!(
18495      polls.load(Ordering::SeqCst),
18496      1,
18497      "the responsive older host must win even when the newest host is starved"
18498    );
18499
18500    // The delayed callback from the starved host carries the same consumed
18501    // capability and cannot poll the task a second time.
18502    executor.drive_host_turn_for_dispatch(dispatch);
18503    assert_eq!(polls.load(Ordering::SeqCst), 1);
18504    futures::executor::block_on(task);
18505  }
18506
18507  #[test]
18508  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18509    const HOSTS: usize = 1_024;
18510
18511    let registry = CurrentThreadTaskDriverRegistry::default();
18512    let mut ids = Vec::with_capacity(HOSTS);
18513    let mut drivers = Vec::with_capacity(HOSTS);
18514    for _ in 0..HOSTS {
18515      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18516      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18517      drivers.push(driver);
18518    }
18519
18520    let dispatch = u64::MAX - 300;
18521    assert_eq!(
18522      registry.dispatch(dispatch),
18523      CurrentThreadHostDispatchResult::Accepted
18524    );
18525    let deliveries = drivers
18526      .iter()
18527      .map(|driver| driver.deliveries()[0])
18528      .collect::<Vec<_>>();
18529    {
18530      let state = registry
18531        .state
18532        .lock()
18533        .unwrap_or_else(std::sync::PoisonError::into_inner);
18534      assert_eq!(state.delivery_drivers.len(), HOSTS);
18535      assert_eq!(
18536        state
18537          .dispatches
18538          .get(&dispatch)
18539          .map(|state| state.references),
18540        Some(HOSTS)
18541      );
18542      assert_eq!(
18543        state
18544          .ordered_snapshot()
18545          .iter()
18546          .map(|(id, _)| *id)
18547          .collect::<Vec<_>>(),
18548        ids,
18549        "keyed driver lookup must retain registration order"
18550      );
18551    }
18552
18553    for delivery in &deliveries {
18554      assert_eq!(
18555        registry.claim_delivery(delivery.capability()),
18556        Some(dispatch)
18557      );
18558    }
18559    registry.mark_serviced(dispatch);
18560    for delivery in deliveries {
18561      assert!(
18562        registry
18563          .finish_delivery(delivery, false)
18564          .failed_dispatches
18565          .is_empty()
18566      );
18567    }
18568    {
18569      let state = registry
18570        .state
18571        .lock()
18572        .unwrap_or_else(std::sync::PoisonError::into_inner);
18573      assert!(state.delivery_drivers.is_empty());
18574      assert!(state.dispatches.is_empty());
18575    }
18576
18577    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18578    for id in &removed {
18579      let completion = registry.unregister(*id);
18580      assert!(completion.failed_dispatches.is_empty());
18581      assert!(!completion.redispatch);
18582    }
18583    let expected = ids
18584      .iter()
18585      .copied()
18586      .filter(|id| !removed.contains(id))
18587      .collect::<Vec<_>>();
18588    let live = registry.live().live;
18589    assert_eq!(
18590      live
18591        .as_slice()
18592        .iter()
18593        .map(|(id, _)| *id)
18594        .collect::<Vec<_>>(),
18595      expected,
18596      "O(1) removals must preserve the relative order of surviving host registrations"
18597    );
18598    drop(live);
18599
18600    for id in expected {
18601      registry.unregister(id);
18602    }
18603    let state = registry
18604      .state
18605      .lock()
18606      .unwrap_or_else(std::sync::PoisonError::into_inner);
18607    assert!(state.entries.is_empty());
18608    assert!(state.oldest.is_none());
18609    assert!(state.newest.is_none());
18610  }
18611
18612  #[test]
18613  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18614    let registry = CurrentThreadTaskDriverRegistry::default();
18615    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18616    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18617    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18618    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18619    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18620    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18621
18622    let dispatch = 41;
18623    assert_eq!(
18624      registry.dispatch(dispatch),
18625      CurrentThreadHostDispatchResult::Accepted
18626    );
18627    let failing_delivery = failing.deliveries()[0];
18628    let delayed_delivery = delayed.deliveries()[0];
18629    let healthy_delivery = healthy.deliveries()[0];
18630    assert_eq!(failing_delivery.dispatch, dispatch);
18631    assert_eq!(delayed_delivery.dispatch, dispatch);
18632    assert_eq!(healthy_delivery.dispatch, dispatch);
18633
18634    assert!(
18635      registry
18636        .finish_delivery(failing_delivery, true)
18637        .failed_dispatches
18638        .is_empty(),
18639      "one host failure must not recover while other accepted attempts remain"
18640    );
18641    assert_eq!(
18642      registry.claim_delivery(healthy_delivery.capability()),
18643      Some(dispatch)
18644    );
18645    registry.mark_serviced(dispatch);
18646    assert!(
18647      registry
18648        .finish_delivery(healthy_delivery, false)
18649        .failed_dispatches
18650        .is_empty()
18651    );
18652    assert_eq!(
18653      registry.claim_delivery(delayed_delivery.capability()),
18654      None,
18655      "a delayed losing delivery must become stale after another host services the dispatch"
18656    );
18657    assert!(
18658      registry
18659        .finish_delivery(delayed_delivery, true)
18660        .failed_dispatches
18661        .is_empty(),
18662      "a stale losing failure must not recover or cancel a serviced dispatch"
18663    );
18664    assert!(
18665      registry.state.lock().unwrap().dispatches.is_empty(),
18666      "all per-dispatch acknowledgement state must retire"
18667    );
18668  }
18669
18670  #[test]
18671  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18672    let registry = CurrentThreadTaskDriverRegistry::default();
18673    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18674    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18675
18676    let dispatch = 56;
18677    assert_eq!(
18678      registry.dispatch(dispatch),
18679      CurrentThreadHostDispatchResult::Accepted
18680    );
18681    let delivery = no_op.deliveries()[0];
18682    let completion = registry.finish_delivery(delivery, false);
18683    let failure = current_thread_dispatch_failure(dispatch, 0);
18684    assert_eq!(
18685      completion.failed_dispatches,
18686      vec![failure],
18687      "a callback return without an exact capability claim must enter recovery"
18688    );
18689    assert!(
18690      registry
18691        .state
18692        .lock()
18693        .unwrap()
18694        .dispatches
18695        .get(&dispatch)
18696        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18697    );
18698    registry.finish_cancellation(failure);
18699    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18700  }
18701
18702  #[test]
18703  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18704    struct FailDuringPublication {
18705      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18706    }
18707
18708    impl CurrentThreadTaskDriver for FailDuringPublication {
18709      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18710        let completion = self
18711          .registry
18712          .upgrade()
18713          .expect("the registry must outlive publication")
18714          .finish_delivery(delivery, true);
18715        assert!(
18716          completion.failed_dispatches.is_empty(),
18717          "failure must remain provisional until the broadcast closes"
18718        );
18719        true
18720      }
18721    }
18722
18723    struct RejectProvisional;
18724
18725    impl CurrentThreadTaskDriver for RejectProvisional {
18726      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18727        false
18728      }
18729    }
18730
18731    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18732    registry.register(Arc::new(RejectProvisional));
18733    // Newest-first publication calls the accepted-then-failing host while the
18734    // rejecting host still has an unresolved provisional delivery.
18735    registry.register(Arc::new(FailDuringPublication {
18736      registry: Arc::downgrade(&registry),
18737    }));
18738
18739    let failure = current_thread_dispatch_failure(57, 0);
18740    assert_eq!(
18741      registry.dispatch(57),
18742      CurrentThreadHostDispatchResult::Failed(failure),
18743      "the final provisional rejection must expose the earlier accepted failure"
18744    );
18745    assert!(
18746      registry
18747        .state
18748        .lock()
18749        .unwrap()
18750        .dispatches
18751        .get(&57)
18752        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18753    );
18754    assert_eq!(
18755      registry.dispatch(57),
18756      CurrentThreadHostDispatchResult::Failed(failure),
18757      "a racing replacement publication must preserve terminal failure until cancellation runs"
18758    );
18759    registry.finish_cancellation(failure);
18760  }
18761
18762  #[test]
18763  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18764    struct RejectProvisional;
18765
18766    impl CurrentThreadTaskDriver for RejectProvisional {
18767      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18768        false
18769      }
18770    }
18771
18772    let registry = CurrentThreadTaskDriverRegistry::default();
18773    registry.register(Arc::new(RejectProvisional));
18774    registry.register(Arc::new(RejectProvisional));
18775
18776    assert_eq!(
18777      registry.dispatch(58),
18778      CurrentThreadHostDispatchResult::Unavailable
18779    );
18780    assert!(
18781      registry.state.lock().unwrap().dispatches.is_empty(),
18782      "a broadcast with no accepted attempt must not enter failure recovery"
18783    );
18784  }
18785
18786  #[test]
18787  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18788    let registry = CurrentThreadTaskDriverRegistry::default();
18789    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18790    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18791
18792    assert_eq!(
18793      registry.dispatch(1),
18794      CurrentThreadHostDispatchResult::Accepted
18795    );
18796    let first = blocked.deliveries()[0];
18797    for dispatch in 2..=256 {
18798      assert_eq!(
18799        registry.dispatch(dispatch),
18800        CurrentThreadHostDispatchResult::Accepted
18801      );
18802      assert_eq!(
18803        blocked.deliveries().len(),
18804        1,
18805        "a blocked host must never receive a second queued or in-flight callback"
18806      );
18807    }
18808    {
18809      let state = registry.state.lock().unwrap();
18810      let entry = state
18811        .entries
18812        .get(&blocked_id)
18813        .expect("the blocked host must remain registered");
18814      assert_eq!(entry.pending, Some(256));
18815      assert_eq!(
18816        state.dispatches.len(),
18817        2,
18818        "only the in-flight and latest pending dispatches may retain registry state"
18819      );
18820    }
18821
18822    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18823    registry.mark_serviced(1);
18824    let completion = registry.finish_delivery(first, false);
18825    assert!(completion.failed_dispatches.is_empty());
18826    assert!(completion.redispatch);
18827    assert_eq!(
18828      registry.dispatch(256),
18829      CurrentThreadHostDispatchResult::Accepted
18830    );
18831    let deliveries = blocked.deliveries();
18832    assert_eq!(deliveries.len(), 2);
18833    assert_eq!(
18834      deliveries[1].dispatch, 256,
18835      "completion must schedule exactly the latest coalesced capability"
18836    );
18837    assert_eq!(registry.claim_delivery(first.capability()), None);
18838    assert_eq!(
18839      registry.claim_delivery(deliveries[1].capability()),
18840      Some(256)
18841    );
18842    registry.mark_serviced(256);
18843    assert!(
18844      registry
18845        .finish_delivery(deliveries[1], false)
18846        .failed_dispatches
18847        .is_empty()
18848    );
18849    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18850  }
18851
18852  #[test]
18853  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18854    struct RejectAfterFirst {
18855      calls: AtomicUsize,
18856      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18857    }
18858
18859    impl CurrentThreadTaskDriver for RejectAfterFirst {
18860      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18861        self.deliveries.lock().unwrap().push(delivery);
18862        self.calls.fetch_add(1, Ordering::SeqCst) == 0
18863      }
18864    }
18865
18866    let registry = CurrentThreadTaskDriverRegistry::default();
18867    let host = Arc::new(RejectAfterFirst {
18868      calls: AtomicUsize::new(0),
18869      deliveries: Mutex::new(Vec::new()),
18870    });
18871    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18872    assert_eq!(
18873      registry.dispatch(11),
18874      CurrentThreadHostDispatchResult::Accepted
18875    );
18876    let first = host.deliveries.lock().unwrap()[0];
18877    assert_eq!(
18878      registry.dispatch(12),
18879      CurrentThreadHostDispatchResult::Accepted
18880    );
18881
18882    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18883    registry.mark_serviced(11);
18884    let completion = registry.finish_delivery(first, false);
18885    assert!(completion.redispatch);
18886    let failure = current_thread_dispatch_failure(12, 0);
18887    assert_eq!(
18888      registry.dispatch(12),
18889      CurrentThreadHostDispatchResult::Failed(failure),
18890      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18891    );
18892    assert!(
18893      registry
18894        .state
18895        .lock()
18896        .unwrap()
18897        .dispatches
18898        .get(&12)
18899        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18900    );
18901    registry.finish_cancellation(failure);
18902    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18903  }
18904
18905  #[test]
18906  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18907    let registry = CurrentThreadTaskDriverRegistry::default();
18908    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18909    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18910    assert_eq!(
18911      registry.dispatch(73),
18912      CurrentThreadHostDispatchResult::Accepted
18913    );
18914    let old_delivery = old.deliveries()[0];
18915
18916    let completion = registry.unregister(old_id);
18917    let failure = current_thread_dispatch_failure(73, 0);
18918    assert_eq!(
18919      completion.failed_dispatches,
18920      vec![failure],
18921      "unregistering the final accepting host must retire its exact dispatch"
18922    );
18923    registry.finish_cancellation(failure);
18924    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18925    let replacement_id =
18926      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18927    assert_ne!(replacement_id, old_id);
18928    assert_eq!(
18929      registry.dispatch(74),
18930      CurrentThreadHostDispatchResult::Accepted
18931    );
18932    let replacement_delivery = replacement.deliveries()[0];
18933    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18934
18935    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18936    assert!(
18937      registry
18938        .finish_delivery(old_delivery, true)
18939        .failed_dispatches
18940        .is_empty()
18941    );
18942    assert!(
18943      registry
18944        .fail_delivery(old_delivery.capability())
18945        .failed_dispatches
18946        .is_empty()
18947    );
18948    assert_eq!(
18949      registry.claim_delivery(replacement_delivery.capability()),
18950      Some(74)
18951    );
18952    registry.mark_serviced(74);
18953    assert!(
18954      registry
18955        .finish_delivery(replacement_delivery, false)
18956        .failed_dispatches
18957        .is_empty()
18958    );
18959    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18960  }
18961
18962  #[test]
18963  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18964    struct QueuePendingThenReject {
18965      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18966    }
18967
18968    impl CurrentThreadTaskDriver for QueuePendingThenReject {
18969      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18970        assert_eq!(delivery.dispatch, 81);
18971        let completion = self
18972          .registry
18973          .upgrade()
18974          .expect("the registry must outlive publication")
18975          .dispatch_with_completion(82);
18976        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18977        assert!(completion.failed_dispatches.is_empty());
18978        false
18979      }
18980    }
18981
18982    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18983    registry.register(Arc::new(QueuePendingThenReject {
18984      registry: Arc::downgrade(&registry),
18985    }));
18986
18987    let completion = registry.dispatch_with_completion(81);
18988    assert_eq!(
18989      completion.result,
18990      CurrentThreadHostDispatchResult::Unavailable
18991    );
18992    let failure = current_thread_dispatch_failure(82, 0);
18993    assert_eq!(
18994      completion.failed_dispatches,
18995      vec![failure],
18996      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18997    );
18998    assert!(
18999      registry
19000        .state
19001        .lock()
19002        .unwrap()
19003        .dispatches
19004        .get(&82)
19005        .is_some_and(CurrentThreadDispatchState::is_cancelling)
19006    );
19007    registry.finish_cancellation(failure);
19008    assert!(registry.state.lock().unwrap().dispatches.is_empty());
19009  }
19010
19011  #[test]
19012  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
19013    let registry = CurrentThreadTaskDriverRegistry::default();
19014    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
19015    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
19016    assert_eq!(
19017      registry.dispatch(91),
19018      CurrentThreadHostDispatchResult::Accepted
19019    );
19020    let old_delivery = host.deliveries()[0];
19021    assert_eq!(
19022      registry.dispatch(92),
19023      CurrentThreadHostDispatchResult::Accepted
19024    );
19025
19026    registry.retire_for_shutdown();
19027    {
19028      let state = registry.state.lock().unwrap();
19029      assert!(state.dispatches.is_empty());
19030      let entry = state
19031        .entries
19032        .get(&host_id)
19033        .expect("the host must remain registered");
19034      assert_eq!(entry.pending, None);
19035      assert!(
19036        entry.delivery.is_some(),
19037        "the physical callback remains single-flight until it actually retires"
19038      );
19039    }
19040    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
19041
19042    assert_eq!(
19043      registry.dispatch(93),
19044      CurrentThreadHostDispatchResult::Accepted
19045    );
19046    assert_eq!(host.deliveries().len(), 1);
19047    let completion = registry.finish_delivery(old_delivery, true);
19048    assert!(
19049      completion.failed_dispatches.is_empty(),
19050      "a retired-generation failure must not cancel restarted work"
19051    );
19052    assert!(completion.redispatch);
19053    assert_eq!(
19054      registry.dispatch(93),
19055      CurrentThreadHostDispatchResult::Accepted
19056    );
19057    let restarted_delivery = host.deliveries()[1];
19058    assert_eq!(restarted_delivery.dispatch, 93);
19059    assert_eq!(
19060      registry.claim_delivery(restarted_delivery.capability()),
19061      Some(93)
19062    );
19063    registry.mark_serviced(93);
19064    assert!(
19065      registry
19066        .finish_delivery(restarted_delivery, false)
19067        .failed_dispatches
19068        .is_empty()
19069    );
19070
19071    registry.unregister(host_id);
19072    let state = registry.state.lock().unwrap();
19073    assert!(state.entries.is_empty());
19074    assert!(state.dispatches.is_empty());
19075  }
19076
19077  #[cfg(napi_runtime_os_threads)]
19078  #[test]
19079  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
19080    use std::{sync::mpsc, time::Duration};
19081
19082    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19083    let fallback = Arc::new(TestCurrentThreadTaskDriver {
19084      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19085      dispatched: AtomicBool::new(false),
19086    });
19087    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19088    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
19089      registry: Arc::downgrade(&registry),
19090      registration: Mutex::new(None),
19091    });
19092    let reentrant_id =
19093      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19094    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
19095
19096    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
19097    let selection_registry = Arc::clone(&registry);
19098    let selection_thread = std::thread::spawn(move || {
19099      selection_tx
19100        .send(selection_registry.current().map(|(id, _)| id))
19101        .unwrap();
19102    });
19103    assert_eq!(
19104      selection_rx
19105        .recv_timeout(Duration::from_secs(10))
19106        .expect("is_live re-entry deadlocked"),
19107      Some(fallback_id)
19108    );
19109    selection_thread.join().unwrap();
19110
19111    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
19112      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
19113        registry: Arc::downgrade(&registry),
19114        fallback: fallback_id,
19115      });
19116    let drop_id = registry.register(Arc::clone(&drop_driver));
19117    drop(drop_driver);
19118
19119    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
19120    let drop_registry = Arc::clone(&registry);
19121    let drop_thread = std::thread::spawn(move || {
19122      drop_registry.unregister(drop_id);
19123      drop_tx.send(()).unwrap();
19124    });
19125    drop_rx
19126      .recv_timeout(Duration::from_secs(10))
19127      .expect("driver destructor re-entry deadlocked");
19128    drop_thread.join().unwrap();
19129    assert!(
19130      registry.current().is_none(),
19131      "the destructor must unregister the fallback"
19132    );
19133  }
19134
19135  #[cfg(not(target_family = "wasm"))]
19136  #[test]
19137  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
19138    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
19139
19140    if std::env::var_os(CHILD_ENV).is_some() {
19141      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19142      let fallback = Arc::new(TestCurrentThreadTaskDriver {
19143        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19144        dispatched: AtomicBool::new(false),
19145      });
19146      let fallback_id =
19147        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19148      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
19149        registry: Arc::downgrade(&registry),
19150        registrations: Mutex::new(Vec::new()),
19151      });
19152      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19153
19154      let first_drops = Arc::new(AtomicUsize::new(0));
19155      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19156        drops: Arc::clone(&first_drops),
19157      });
19158      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19159      drop(first);
19160
19161      let second_drops = Arc::new(AtomicUsize::new(0));
19162      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19163        drops: Arc::clone(&second_drops),
19164      });
19165      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19166      drop(second);
19167      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
19168
19169      assert_eq!(
19170        registry.current().map(|(id, _)| id),
19171        Some(fallback_id),
19172        "selection must retry after reentrant removals and preserve the healthy fallback"
19173      );
19174      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19175      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19176      let state = registry
19177        .state
19178        .lock()
19179        .unwrap_or_else(std::sync::PoisonError::into_inner);
19180      assert_eq!(state.entries.len(), 1);
19181      assert!(state.entries.contains_key(&fallback_id));
19182      assert!(state.dispatches.is_empty());
19183      return;
19184    }
19185
19186    let output = std::process::Command::new(std::env::current_exe().unwrap())
19187      .arg("--exact")
19188      .arg(
19189        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
19190      )
19191      .arg("--nocapture")
19192      .env(CHILD_ENV, "1")
19193      .output()
19194      .expect("the hostile task-driver snapshot subprocess test must start");
19195    assert!(
19196      output.status.success(),
19197      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19198      output.status.code(),
19199      String::from_utf8_lossy(&output.stdout),
19200      String::from_utf8_lossy(&output.stderr)
19201    );
19202  }
19203
19204  #[cfg(not(target_family = "wasm"))]
19205  #[test]
19206  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
19207    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
19208
19209    if std::env::var_os(CHILD_ENV).is_some() {
19210      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19211
19212      let first_drops = Arc::new(AtomicUsize::new(0));
19213      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19214        drops: Arc::clone(&first_drops),
19215      });
19216      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19217      drop(first);
19218
19219      let second_drops = Arc::new(AtomicUsize::new(0));
19220      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19221        drops: Arc::clone(&second_drops),
19222      });
19223      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19224      drop(second);
19225
19226      let reentrant_drops = Arc::new(AtomicUsize::new(0));
19227      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
19228        registry: Arc::downgrade(&registry),
19229        registrations: Mutex::new(Vec::new()),
19230        drops: Arc::clone(&reentrant_drops),
19231      });
19232      let reentrant_id =
19233        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19234      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
19235      drop(reentrant);
19236
19237      let dispatch = 59;
19238      let failure = current_thread_dispatch_failure(dispatch, 0);
19239      assert_eq!(
19240        registry.dispatch(dispatch),
19241        CurrentThreadHostDispatchResult::Failed(failure),
19242        "reentrant removal of every provisional host must report accepted delivery failure"
19243      );
19244      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
19245      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19246      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19247      registry.finish_cancellation(failure);
19248      let state = registry
19249        .state
19250        .lock()
19251        .unwrap_or_else(std::sync::PoisonError::into_inner);
19252      assert!(state.entries.is_empty());
19253      assert!(state.dispatches.is_empty());
19254      return;
19255    }
19256
19257    let output = std::process::Command::new(std::env::current_exe().unwrap())
19258      .arg("--exact")
19259      .arg(
19260        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
19261      )
19262      .arg("--nocapture")
19263      .env(CHILD_ENV, "1")
19264      .output()
19265      .expect("the hostile task-driver delivery subprocess test must start");
19266    assert!(
19267      output.status.success(),
19268      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19269      output.status.code(),
19270      String::from_utf8_lossy(&output.stdout),
19271      String::from_utf8_lossy(&output.stderr)
19272    );
19273  }
19274
19275  // Builds the `JoinHandleInner::Task` literal directly, whose `dependency`
19276  // field only exists where the MultiThread machinery is compiled in.
19277  #[cfg(napi_runtime_os_threads)]
19278  #[test]
19279  fn dropping_join_handle_detaches_task_like_tokio() {
19280    let completed = Arc::new(AtomicBool::new(false));
19281    let completed_task = Arc::clone(&completed);
19282    let (runnable, task) = async_task::spawn(
19283      async move {
19284        completed_task.store(true, Ordering::SeqCst);
19285        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
19286      },
19287      |_| {},
19288    );
19289    let handle = JoinHandle(JoinHandleInner::Task {
19290      task: task.fallible(),
19291      awaiter: None,
19292      dependency: Arc::new(TaskDependency::default()),
19293    });
19294
19295    drop(handle);
19296    runnable.run();
19297    assert!(completed.load(Ordering::SeqCst));
19298  }
19299
19300  #[cfg(not(target_family = "wasm"))]
19301  #[test]
19302  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19303    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19304
19305    if std::env::var_os(CHILD_ENV).is_some() {
19306      let wakes = Arc::new(AtomicUsize::new(0));
19307      let drops = Arc::new(AtomicUsize::new(0));
19308      let (runnable, task) = async_task::spawn(
19309        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19310        |_| {},
19311      );
19312      let mut handle = JoinHandle(JoinHandleInner::Task {
19313        task: task.fallible(),
19314        awaiter: None,
19315        dependency: Arc::new(TaskDependency::default()),
19316      });
19317      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19318      {
19319        let mut cx = Context::from_waker(&waker);
19320        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19321      }
19322      drop(waker);
19323
19324      runnable.run();
19325
19326      let waker = futures::task::noop_waker();
19327      let mut cx = Context::from_waker(&waker);
19328      assert!(matches!(
19329        Pin::new(&mut handle).poll(&mut cx),
19330        Poll::Ready(Ok(()))
19331      ));
19332      assert_eq!(
19333        drops.load(Ordering::SeqCst),
19334        1,
19335        "a completed handle must not retain the caller's awaiter"
19336      );
19337      drop(handle);
19338      assert!(wakes.load(Ordering::SeqCst) >= 1);
19339      assert_eq!(drops.load(Ordering::SeqCst), 1);
19340      return;
19341    }
19342
19343    let output = std::process::Command::new(std::env::current_exe().unwrap())
19344      .arg("--exact")
19345      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19346      .arg("--nocapture")
19347      .env(CHILD_ENV, "1")
19348      .output()
19349      .expect("the JoinHandle completion awaiter subprocess must start");
19350    assert!(
19351      output.status.success(),
19352      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19353      output.status.code(),
19354      String::from_utf8_lossy(&output.stdout),
19355      String::from_utf8_lossy(&output.stderr)
19356    );
19357  }
19358
19359  #[cfg(not(target_family = "wasm"))]
19360  #[test]
19361  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19362    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19363
19364    if std::env::var_os(CHILD_ENV).is_some() {
19365      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19366      let wakes = Arc::new(AtomicUsize::new(0));
19367      let drops = Arc::new(AtomicUsize::new(0));
19368      let mut handle = controller.spawn(std::future::pending::<()>());
19369      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19370      {
19371        let mut cx = Context::from_waker(&waker);
19372        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19373      }
19374      drop(waker);
19375
19376      controller
19377        .shutdown()
19378        .expect("shutdown must survive the hostile awaiter");
19379      drop(handle);
19380      assert!(wakes.load(Ordering::SeqCst) >= 1);
19381      assert_eq!(drops.load(Ordering::SeqCst), 1);
19382      return;
19383    }
19384
19385    let output = std::process::Command::new(std::env::current_exe().unwrap())
19386      .arg("--exact")
19387      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19388      .arg("--nocapture")
19389      .env(CHILD_ENV, "1")
19390      .output()
19391      .expect("the JoinHandle shutdown awaiter subprocess must start");
19392    assert!(
19393      output.status.success(),
19394      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19395      output.status.code(),
19396      String::from_utf8_lossy(&output.stdout),
19397      String::from_utf8_lossy(&output.stderr)
19398    );
19399  }
19400
19401  #[cfg(napi_runtime_os_threads)]
19402  #[test]
19403  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19404    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19405    let mut handle = controller.spawn(std::future::pending::<()>());
19406    let old_generation = controller.backend().generation();
19407    let state = Arc::new(JoinAwaiterWakerState {
19408      events: Mutex::new(Vec::new()),
19409    });
19410    let waker = {
19411      let _generation = RuntimeGenerationGuard::enter(old_generation);
19412      join_awaiter_waker(Arc::clone(&state))
19413    };
19414    {
19415      let _generation = RuntimeGenerationGuard::enter(old_generation);
19416      let mut cx = Context::from_waker(&waker);
19417      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19418    }
19419    {
19420      let _generation = RuntimeGenerationGuard::enter(old_generation);
19421      drop(waker);
19422    }
19423
19424    controller
19425      .shutdown()
19426      .expect("the first generation must stop");
19427    controller.start().expect("the runtime must restart");
19428    let new_generation = controller.backend().generation();
19429    assert_ne!(new_generation, old_generation);
19430    state.clear();
19431
19432    let replacement = futures::task::noop_waker();
19433    {
19434      let _generation = RuntimeGenerationGuard::enter(new_generation);
19435      let mut cx = Context::from_waker(&replacement);
19436      assert!(matches!(
19437        Pin::new(&mut handle).poll(&mut cx),
19438        Poll::Ready(Err(_))
19439      ));
19440    }
19441
19442    let events = state.snapshot();
19443    assert!(
19444      !events.is_empty(),
19445      "completion must retire the registered awaiter waker"
19446    );
19447    assert!(
19448      events.iter().all(|(kind, generation)| {
19449        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19450      }),
19451      "old-generation awaiter retirement escaped its generation: {events:?}"
19452    );
19453    controller
19454      .shutdown()
19455      .expect("the replacement generation must stop");
19456  }
19457
19458  #[cfg(napi_runtime_os_threads)]
19459  #[test]
19460  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19461    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19462    let mut handle = controller.spawn(std::future::pending::<()>());
19463    let old_generation = controller.backend().generation();
19464    let state = Arc::new(JoinAwaiterWakerState {
19465      events: Mutex::new(Vec::new()),
19466    });
19467    let waker = {
19468      let _generation = RuntimeGenerationGuard::enter(old_generation);
19469      join_awaiter_waker(Arc::clone(&state))
19470    };
19471    {
19472      let _generation = RuntimeGenerationGuard::enter(old_generation);
19473      let mut cx = Context::from_waker(&waker);
19474      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19475    }
19476    {
19477      let _generation = RuntimeGenerationGuard::enter(old_generation);
19478      drop(waker);
19479    }
19480
19481    controller
19482      .shutdown()
19483      .expect("the first generation must stop");
19484    controller.start().expect("the runtime must restart");
19485    let new_generation = controller.backend().generation();
19486    assert_ne!(new_generation, old_generation);
19487    state.clear();
19488
19489    {
19490      let _generation = RuntimeGenerationGuard::enter(new_generation);
19491      drop(handle);
19492    }
19493
19494    let events = state.snapshot();
19495    assert!(
19496      !events.is_empty(),
19497      "handle drop must retire the registered awaiter waker"
19498    );
19499    assert!(
19500      events.iter().all(|(kind, generation)| {
19501        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19502      }),
19503      "old-generation awaiter destruction escaped its generation: {events:?}"
19504    );
19505    controller
19506      .shutdown()
19507      .expect("the replacement generation must stop");
19508  }
19509
19510  #[cfg(napi_runtime_os_threads)]
19511  #[test]
19512  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19513    use std::{sync::mpsc, time::Duration};
19514
19515    let options = RuntimeOptions {
19516      flavor: RuntimeFlavor::MultiThread,
19517      worker_threads: 2,
19518      max_blocking_tasks: 1,
19519      thread_name_prefix: "worker-classification".to_string(),
19520      park_deadline: None,
19521      drain_linger: DEFAULT_DRAIN_LINGER,
19522    };
19523    let executor =
19524      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19525    let id = executor.id;
19526    let (tx, rx) = mpsc::sync_channel(1);
19527
19528    executor.pool.spawn(move || {
19529      tx.send((
19530        ON_POOL_WORKER.with(std::cell::Cell::get),
19531        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19532      ))
19533      .unwrap();
19534    });
19535
19536    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19537    assert_eq!(
19538      worker,
19539      Some(id),
19540      "nested Rayon work must use cooperative block_on"
19541    );
19542    assert_eq!(
19543      driver, None,
19544      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19545    );
19546  }
19547
19548  #[cfg(napi_runtime_os_threads)]
19549  #[test]
19550  fn multi_thread_block_on_does_not_park_pool_worker() {
19551    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
19552    // future depending on pool-scheduled blocking work must not park its worker.
19553    // If every worker parks this way, no worker remains to pick up the blocking
19554    // jobs they await.
19555    use std::sync::{Barrier, mpsc};
19556    use std::time::Duration;
19557
19558    let (done_tx, done_rx) = mpsc::channel();
19559    let runner = std::thread::spawn(move || {
19560      let metrics = Arc::new(RuntimeMetrics::default());
19561      let options = RuntimeOptions {
19562        flavor: RuntimeFlavor::MultiThread,
19563        worker_threads: 2,
19564        max_blocking_tasks: 2,
19565        thread_name_prefix: "rd1-test".to_string(),
19566        park_deadline: None,
19567        drain_linger: DEFAULT_DRAIN_LINGER,
19568      };
19569      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19570
19571      let task_count = 2usize;
19572      // Guarantees every task is being polled on its own pool worker *before*
19573      // any of them parks in `block_on`, so the deadlock is deterministic.
19574      let barrier = Arc::new(Barrier::new(task_count));
19575
19576      let mut tasks = Vec::new();
19577      for _ in 0..task_count {
19578        let exec = Arc::clone(&executor);
19579        let barrier = Arc::clone(&barrier);
19580        let body = async move {
19581          barrier.wait();
19582          let inner_exec = Arc::clone(&exec);
19583          let mut inner = std::pin::pin!(async move {
19584            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19585            assert_eq!(value, 7);
19586          });
19587          // Synchronous `block_on` from inside a pool-worker task: the vector.
19588          exec.block_on(inner.as_mut());
19589        };
19590        let scheduler = Arc::clone(&executor);
19591        let (runnable, task) =
19592          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19593        executor.schedule(runnable);
19594        tasks.push(task);
19595      }
19596
19597      for task in tasks {
19598        futures::executor::block_on(task);
19599      }
19600      let _ = done_tx.send(());
19601    });
19602
19603    match done_rx.recv_timeout(Duration::from_secs(10)) {
19604      Ok(()) => runner.join().unwrap(),
19605      Err(error) => panic!(
19606        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19607      ),
19608    }
19609  }
19610
19611  #[cfg(napi_runtime_os_threads)]
19612  #[test]
19613  fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19614    // Idle-linger residence bound (adversarial rayon-starvation shape).
19615    //
19616    // W=2 pool. Worker B runs a blocking closure that spawns a raw rayon job
19617    // R onto the SAME pool (it lands in worker B's rayon deque) and then
19618    // cooperatively block_on's R's completion -- so worker B never returns to
19619    // rayon's scheduler. Worker A's drain frame runs the queues empty and
19620    // lingers on its own parker. A producer then publishes one trivial
19621    // runnable every ~200us (< the 500us idle budget), stopping only after R
19622    // ran. Every wake pops the MOST recently registered parker -- the
19623    // lingering drainer, which re-registers on top after each task -- and
19624    // every executed task clears the idle deadline, so without a bound on the
19625    // frame's TOTAL lingering residence worker A never exits its drain frame,
19626    // never returns its thread to rayon, and R (which only an idle rayon
19627    // worker can steal) starves indefinitely: producer never stops, worker B
19628    // never unparks. The frame residence bound must force the drainer out
19629    // through `finish_draining` (exit-and-respawn through rayon's queue is
19630    // exactly the interleaving point that lets R run), well inside the
19631    // watchdog below.
19632    use std::sync::mpsc;
19633
19634    for round in 0..3 {
19635      let metrics = Arc::new(RuntimeMetrics::default());
19636      let options = RuntimeOptions {
19637        flavor: RuntimeFlavor::MultiThread,
19638        worker_threads: 2,
19639        max_blocking_tasks: 1,
19640        thread_name_prefix: format!("linger-starve-{round}"),
19641        park_deadline: None,
19642        drain_linger: DEFAULT_DRAIN_LINGER,
19643      };
19644      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19645
19646      let r_done = Arc::new(AtomicBool::new(false));
19647      let give_up = Arc::new(AtomicBool::new(false));
19648      let (done_tx, done_rx) = mpsc::channel::<Duration>();
19649
19650      // T1 pins worker A until the cooperative driver (worker B, inside the
19651      // blocking closure's re-entrant block_on) has parked. Worker A's drain
19652      // frame then observes empty queues and starts lingering AFTER the
19653      // cooperative driver registered, making the lingering drainer the
19654      // most-recently-registered (i.e. wake-preferred) parker.
19655      let t1_executor = Arc::clone(&executor);
19656      let t1_started = Arc::new(AtomicBool::new(false));
19657      let t1_started_setter = Arc::clone(&t1_started);
19658      let t1_body = async move {
19659        t1_started_setter.store(true, Ordering::SeqCst);
19660        let deadline = Instant::now() + Duration::from_secs(10);
19661        while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19662          if Instant::now() >= deadline {
19663            // Give the worker back; the watchdog below reports the failure.
19664            return;
19665          }
19666          std::hint::spin_loop();
19667        }
19668      };
19669      let t1_scheduler = Arc::clone(&executor);
19670      let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19671      executor.schedule(t1_runnable);
19672      wait_until("the worker-pinning task to start", || {
19673        t1_started.load(Ordering::SeqCst)
19674      });
19675
19676      // The blocking closure on worker B: spawn R, then block_on its result.
19677      let blocking_executor = Arc::clone(&executor);
19678      let r_done_setter = Arc::clone(&r_done);
19679      let started = Instant::now();
19680      let handle = executor.schedule_blocking(move || {
19681        let (r_tx, r_rx) = oneshot::channel::<()>();
19682        blocking_executor.pool.spawn(move || {
19683          r_done_setter.store(true, Ordering::SeqCst);
19684          let _ = r_tx.send(());
19685        });
19686        let mut wait_for_r = std::pin::pin!(async move {
19687          let _ = r_rx.await;
19688        });
19689        blocking_executor.block_on(wait_for_r.as_mut());
19690        let _ = done_tx.send(started.elapsed());
19691      });
19692
19693      // Wait until both the cooperative driver and the lingering drainer are
19694      // registered; if the linger expired first (R already ran), that round
19695      // completed without ever reaching the adversarial interleaving.
19696      let arm_deadline = Instant::now() + Duration::from_secs(15);
19697      while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19698        && !r_done.load(Ordering::SeqCst)
19699      {
19700        assert!(
19701          Instant::now() < arm_deadline,
19702          "timed out arming the linger-starvation interleaving"
19703        );
19704        std::thread::yield_now();
19705      }
19706
19707      // Producer: one trivial runnable every ~200us (spin-paced, so cadence
19708      // stays far below the 500us idle budget), until R completed.
19709      let producer_executor = Arc::clone(&executor);
19710      let producer_r_done = Arc::clone(&r_done);
19711      let producer_give_up = Arc::clone(&give_up);
19712      let producer = std::thread::spawn(move || {
19713        let cadence = Duration::from_micros(200);
19714        let mut next_tick = Instant::now();
19715        while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19716          let scheduler = Arc::clone(&producer_executor);
19717          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19718          producer_executor.schedule(runnable);
19719          task.detach();
19720          next_tick += cadence;
19721          while Instant::now() < next_tick {
19722            std::hint::spin_loop();
19723          }
19724        }
19725      });
19726
19727      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19728      give_up.store(true, Ordering::SeqCst);
19729      producer.join().unwrap();
19730      match outcome {
19731        Ok(elapsed) => {
19732          futures::executor::block_on(handle).expect("blocking closure join");
19733          drop(t1_task);
19734          eprintln!("round {round}: completed in {elapsed:?}");
19735        }
19736        Err(error) => panic!(
19737          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19738           sub-budget periodic work must not keep a drain frame resident forever"
19739        ),
19740      }
19741    }
19742  }
19743
19744  #[cfg(napi_runtime_os_threads)]
19745  #[test]
19746  #[ignore = "microbenchmark: run explicitly in release mode"]
19747  fn parked_drivers_registry_cycle_microbench() {
19748    // Isolates the registry traffic of one steady-state linger cycle at
19749    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
19750    // drainer deregisters (the entry is already gone, so the position scan
19751    // walks the WHOLE vector and fails), then re-registers (push). Also
19752    // measures the timeout-path deregister (entry present) and a
19753    // wake_one_owner_dependency sweep over dependency-less lingerers.
19754    for &n in &[8usize, 64, 256] {
19755      let registry = ParkedDrivers::default();
19756      let parkers: Vec<Arc<DriverParker>> =
19757        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19758      for parker in &parkers {
19759        registry.register_with_role(parker, true, None);
19760      }
19761      let hot = &parkers[n - 1];
19762
19763      let iters = 2_000_000u32;
19764      let start = Instant::now();
19765      for _ in 0..iters {
19766        assert!(registry.wake_one());
19767        registry.deregister(hot);
19768        assert!(hot.consume_permit());
19769        registry.register_with_role(hot, true, None);
19770      }
19771      let wake_cycle = start.elapsed() / iters;
19772
19773      let start = Instant::now();
19774      for _ in 0..iters {
19775        registry.deregister(hot);
19776        registry.register_with_role(hot, true, None);
19777      }
19778      let timeout_cycle = start.elapsed() / iters;
19779
19780      let owner = BlockingOwnerToken {
19781        executor_id: u64::MAX,
19782        frame: u64::MAX,
19783      };
19784      let start = Instant::now();
19785      for _ in 0..iters {
19786        assert!(!registry.wake_one_owner_dependency(owner));
19787      }
19788      let owner_sweep = start.elapsed() / iters;
19789
19790      eprintln!(
19791        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19792         timeout-deregister+re-register {timeout_cycle:?}, \
19793         owner-dependency sweep {owner_sweep:?}"
19794      );
19795    }
19796  }
19797
19798  #[cfg(napi_runtime_os_threads)]
19799  #[test]
19800  fn multi_thread_block_on_from_caller_thread_still_parks() {
19801    // The non-pool (napi caller) path must keep using the plain parking
19802    // `block_on`; here we just assert it drives a ready future to completion.
19803    let metrics = Arc::new(RuntimeMetrics::default());
19804    let options = RuntimeOptions {
19805      flavor: RuntimeFlavor::MultiThread,
19806      worker_threads: 3,
19807      max_blocking_tasks: 2,
19808      thread_name_prefix: "rd1-caller".to_string(),
19809      park_deadline: None,
19810      drain_linger: DEFAULT_DRAIN_LINGER,
19811    };
19812    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19813
19814    let mut output = None;
19815    let mut future = std::pin::pin!(async {
19816      output = Some(41usize + 1);
19817    });
19818    executor.block_on(future.as_mut());
19819    assert_eq!(output, Some(42));
19820  }
19821
19822  #[cfg(napi_runtime_os_threads)]
19823  #[test]
19824  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19825    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
19826    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
19827    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
19828    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
19829    // This pins ONLY the cross-thread wake path: the pool task completing must wake
19830    // the parked caller's waker so `block_on` returns the pool-computed value.
19831    //
19832    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
19833    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
19834    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
19835    // parking waker is registered on the task), signals "caller parked" to a releaser
19836    // thread. The releaser then -- and only then -- releases the gate, so the pool
19837    // task completes strictly AFTER the caller registered its waker. This forecloses
19838    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
19839    // caller's first poll, because its gate is opened only in response to that first
19840    // `Pending` poll. The completion is thus forced to travel the real cross-thread
19841    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
19842    // if that wake is broken the caller parks forever and the bounded recv_timeout
19843    // below fails loudly instead of hanging the suite.
19844    //
19845    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
19846    // caller-is-pool-worker case, which has dedicated reentrancy tests.
19847    use std::sync::mpsc;
19848    use std::time::Duration;
19849
19850    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
19851    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
19852    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
19853    // is already registered before the gate can open.
19854    struct CallerProbe {
19855      task: Task<usize>,
19856      parked_tx: Option<mpsc::Sender<()>>,
19857    }
19858    impl Future for CallerProbe {
19859      type Output = usize;
19860      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19861        // `Task` is `Unpin`; poll it through a fresh `Pin`.
19862        let poll = Pin::new(&mut self.task).poll(cx);
19863        if poll.is_pending()
19864          && let Some(tx) = self.parked_tx.take()
19865        {
19866          // Waker is now registered on `task`; release the gate is safe.
19867          let _ = tx.send(());
19868        }
19869        poll
19870      }
19871    }
19872
19873    let (done_tx, done_rx) = mpsc::channel();
19874    let runner = std::thread::spawn(move || {
19875      let metrics = Arc::new(RuntimeMetrics::default());
19876      let options = RuntimeOptions {
19877        flavor: RuntimeFlavor::MultiThread,
19878        worker_threads: 2,
19879        max_blocking_tasks: 2,
19880        thread_name_prefix: "rd10-caller".to_string(),
19881        park_deadline: None,
19882        drain_linger: DEFAULT_DRAIN_LINGER,
19883      };
19884      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19885
19886      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
19887      // worker registers the gate waker and returns `Pending`. When the releaser sends
19888      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
19889      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
19890      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19891      let scheduler = Arc::clone(&executor);
19892      let (runnable, task) = async_task::spawn(
19893        async move { gate_rx.await.expect("gate sender dropped") },
19894        move |r| {
19895          scheduler.schedule(r);
19896        },
19897      );
19898      executor.schedule(runnable);
19899
19900      // Releaser thread: opens the gate ONLY after the caller has parked (registered
19901      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
19902      // completes, so completion must travel the cross-thread wake path.
19903      let (parked_tx, parked_rx) = mpsc::channel::<()>();
19904      let releaser = std::thread::spawn(move || {
19905        if parked_rx.recv().is_ok() {
19906          let _ = gate_tx.send(42usize);
19907        }
19908      });
19909
19910      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
19911      // `futures::executor::block_on` rather than driving the queue cooperatively.
19912      let mut output = None;
19913      {
19914        let mut future = std::pin::pin!(async {
19915          output = Some(
19916            CallerProbe {
19917              task,
19918              parked_tx: Some(parked_tx),
19919            }
19920            .await,
19921          );
19922        });
19923        executor.block_on(future.as_mut());
19924      }
19925
19926      releaser.join().unwrap();
19927      assert_eq!(
19928        output,
19929        Some(42usize),
19930        "block_on must return the pool-computed value"
19931      );
19932      let _ = done_tx.send(());
19933    });
19934
19935    match done_rx.recv_timeout(Duration::from_secs(10)) {
19936      Ok(()) => runner.join().unwrap(),
19937      Err(error) => panic!(
19938        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19939      ),
19940    }
19941  }
19942
19943  #[cfg(napi_runtime_os_threads)]
19944  #[test]
19945  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19946    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
19947    // before the work that will wake it is scheduled. Once every pool worker
19948    // is parked this way, `ensure_drainer` cannot spawn a replacement because
19949    // the parked workers are still counted as active drainers. A later
19950    // `schedule()` must wake a parked driver directly so the runnable can run.
19951    use std::sync::{Barrier, mpsc};
19952    use std::time::Duration;
19953
19954    let (done_tx, done_rx) = mpsc::channel();
19955    let runner = std::thread::spawn(move || {
19956      let metrics = Arc::new(RuntimeMetrics::default());
19957      let options = RuntimeOptions {
19958        flavor: RuntimeFlavor::MultiThread,
19959        worker_threads: 2,
19960        max_blocking_tasks: 2,
19961        thread_name_prefix: "rd1-park".to_string(),
19962        park_deadline: None,
19963        drain_linger: DEFAULT_DRAIN_LINGER,
19964      };
19965      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19966
19967      let task_count = 2usize;
19968      let barrier = Arc::new(Barrier::new(task_count));
19969
19970      let mut senders = Vec::new();
19971      let mut tasks = Vec::new();
19972      for _ in 0..task_count {
19973        let (tx, rx) = oneshot::channel::<usize>();
19974        senders.push(tx);
19975        let exec = Arc::clone(&executor);
19976        let barrier = Arc::clone(&barrier);
19977        let body = async move {
19978          // Make sure both pool workers are occupied before either parks.
19979          barrier.wait();
19980          // Child task whose wake routes through `schedule()`: the oneshot
19981          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
19982          let child_exec = Arc::clone(&exec);
19983          let (child_runnable, child_task) =
19984            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19985              child_exec.schedule(r)
19986            });
19987          exec.schedule(child_runnable);
19988          let mut inner = std::pin::pin!(async move {
19989            assert_eq!(child_task.await, 9usize);
19990          });
19991          exec.block_on(inner.as_mut());
19992        };
19993        let scheduler = Arc::clone(&executor);
19994        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19995        executor.schedule(runnable);
19996        tasks.push(task);
19997      }
19998
19999      // Give both workers time to poll their child Pending and park in block_on.
20000      std::thread::sleep(Duration::from_millis(200));
20001      for tx in senders {
20002        let _ = tx.send(9usize);
20003      }
20004
20005      for task in tasks {
20006        futures::executor::block_on(task);
20007      }
20008      let _ = done_tx.send(());
20009    });
20010
20011    match done_rx.recv_timeout(Duration::from_secs(10)) {
20012      Ok(()) => runner.join().unwrap(),
20013      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
20014    }
20015  }
20016
20017  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
20018  /// Used to observe executor-internal states (e.g. "both drivers are
20019  /// registered as parked") without sleeps. The bound is a HANG detector,
20020  /// not a latency assertion: starved CI runners can stall fresh threads for
20021  /// hundreds of milliseconds, so it is deliberately generous.
20022  #[cfg(napi_runtime_os_threads)]
20023  fn wait_until(what: &str, condition: impl Fn() -> bool) {
20024    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
20025    while !condition() {
20026      assert!(
20027        std::time::Instant::now() < deadline,
20028        "timed out waiting for: {what}"
20029      );
20030      std::thread::yield_now();
20031    }
20032  }
20033
20034  /// Bounded join for harness threads that already signalled completion of
20035  /// their assertions: everything left on the thread is teardown (executor
20036  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
20037  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
20038  /// into a silent libtest-worker hang for the full CI step timeout (observed
20039  /// on Windows in run 29195160953: several park-deadline-family tests never
20040  /// completed while every in-test assertion had already passed). Fail loudly
20041  /// and NAME the awaited phase instead.
20042  #[cfg(napi_runtime_os_threads)]
20043  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
20044    let deadline = std::time::Instant::now() + timeout;
20045    while !handle.is_finished() {
20046      assert!(
20047        std::time::Instant::now() < deadline,
20048        "thread wedged past its {timeout:?} bound while: {what}"
20049      );
20050      std::thread::sleep(Duration::from_millis(10));
20051    }
20052    handle.join().unwrap();
20053  }
20054
20055  /// Whether this toolchain's std runs `thread_local!` destructors when a
20056  /// thread exits, before `join()` returns. Rust 1.99.0 stopped doing that
20057  /// on `wasm32-wasip1-threads`: rust-lang/rust#160868 moved the target into
20058  /// the "leak everything" TLS guard (`sys/thread_local/mod.rs`), so no
20059  /// destructor runs at all and there is no destructor completion for the
20060  /// worker-join barrier to include. Rust 1.98 and earlier ran them through a
20061  /// pthread key, which `pthread_join` waits for.
20062  #[cfg(napi_runtime_wasi_threads)]
20063  fn std_runs_tls_destructors_before_join() -> bool {
20064    struct Probe(Arc<AtomicBool>);
20065    impl Drop for Probe {
20066      fn drop(&mut self) {
20067        self.0.store(true, Ordering::SeqCst);
20068      }
20069    }
20070    thread_local! {
20071      static PROBE: std::cell::RefCell<Option<Probe>> = const { std::cell::RefCell::new(None) };
20072    }
20073    let ran = Arc::new(AtomicBool::new(false));
20074    let probe = Probe(Arc::clone(&ran));
20075    std::thread::spawn(move || PROBE.with(|slot| *slot.borrow_mut() = Some(probe)))
20076      .join()
20077      .unwrap();
20078    ran.load(Ordering::SeqCst)
20079  }
20080
20081  #[cfg(napi_runtime_os_threads)]
20082  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
20083    executor: &Arc<MultiThreadExecutor>,
20084    start_counter: &AtomicU64,
20085    claim: impl FnOnce() -> T + Send + 'static,
20086  ) -> T {
20087    use std::sync::mpsc;
20088
20089    let (claimed_tx, claimed_rx) = mpsc::channel();
20090    let (release_claim_tx, release_claim_rx) = mpsc::channel();
20091    let (started_tx, started_rx) = mpsc::channel();
20092    let (release_start_tx, release_start_rx) = mpsc::channel();
20093    let starts_before = start_counter.load(Ordering::SeqCst);
20094    let worker = std::thread::spawn(move || {
20095      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
20096        *slot.borrow_mut() = Some(Box::new(move || {
20097          claimed_tx.send(()).unwrap();
20098          release_claim_rx.recv().unwrap();
20099        }));
20100      });
20101      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
20102        *slot.borrow_mut() = Some(Box::new(move || {
20103          started_tx.send(()).unwrap();
20104          release_start_rx.recv().unwrap();
20105        }));
20106      });
20107      claim()
20108    });
20109    claimed_rx
20110      .recv_timeout(Duration::from_secs(2))
20111      .expect("the scheduler claim must pause after removing work");
20112
20113    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20114    let verdict = executor.try_begin_deadlock_verdict();
20115    let claim_verdict_rejected = verdict.is_none();
20116    drop(verdict);
20117    release_claim_tx.send(()).unwrap();
20118
20119    started_rx
20120      .recv_timeout(Duration::from_secs(2))
20121      .expect("the scheduler claim must pause after publishing its start");
20122    let admission_active_after_start =
20123      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20124    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
20125    let verdict = executor.try_begin_deadlock_verdict();
20126    let start_verdict_rejected = verdict.is_none();
20127    drop(verdict);
20128    release_start_tx.send(()).unwrap();
20129    let output = worker.join().unwrap();
20130
20131    assert!(
20132      admission_active,
20133      "removing scheduler work must retain a detector admission until start accounting"
20134    );
20135    assert!(
20136      claim_verdict_rejected,
20137      "a deadline verdict must not close while removed work has not published its start"
20138    );
20139    assert!(
20140      start_published,
20141      "work start accounting must publish before admission retires"
20142    );
20143    assert!(
20144      admission_active_after_start,
20145      "the detector admission must remain active until after start accounting"
20146    );
20147    assert!(
20148      start_verdict_rejected,
20149      "a deadline verdict must not close between start accounting and admission retirement"
20150    );
20151    output
20152  }
20153
20154  #[cfg(napi_runtime_os_threads)]
20155  #[test]
20156  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
20157    let executor =
20158      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
20159
20160    let fifo_ran = Arc::new(AtomicBool::new(false));
20161    let fifo_task_ran = Arc::clone(&fifo_ran);
20162    let (fifo, fifo_task) = async_task::spawn(
20163      async move {
20164        fifo_task_ran.store(true, Ordering::SeqCst);
20165      },
20166      |_| {},
20167    );
20168    executor.metrics.runnable_scheduled();
20169    executor.push_fifo_raw(fifo);
20170    let fifo_executor = Arc::clone(&executor);
20171    assert!(run_claim_with_deadlock_gate_assertion(
20172      &executor,
20173      &executor.metrics.runnable_polls,
20174      move || {
20175        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20176        fifo_executor.run_one_fifo_runnable()
20177      }
20178    ));
20179    futures::executor::block_on(fifo_task);
20180    assert!(fifo_ran.load(Ordering::SeqCst));
20181
20182    let fifo_order = Arc::new(Mutex::new(Vec::new()));
20183    let mut fifo_tasks = Vec::new();
20184    for name in ["first", "second"] {
20185      let task_order = Arc::clone(&fifo_order);
20186      let (runnable, task) = async_task::spawn(
20187        async move {
20188          task_order.lock().unwrap().push(name);
20189        },
20190        |_| {},
20191      );
20192      executor.metrics.runnable_scheduled();
20193      executor.push_fifo_raw(runnable);
20194      fifo_tasks.push(task);
20195    }
20196    for _ in 0..2 {
20197      let fifo_executor = Arc::clone(&executor);
20198      assert!(run_claim_with_deadlock_gate_assertion(
20199        &executor,
20200        &executor.metrics.runnable_polls,
20201        move || {
20202          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20203          fifo_executor.run_one_runnable()
20204        }
20205      ));
20206    }
20207    for task in fifo_tasks {
20208      futures::executor::block_on(task);
20209    }
20210    assert_eq!(
20211      *fifo_order.lock().unwrap(),
20212      ["first", "second"],
20213      "the detector-enabled normal fallback must preserve FIFO order"
20214    );
20215
20216    let lifo_ran = Arc::new(AtomicBool::new(false));
20217    let lifo_task_ran = Arc::clone(&lifo_ran);
20218    let (lifo, lifo_task) = async_task::spawn(
20219      async move {
20220        lifo_task_ran.store(true, Ordering::SeqCst);
20221      },
20222      |_| {},
20223    );
20224    executor.metrics.runnable_scheduled();
20225    let lifo_executor = Arc::clone(&executor);
20226    assert!(run_claim_with_deadlock_gate_assertion(
20227      &executor,
20228      &executor.metrics.runnable_polls,
20229      move || {
20230        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
20231        LIFO_SLOT.with(|slot| {
20232          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
20233        });
20234        lifo_executor.run_one_runnable()
20235      }
20236    ));
20237    futures::executor::block_on(lifo_task);
20238    assert!(lifo_ran.load(Ordering::SeqCst));
20239
20240    executor.begin_shutdown();
20241    executor.wait_until_scheduler_idle();
20242  }
20243
20244  #[cfg(napi_runtime_os_threads)]
20245  #[test]
20246  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
20247    let executor =
20248      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
20249
20250    let fifo_ran = Arc::new(AtomicBool::new(false));
20251    let fifo_job_ran = Arc::clone(&fifo_ran);
20252    executor.metrics.blocking_scheduled();
20253    executor
20254      .blocking_queue
20255      .lock()
20256      .unwrap()
20257      .push(QueuedBlocking {
20258        id: BlockingJobId(u64::MAX - 90),
20259        run: Box::new(move || {
20260          fifo_job_ran.store(true, Ordering::SeqCst);
20261        }),
20262      });
20263    let fifo_executor = Arc::clone(&executor);
20264    assert!(run_claim_with_deadlock_gate_assertion(
20265      &executor,
20266      &executor.metrics.blocking_tasks_started,
20267      move || {
20268        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20269        fifo_executor.run_one_blocking()
20270      }
20271    ));
20272    assert!(fifo_ran.load(Ordering::SeqCst));
20273
20274    let owner = executor.fresh_owner_token();
20275    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20276    let exact_job = BlockingJobId(u64::MAX - 91);
20277    let exact_ran = Arc::new(AtomicBool::new(false));
20278    let exact_job_ran = Arc::clone(&exact_ran);
20279    executor.metrics.blocking_scheduled();
20280    executor
20281      .blocking_queue
20282      .lock()
20283      .unwrap()
20284      .push(QueuedBlocking {
20285        id: exact_job,
20286        run: Box::new(move || {
20287          exact_job_ran.store(true, Ordering::SeqCst);
20288        }),
20289      });
20290    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
20291    dependency.set_owned(BlockingDependency {
20292      job: exact_job,
20293      owner: Some(owner),
20294    });
20295    let exact_executor = Arc::clone(&executor);
20296    assert!(run_claim_with_deadlock_gate_assertion(
20297      &executor,
20298      &executor.metrics.blocking_tasks_started,
20299      move || {
20300        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
20301        let _owner = BlockingOwnerGuard::enter(Some(owner));
20302        exact_executor.try_owned_blocking_over_cap(&dependency)
20303      }
20304    ));
20305    assert!(exact_ran.load(Ordering::SeqCst));
20306
20307    drop(active_owner);
20308    executor.begin_shutdown();
20309    executor.wait_until_scheduler_idle();
20310  }
20311
20312  #[cfg(napi_runtime_os_threads)]
20313  #[test]
20314  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
20315    // Empty-registry behavior: with nothing registered, `wake_one` must return
20316    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
20317    // the common case, since queue-wakes fire on every schedule while drivers
20318    // park only in rare re-entrant `block_on`). Proxy for "did not take the
20319    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
20320    // anyway, while (control) a wake_one issued with a parker registered must
20321    // take the slow path and block on that held lock until it is released.
20322    use std::sync::mpsc;
20323    use std::time::Duration;
20324
20325    let registry = Arc::new(ParkedDrivers::default());
20326
20327    // Fast path: empty registry -> wake_one completes although the lock is held.
20328    let guard = registry.parked.lock().unwrap();
20329    let (done_tx, done_rx) = mpsc::channel();
20330    let waker_thread = {
20331      let registry = Arc::clone(&registry);
20332      std::thread::spawn(move || {
20333        done_tx.send(registry.wake_one()).unwrap();
20334      })
20335    };
20336    let woke = done_rx
20337      .recv_timeout(Duration::from_secs(5))
20338      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20339    assert!(!woke, "an empty registry has nobody to wake");
20340    waker_thread.join().unwrap();
20341    drop(guard);
20342
20343    // Control: with a parker registered the SAME held lock must block
20344    // wake_one (slow path). This proves the fast path above genuinely skipped
20345    // the lock rather than wake_one never locking at all.
20346    let parker = Arc::new(DriverParker::default());
20347    registry.register(&parker);
20348    let guard = registry.parked.lock().unwrap();
20349    let (done_tx, done_rx) = mpsc::channel();
20350    let waker_thread = {
20351      let registry = Arc::clone(&registry);
20352      std::thread::spawn(move || {
20353        done_tx.send(registry.wake_one()).unwrap();
20354      })
20355    };
20356    assert!(
20357      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20358      "wake_one with a registered parker must take the slow path (block on the held lock)"
20359    );
20360    drop(guard);
20361    let woke = done_rx
20362      .recv_timeout(Duration::from_secs(5))
20363      .expect("slow-path wake_one must complete once the lock is released");
20364    assert!(
20365      woke,
20366      "the slow path must pop and wake the registered parker"
20367    );
20368    waker_thread.join().unwrap();
20369    // The popped parker received its permit: a park now returns immediately
20370    // instead of sleeping (bounded by the test harness if broken).
20371    parker.park();
20372    assert_eq!(
20373      registry.count.load(Ordering::SeqCst),
20374      0,
20375      "wake_one deregisters what it pops"
20376    );
20377  }
20378
20379  #[cfg(napi_runtime_os_threads)]
20380  #[test]
20381  fn driver_parker_permit_granted_while_running_is_not_lost() {
20382    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
20383    // parked) must be stored as a permit that makes the next `park` return
20384    // immediately; multiple such wakes coalesce into ONE permit.
20385    use std::sync::mpsc;
20386    use std::time::Duration;
20387
20388    let parker = Arc::new(DriverParker::default());
20389    // Two wakes while "running" (nobody parked yet).
20390    parker.unpark();
20391    parker.unpark();
20392
20393    let (tx, rx) = mpsc::channel();
20394    let driver = {
20395      let parker = Arc::clone(&parker);
20396      std::thread::spawn(move || {
20397        parker.park(); // must consume the stored permit without sleeping
20398        tx.send(1).unwrap();
20399        parker.park(); // no permit left: must genuinely sleep
20400        tx.send(2).unwrap();
20401      })
20402    };
20403
20404    assert_eq!(
20405      rx.recv_timeout(Duration::from_secs(5))
20406        .expect("a stored permit must not be lost"),
20407      1
20408    );
20409    assert!(
20410      rx.recv_timeout(Duration::from_millis(300)).is_err(),
20411      "two pre-park wakes must coalesce into one permit (second park must sleep)"
20412    );
20413    parker.unpark();
20414    assert_eq!(
20415      rx.recv_timeout(Duration::from_secs(5))
20416        .expect("unpark must wake a sleeping driver"),
20417      2
20418    );
20419    driver.join().unwrap();
20420  }
20421
20422  #[cfg(napi_runtime_os_threads)]
20423  #[test]
20424  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20425    use std::sync::mpsc;
20426
20427    let parker = Arc::new(DriverParker::default());
20428    assert!(
20429      Instant::now().checked_add(Duration::MAX).is_none(),
20430      "Duration::MAX must exercise the unrepresentable-deadline path"
20431    );
20432    let (done_tx, done_rx) = mpsc::channel();
20433    let driver = {
20434      let parker = Arc::clone(&parker);
20435      std::thread::spawn(move || {
20436        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20437      })
20438    };
20439    wait_until("the unbounded timeout driver to sleep", || {
20440      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20441    });
20442    assert!(
20443      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20444      "an unrepresentable timeout must not expire immediately"
20445    );
20446
20447    let unparker = {
20448      let parker = Arc::clone(&parker);
20449      std::thread::spawn(move || parker.unpark())
20450    };
20451    assert!(
20452      done_rx
20453        .recv_timeout(Duration::from_secs(1))
20454        .expect("an unrepresentable timeout must remain wakeable"),
20455      "a permit must be reported as a wake"
20456    );
20457    unparker.join().unwrap();
20458    driver.join().unwrap();
20459  }
20460
20461  #[cfg(napi_runtime_os_threads)]
20462  #[test]
20463  fn parked_drivers_wake_one_wakes_exactly_one() {
20464    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
20465    // other stays parked. Targeted wakes must neither wake everyone nor lose
20466    // the wake.
20467    use std::sync::mpsc;
20468    use std::time::Duration;
20469
20470    let registry = Arc::new(ParkedDrivers::default());
20471    let (woke_tx, woke_rx) = mpsc::channel();
20472    let mut drivers = Vec::new();
20473    for index in 0..2 {
20474      let registry = Arc::clone(&registry);
20475      let woke_tx = woke_tx.clone();
20476      drivers.push(std::thread::spawn(move || {
20477        let parker = Arc::new(DriverParker::default());
20478        registry.register(&parker);
20479        parker.park();
20480        registry.deregister(&parker);
20481        woke_tx.send(index).unwrap();
20482      }));
20483    }
20484    wait_until("both drivers registered as parked", || {
20485      registry.count.load(Ordering::SeqCst) == 2
20486    });
20487
20488    assert!(registry.wake_one(), "wake_one must wake a parked driver");
20489    woke_rx
20490      .recv_timeout(Duration::from_secs(5))
20491      .expect("exactly one driver must wake");
20492    assert!(
20493      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20494      "the second driver must stay parked after a single wake_one"
20495    );
20496    assert_eq!(
20497      registry.count.load(Ordering::SeqCst),
20498      1,
20499      "one driver must remain registered"
20500    );
20501
20502    assert!(
20503      registry.wake_one(),
20504      "a second wake_one must wake the remaining driver"
20505    );
20506    woke_rx
20507      .recv_timeout(Duration::from_secs(5))
20508      .expect("the second driver must wake");
20509    for driver in drivers {
20510      driver.join().unwrap();
20511    }
20512  }
20513
20514  #[cfg(napi_runtime_os_threads)]
20515  #[test]
20516  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20517    use std::sync::mpsc;
20518
20519    struct ReenterRegistryOnDrop {
20520      registry: Arc<ParkedDrivers>,
20521      dropped: mpsc::Sender<()>,
20522    }
20523
20524    #[expect(
20525      clippy::manual_noop_waker,
20526      reason = "the test observes the waker payload's Drop"
20527    )]
20528    impl std::task::Wake for ReenterRegistryOnDrop {
20529      fn wake(self: Arc<Self>) {}
20530    }
20531
20532    impl Drop for ReenterRegistryOnDrop {
20533      fn drop(&mut self) {
20534        let parker = Arc::new(DriverParker::default());
20535        self.registry.register(&parker);
20536        self.registry.deregister(&parker);
20537        self.dropped.send(()).unwrap();
20538      }
20539    }
20540
20541    let registry = Arc::new(ParkedDrivers::default());
20542    let dependency = Arc::new(TaskDependency::default());
20543    let (dropped_tx, dropped_rx) = mpsc::channel();
20544    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20545      registry: Arc::clone(&registry),
20546      dropped: dropped_tx,
20547    }));
20548    dependency.register_waiter(&waiter);
20549    drop(waiter);
20550
20551    let parker = Arc::new(DriverParker::default());
20552    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20553    drop(dependency);
20554
20555    let retiring_registry = Arc::clone(&registry);
20556    let retiring_parker = Arc::clone(&parker);
20557    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20558    dropped_rx
20559      .recv_timeout(Duration::from_secs(1))
20560      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20561    retiring.join().unwrap();
20562  }
20563
20564  #[cfg(napi_runtime_os_threads)]
20565  #[test]
20566  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20567    use std::sync::mpsc;
20568
20569    let registry = Arc::new(ParkedDrivers::default());
20570    let owner = BlockingOwnerToken {
20571      executor_id: u64::MAX - 70,
20572      frame: u64::MAX - 71,
20573    };
20574    let dependency = Arc::new(TaskDependency::default());
20575    dependency.set_owned(BlockingDependency {
20576      job: BlockingJobId(u64::MAX - 72),
20577      owner: Some(owner),
20578    });
20579    let parker = Arc::new(DriverParker::default());
20580    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20581
20582    let (predicate_tx, predicate_rx) = mpsc::channel();
20583    let (release_tx, release_rx) = mpsc::channel();
20584    let selecting_registry = Arc::clone(&registry);
20585    let selector = std::thread::spawn(move || {
20586      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20587        *slot.borrow_mut() = Some(Box::new(move || {
20588          predicate_tx.send(()).unwrap();
20589          release_rx.recv().unwrap();
20590        }));
20591      });
20592      selecting_registry.wake_one_owner_dependency(owner)
20593    });
20594
20595    predicate_rx
20596      .recv_timeout(Duration::from_secs(2))
20597      .expect("owner selection must reach the dependency predicate");
20598    let registry_was_unlocked = registry.parked.try_lock().is_ok();
20599    release_tx.send(()).unwrap();
20600    assert!(
20601      selector.join().unwrap(),
20602      "the exact-owner parker must still be selected"
20603    );
20604
20605    assert!(
20606      registry_was_unlocked,
20607      "owner dependency predicates must not run while the parked-driver registry is locked"
20608    );
20609    assert!(parker.consume_permit());
20610    assert_eq!(parker.take_owner_handoff(), Some(owner));
20611  }
20612
20613  #[cfg(napi_runtime_os_threads)]
20614  #[test]
20615  fn owner_handoff_skips_newer_untagged_dependency() {
20616    let registry = ParkedDrivers::default();
20617    let owner = BlockingOwnerToken {
20618      executor_id: u64::MAX - 1,
20619      frame: u64::MAX - 2,
20620    };
20621    let owned_dependency = Arc::new(TaskDependency::default());
20622    owned_dependency.set_owned(BlockingDependency {
20623      job: BlockingJobId(u64::MAX - 3),
20624      owner: Some(owner),
20625    });
20626    let untagged_dependency = Arc::new(TaskDependency::default());
20627    untagged_dependency.set_owned(BlockingDependency {
20628      job: BlockingJobId(u64::MAX - 4),
20629      owner: None,
20630    });
20631    let owned = Arc::new(DriverParker::default());
20632    let untagged = Arc::new(DriverParker::default());
20633
20634    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20635    // Register the unrelated dependency last so an unfiltered LIFO retry
20636    // would consume the exact owner handoff.
20637    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20638
20639    assert!(registry.wake_one_owner_dependency(owner));
20640    assert!(
20641      owned.consume_permit(),
20642      "the exact owner dependency must receive the retry"
20643    );
20644    assert!(
20645      !untagged.consume_permit(),
20646      "an untagged dependency must not consume an exact owner handoff"
20647    );
20648    registry.deregister(&untagged);
20649  }
20650
20651  #[cfg(napi_runtime_os_threads)]
20652  #[test]
20653  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20654    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20655    let owner = executor.fresh_owner_token();
20656    let valid_dependency = Arc::new(TaskDependency::default());
20657    valid_dependency.set_owned(BlockingDependency {
20658      job: BlockingJobId(u64::MAX - 45),
20659      owner: Some(owner),
20660    });
20661    let withdrawn_dependency = Arc::new(TaskDependency::default());
20662    withdrawn_dependency.set_owned(BlockingDependency {
20663      job: BlockingJobId(u64::MAX - 46),
20664      owner: Some(owner),
20665    });
20666    let valid = Arc::new(DriverParker::default());
20667    let withdrawn = Arc::new(DriverParker::default());
20668
20669    executor
20670      .parked_drivers
20671      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20672    executor.parked_drivers.register_with_role(
20673      &withdrawn,
20674      true,
20675      Some(Arc::clone(&withdrawn_dependency)),
20676    );
20677    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20678    withdrawn_dependency.clear();
20679
20680    assert_eq!(
20681      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20682      Some(false),
20683      "a selected dependency withdrawn before retry must forward the owner handoff"
20684    );
20685    assert!(
20686      valid.consume_permit(),
20687      "the next live same-owner dependency must receive the forwarded wake"
20688    );
20689    assert_eq!(
20690      valid.take_owner_handoff(),
20691      Some(owner),
20692      "the forwarded wake must retain the exact owner identity"
20693    );
20694  }
20695
20696  #[cfg(napi_runtime_os_threads)]
20697  #[test]
20698  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20699    use std::sync::mpsc;
20700
20701    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20702    let owner = executor.fresh_owner_token();
20703    let fallback_dependency = Arc::new(TaskDependency::default());
20704    fallback_dependency.set_owned(BlockingDependency {
20705      job: BlockingJobId(u64::MAX - 47),
20706      owner: Some(owner),
20707    });
20708    let exiting_dependency = Arc::new(TaskDependency::default());
20709    exiting_dependency.set_owned(BlockingDependency {
20710      job: BlockingJobId(u64::MAX - 48),
20711      owner: Some(owner),
20712    });
20713    let fallback = Arc::new(DriverParker::default());
20714    let exiting = Arc::new(DriverParker::default());
20715
20716    executor.parked_drivers.register_with_role(
20717      &fallback,
20718      true,
20719      Some(Arc::clone(&fallback_dependency)),
20720    );
20721    executor.parked_drivers.register_with_role(
20722      &exiting,
20723      true,
20724      Some(Arc::clone(&exiting_dependency)),
20725    );
20726
20727    let (selected_tx, selected_rx) = mpsc::channel();
20728    let (release_selection_tx, release_selection_rx) = mpsc::channel();
20729    let selector_executor = Arc::clone(&executor);
20730    let selector = std::thread::spawn(move || {
20731      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20732        *slot.borrow_mut() = Some(Box::new(move || {
20733          selected_tx.send(()).unwrap();
20734          release_selection_rx.recv().unwrap();
20735        }));
20736      });
20737      assert!(
20738        selector_executor
20739          .parked_drivers
20740          .wake_one_owner_dependency(owner)
20741      );
20742    });
20743    selected_rx
20744      .recv_timeout(Duration::from_secs(2))
20745      .expect("selection must pause after removing the exact-owner parker");
20746
20747    let (exited_tx, exited_rx) = mpsc::channel();
20748    let exiting_executor = Arc::clone(&executor);
20749    let exiting_driver = Arc::clone(&exiting);
20750    let driver = std::thread::spawn(move || {
20751      exiting_driver.park();
20752      exiting_executor.parked_drivers.deregister(&exiting_driver);
20753      exiting_executor.forward_owner_handoff(&exiting_driver);
20754      exited_tx.send(()).unwrap();
20755    });
20756    exiting.unpark();
20757    exited_rx
20758      .recv_timeout(Duration::from_secs(2))
20759      .expect("the direct future wake must let the selected driver exit");
20760
20761    release_selection_tx.send(()).unwrap();
20762    selector.join().unwrap();
20763    driver.join().unwrap();
20764
20765    assert!(
20766      fallback.consume_permit(),
20767      "an exiting selected driver must forward the owner handoff to the next dependency"
20768    );
20769    assert_eq!(
20770      fallback.take_owner_handoff(),
20771      Some(owner),
20772      "the forwarded handoff must retain the exact owner identity"
20773    );
20774  }
20775
20776  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20777  #[test]
20778  fn cooperative_panic_forwards_pending_owner_handoff() {
20779    use std::sync::mpsc;
20780
20781    struct PanicAfterWake {
20782      dependency: BlockingDependency,
20783      first_poll: Option<mpsc::Sender<()>>,
20784    }
20785
20786    impl Future for PanicAfterWake {
20787      type Output = ();
20788
20789      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20790        if let Some(first_poll) = self.first_poll.take() {
20791          // This shape never retires its publication -- the driver panics
20792          // instead -- so the returned token is dropped straight away.
20793          let mut published = PublishedBlockingDependencies::default();
20794          record_blocking_dependency(&mut published, self.dependency);
20795          first_poll.send(()).unwrap();
20796          return Poll::Pending;
20797        }
20798        panic!("intentional cooperative driver panic after owner handoff");
20799      }
20800    }
20801
20802    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20803    let owner = executor.fresh_owner_token();
20804    let fallback_dependency = Arc::new(TaskDependency::default());
20805    fallback_dependency.set_owned(BlockingDependency {
20806      job: BlockingJobId(u64::MAX - 49),
20807      owner: Some(owner),
20808    });
20809    let fallback = Arc::new(DriverParker::default());
20810    executor.parked_drivers.register_with_role(
20811      &fallback,
20812      true,
20813      Some(Arc::clone(&fallback_dependency)),
20814    );
20815
20816    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20817    let (panicked_tx, panicked_rx) = mpsc::channel();
20818    let driver_executor = Arc::clone(&executor);
20819    let driver = std::thread::spawn(move || {
20820      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20821      let mut future = std::pin::pin!(PanicAfterWake {
20822        dependency: BlockingDependency {
20823          job: BlockingJobId(u64::MAX - 50),
20824          owner: Some(owner)
20825        },
20826        first_poll: Some(first_poll_tx),
20827      });
20828      let panicked = catch_unwind(AssertUnwindSafe(|| {
20829        driver_executor.block_on(future.as_mut());
20830      }))
20831      .is_err();
20832      panicked_tx.send(panicked).unwrap();
20833    });
20834
20835    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20836    wait_until("the panicking cooperative driver to park", || {
20837      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20838    });
20839    assert!(
20840      executor.parked_drivers.wake_one_owner_dependency(owner),
20841      "the exact owner handoff must select the cooperative driver"
20842    );
20843    assert!(
20844      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20845      "the test future must unwind from its second poll"
20846    );
20847    driver.join().unwrap();
20848
20849    assert!(
20850      fallback.consume_permit(),
20851      "panic cleanup must forward the unconsumed owner handoff"
20852    );
20853    assert_eq!(
20854      fallback.take_owner_handoff(),
20855      Some(owner),
20856      "panic cleanup must preserve the exact owner identity"
20857    );
20858  }
20859
20860  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20861  #[test]
20862  fn cooperative_panic_flushes_lifo_slot() {
20863    struct ScheduleThenPanic {
20864      executor: Arc<MultiThreadExecutor>,
20865      ran: Arc<AtomicBool>,
20866    }
20867
20868    impl Future for ScheduleThenPanic {
20869      type Output = ();
20870
20871      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20872        let ran = Arc::clone(&self.ran);
20873        let scheduler = Arc::clone(&self.executor);
20874        let (runnable, task) = async_task::spawn(
20875          async move {
20876            ran.store(true, Ordering::SeqCst);
20877          },
20878          move |runnable| scheduler.schedule(runnable),
20879        );
20880        self.executor.schedule(runnable);
20881        task.detach();
20882        panic!("intentional cooperative driver panic with a populated LIFO slot");
20883      }
20884    }
20885
20886    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20887    executor
20888      .active_drainers
20889      .store(executor.max_drainers, Ordering::SeqCst);
20890    let ran = Arc::new(AtomicBool::new(false));
20891    let panicked = {
20892      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20893      let mut future = std::pin::pin!(ScheduleThenPanic {
20894        executor: Arc::clone(&executor),
20895        ran: Arc::clone(&ran),
20896      });
20897      catch_unwind(AssertUnwindSafe(|| {
20898        executor.block_on(future.as_mut());
20899      }))
20900      .is_err()
20901    };
20902
20903    assert!(panicked, "the test future must unwind from its first poll");
20904    assert!(
20905      executor.pop_lifo_slot().is_none(),
20906      "panic cleanup must empty the LIFO slot"
20907    );
20908    assert_eq!(
20909      executor.injector.len(),
20910      1,
20911      "panic cleanup must move the slot runnable to the shared FIFO"
20912    );
20913    assert!(
20914      executor.run_one(),
20915      "the flushed runnable must remain executable"
20916    );
20917    assert!(ran.load(Ordering::SeqCst));
20918    executor.active_drainers.store(0, Ordering::SeqCst);
20919  }
20920
20921  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20922  #[test]
20923  fn cooperative_panic_compensates_absorbed_queue_wake() {
20924    use std::sync::mpsc;
20925
20926    struct PanicAfterWake {
20927      first_poll: Option<mpsc::Sender<()>>,
20928    }
20929
20930    impl Future for PanicAfterWake {
20931      type Output = ();
20932
20933      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20934        if let Some(first_poll) = self.first_poll.take() {
20935          first_poll.send(()).unwrap();
20936          return Poll::Pending;
20937        }
20938        panic!("intentional cooperative driver panic after a queue wake");
20939      }
20940    }
20941
20942    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20943    executor
20944      .active_drainers
20945      .store(executor.max_drainers, Ordering::SeqCst);
20946    let fallback = Arc::new(DriverParker::default());
20947    executor.parked_drivers.register(&fallback);
20948    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20949    let (panicked_tx, panicked_rx) = mpsc::channel();
20950    let driver_executor = Arc::clone(&executor);
20951    let driver = std::thread::spawn(move || {
20952      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20953      let mut future = std::pin::pin!(PanicAfterWake {
20954        first_poll: Some(first_poll_tx)
20955      });
20956      let panicked = catch_unwind(AssertUnwindSafe(|| {
20957        driver_executor.block_on(future.as_mut());
20958      }))
20959      .is_err();
20960      panicked_tx.send(panicked).unwrap();
20961    });
20962
20963    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20964    wait_until(
20965      "the panicking cooperative driver to park after the fallback",
20966      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20967    );
20968
20969    let ran = Arc::new(AtomicBool::new(false));
20970    let ran_task = Arc::clone(&ran);
20971    let scheduler = Arc::clone(&executor);
20972    let (runnable, task) = async_task::spawn(
20973      async move {
20974        ran_task.store(true, Ordering::SeqCst);
20975      },
20976      move |runnable| scheduler.schedule(runnable),
20977    );
20978    task.detach();
20979    executor.metrics.runnable_scheduled();
20980    executor.push_fifo_raw(runnable);
20981    assert!(
20982      executor.parked_drivers.wake_one(),
20983      "the queued runnable's wake must be absorbed by the cooperative driver"
20984    );
20985
20986    assert!(
20987      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20988      "the test future must unwind after absorbing the queue wake"
20989    );
20990    driver.join().unwrap();
20991    assert!(
20992      fallback.consume_permit(),
20993      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20994    );
20995    assert!(
20996      executor.run_one(),
20997      "the queued runnable must remain executable"
20998    );
20999    assert!(ran.load(Ordering::SeqCst));
21000    executor.active_drainers.store(0, Ordering::SeqCst);
21001  }
21002
21003  #[cfg(napi_runtime_os_threads)]
21004  #[test]
21005  fn multi_thread_future_wake_targets_its_own_parked_driver() {
21006    // Future-wake targeting invariant: a future-wake must reach the driver that
21007    // AWAITS that future, not "some parked driver". D1 parks first awaiting
21008    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
21009    // though D2 parked more recently -- a future-wake misrouted through the
21010    // registry (which pops most-recently-parked) would wake D2, leave D1
21011    // parked forever, and trip the bounded timeout. The old notify_all
21012    // broadcast never distinguished drivers, so this pins the new obligation.
21013    use std::sync::mpsc;
21014    use std::time::Duration;
21015
21016    let (done_tx, done_rx) = mpsc::channel();
21017    let runner = std::thread::spawn(move || {
21018      let metrics = Arc::new(RuntimeMetrics::default());
21019      let options = RuntimeOptions {
21020        flavor: RuntimeFlavor::MultiThread,
21021        worker_threads: 2,
21022        max_blocking_tasks: 2,
21023        thread_name_prefix: "wake-target".to_string(),
21024        park_deadline: None,
21025        drain_linger: DEFAULT_DRAIN_LINGER,
21026      };
21027      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21028
21029      let (tx1, rx1) = oneshot::channel::<usize>();
21030      let (tx2, rx2) = oneshot::channel::<usize>();
21031      let t1_done = Arc::new(AtomicBool::new(false));
21032      let t2_done = Arc::new(AtomicBool::new(false));
21033
21034      // T1: parks FIRST, awaiting rx1.
21035      let t1_exec = Arc::clone(&executor);
21036      let t1_flag = Arc::clone(&t1_done);
21037      let t1_body = async move {
21038        let mut inner = std::pin::pin!(async move {
21039          assert_eq!(rx1.await.unwrap(), 7usize);
21040        });
21041        t1_exec.block_on(inner.as_mut());
21042        t1_flag.store(true, Ordering::SeqCst);
21043      };
21044      let sched1 = Arc::clone(&executor);
21045      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
21046      executor.schedule(runnable1);
21047      wait_until("driver 1 parked", || {
21048        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21049      });
21050
21051      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
21052      // `schedule`, so no wake_one can pop the already-parked D1) and a
21053      // drainer is spawned for it on the free second worker.
21054      let t2_exec = Arc::clone(&executor);
21055      let t2_flag = Arc::clone(&t2_done);
21056      let t2_body = async move {
21057        let mut inner = std::pin::pin!(async move {
21058          assert_eq!(rx2.await.unwrap(), 9usize);
21059        });
21060        t2_exec.block_on(inner.as_mut());
21061        t2_flag.store(true, Ordering::SeqCst);
21062      };
21063      let sched2 = Arc::clone(&executor);
21064      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
21065      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21066      executor.push_fifo_raw(runnable2);
21067      executor.ensure_drainer();
21068      wait_until("driver 2 parked", || {
21069        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
21070      });
21071
21072      // Complete D1's future. The wake must land on D1 (first-parked), the
21073      // driver that owns rx1's waker -- not on the more recently parked D2.
21074      tx1.send(7usize).unwrap();
21075      wait_until("T1 completed via its own driver's wake", || {
21076        t1_done.load(Ordering::SeqCst)
21077      });
21078      futures::executor::block_on(task1);
21079      assert!(
21080        !t2_done.load(Ordering::SeqCst),
21081        "D2 must still be parked: its future was never completed"
21082      );
21083      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
21084      // linger): its linger parker joins the registry alongside D2 for the
21085      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
21086      // in the window between the cooperative parker's wake-deregistration
21087      // and the drain frame's linger registration, then 2 while lingering,
21088      // then 1 again after expiry -- so a `count == 1` wait can return
21089      // during the transient window and prove nothing about the linger.
21090      // Observe the lifecycle itself instead, via the test counters that
21091      // `drain` maintains: the linger parker must REGISTER, and its idle
21092      // budget must EXPIRE (`drain_linger_expiries` is incremented only
21093      // after the expiry path deregistered the parker, and no further work
21094      // exists to start another episode), after which the registry has
21095      // deterministically settled: EXACTLY the untargeted driver D2 remains
21096      // (a misrouted wake would have left D1 cooperatively parked instead,
21097      // already excluded by the `t1_done` wait above, and a linger parker
21098      // stranded in the registry would read 2 here).
21099      wait_until(
21100        "the retiring drain frame registered its linger parker",
21101        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
21102      );
21103      wait_until("the lingering drainer's idle budget expired", || {
21104        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
21105      });
21106      assert_eq!(
21107        executor.parked_drivers.count.load(Ordering::SeqCst),
21108        1,
21109        "exactly the untargeted driver must remain parked after the linger expired"
21110      );
21111      assert!(
21112        !t2_done.load(Ordering::SeqCst),
21113        "D2 must still be parked after the lingering drainer expired"
21114      );
21115
21116      // Teardown: release D2 as well.
21117      tx2.send(9usize).unwrap();
21118      futures::executor::block_on(task2);
21119      assert!(t2_done.load(Ordering::SeqCst));
21120      let _ = done_tx.send(());
21121    });
21122
21123    match done_rx.recv_timeout(Duration::from_secs(10)) {
21124      Ok(()) => runner.join().unwrap(),
21125      Err(error) => panic!(
21126        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
21127      ),
21128    }
21129  }
21130
21131  #[cfg(napi_runtime_os_threads)]
21132  #[test]
21133  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
21134    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
21135    // with queue work remaining must hand the wake on because it may have
21136    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
21137    // is parked as a cooperative driver, a runnable is pushed without a wake,
21138    // and the test thread forces a ready future through the cooperative branch.
21139    // Exit compensation is the ONLY mechanism left that can wake the parked
21140    // worker to drain the runnable.
21141    use std::sync::mpsc;
21142    use std::time::Duration;
21143
21144    let (done_tx, done_rx) = mpsc::channel();
21145    let runner = std::thread::spawn(move || {
21146      let metrics = Arc::new(RuntimeMetrics::default());
21147      let options = RuntimeOptions {
21148        flavor: RuntimeFlavor::MultiThread,
21149        worker_threads: 1,
21150        max_blocking_tasks: 1,
21151        thread_name_prefix: "wake-compensate".to_string(),
21152        park_deadline: None,
21153        drain_linger: DEFAULT_DRAIN_LINGER,
21154      };
21155      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21156
21157      // Park the sole worker as a cooperative driver awaiting a gate.
21158      let (gate_tx, gate_rx) = oneshot::channel::<()>();
21159      let td_exec = Arc::clone(&executor);
21160      let td_body = async move {
21161        let mut inner = std::pin::pin!(async move {
21162          gate_rx.await.unwrap();
21163        });
21164        td_exec.block_on(inner.as_mut());
21165      };
21166      let sched = Arc::clone(&executor);
21167      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
21168      executor.schedule(td_runnable);
21169      wait_until("the sole driver parked", || {
21170        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21171      });
21172
21173      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
21174      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
21175      let (ran_tx, ran_rx) = mpsc::channel::<()>();
21176      let sched_stranded = Arc::clone(&executor);
21177      let (stranded_runnable, stranded_task) = async_task::spawn(
21178        async move {
21179          ran_tx.send(()).unwrap();
21180        },
21181        move |r| sched_stranded.schedule(r),
21182      );
21183      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21184      executor.push_fifo_raw(stranded_runnable);
21185      stranded_task.detach();
21186
21187      // Drive a ready future through the cooperative branch on THIS thread
21188      // (fake on-pool marker, as in the executor-scoping test). Its first
21189      // poll is Ready, so the loop performs no work itself -- only the exit
21190      // compensation can hand the queued runnable to the parked driver.
21191      {
21192        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21193        let mut ready = std::pin::pin!(async {});
21194        executor.block_on(ready.as_mut());
21195      }
21196
21197      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
21198        "exit compensation must wake the parked driver to run the stranded queued runnable",
21199      );
21200
21201      // Teardown: release the parked driver.
21202      gate_tx.send(()).unwrap();
21203      futures::executor::block_on(td_task);
21204      let _ = done_tx.send(());
21205    });
21206
21207    match done_rx.recv_timeout(Duration::from_secs(10)) {
21208      Ok(()) => runner.join().unwrap(),
21209      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
21210    }
21211  }
21212
21213  #[cfg(napi_runtime_os_threads)]
21214  #[test]
21215  fn cooperative_exit_compensates_blocking_only_residue() {
21216    use std::sync::mpsc;
21217
21218    struct Controlled {
21219      ready: Arc<AtomicBool>,
21220      entered: Option<mpsc::Sender<()>>,
21221    }
21222
21223    impl Future for Controlled {
21224      type Output = ();
21225
21226      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21227        if let Some(entered) = self.entered.take() {
21228          entered.send(()).unwrap();
21229        }
21230        if self.ready.load(Ordering::SeqCst) {
21231          Poll::Ready(())
21232        } else {
21233          Poll::Pending
21234        }
21235      }
21236    }
21237
21238    let options = RuntimeOptions {
21239      flavor: RuntimeFlavor::MultiThread,
21240      worker_threads: 2,
21241      max_blocking_tasks: 1,
21242      thread_name_prefix: "blocking-exit-compensation".to_string(),
21243      park_deadline: None,
21244      drain_linger: DEFAULT_DRAIN_LINGER,
21245    };
21246    let executor =
21247      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21248    let ready = Arc::new(AtomicBool::new(false));
21249    let (entered_tx, entered_rx) = mpsc::channel();
21250    let (returned_tx, returned_rx) = mpsc::channel();
21251    let (release_tx, release_rx) = mpsc::channel();
21252    let task_exec = Arc::clone(&executor);
21253    let task_ready = Arc::clone(&ready);
21254    let scheduler = Arc::clone(&executor);
21255    let (runnable, task) = async_task::spawn(
21256      async move {
21257        let mut future = std::pin::pin!(Controlled {
21258          ready: task_ready,
21259          entered: Some(entered_tx)
21260        });
21261        task_exec.block_on(future.as_mut());
21262        returned_tx.send(()).unwrap();
21263        release_rx.recv().unwrap();
21264      },
21265      move |runnable| scheduler.schedule(runnable),
21266    );
21267    executor.schedule(runnable);
21268    entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
21269
21270    // The cooperative frame must be PARKED (registered) before the timer task
21271    // is scheduled, so that schedule's wake targets the frame instead of
21272    // spawning a second drainer. A second drainer would later LINGER as a
21273    // registered blocking-capable parker, and `wake_one_blocking` prefers the
21274    // most recently parked candidate: on a starved runner (observed on the
21275    // Windows CI) the linger parker absorbs the blocking wake below, the
21276    // drainer services the job, and the frame -- whose exit compensation is
21277    // the subject under test -- sleeps in its timer-bounded park until the
21278    // 120s heap timer, far past any sane recv bound.
21279    wait_until("the cooperative frame parked", || {
21280      executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21281    });
21282
21283    let timer_exec = Arc::clone(&executor);
21284    let timer_scheduler = Arc::clone(&executor);
21285    let (timer_runnable, timer_task) = async_task::spawn(
21286      async move {
21287        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
21288      },
21289      move |runnable| timer_scheduler.schedule(runnable),
21290    );
21291    executor.schedule(timer_runnable);
21292    wait_until("the dedicated timekeeper parked", || {
21293      executor
21294        .timers
21295        .inner
21296        .lock()
21297        .unwrap()
21298        .timekeeper_parker
21299        .is_some()
21300    });
21301    // Re-parked after running the timer runnable: the blocking wake below
21302    // must find the frame registered (the sole candidate), so the frame
21303    // absorbs the wake, observes its ready future, and its EXIT COMPENSATION
21304    // services the blocking-only residue.
21305    wait_until(
21306      "the cooperative frame re-parked in its timer-bounded park",
21307      || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
21308    );
21309
21310    ready.store(true, Ordering::SeqCst);
21311    let (blocking_tx, blocking_rx) = mpsc::channel();
21312    let blocking = executor.schedule_blocking(move || {
21313      blocking_tx.send(()).unwrap();
21314    });
21315    // Generous hang-detector bounds, not latency assertions: starved CI
21316    // runners (msys2 CLANG64) can stall a woken thread for whole seconds.
21317    blocking_rx
21318      .recv_timeout(Duration::from_secs(10))
21319      .expect("blocking-only residue must be serviced by a Rayon worker");
21320    returned_rx
21321      .recv_timeout(Duration::from_secs(10))
21322      .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
21323    release_tx.send(()).unwrap();
21324    futures::executor::block_on(task);
21325    futures::executor::block_on(blocking).unwrap();
21326    executor.shutdown_timers();
21327    futures::executor::block_on(timer_task);
21328  }
21329
21330  #[cfg(napi_runtime_os_threads)]
21331  #[test]
21332  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21333    let options = RuntimeOptions {
21334      flavor: RuntimeFlavor::MultiThread,
21335      worker_threads: 2,
21336      max_blocking_tasks: 1,
21337      thread_name_prefix: "stopped-exit-compensation".to_string(),
21338      park_deadline: None,
21339      drain_linger: DEFAULT_DRAIN_LINGER,
21340    };
21341    let executor =
21342      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21343    let ran = Arc::new(AtomicBool::new(false));
21344    let blocking_ran = Arc::clone(&ran);
21345
21346    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
21347    // become observable while this job remains queued, matching the original
21348    // begin_shutdown ordering window exactly.
21349    executor
21350      .active_drainers
21351      .store(executor.max_drainers, Ordering::SeqCst);
21352    let blocking = executor.schedule_blocking(move || {
21353      blocking_ran.store(true, Ordering::SeqCst);
21354    });
21355    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21356
21357    executor.stop.begin_shutdown();
21358    executor.compensate_queued_work_wake(true);
21359    let ran_after_stop = ran.load(Ordering::SeqCst);
21360
21361    executor.active_drainers.store(0, Ordering::SeqCst);
21362    executor.begin_shutdown();
21363    let blocking_result = futures::executor::block_on(blocking);
21364
21365    assert!(
21366      !ran_after_stop,
21367      "exit compensation started queued blocking work after stop"
21368    );
21369    assert!(
21370      blocking_result.is_err(),
21371      "shutdown must cancel the queued blocking job"
21372    );
21373  }
21374
21375  #[cfg(napi_runtime_os_threads)]
21376  #[test]
21377  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21378    // Targeted-wake stress: N rounds of {producer: publish work,
21379    // wake_one()} racing {consumer: check work, register, re-check, park} --
21380    // the exact schedule-vs-park protocol `schedule` and
21381    // `cooperative_block_on` run. This walks the interleaving at every offset
21382    // the OS scheduler produces; a single lost wakeup parks the consumer
21383    // forever with work pending, and the bounded recv_timeout turns that into
21384    // a loud failure instead of a hung suite. (No loom in this repo; std
21385    // threads.)
21386    use std::sync::mpsc;
21387    use std::time::Duration;
21388
21389    const ROUNDS: usize = 20_000;
21390
21391    let (done_tx, done_rx) = mpsc::channel();
21392    let registry = Arc::new(ParkedDrivers::default());
21393    let work = Arc::new(AtomicBool::new(false));
21394
21395    let consumer = {
21396      let registry = Arc::clone(&registry);
21397      let work = Arc::clone(&work);
21398      std::thread::spawn(move || {
21399        let parker = Arc::new(DriverParker::default());
21400        for _ in 0..ROUNDS {
21401          loop {
21402            if work.swap(false, Ordering::SeqCst) {
21403              break;
21404            }
21405            // The cooperative park protocol: register FIRST, then re-check,
21406            // then park (see `ParkedDrivers::wake_one`).
21407            registry.register(&parker);
21408            if work.load(Ordering::SeqCst) {
21409              registry.deregister(&parker);
21410              continue;
21411            }
21412            parker.park();
21413            registry.deregister(&parker);
21414          }
21415        }
21416        done_tx.send(()).unwrap();
21417      })
21418    };
21419
21420    for _ in 0..ROUNDS {
21421      work.store(true, Ordering::SeqCst);
21422      // The schedule side: publish work, then wake one parked driver. When
21423      // wake_one misses (consumer not registered yet), the consumer's
21424      // post-register re-check must see the work instead.
21425      let _ = registry.wake_one();
21426      while work.load(Ordering::SeqCst) {
21427        std::hint::spin_loop();
21428      }
21429    }
21430
21431    done_rx
21432      .recv_timeout(Duration::from_secs(30))
21433      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21434    consumer.join().unwrap();
21435  }
21436
21437  #[cfg(napi_runtime_os_threads)]
21438  #[test]
21439  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21440    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
21441    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
21442    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
21443    let options = RuntimeOptions {
21444      flavor: RuntimeFlavor::MultiThread,
21445      worker_threads: 2,
21446      max_blocking_tasks: 2,
21447      thread_name_prefix: "lifo-order".to_string(),
21448      park_deadline: None,
21449      drain_linger: DEFAULT_DRAIN_LINGER,
21450    };
21451    let executor =
21452      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21453    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21454
21455    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
21456    // could race the manual `run_one` calls below).
21457    let old_order = Arc::clone(&order);
21458    let sched = Arc::clone(&executor);
21459    let (old_runnable, old_task) = async_task::spawn(
21460      async move {
21461        old_order.lock().unwrap().push("old");
21462      },
21463      move |r| sched.schedule(r),
21464    );
21465    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21466    executor.push_fifo_raw(old_runnable);
21467    old_task.detach();
21468
21469    // Simulate being on this executor's pool worker: the schedule below must
21470    // take the slot path (which never touches the FIFO nor spawns a drainer).
21471    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21472    let new_order = Arc::clone(&order);
21473    let sched = Arc::clone(&executor);
21474    let (new_runnable, new_task) = async_task::spawn(
21475      async move {
21476        new_order.lock().unwrap().push("new");
21477      },
21478      move |r| sched.schedule(r),
21479    );
21480    executor.schedule(new_runnable);
21481    new_task.detach();
21482    assert_eq!(
21483      executor.injector.len(),
21484      1,
21485      "the slot-path schedule must not push to the shared FIFO"
21486    );
21487
21488    assert!(executor.run_one(), "run_one must pop the slot first");
21489    assert!(executor.run_one(), "run_one must then pop the FIFO");
21490    assert!(!executor.run_one(), "no work must remain");
21491    assert_eq!(
21492      *order.lock().unwrap(),
21493      vec!["new", "old"],
21494      "the slot runnable (newest) must run before older FIFO work"
21495    );
21496  }
21497
21498  #[cfg(napi_runtime_os_threads)]
21499  #[test]
21500  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21501    let options = RuntimeOptions {
21502      flavor: RuntimeFlavor::MultiThread,
21503      worker_threads: 2,
21504      max_blocking_tasks: 1,
21505      thread_name_prefix: "fifo-owner".to_string(),
21506      park_deadline: None,
21507      drain_linger: DEFAULT_DRAIN_LINGER,
21508    };
21509    let executor =
21510      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21511    let observed_owner = Arc::new(Mutex::new(None));
21512    let observed = Arc::clone(&observed_owner);
21513    let (runnable, task) = async_task::spawn(
21514      async move {
21515        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21516      },
21517      |_| {},
21518    );
21519    task.detach();
21520    executor.metrics.runnable_scheduled();
21521    executor.push_fifo_raw(runnable);
21522    executor.active_drainers.store(1, Ordering::Release);
21523
21524    let owner = executor.fresh_owner_token();
21525    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21526    Arc::clone(&executor).drain();
21527
21528    assert_eq!(
21529      *observed_owner.lock().unwrap(),
21530      Some(None),
21531      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21532    );
21533    assert_eq!(
21534      BLOCKING_OWNER.with(std::cell::Cell::get),
21535      Some(owner),
21536      "the ambient blocking owner must be restored after the runnable"
21537    );
21538  }
21539
21540  #[cfg(napi_runtime_os_threads)]
21541  #[test]
21542  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21543    // Drain integration: with one worker, task P scheduling
21544    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
21545    // jumping ahead of the older queued T1/T2.
21546    use std::sync::mpsc;
21547    use std::time::Duration;
21548
21549    let (done_tx, done_rx) = mpsc::channel();
21550    let runner = std::thread::spawn(move || {
21551      let options = RuntimeOptions {
21552        flavor: RuntimeFlavor::MultiThread,
21553        worker_threads: 1,
21554        max_blocking_tasks: 1,
21555        thread_name_prefix: "lifo-drain".to_string(),
21556        park_deadline: None,
21557        drain_linger: DEFAULT_DRAIN_LINGER,
21558      };
21559      let executor =
21560        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21561      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21562
21563      let p_exec = Arc::clone(&executor);
21564      let p_order = Arc::clone(&order);
21565      let p_body = async move {
21566        p_order.lock().unwrap().push("P");
21567        // Scheduled from the pool worker mid-drain: must land in the slot.
21568        let c_order = Arc::clone(&p_order);
21569        let sched = Arc::clone(&p_exec);
21570        let (c_runnable, c_task) = async_task::spawn(
21571          async move {
21572            c_order.lock().unwrap().push("C");
21573          },
21574          move |r| sched.schedule(r),
21575        );
21576        p_exec.schedule(c_runnable);
21577        c_task.detach();
21578      };
21579
21580      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
21581      // mirroring the `runnable_scheduled` accounting a real schedule does so
21582      // the queued-runnables counter stays balanced.
21583      let mut tasks = Vec::new();
21584      let sched = Arc::clone(&executor);
21585      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21586      executor.metrics.runnable_scheduled();
21587      executor.push_fifo_raw(p_runnable);
21588      tasks.push(p_task);
21589      for name in ["T1", "T2"] {
21590        let t_order = Arc::clone(&order);
21591        let sched = Arc::clone(&executor);
21592        let (t_runnable, t_task) = async_task::spawn(
21593          async move {
21594            t_order.lock().unwrap().push(name);
21595          },
21596          move |r| sched.schedule(r),
21597        );
21598        executor.metrics.runnable_scheduled();
21599        executor.push_fifo_raw(t_runnable);
21600        tasks.push(t_task);
21601      }
21602      executor.ensure_drainer();
21603
21604      for task in tasks {
21605        futures::executor::block_on(task);
21606      }
21607      wait_until("the detached child C ran", || {
21608        order.lock().unwrap().len() == 4
21609      });
21610      assert_eq!(
21611        *order.lock().unwrap(),
21612        vec!["P", "C", "T1", "T2"],
21613        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21614      );
21615      let _ = done_tx.send(());
21616    });
21617
21618    match done_rx.recv_timeout(Duration::from_secs(10)) {
21619      Ok(()) => runner.join().unwrap(),
21620      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21621    }
21622  }
21623
21624  #[cfg(napi_runtime_os_threads)]
21625  #[test]
21626  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21627    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
21628    // NEWEST takes the slot; the displaced occupant must land on the shared
21629    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
21630    // the displacement wake cannot spawn a real drainer racing the manual
21631    // `run_one` calls.
21632    let options = RuntimeOptions {
21633      flavor: RuntimeFlavor::MultiThread,
21634      worker_threads: 2,
21635      max_blocking_tasks: 2,
21636      thread_name_prefix: "lifo-displace".to_string(),
21637      park_deadline: None,
21638      drain_linger: DEFAULT_DRAIN_LINGER,
21639    };
21640    let executor =
21641      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21642    executor
21643      .active_drainers
21644      .store(executor.max_drainers, Ordering::SeqCst);
21645    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21646
21647    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21648    for name in ["a", "b"] {
21649      let n_order = Arc::clone(&order);
21650      let sched = Arc::clone(&executor);
21651      let (runnable, task) = async_task::spawn(
21652        async move {
21653          n_order.lock().unwrap().push(name);
21654        },
21655        move |r| sched.schedule(r),
21656      );
21657      executor.schedule(runnable);
21658      task.detach();
21659    }
21660    assert_eq!(
21661      executor.injector.len(),
21662      1,
21663      "the displaced occupant (a) must have been pushed to the shared FIFO"
21664    );
21665
21666    assert!(executor.run_one(), "run_one must pop the slot (b) first");
21667    assert!(
21668      executor.run_one(),
21669      "run_one must then pop the displaced (a) from the FIFO"
21670    );
21671    assert!(!executor.run_one(), "no work must remain");
21672    assert_eq!(
21673      *order.lock().unwrap(),
21674      vec!["b", "a"],
21675      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21676    );
21677    executor.active_drainers.store(0, Ordering::SeqCst);
21678  }
21679
21680  #[cfg(napi_runtime_os_threads)]
21681  #[test]
21682  fn lifo_slot_is_executor_scoped() {
21683    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
21684    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
21685    // scheduling onto executor B must bypass the slot (B's FIFO); and with
21686    // A's runnable IN the slot, B must neither pop nor displace it.
21687    let options = RuntimeOptions {
21688      flavor: RuntimeFlavor::MultiThread,
21689      worker_threads: 2,
21690      max_blocking_tasks: 2,
21691      thread_name_prefix: "lifo-scope".to_string(),
21692      park_deadline: None,
21693      drain_linger: DEFAULT_DRAIN_LINGER,
21694    };
21695    let exec1 =
21696      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21697    let exec2 =
21698      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21699    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21700    exec1
21701      .active_drainers
21702      .store(exec1.max_drainers, Ordering::SeqCst);
21703    exec2
21704      .active_drainers
21705      .store(exec2.max_drainers, Ordering::SeqCst);
21706    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21707
21708    // As exec1's worker: r1 goes to the slot, tagged exec1.
21709    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21710    let r1_order = Arc::clone(&order);
21711    let sched = Arc::clone(&exec1);
21712    let (r1, t1) = async_task::spawn(
21713      async move {
21714        r1_order.lock().unwrap().push("one");
21715      },
21716      move |r| sched.schedule(r),
21717    );
21718    exec1.schedule(r1);
21719    t1.detach();
21720    assert!(
21721      exec1.injector.is_empty(),
21722      "r1 must sit in the slot, not exec1's FIFO"
21723    );
21724
21725    {
21726      // Nested: now act as exec2's worker while exec1's runnable occupies the
21727      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
21728      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21729      let r2_order = Arc::clone(&order);
21730      let sched = Arc::clone(&exec2);
21731      let (r2, t2) = async_task::spawn(
21732        async move {
21733          r2_order.lock().unwrap().push("two");
21734        },
21735        move |r| sched.schedule(r),
21736      );
21737      exec2.schedule(r2);
21738      t2.detach();
21739      assert_eq!(
21740        exec2.injector.len(),
21741        1,
21742        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21743      );
21744      assert!(
21745        exec2.pop_lifo_slot().is_none(),
21746        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21747      );
21748      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21749    }
21750
21751    // Back as exec1's worker: its slot entry is intact and runnable.
21752    assert!(
21753      exec1.run_one(),
21754      "exec1's slot entry must have survived the foreign activity"
21755    );
21756    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21757    exec1.active_drainers.store(0, Ordering::SeqCst);
21758    exec2.active_drainers.store(0, Ordering::SeqCst);
21759  }
21760
21761  #[cfg(napi_runtime_os_threads)]
21762  #[test]
21763  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21764    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
21765    // next FROM the pool worker (every link lands in the slot) crosses
21766    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
21767    // flushed to the FIFO before `finish_draining` (so its re-check sees the
21768    // runnable and re-arms a drainer); a missing flush strands the chain at
21769    // the first budget boundary (lost task + queued_runnables leak).
21770    use std::sync::mpsc;
21771    use std::time::Duration;
21772
21773    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
21774
21775    fn spawn_link(
21776      executor: &Arc<MultiThreadExecutor>,
21777      remaining: u64,
21778      counter: Arc<AtomicU64>,
21779      done: mpsc::Sender<()>,
21780    ) {
21781      let body_exec = Arc::clone(executor);
21782      let body = async move {
21783        counter.fetch_add(1, Ordering::SeqCst);
21784        if remaining > 0 {
21785          spawn_link(&body_exec, remaining - 1, counter, done);
21786        } else {
21787          done.send(()).unwrap();
21788        }
21789      };
21790      let sched = Arc::clone(executor);
21791      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21792      executor.schedule(runnable);
21793      task.detach();
21794    }
21795
21796    let (done_tx, done_rx) = mpsc::channel();
21797    let runner = std::thread::spawn(move || {
21798      let metrics = Arc::new(RuntimeMetrics::default());
21799      let options = RuntimeOptions {
21800        flavor: RuntimeFlavor::MultiThread,
21801        worker_threads: 1,
21802        max_blocking_tasks: 1,
21803        thread_name_prefix: "lifo-budget".to_string(),
21804        park_deadline: None,
21805        drain_linger: DEFAULT_DRAIN_LINGER,
21806      };
21807      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21808
21809      let counter = Arc::new(AtomicU64::new(0));
21810      let (chain_tx, chain_rx) = mpsc::channel();
21811      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21812
21813      chain_rx
21814        .recv_timeout(Duration::from_secs(10))
21815        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21816      assert_eq!(
21817        counter.load(Ordering::SeqCst),
21818        CHAIN,
21819        "every chain link must have run"
21820      );
21821      wait_until("runnable counters settle to zero (no slot leak)", || {
21822        metrics.queued_runnables.load(Ordering::Relaxed) == 0
21823          && metrics.active_runnables.load(Ordering::Relaxed) == 0
21824      });
21825      let _ = done_tx.send(());
21826    });
21827
21828    match done_rx.recv_timeout(Duration::from_secs(15)) {
21829      Ok(()) => runner.join().unwrap(),
21830      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21831    }
21832  }
21833
21834  #[cfg(napi_runtime_os_threads)]
21835  #[test]
21836  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21837    // Direct flush primitive test: pin the flush behavior used at drain
21838    // exits, before cooperative parks and by the unwind guard -- a
21839    // slot-resident runnable moves to the shared FIFO with its task still
21840    // completable, never dropped. (The pre-park call is defensive: `run_one`
21841    // pops the slot before any park is reachable, so this primitive is its
21842    // only observable seam.)
21843    let options = RuntimeOptions {
21844      flavor: RuntimeFlavor::MultiThread,
21845      worker_threads: 2,
21846      max_blocking_tasks: 2,
21847      thread_name_prefix: "lifo-flush".to_string(),
21848      park_deadline: None,
21849      drain_linger: DEFAULT_DRAIN_LINGER,
21850    };
21851    let executor =
21852      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21853    executor
21854      .active_drainers
21855      .store(executor.max_drainers, Ordering::SeqCst);
21856    let ran = Arc::new(AtomicBool::new(false));
21857
21858    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21859    let ran_flag = Arc::clone(&ran);
21860    let sched = Arc::clone(&executor);
21861    let (runnable, task) = async_task::spawn(
21862      async move {
21863        ran_flag.store(true, Ordering::SeqCst);
21864      },
21865      move |r| sched.schedule(r),
21866    );
21867    executor.schedule(runnable);
21868    task.detach();
21869    assert!(
21870      executor.injector.is_empty(),
21871      "the runnable must start in the slot"
21872    );
21873
21874    executor.flush_lifo_slot();
21875    assert!(
21876      executor.pop_lifo_slot().is_none(),
21877      "the slot must be empty after a flush"
21878    );
21879    assert_eq!(
21880      executor.injector.len(),
21881      1,
21882      "the flushed runnable must be on the shared FIFO"
21883    );
21884    assert!(executor.run_one(), "the flushed runnable must still run");
21885    assert!(
21886      ran.load(Ordering::SeqCst),
21887      "the flushed task must complete, not be dropped"
21888    );
21889    executor.active_drainers.store(0, Ordering::SeqCst);
21890  }
21891
21892  #[cfg(napi_runtime_os_threads)]
21893  #[test]
21894  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21895    // Blocking-owner isolation must cover the LIFO slot path: a runnable
21896    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
21897    // borrow the driving owner frame's over-cap privilege; the ambient owner
21898    // is restored afterwards.
21899    let options = RuntimeOptions {
21900      flavor: RuntimeFlavor::MultiThread,
21901      worker_threads: 2,
21902      max_blocking_tasks: 2,
21903      thread_name_prefix: "lifo-owner".to_string(),
21904      park_deadline: None,
21905      drain_linger: DEFAULT_DRAIN_LINGER,
21906    };
21907    let executor =
21908      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21909    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21910
21911    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21912
21913    // Install the slot entry from RUNNABLE context (no ambient owner), the
21914    // only context that may use the slot because schedules issued under an
21915    // owner frame are diverted to the shared FIFO.
21916    let observed_slot = Arc::clone(&observed);
21917    let sched = Arc::clone(&executor);
21918    let (runnable, task) = async_task::spawn(
21919      async move {
21920        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21921      },
21922      move |r| sched.schedule(r),
21923    );
21924    executor.schedule(runnable);
21925    task.detach();
21926    assert!(
21927      executor.injector.is_empty(),
21928      "the runnable must sit in the slot"
21929    );
21930
21931    // The POP can still happen while an owner frame is ambient (a cooperative
21932    // `block_on` entered from a blocking closure drives `run_one` with the
21933    // closure's token on the stack); the slot branch must clear it.
21934    let token = executor.fresh_owner_token();
21935    let _owner = BlockingOwnerGuard::enter(Some(token));
21936    assert!(executor.run_one(), "the slot runnable must run");
21937    assert_eq!(
21938      *observed.lock().unwrap(),
21939      Some(None),
21940      "a slot runnable must observe a CLEARED blocking owner"
21941    );
21942    assert_eq!(
21943      BLOCKING_OWNER.with(std::cell::Cell::get),
21944      Some(token),
21945      "the ambient owner frame must be restored after the slot runnable"
21946    );
21947  }
21948
21949  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
21950  // each other on every poll. Scheduled from a pool worker, each wake lands in
21951  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
21952  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
21953  // its partner's stored waker so both complete.
21954  #[cfg(napi_runtime_os_threads)]
21955  struct PingPongShared {
21956    count: AtomicU64,
21957    stop: AtomicBool,
21958    stop_after: u64,
21959    wakers: Mutex<[Option<std::task::Waker>; 2]>,
21960  }
21961
21962  #[cfg(napi_runtime_os_threads)]
21963  struct PingPong {
21964    shared: Arc<PingPongShared>,
21965    me: usize,
21966  }
21967
21968  #[cfg(napi_runtime_os_threads)]
21969  impl Future for PingPong {
21970    type Output = ();
21971    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21972      let shared = &self.shared;
21973      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21974      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21975        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21976        if let Some(partner) = partner {
21977          partner.wake();
21978        }
21979        return Poll::Ready(());
21980      }
21981      let mut wakers = shared.wakers.lock().unwrap();
21982      wakers[self.me] = Some(cx.waker().clone());
21983      if let Some(partner) = wakers[1 - self.me].take() {
21984        drop(wakers);
21985        partner.wake();
21986      }
21987      Poll::Pending
21988    }
21989  }
21990
21991  #[cfg(napi_runtime_os_threads)]
21992  fn spawn_ping_pong_pair(
21993    executor: &Arc<MultiThreadExecutor>,
21994    shared: &Arc<PingPongShared>,
21995  ) -> Vec<Task<()>> {
21996    let mut tasks = Vec::new();
21997    for me in 0..2 {
21998      let sched = Arc::clone(executor);
21999      let (runnable, task) = async_task::spawn(
22000        PingPong {
22001          shared: Arc::clone(shared),
22002          me,
22003        },
22004        move |r| sched.schedule(r),
22005      );
22006      executor.schedule(runnable);
22007      tasks.push(task);
22008    }
22009    tasks
22010  }
22011
22012  #[cfg(napi_runtime_os_threads)]
22013  #[test]
22014  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
22015    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
22016    // a hot wake/await pair holds a worker for at most one budget window
22017    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
22018    // a third task queued behind an unbounded ping-pong pair must still run;
22019    // if slot pops bypassed the budget the drain would never exit and T3
22020    // would starve forever (caught by the bounded timeout).
22021    use std::sync::mpsc;
22022    use std::time::Duration;
22023
22024    let (done_tx, done_rx) = mpsc::channel();
22025    let runner = std::thread::spawn(move || {
22026      let options = RuntimeOptions {
22027        flavor: RuntimeFlavor::MultiThread,
22028        worker_threads: 1,
22029        max_blocking_tasks: 0,
22030        thread_name_prefix: "lifo-streak".to_string(),
22031        park_deadline: None,
22032        drain_linger: DEFAULT_DRAIN_LINGER,
22033      };
22034      let executor =
22035        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22036
22037      let shared = Arc::new(PingPongShared {
22038        count: AtomicU64::new(0),
22039        stop: AtomicBool::new(false),
22040        stop_after: u64::MAX,
22041        wakers: Mutex::new([None, None]),
22042      });
22043      let pair = spawn_ping_pong_pair(&executor, &shared);
22044
22045      // T3: queued on the shared FIFO behind the hot pair.
22046      let (t3_tx, t3_rx) = mpsc::channel::<()>();
22047      let sched = Arc::clone(&executor);
22048      let (t3_runnable, t3_task) = async_task::spawn(
22049        async move {
22050          t3_tx.send(()).unwrap();
22051        },
22052        move |r| sched.schedule(r),
22053      );
22054      executor.schedule(t3_runnable);
22055
22056      t3_rx
22057        .recv_timeout(Duration::from_secs(10))
22058        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
22059      assert!(
22060        shared.count.load(Ordering::SeqCst) >= 32,
22061        "the pair must have been bouncing through the slot before T3 got its turn"
22062      );
22063
22064      // Teardown: stop the pair and release whichever side is waiting.
22065      shared.stop.store(true, Ordering::SeqCst);
22066      let stored: Vec<_> = shared
22067        .wakers
22068        .lock()
22069        .unwrap()
22070        .iter_mut()
22071        .filter_map(Option::take)
22072        .collect();
22073      for waker in stored {
22074        waker.wake();
22075      }
22076      for task in pair {
22077        futures::executor::block_on(task);
22078      }
22079      futures::executor::block_on(t3_task);
22080      let _ = done_tx.send(());
22081    });
22082
22083    match done_rx.recv_timeout(Duration::from_secs(15)) {
22084      Ok(()) => runner.join().unwrap(),
22085      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
22086    }
22087  }
22088
22089  #[cfg(napi_runtime_os_threads)]
22090  #[test]
22091  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
22092    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
22093    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
22094    // pair must not turn that priority into a permanent wedge. The budget
22095    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
22096    // blocking queue, and the queued blocking job completes once the pair
22097    // finishes its (long) run on the SOLE worker.
22098    use std::sync::mpsc;
22099    use std::time::Duration;
22100
22101    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
22102
22103    let (done_tx, done_rx) = mpsc::channel();
22104    let runner = std::thread::spawn(move || {
22105      let options = RuntimeOptions {
22106        flavor: RuntimeFlavor::MultiThread,
22107        worker_threads: 1,
22108        max_blocking_tasks: 1,
22109        thread_name_prefix: "lifo-blocking".to_string(),
22110        park_deadline: None,
22111        drain_linger: DEFAULT_DRAIN_LINGER,
22112      };
22113      let executor =
22114        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22115
22116      let shared = Arc::new(PingPongShared {
22117        count: AtomicU64::new(0),
22118        stop: AtomicBool::new(false),
22119        stop_after: BOUNCES,
22120        wakers: Mutex::new([None, None]),
22121      });
22122      let pair = spawn_ping_pong_pair(&executor, &shared);
22123
22124      // Queued while the pair keeps the slot hot on the only worker.
22125      let blocking = executor.schedule_blocking(|| 11usize);
22126
22127      assert_eq!(
22128        futures::executor::block_on(blocking).unwrap(),
22129        11,
22130        "the queued blocking job must complete despite the hot LIFO pair"
22131      );
22132      for task in pair {
22133        futures::executor::block_on(task);
22134      }
22135      assert!(
22136        shared.count.load(Ordering::SeqCst) >= BOUNCES,
22137        "the pair must have actually run its full bounce quota"
22138      );
22139      let _ = done_tx.send(());
22140    });
22141
22142    match done_rx.recv_timeout(Duration::from_secs(15)) {
22143      Ok(()) => runner.join().unwrap(),
22144      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
22145    }
22146  }
22147
22148  #[cfg(napi_runtime_os_threads)]
22149  #[test]
22150  fn infinite_hot_runnable_yields_to_blocking_fifo() {
22151    use std::sync::mpsc;
22152
22153    struct HotFuture {
22154      stop: Arc<AtomicBool>,
22155    }
22156
22157    impl Future for HotFuture {
22158      type Output = ();
22159
22160      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22161        if self.stop.load(Ordering::SeqCst) {
22162          Poll::Ready(())
22163        } else {
22164          cx.waker().wake_by_ref();
22165          Poll::Pending
22166        }
22167      }
22168    }
22169
22170    let options = RuntimeOptions {
22171      flavor: RuntimeFlavor::MultiThread,
22172      worker_threads: 2,
22173      max_blocking_tasks: 1,
22174      thread_name_prefix: "blocking-fairness".to_string(),
22175      park_deadline: None,
22176      drain_linger: DEFAULT_DRAIN_LINGER,
22177    };
22178    let executor =
22179      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22180    let stop = Arc::new(AtomicBool::new(false));
22181    let scheduler = Arc::clone(&executor);
22182    let (runnable, task) = async_task::spawn(
22183      HotFuture {
22184        stop: Arc::clone(&stop),
22185      },
22186      move |r| scheduler.schedule(r),
22187    );
22188    task.detach();
22189    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
22190    executor
22191      .blocking_queue
22192      .lock()
22193      .unwrap()
22194      .push(QueuedBlocking {
22195        id: BlockingJobId(u64::MAX),
22196        run: Box::new(move || blocking_tx.send(()).unwrap()),
22197      });
22198
22199    let _driver = OnPoolWorkerGuard::enter(executor.id);
22200    executor.schedule(runnable);
22201    let mut runnable_streak = 0;
22202    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
22203      assert!(executor.run_one_fair(&mut runnable_streak));
22204    }
22205    blocking_rx
22206      .try_recv()
22207      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
22208
22209    stop.store(true, Ordering::SeqCst);
22210    assert!(executor.run_one_fair(&mut runnable_streak));
22211  }
22212
22213  #[cfg(napi_runtime_os_threads)]
22214  #[test]
22215  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22216    use std::sync::mpsc;
22217
22218    struct HotFuture {
22219      stop: Arc<AtomicBool>,
22220    }
22221
22222    impl Future for HotFuture {
22223      type Output = ();
22224
22225      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22226        if self.stop.load(Ordering::SeqCst) {
22227          Poll::Ready(())
22228        } else {
22229          cx.waker().wake_by_ref();
22230          Poll::Pending
22231        }
22232      }
22233    }
22234
22235    let options = RuntimeOptions {
22236      flavor: RuntimeFlavor::MultiThread,
22237      worker_threads: 1,
22238      max_blocking_tasks: 1,
22239      thread_name_prefix: "exact-dependency-fairness".to_string(),
22240      park_deadline: None,
22241      drain_linger: DEFAULT_DRAIN_LINGER,
22242    };
22243    let executor =
22244      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22245    let stop = Arc::new(AtomicBool::new(false));
22246    let (queued_tx, queued_rx) = mpsc::channel();
22247    let (exact_tx, exact_rx) = mpsc::channel();
22248    let owner_executor = Arc::clone(&executor);
22249    let owner_stop = Arc::clone(&stop);
22250    let owner = executor.schedule_blocking(move || {
22251      let scheduler = Arc::clone(&owner_executor);
22252      let hot_stop = Arc::clone(&owner_stop);
22253      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22254        scheduler.schedule(runnable);
22255      });
22256      owner_executor.schedule(runnable);
22257
22258      let exact_stop = Arc::clone(&owner_stop);
22259      let exact = owner_executor.schedule_blocking(move || {
22260        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22261        41usize
22262      });
22263      queued_tx.send(()).unwrap();
22264
22265      let mut value = None;
22266      {
22267        let mut future = std::pin::pin!(async {
22268          value = Some(exact.await.unwrap());
22269          hot_task.await;
22270        });
22271        assert_eq!(
22272          owner_executor.block_on(future.as_mut()),
22273          BlockOnOutcome::Completed
22274        );
22275      }
22276      value.unwrap()
22277    });
22278
22279    queued_rx
22280      .recv_timeout(Duration::from_secs(2))
22281      .expect("the exact dependency must be queued behind the saturated owner");
22282    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22283    stop.store(true, Ordering::SeqCst);
22284    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
22285    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22286    assert_eq!(
22287      exact_before_stop,
22288      Some(true),
22289      "a hot local runnable must yield to the exact dependency before it is stopped"
22290    );
22291  }
22292
22293  #[cfg(napi_runtime_os_threads)]
22294  #[test]
22295  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
22296    use std::sync::mpsc;
22297
22298    struct BlockingPoll {
22299      started: Option<mpsc::Sender<()>>,
22300      released: Arc<(Mutex<bool>, Condvar)>,
22301    }
22302
22303    impl Future for BlockingPoll {
22304      type Output = ();
22305
22306      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22307        if let Some(started) = self.started.take() {
22308          started.send(()).unwrap();
22309        }
22310        let (released, changed) = &*self.released;
22311        let mut released = released
22312          .lock()
22313          .unwrap_or_else(std::sync::PoisonError::into_inner);
22314        while !*released {
22315          released = changed
22316            .wait(released)
22317            .unwrap_or_else(std::sync::PoisonError::into_inner);
22318        }
22319        Poll::Ready(())
22320      }
22321    }
22322
22323    let options = RuntimeOptions {
22324      flavor: RuntimeFlavor::MultiThread,
22325      worker_threads: 3,
22326      max_blocking_tasks: 2,
22327      thread_name_prefix: "exact-before-last-slot".to_string(),
22328      park_deadline: None,
22329      drain_linger: DEFAULT_DRAIN_LINGER,
22330    };
22331    let executor =
22332      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22333
22334    let released = Arc::new((Mutex::new(false), Condvar::new()));
22335    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22336    let mut blockers = Vec::new();
22337    for _ in 0..2 {
22338      let scheduler = Arc::clone(&executor);
22339      let blocker_released = Arc::clone(&released);
22340      let started = blocker_started_tx.clone();
22341      let (runnable, task) = async_task::spawn(
22342        BlockingPoll {
22343          started: Some(started),
22344          released: blocker_released,
22345        },
22346        move |runnable| scheduler.schedule(runnable),
22347      );
22348      executor.schedule(runnable);
22349      blockers.push(task);
22350    }
22351    drop(blocker_started_tx);
22352    for _ in 0..2 {
22353      blocker_started_rx
22354        .recv_timeout(Duration::from_secs(2))
22355        .expect("two workers must be gated before the owner starts");
22356    }
22357
22358    let dependency_started = Arc::new(AtomicBool::new(false));
22359    let owner_executor = Arc::clone(&executor);
22360    let owner_dependency_started = Arc::clone(&dependency_started);
22361    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22362    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22363    let owner_signal = Arc::clone(&dependency_signal);
22364    let owner = executor.schedule_blocking(move || {
22365      let unrelated_signal = Arc::clone(&owner_signal);
22366      let unrelated_started = Arc::clone(&owner_dependency_started);
22367      let unrelated = owner_executor.schedule_blocking(move || {
22368        let observed = unrelated_started.load(Ordering::SeqCst);
22369        unrelated_observed_tx.send(observed).unwrap();
22370        let (ready, changed) = &*unrelated_signal;
22371        let mut ready = ready
22372          .lock()
22373          .unwrap_or_else(std::sync::PoisonError::into_inner);
22374        while !*ready {
22375          ready = changed
22376            .wait(ready)
22377            .unwrap_or_else(std::sync::PoisonError::into_inner);
22378        }
22379      });
22380
22381      let exact_signal = Arc::clone(&owner_signal);
22382      let exact_started = Arc::clone(&owner_dependency_started);
22383      let exact = owner_executor.schedule_blocking(move || {
22384        exact_started.store(true, Ordering::SeqCst);
22385        let (ready, changed) = &*exact_signal;
22386        *ready
22387          .lock()
22388          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22389        changed.notify_all();
22390        41usize
22391      });
22392
22393      let mut output = None;
22394      {
22395        let mut future = std::pin::pin!(async {
22396          output = Some(exact.await.unwrap());
22397          unrelated.await.unwrap();
22398        });
22399        assert_eq!(
22400          owner_executor.block_on(future.as_mut()),
22401          BlockOnOutcome::Completed
22402        );
22403      }
22404      output.unwrap()
22405    });
22406
22407    let (owner_done_tx, owner_done_rx) = mpsc::channel();
22408    let owner_waiter = std::thread::spawn(move || {
22409      owner_done_tx
22410        .send(futures::executor::block_on(owner))
22411        .unwrap();
22412    });
22413    let (completed_while_gated, owner_result) =
22414      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22415        Ok(result) => (true, result),
22416        Err(_) => {
22417          // Let a broken implementation recover so the regression fails
22418          // deterministically instead of leaving blocked Rayon workers behind.
22419          let (released, changed) = &*released;
22420          *released
22421            .lock()
22422            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22423          changed.notify_all();
22424          (
22425            false,
22426            owner_done_rx
22427              .recv_timeout(Duration::from_secs(2))
22428              .expect("releasing the other workers must let the broken ordering recover"),
22429          )
22430        }
22431      };
22432    let unrelated_observed = unrelated_observed_rx
22433      .recv_timeout(Duration::from_secs(2))
22434      .expect("the unrelated FIFO job must eventually run");
22435
22436    {
22437      let (released, changed) = &*released;
22438      *released
22439        .lock()
22440        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22441      changed.notify_all();
22442    }
22443    owner_waiter.join().unwrap();
22444    for blocker in blockers {
22445      futures::executor::block_on(blocker);
22446    }
22447    executor.begin_shutdown();
22448    executor.wait_until_scheduler_idle();
22449
22450    assert!(
22451      completed_while_gated,
22452      "the exact dependency must complete while unrelated workers remain gated"
22453    );
22454    assert_eq!(owner_result.unwrap(), 41);
22455    assert!(
22456      unrelated_observed,
22457      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22458    );
22459  }
22460
22461  #[cfg(napi_runtime_os_threads)]
22462  #[test]
22463  fn blocking_saturation_preserves_a_runnable_execution_lane() {
22464    use std::{sync::mpsc, time::Duration};
22465
22466    let options = RuntimeOptions {
22467      flavor: RuntimeFlavor::MultiThread,
22468      worker_threads: 2,
22469      max_blocking_tasks: 2,
22470      thread_name_prefix: "blocking-reserve".to_string(),
22471      park_deadline: None,
22472      drain_linger: DEFAULT_DRAIN_LINGER,
22473    }
22474    .validate()
22475    .expect("production options must reserve a runnable lane");
22476    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22477    let executor =
22478      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22479    let (release_tx, release_rx) = mpsc::channel();
22480    let (started_tx, started_rx) = mpsc::channel();
22481    let first = executor.schedule_blocking(move || {
22482      started_tx.send(()).unwrap();
22483      release_rx.recv().unwrap();
22484    });
22485    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22486
22487    let (queued_tx, queued_rx) = mpsc::channel();
22488    let second = executor.schedule_blocking(move || {
22489      queued_tx.send(()).unwrap();
22490    });
22491    assert!(
22492      queued_rx.try_recv().is_err(),
22493      "the second blocking job must remain queued while the sole blocking slot is occupied"
22494    );
22495
22496    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22497    let scheduler = Arc::clone(&executor);
22498    let (runnable, task) = async_task::spawn(
22499      async move {
22500        runnable_tx.send(()).unwrap();
22501      },
22502      move |r| scheduler.schedule(r),
22503    );
22504    task.detach();
22505    executor.schedule(runnable);
22506    runnable_rx
22507      .recv_timeout(Duration::from_secs(1))
22508      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22509
22510    release_tx.send(()).unwrap();
22511    futures::executor::block_on(first).unwrap();
22512    futures::executor::block_on(second).unwrap();
22513    queued_rx
22514      .recv_timeout(Duration::from_secs(1))
22515      .expect("queued blocking work must run after the occupied slot is released");
22516  }
22517
22518  #[cfg(napi_runtime_os_threads)]
22519  #[test]
22520  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22521    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
22522    // schedule same-executor work, which lands in THIS worker's LIFO slot --
22523    // no FIFO push, no wake. The Ready exit of
22524    // `cooperative_block_on` must flush the slot: the caller may wait on that
22525    // child synchronously (and the enclosing drain frame can be arbitrarily
22526    // far away -- here it does not exist at all, via the fake on-pool
22527    // marker), so a stranded slot entry is an invisible, dead task.
22528    use std::sync::mpsc;
22529    use std::time::Duration;
22530
22531    struct SpawnOnReady {
22532      executor: Arc<MultiThreadExecutor>,
22533      child_tx: Option<mpsc::Sender<u64>>,
22534    }
22535    impl Future for SpawnOnReady {
22536      type Output = ();
22537      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22538        let child_tx = self.child_tx.take().expect("polled once");
22539        let sched = Arc::clone(&self.executor);
22540        let (runnable, task) = async_task::spawn(
22541          async move {
22542            child_tx.send(21).unwrap();
22543          },
22544          move |r| sched.schedule(r),
22545        );
22546        // On-pool + same executor + no ambient owner: lands in the slot.
22547        self.executor.schedule(runnable);
22548        task.detach();
22549        Poll::Ready(())
22550      }
22551    }
22552
22553    let (done_tx, done_rx) = mpsc::channel();
22554    let runner = std::thread::spawn(move || {
22555      let options = RuntimeOptions {
22556        flavor: RuntimeFlavor::MultiThread,
22557        worker_threads: 2,
22558        max_blocking_tasks: 2,
22559        thread_name_prefix: "exit-flush".to_string(),
22560        park_deadline: None,
22561        drain_linger: DEFAULT_DRAIN_LINGER,
22562      };
22563      let executor =
22564        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22565
22566      let (child_tx, child_rx) = mpsc::channel::<u64>();
22567      {
22568        // The test-only on-pool marker runs the cooperative branch on THIS
22569        // thread, with NO enclosing drain loop to bail the slot out.
22570        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22571        let mut future = std::pin::pin!(SpawnOnReady {
22572          executor: Arc::clone(&executor),
22573          child_tx: Some(child_tx)
22574        });
22575        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22576      }
22577
22578      // Synchronous bounded wait, NOT via the executor: only the exit flush
22579      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
22580      // complete the child.
22581      let value = child_rx
22582        .recv_timeout(Duration::from_secs(5))
22583        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22584      assert_eq!(value, 21);
22585      let _ = done_tx.send(());
22586    });
22587
22588    match done_rx.recv_timeout(Duration::from_secs(10)) {
22589      Ok(()) => runner.join().unwrap(),
22590      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22591    }
22592  }
22593
22594  /// Perpetual same-executor chain: each link schedules its successor from
22595  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
22596  /// raised. `hops` counts executed links.
22597  #[cfg(napi_runtime_os_threads)]
22598  fn spawn_hot_chain(
22599    executor: &Arc<MultiThreadExecutor>,
22600    stop: Arc<AtomicBool>,
22601    hops: Arc<AtomicU64>,
22602  ) {
22603    if stop.load(Ordering::SeqCst) {
22604      return;
22605    }
22606    let link_exec = Arc::clone(executor);
22607    let body = async move {
22608      hops.fetch_add(1, Ordering::SeqCst);
22609      spawn_hot_chain(&link_exec, stop, hops);
22610    };
22611    let sched = Arc::clone(executor);
22612    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22613    executor.schedule(runnable);
22614    task.detach();
22615  }
22616
22617  #[cfg(napi_runtime_os_threads)]
22618  #[test]
22619  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22620    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
22621    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
22622    // the FIFO, so a hot chain (every link schedules its successor into the
22623    // slot) keeps run_one succeeding forever and the FIFO is never reached
22624    // from this thread. On the direct 1-worker test topology a FIFO task that
22625    // completes the awaited future then never runs: deadlock. The budgeted
22626    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
22627    // gets its turn.
22628    use std::sync::mpsc;
22629    use std::time::Duration;
22630
22631    let (done_tx, done_rx) = mpsc::channel();
22632    let runner = std::thread::spawn(move || {
22633      let options = RuntimeOptions {
22634        flavor: RuntimeFlavor::MultiThread,
22635        worker_threads: 1,
22636        // This locality test intentionally bypasses RuntimeOptions::validate
22637        // to isolate one physical worker without admitting blocking work.
22638        max_blocking_tasks: 0,
22639        thread_name_prefix: "coop-budget".to_string(),
22640        park_deadline: None,
22641        drain_linger: DEFAULT_DRAIN_LINGER,
22642      };
22643      let executor =
22644        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22645
22646      let stop = Arc::new(AtomicBool::new(false));
22647      let hops = Arc::new(AtomicU64::new(0));
22648
22649      // M runs on the sole worker: it queues F (the task that completes the
22650      // awaited future) on the FIFO, heats the slot with the chain, then
22651      // block_on's the value F produces.
22652      let m_exec = Arc::clone(&executor);
22653      let m_stop = Arc::clone(&stop);
22654      let m_hops = Arc::clone(&hops);
22655      let m_body = async move {
22656        let (tx, rx) = oneshot::channel::<u64>();
22657        // Schedule F first: it takes the slot momentarily...
22658        let sched_f = Arc::clone(&m_exec);
22659        let (f_runnable, f_task) = async_task::spawn(
22660          async move {
22661            let _ = tx.send(7);
22662          },
22663          move |r| sched_f.schedule(r),
22664        );
22665        m_exec.schedule(f_runnable);
22666        f_task.detach();
22667        // ...then the chain starter displaces it to the FIFO and keeps the
22668        // slot hot from here on.
22669        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22670
22671        let mut inner = std::pin::pin!(async move {
22672          assert_eq!(
22673            rx.await.unwrap(),
22674            7,
22675            "F must run and complete the awaited future"
22676          );
22677        });
22678        m_exec.block_on(inner.as_mut());
22679        m_stop.store(true, Ordering::SeqCst);
22680      };
22681      let sched_m = Arc::clone(&executor);
22682      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22683      executor.schedule(m_runnable);
22684
22685      futures::executor::block_on(m_task);
22686      assert!(
22687        hops.load(Ordering::SeqCst) >= 64,
22688        "the chain must have been genuinely hot (>= one budget window) before F ran"
22689      );
22690      let _ = done_tx.send(());
22691    });
22692
22693    match done_rx.recv_timeout(Duration::from_secs(10)) {
22694      Ok(()) => runner.join().unwrap(),
22695      Err(error) => panic!(
22696        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22697      ),
22698    }
22699  }
22700
22701  #[cfg(napi_runtime_os_threads)]
22702  #[test]
22703  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22704    use std::sync::mpsc;
22705
22706    struct RefillLifo {
22707      executor: Arc<MultiThreadExecutor>,
22708      fifo_ran: Arc<AtomicBool>,
22709    }
22710
22711    impl Future for RefillLifo {
22712      type Output = ();
22713
22714      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22715        if self.fifo_ran.load(Ordering::SeqCst) {
22716          return Poll::Ready(());
22717        }
22718        let scheduler = Arc::clone(&self.executor);
22719        let (runnable, task) =
22720          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22721        self.executor.schedule(runnable);
22722        task.detach();
22723        Poll::Pending
22724      }
22725    }
22726
22727    let (done_tx, done_rx) = mpsc::channel();
22728    let runner = std::thread::spawn(move || {
22729      let options = RuntimeOptions {
22730        flavor: RuntimeFlavor::MultiThread,
22731        worker_threads: 1,
22732        max_blocking_tasks: 0,
22733        thread_name_prefix: "forced-fifo".to_string(),
22734        park_deadline: None,
22735        drain_linger: DEFAULT_DRAIN_LINGER,
22736      };
22737      let executor =
22738        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22739      executor
22740        .active_drainers
22741        .store(executor.max_drainers, Ordering::SeqCst);
22742
22743      let fifo_ran = Arc::new(AtomicBool::new(false));
22744      let fifo_flag = Arc::clone(&fifo_ran);
22745      let scheduler = Arc::clone(&executor);
22746      let (fifo, task) = async_task::spawn(
22747        async move {
22748          fifo_flag.store(true, Ordering::SeqCst);
22749        },
22750        move |runnable| scheduler.schedule(runnable),
22751      );
22752      task.detach();
22753      executor.metrics.runnable_scheduled();
22754      executor.push_fifo_raw(fifo);
22755
22756      {
22757        let _driver = OnPoolWorkerGuard::enter(executor.id);
22758        let mut future = std::pin::pin!(RefillLifo {
22759          executor: Arc::clone(&executor),
22760          fifo_ran: Arc::clone(&fifo_ran)
22761        });
22762        executor.block_on(future.as_mut());
22763        while executor.run_one() {}
22764      }
22765      executor.active_drainers.store(0, Ordering::SeqCst);
22766      assert!(fifo_ran.load(Ordering::SeqCst));
22767      done_tx.send(()).unwrap();
22768    });
22769    done_rx
22770      .recv_timeout(Duration::from_secs(3))
22771      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22772    runner.join().unwrap();
22773  }
22774
22775  #[cfg(napi_runtime_os_threads)]
22776  #[test]
22777  fn blocking_closure_spawn_is_not_stranded_in_slot() {
22778    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
22779    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
22780    // same-executor task and then waits for it synchronously would deadlock if
22781    // the child were slotted because the slot only drains after the closure
22782    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
22783    // push to the shared FIFO with a wake.
22784    use std::sync::mpsc;
22785    use std::time::Duration;
22786
22787    let (done_tx, done_rx) = mpsc::channel();
22788    let runner = std::thread::spawn(move || {
22789      let options = RuntimeOptions {
22790        flavor: RuntimeFlavor::MultiThread,
22791        worker_threads: 2,
22792        max_blocking_tasks: 2,
22793        thread_name_prefix: "blk-spawn-wait".to_string(),
22794        park_deadline: None,
22795        drain_linger: DEFAULT_DRAIN_LINGER,
22796      };
22797      let executor =
22798        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22799
22800      // Closure spawns a child and BLOCKS on the channel the child must fill.
22801      // A spare worker exists; only a real FIFO push + wake can reach it.
22802      let blk_exec = Arc::clone(&executor);
22803      let handle = executor.schedule_blocking(move || {
22804        let (tx, rx) = mpsc::channel::<u64>();
22805        let sched = Arc::clone(&blk_exec);
22806        let (runnable, task) = async_task::spawn(
22807          async move {
22808            tx.send(21).unwrap();
22809          },
22810          move |r| sched.schedule(r),
22811        );
22812        blk_exec.schedule(runnable);
22813        task.detach();
22814        rx.recv_timeout(Duration::from_secs(5))
22815          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22816      });
22817      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22818
22819      // Variant: the closure exits WITHOUT waiting; the child must still
22820      // complete promptly (no stranding on the closure-exit path either).
22821      let (v_tx, v_rx) = mpsc::channel::<u64>();
22822      let v_exec = Arc::clone(&executor);
22823      let v_handle = executor.schedule_blocking(move || {
22824        let sched = Arc::clone(&v_exec);
22825        let (runnable, task) = async_task::spawn(
22826          async move {
22827            v_tx.send(42).unwrap();
22828          },
22829          move |r| sched.schedule(r),
22830        );
22831        v_exec.schedule(runnable);
22832        task.detach();
22833        1u64
22834      });
22835      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22836      assert_eq!(
22837        v_rx
22838          .recv_timeout(Duration::from_secs(5))
22839          .expect("fire-and-forget child must complete"),
22840        42
22841      );
22842      let _ = done_tx.send(());
22843    });
22844
22845    match done_rx.recv_timeout(Duration::from_secs(15)) {
22846      Ok(()) => runner.join().unwrap(),
22847      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22848    }
22849  }
22850
22851  #[cfg(napi_runtime_os_threads)]
22852  #[test]
22853  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22854    // Saturated nested-blocking invariant: a blocking closure re-enters
22855    // `block_on` and awaits another `spawn_blocking` while
22856    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
22857    // The driver must not park while still holding its blocking slot, because
22858    // the inner job would then have no capacity to run.
22859    use std::sync::{Barrier, mpsc};
22860    use std::time::Duration;
22861
22862    let (done_tx, done_rx) = mpsc::channel();
22863    let runner = std::thread::spawn(move || {
22864      let metrics = Arc::new(RuntimeMetrics::default());
22865      let options = RuntimeOptions {
22866        flavor: RuntimeFlavor::MultiThread,
22867        worker_threads: 2,
22868        max_blocking_tasks: 2,
22869        thread_name_prefix: "rd1-nested-blk".to_string(),
22870        park_deadline: None,
22871        drain_linger: DEFAULT_DRAIN_LINGER,
22872      };
22873      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22874
22875      let outer_count = 2usize; // saturates max_blocking_tasks
22876      let barrier = Arc::new(Barrier::new(outer_count));
22877
22878      let mut handles = Vec::new();
22879      for _ in 0..outer_count {
22880        let exec = Arc::clone(&executor);
22881        let barrier = Arc::clone(&barrier);
22882        let handle = executor.schedule_blocking(move || {
22883          // Both outer blocking jobs hold a slot before either nests.
22884          barrier.wait();
22885          let inner_exec = Arc::clone(&exec);
22886          let mut value = 0usize;
22887          {
22888            let mut inner = std::pin::pin!(async {
22889              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22890            });
22891            exec.block_on(inner.as_mut());
22892          }
22893          value
22894        });
22895        handles.push(handle);
22896      }
22897
22898      for handle in handles {
22899        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22900      }
22901      let _ = done_tx.send(());
22902    });
22903
22904    match done_rx.recv_timeout(Duration::from_secs(10)) {
22905      Ok(()) => runner.join().unwrap(),
22906      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22907    }
22908  }
22909
22910  #[cfg(napi_runtime_os_threads)]
22911  #[test]
22912  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22913    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22914    let owner = executor.fresh_owner_token();
22915    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22916    let job = BlockingJobId(u64::MAX - 5);
22917    let dependency = TaskDependency::default();
22918    dependency.set_owned(BlockingDependency { job, owner: None });
22919    let ran = Arc::new(AtomicBool::new(false));
22920    let job_ran = Arc::clone(&ran);
22921    executor
22922      .blocking_queue
22923      .lock()
22924      .unwrap()
22925      .push(QueuedBlocking {
22926        id: job,
22927        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22928      });
22929
22930    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22931    assert!(
22932      !executor.try_owned_blocking_over_cap(&dependency),
22933      "an untagged worker must not infer ancestry from a sole active owner"
22934    );
22935    assert!(!ran.load(Ordering::SeqCst));
22936    assert!(
22937      executor
22938        .blocking_queue
22939        .lock()
22940        .unwrap()
22941        .jobs
22942        .contains_key(&job),
22943      "rejected owner inference must leave the exact job queued"
22944    );
22945  }
22946
22947  #[cfg(napi_runtime_os_threads)]
22948  #[test]
22949  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22950    use std::sync::{Barrier, mpsc};
22951
22952    let options = RuntimeOptions {
22953      flavor: RuntimeFlavor::MultiThread,
22954      worker_threads: 2,
22955      max_blocking_tasks: 2,
22956      thread_name_prefix: "exact-owner-frame".to_string(),
22957      park_deadline: None,
22958      drain_linger: DEFAULT_DRAIN_LINGER,
22959    };
22960    let executor =
22961      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22962    let owners_saturated = Arc::new(Barrier::new(3));
22963    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22964    let begin_lending = Arc::new(Barrier::new(3));
22965    let release_dependencies = Arc::new(Barrier::new(3));
22966    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22967    let mut owners = Vec::new();
22968
22969    for owner_index in 0..2 {
22970      let owner_executor = Arc::clone(&executor);
22971      let owners_saturated = Arc::clone(&owners_saturated);
22972      let dependency_queued_tx = dependency_queued_tx.clone();
22973      let begin_lending = Arc::clone(&begin_lending);
22974      let release_dependencies = Arc::clone(&release_dependencies);
22975      let dependency_started_tx = dependency_started_tx.clone();
22976      owners.push(executor.schedule_blocking(move || {
22977        let owner_thread = std::thread::current().id();
22978        owners_saturated.wait();
22979        let child_started = dependency_started_tx.clone();
22980        let child_release = Arc::clone(&release_dependencies);
22981        let child = owner_executor.schedule_blocking(move || {
22982          assert_eq!(
22983            std::thread::current().id(),
22984            owner_thread,
22985            "an owner frame must execute only its own exact dependency on its lent lane"
22986          );
22987          child_started.send(owner_index).unwrap();
22988          child_release.wait();
22989          owner_index
22990        });
22991        dependency_queued_tx.send(()).unwrap();
22992        begin_lending.wait();
22993
22994        let mut result = None;
22995        {
22996          let mut future = std::pin::pin!(async {
22997            result = Some(child.await.unwrap());
22998          });
22999          assert_eq!(
23000            owner_executor.block_on(future.as_mut()),
23001            BlockOnOutcome::Completed
23002          );
23003        }
23004        result.unwrap()
23005      }));
23006    }
23007    drop(dependency_queued_tx);
23008    drop(dependency_started_tx);
23009    owners_saturated.wait();
23010
23011    for _ in 0..2 {
23012      dependency_queued_rx
23013        .recv_timeout(Duration::from_secs(2))
23014        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
23015    }
23016    let (unrelated_tx, unrelated_rx) = mpsc::channel();
23017    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
23018    begin_lending.wait();
23019
23020    let mut started = Vec::new();
23021    for _ in 0..2 {
23022      started.push(
23023        dependency_started_rx
23024          .recv_timeout(Duration::from_secs(2))
23025          .expect("each owner must service its own exact dependency"),
23026      );
23027    }
23028    started.sort_unstable();
23029    assert_eq!(started, vec![0, 1]);
23030    assert!(
23031      unrelated_rx.try_recv().is_err(),
23032      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
23033    );
23034
23035    release_dependencies.wait();
23036    for (owner_index, owner) in owners.into_iter().enumerate() {
23037      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
23038    }
23039    unrelated_rx
23040      .recv_timeout(Duration::from_secs(2))
23041      .expect("the unrelated job must run after an owner releases a counted lane");
23042    futures::executor::block_on(unrelated).unwrap();
23043  }
23044
23045  #[cfg(napi_runtime_os_threads)]
23046  #[test]
23047  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
23048    use std::sync::{Mutex, mpsc};
23049
23050    const DEPENDENCY_COUNT: usize = 4;
23051
23052    let options = RuntimeOptions {
23053      flavor: RuntimeFlavor::MultiThread,
23054      worker_threads: 3,
23055      max_blocking_tasks: 1,
23056      thread_name_prefix: "parked-unrelated-worker".to_string(),
23057      park_deadline: None,
23058      drain_linger: DEFAULT_DRAIN_LINGER,
23059    };
23060    let executor =
23061      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23062    let (parked_tx, parked_rx) = mpsc::channel();
23063    let (release_parked_tx, release_parked_rx) = mpsc::channel();
23064    executor.pool.spawn(move || {
23065      parked_tx.send(()).unwrap();
23066      release_parked_rx.recv().unwrap();
23067    });
23068    parked_rx
23069      .recv_timeout(Duration::from_secs(2))
23070      .expect("the unrelated Rayon worker must remain occupied");
23071
23072    let owner_executor = Arc::clone(&executor);
23073    let owner = executor.schedule_blocking(move || {
23074      let owner = BLOCKING_OWNER
23075        .with(std::cell::Cell::get)
23076        .expect("the blocking closure must carry its exact owner frame");
23077      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
23078      let results = Arc::new(Mutex::new(Vec::new()));
23079      let descendant_results = Arc::clone(&results);
23080      let descendant_executor = Arc::clone(&owner_executor);
23081      rayon::scope(move |scope| {
23082        scope.spawn(move |_| {
23083          assert_eq!(
23084            BLOCKING_OWNER.with(std::cell::Cell::get),
23085            None,
23086            "the parked owner must force its descendant onto the remaining worker"
23087          );
23088          let _owner = BlockingOwnerGuard::enter(Some(owner));
23089          descendant_started_tx.send(()).unwrap();
23090          for value in 0..DEPENDENCY_COUNT {
23091            let handle = descendant_executor.schedule_blocking(move || value);
23092            let mut result = None;
23093            {
23094              let mut future = std::pin::pin!(async {
23095                result = Some(handle.await.unwrap());
23096              });
23097              assert_eq!(
23098                descendant_executor.block_on(future.as_mut()),
23099                BlockOnOutcome::Completed
23100              );
23101            }
23102            descendant_results.lock().unwrap().push(result.unwrap());
23103          }
23104        });
23105        descendant_started_rx
23106          .recv_timeout(Duration::from_secs(2))
23107          .expect("the nested descendant must start on the only unoccupied worker");
23108      });
23109      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23110    });
23111
23112    assert_eq!(
23113      futures::executor::block_on(owner).unwrap(),
23114      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
23115    );
23116
23117    let (idle_tx, idle_rx) = mpsc::channel();
23118    let idle_executor = Arc::clone(&executor);
23119    let idle_waiter = std::thread::spawn(move || {
23120      idle_executor.begin_shutdown();
23121      idle_executor.wait_until_scheduler_idle();
23122      idle_tx.send(()).unwrap();
23123    });
23124    idle_rx
23125      .recv_timeout(Duration::from_secs(2))
23126      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
23127
23128    release_parked_tx.send(()).unwrap();
23129    idle_waiter.join().unwrap();
23130  }
23131
23132  #[cfg(napi_runtime_os_threads)]
23133  #[test]
23134  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
23135    use std::sync::{Condvar, Mutex, mpsc};
23136
23137    let options = RuntimeOptions {
23138      flavor: RuntimeFlavor::MultiThread,
23139      worker_threads: 3,
23140      max_blocking_tasks: 1,
23141      thread_name_prefix: "same-owner-rearm".to_string(),
23142      park_deadline: None,
23143      drain_linger: DEFAULT_DRAIN_LINGER,
23144    };
23145    let executor =
23146      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23147    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
23148    let (job_started_tx, job_started_rx) = mpsc::channel();
23149    let owner_executor = Arc::clone(&executor);
23150    let owner_releases = Arc::clone(&releases);
23151    let owner = executor.schedule_blocking(move || {
23152      let owner = BLOCKING_OWNER
23153        .with(std::cell::Cell::get)
23154        .expect("the blocking closure must carry its exact owner frame");
23155      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
23156      let results = Arc::new(Mutex::new(Vec::new()));
23157      let scope_results = Arc::clone(&results);
23158      rayon::scope(move |scope| {
23159        for index in 0..2 {
23160          let descendant_ready_tx = descendant_ready_tx.clone();
23161          let descendant_executor = Arc::clone(&owner_executor);
23162          let descendant_results = Arc::clone(&scope_results);
23163          let job_started_tx = job_started_tx.clone();
23164          let releases = Arc::clone(&owner_releases);
23165          scope.spawn(move |_| {
23166            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
23167            let _owner = BlockingOwnerGuard::enter(Some(owner));
23168            descendant_ready_tx.send(()).unwrap();
23169            let handle = descendant_executor.schedule_blocking(move || {
23170              job_started_tx.send(index).unwrap();
23171              let (released, changed) = &*releases;
23172              let mut state = released.lock().unwrap();
23173              while !state[index] {
23174                state = changed.wait(state).unwrap();
23175              }
23176              index
23177            });
23178            let mut result = None;
23179            {
23180              let mut future = std::pin::pin!(async {
23181                result = Some(handle.await.unwrap());
23182              });
23183              assert_eq!(
23184                descendant_executor.block_on(future.as_mut()),
23185                BlockOnOutcome::Completed
23186              );
23187            }
23188            descendant_results.lock().unwrap().push(result.unwrap());
23189          });
23190        }
23191        drop(descendant_ready_tx);
23192        for _ in 0..2 {
23193          descendant_ready_rx
23194            .recv_timeout(Duration::from_secs(2))
23195            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
23196        }
23197      });
23198      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23199    });
23200
23201    let first = job_started_rx
23202      .recv_timeout(Duration::from_secs(2))
23203      .expect("one exact dependency must borrow the owner lane");
23204    assert!(
23205      job_started_rx
23206        .recv_timeout(Duration::from_millis(100))
23207        .is_err(),
23208      "one owner frame must not service two dependencies concurrently"
23209    );
23210    let unrelated_parker = Arc::new(DriverParker::default());
23211    executor
23212      .parked_drivers
23213      .register_with_role(&unrelated_parker, true, None);
23214    {
23215      let (released, changed) = &*releases;
23216      released.lock().unwrap()[first] = true;
23217      changed.notify_all();
23218    }
23219
23220    let second = job_started_rx
23221      .recv_timeout(Duration::from_secs(2))
23222      .expect("completion must rearm one parked same-owner dependency");
23223    assert_ne!(second, first);
23224    assert!(
23225      !unrelated_parker.consume_permit(),
23226      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
23227    );
23228    executor.parked_drivers.deregister(&unrelated_parker);
23229    {
23230      let (released, changed) = &*releases;
23231      released.lock().unwrap()[second] = true;
23232      changed.notify_all();
23233    }
23234
23235    let mut results = futures::executor::block_on(owner).unwrap();
23236    results.sort_unstable();
23237    assert_eq!(results, vec![0, 1]);
23238  }
23239
23240  #[cfg(napi_runtime_os_threads)]
23241  #[test]
23242  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
23243    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
23244    let owner = executor.fresh_owner_token();
23245    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23246    let failed_dependency = Arc::new(TaskDependency::default());
23247    failed_dependency.set_owned(BlockingDependency {
23248      job: BlockingJobId(u64::MAX - 21),
23249      owner: Some(owner),
23250    });
23251    let parked_dependency = Arc::new(TaskDependency::default());
23252    parked_dependency.set_owned(BlockingDependency {
23253      job: BlockingJobId(u64::MAX - 22),
23254      owner: Some(owner),
23255    });
23256    let parker = Arc::new(DriverParker::default());
23257
23258    let queue = executor.blocking_queue.lock().unwrap();
23259    let claimant_executor = Arc::clone(&executor);
23260    let claimant_dependency = Arc::clone(&failed_dependency);
23261    let claimant = std::thread::spawn(move || {
23262      let _owner = BlockingOwnerGuard::enter(Some(owner));
23263      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
23264    });
23265    wait_until("the failed claimant to reserve the owner lane", || {
23266      !executor.active_blocking_owners.is_available(owner)
23267    });
23268    executor
23269      .parked_drivers
23270      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
23271    failed_dependency.clear();
23272    drop(queue);
23273
23274    assert!(
23275      !claimant.join().unwrap(),
23276      "the cancelled dependency must not execute"
23277    );
23278    assert!(
23279      parker.consume_permit(),
23280      "releasing a failed exact claim must hand the owner lane to another parked dependency"
23281    );
23282  }
23283
23284  #[cfg(napi_runtime_os_threads)]
23285  #[test]
23286  fn exact_owner_lending_does_not_start_blocking_after_stop() {
23287    use std::sync::mpsc;
23288
23289    let options = RuntimeOptions {
23290      flavor: RuntimeFlavor::MultiThread,
23291      worker_threads: 2,
23292      max_blocking_tasks: 1,
23293      thread_name_prefix: "stopped-exact-owner".to_string(),
23294      park_deadline: None,
23295      drain_linger: DEFAULT_DRAIN_LINGER,
23296    };
23297    let executor =
23298      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23299    let ran = Arc::new(AtomicBool::new(false));
23300    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
23301    let (release_claim_tx, release_claim_rx) = mpsc::channel();
23302    let owner_executor = Arc::clone(&executor);
23303    let child_ran = Arc::clone(&ran);
23304    let owner = executor.schedule_blocking(move || {
23305      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
23306        *slot.borrow_mut() = Some(Box::new(move || {
23307          claim_entered_tx.send(()).unwrap();
23308          release_claim_rx.recv().unwrap();
23309        }));
23310      });
23311      let child = owner_executor.schedule_blocking(move || {
23312        child_ran.store(true, Ordering::SeqCst);
23313      });
23314      let mut future = std::pin::pin!(async move {
23315        let _ = child.await;
23316      });
23317      owner_executor.block_on(future.as_mut())
23318    });
23319
23320    claim_entered_rx
23321      .recv_timeout(Duration::from_secs(2))
23322      .expect("the cooperative driver must pause before the exact-owner queue claim");
23323    assert_eq!(
23324      executor.blocking_queue.lock().unwrap().jobs.len(),
23325      1,
23326      "the exact dependency must remain queued before stop publication"
23327    );
23328
23329    // Pin the reported race: the cooperative driver already passed its loop's
23330    // stop check, then shutdown publishes stop before the exact queue claim.
23331    executor.stop.begin_shutdown();
23332    release_claim_tx.send(()).unwrap();
23333
23334    assert_eq!(
23335      futures::executor::block_on(owner).unwrap(),
23336      BlockOnOutcome::Stopped,
23337      "the cooperative driver must stop instead of executing its queued dependency"
23338    );
23339    assert!(
23340      !ran.load(Ordering::SeqCst),
23341      "exact-owner lending started blocking work after stop"
23342    );
23343    assert_eq!(
23344      executor.blocking_queue.lock().unwrap().jobs.len(),
23345      1,
23346      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23347    );
23348
23349    executor.begin_shutdown();
23350    executor.wait_until_scheduler_idle();
23351    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23352    assert!(!ran.load(Ordering::SeqCst));
23353  }
23354
23355  #[cfg(napi_runtime_os_threads)]
23356  #[test]
23357  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23358    const DEPENDENCY_COUNT: usize = 1024;
23359
23360    let options = RuntimeOptions {
23361      flavor: RuntimeFlavor::MultiThread,
23362      worker_threads: 2,
23363      max_blocking_tasks: 1,
23364      thread_name_prefix: "linear-owner-lending".to_string(),
23365      park_deadline: None,
23366      drain_linger: DEFAULT_DRAIN_LINGER,
23367    };
23368    let executor =
23369      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23370    let completed = Arc::new(AtomicUsize::new(0));
23371
23372    for index in 0..DEPENDENCY_COUNT {
23373      let owner = executor.fresh_owner_token();
23374      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23375      let dependency = TaskDependency::new(u64::MAX, Some(owner));
23376      let blocking = BlockingDependency {
23377        job: BlockingJobId(u64::MAX - index as u64),
23378        owner: Some(owner),
23379      };
23380      dependency.set_owned(blocking);
23381      let completed = Arc::clone(&completed);
23382      executor
23383        .blocking_queue
23384        .lock()
23385        .unwrap()
23386        .push(QueuedBlocking {
23387          id: blocking.job,
23388          run: Box::new(move || {
23389            completed.fetch_add(1, Ordering::SeqCst);
23390          }),
23391        });
23392
23393      let _owner = BlockingOwnerGuard::enter(Some(owner));
23394      assert!(executor.try_owned_blocking_over_cap(&dependency));
23395    }
23396
23397    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23398    assert_eq!(
23399      executor.owner_lending_attempts.load(Ordering::Relaxed),
23400      DEPENDENCY_COUNT,
23401      "targeted lending must perform one O(1) claim attempt per exact dependency"
23402    );
23403    let queue = executor.blocking_queue.lock().unwrap();
23404    assert!(queue.is_empty());
23405    assert_eq!(queue.head, None);
23406    assert_eq!(queue.tail, None);
23407  }
23408
23409  #[cfg(napi_runtime_os_threads)]
23410  #[test]
23411  fn one_dependency_interspersed_owner_publications_stays_linear() {
23412    const PUBLICATION_COUNT: usize = 1024;
23413
23414    let executor = multi_thread_executor(2, None, "linear-publication-lending");
23415    let owner = executor.fresh_owner_token();
23416    let wrong_owner = executor.fresh_owner_token();
23417    let dependency = TaskDependency::new(u64::MAX, Some(owner));
23418    let completed = Arc::new(AtomicUsize::new(0));
23419    for index in 0..PUBLICATION_COUNT {
23420      let cancelled_source = TaskDependency::default();
23421      cancelled_source.set_owned(BlockingDependency {
23422        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23423        owner: Some(owner),
23424      });
23425      dependency.set_borrowed(
23426        &cancelled_source
23427          .get()
23428          .expect("the source publication must initially be live"),
23429      );
23430      cancelled_source.clear();
23431
23432      dependency.set_owned(BlockingDependency {
23433        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23434        owner: Some(wrong_owner),
23435      });
23436      let blocking = BlockingDependency {
23437        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23438        owner: Some(owner),
23439      };
23440      dependency.set_owned(blocking);
23441      let completed = Arc::clone(&completed);
23442      executor
23443        .blocking_queue
23444        .lock()
23445        .unwrap()
23446        .push(QueuedBlocking {
23447          id: blocking.job,
23448          run: Box::new(move || {
23449            completed.fetch_add(1, Ordering::SeqCst);
23450          }),
23451        });
23452    }
23453    dependency.reset_entry_visits();
23454
23455    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23456    let _owner = BlockingOwnerGuard::enter(Some(owner));
23457    for _ in 0..PUBLICATION_COUNT {
23458      assert!(
23459        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23460        "the owner-handoff predicate must find the next exact publication"
23461      );
23462      assert!(
23463        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23464        "the park-availability predicate must find the next serviceable publication"
23465      );
23466      assert!(executor.try_owned_blocking_over_cap(&dependency));
23467    }
23468
23469    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23470    assert!(
23471      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23472      "wrong-owner publications must not satisfy an exact handoff"
23473    );
23474    assert!(
23475      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23476      "wrong-owner publications must not make the owner's lane appear serviceable"
23477    );
23478    assert!(
23479      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23480      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23481      dependency.entry_visits()
23482    );
23483    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23484  }
23485
23486  #[cfg(napi_runtime_os_threads)]
23487  #[test]
23488  fn binding_unowned_dependency_updates_exact_owner_predicate() {
23489    let owner = BlockingOwnerToken {
23490      executor_id: u64::MAX - 200,
23491      frame: u64::MAX - 201,
23492    };
23493    let dependency = TaskDependency::default();
23494    let blocking = BlockingDependency {
23495      job: BlockingJobId(u64::MAX - 202),
23496      owner: None,
23497    };
23498    dependency.set_owned(blocking);
23499
23500    assert!(
23501      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23502      "an unowned publication must not satisfy an exact owner handoff"
23503    );
23504    assert!(
23505      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23506      "an unowned publication is available to the ambient owner"
23507    );
23508
23509    let selected = dependency
23510      .select_for_owner(owner)
23511      .expect("the owner must bind the unowned publication");
23512    assert_eq!(selected.publication.dependency.owner, Some(owner));
23513    assert!(
23514      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23515      "binding must immediately publish the entry to exact-owner predicates"
23516    );
23517    assert!(dependency.try_claim(&selected));
23518    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23519  }
23520
23521  #[cfg(napi_runtime_os_threads)]
23522  #[test]
23523  fn owner_selection_preserves_unowned_and_exact_publication_order() {
23524    let owner = BlockingOwnerToken {
23525      executor_id: u64::MAX - 210,
23526      frame: u64::MAX - 211,
23527    };
23528    let dependency = TaskDependency::default();
23529    let first = BlockingDependency {
23530      job: BlockingJobId(u64::MAX - 212),
23531      owner: None,
23532    };
23533    let second = BlockingDependency {
23534      job: BlockingJobId(u64::MAX - 213),
23535      owner: Some(owner),
23536    };
23537    dependency.set_owned(first);
23538    dependency.set_owned(second);
23539
23540    let selected = dependency
23541      .select_for_owner(owner)
23542      .expect("the first publication must be found");
23543    assert_eq!(
23544      selected.publication.dependency,
23545      BlockingDependency {
23546        owner: Some(owner),
23547        ..first
23548      }
23549    );
23550    assert!(dependency.try_claim(&selected));
23551
23552    let selected = dependency
23553      .select_for_owner(owner)
23554      .expect("the second publication must remain eligible");
23555    assert_eq!(selected.publication.dependency, second);
23556  }
23557
23558  #[cfg(napi_runtime_os_threads)]
23559  #[test]
23560  fn blocking_queue_exact_removal_reclaims_fifo_links() {
23561    const JOB_COUNT: u64 = 4096;
23562
23563    let mut queue = BlockingQueue {
23564      closed: false,
23565      head: None,
23566      tail: None,
23567      jobs: FxHashMap::default(),
23568    };
23569    for id in 1..=JOB_COUNT {
23570      queue.push(QueuedBlocking {
23571        id: BlockingJobId(id),
23572        run: Box::new(|| {}),
23573      });
23574    }
23575    for id in (2..=JOB_COUNT).step_by(2) {
23576      drop(
23577        queue
23578          .remove(BlockingJobId(id))
23579          .expect("even job must remain indexed"),
23580      );
23581    }
23582    for id in (1..=JOB_COUNT).step_by(2) {
23583      drop(
23584        queue
23585          .remove(BlockingJobId(id))
23586          .expect("odd job must remain indexed"),
23587      );
23588    }
23589    assert!(queue.jobs.is_empty());
23590    assert_eq!(queue.head, None);
23591    assert_eq!(queue.tail, None);
23592
23593    let order = Arc::new(Mutex::new(Vec::new()));
23594    for id in 1..=3 {
23595      let order = Arc::clone(&order);
23596      queue.push(QueuedBlocking {
23597        id: BlockingJobId(id),
23598        run: Box::new(move || order.lock().unwrap().push(id)),
23599      });
23600    }
23601    drop(
23602      queue
23603        .remove(BlockingJobId(2))
23604        .expect("middle job must be removable"),
23605    );
23606    queue.pop_front().expect("FIFO head must remain linked")();
23607    queue.pop_front().expect("FIFO tail must remain linked")();
23608    assert!(queue.pop_front().is_none());
23609    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23610    assert_eq!(queue.head, None);
23611    assert_eq!(queue.tail, None);
23612  }
23613
23614  #[cfg(not(target_family = "wasm"))]
23615  #[test]
23616  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23617    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23618
23619    if std::env::var_os(CHILD_ENV).is_some() {
23620      configure(RuntimeOptions {
23621        flavor: RuntimeFlavor::MultiThread,
23622        worker_threads: 2,
23623        max_blocking_tasks: 1,
23624        thread_name_prefix: "dependency-lending-metrics".to_string(),
23625        park_deadline: None,
23626        drain_linger: DEFAULT_DRAIN_LINGER,
23627      })
23628      .unwrap();
23629
23630      let outer = spawn_blocking(|| {
23631        let inner = spawn_blocking(|| 42usize);
23632        block_on(inner).unwrap()
23633      });
23634      assert_eq!(block_on(outer).unwrap(), 42);
23635
23636      // Result delivery wakes the joiner from inside the blocking closure,
23637      // immediately before the scheduler-owned metrics guard retires. Use
23638      // shutdown quiescence before sampling completed/live counters so this
23639      // test does not race that final bookkeeping.
23640      shutdown().unwrap();
23641      let snapshot = metrics();
23642      assert_eq!(snapshot.blocking_tasks_started, 2);
23643      assert_eq!(snapshot.blocking_tasks_completed, 2);
23644      assert_eq!(snapshot.active_blocking_tasks, 0);
23645      assert_eq!(
23646        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23647        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23648      );
23649      return;
23650    }
23651
23652    let output = std::process::Command::new(std::env::current_exe().unwrap())
23653      .arg("--exact")
23654      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23655      .arg("--nocapture")
23656      .env(CHILD_ENV, "1")
23657      .output()
23658      .expect("the dependency-lending metrics subprocess must start");
23659    assert!(
23660      output.status.success(),
23661      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23662      output.status.code(),
23663      String::from_utf8_lossy(&output.stdout),
23664      String::from_utf8_lossy(&output.stderr)
23665    );
23666  }
23667
23668  #[cfg(napi_runtime_os_threads)]
23669  #[test]
23670  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23671    use std::sync::mpsc;
23672
23673    let (done_tx, done_rx) = mpsc::channel();
23674    let runner = std::thread::spawn(move || {
23675      let options = RuntimeOptions {
23676        flavor: RuntimeFlavor::MultiThread,
23677        worker_threads: 2,
23678        max_blocking_tasks: 1,
23679        thread_name_prefix: "exact-blocking-job".to_string(),
23680        park_deadline: None,
23681        drain_linger: DEFAULT_DRAIN_LINGER,
23682      };
23683      let executor =
23684        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23685      let outer_exec = Arc::clone(&executor);
23686      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23687      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23688      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23689      let outer = executor.schedule_blocking(move || {
23690        let sibling = outer_exec.schedule_blocking(move || {
23691          sibling_started_tx.send(()).unwrap();
23692          release_sibling_rx.recv().unwrap();
23693        });
23694        let awaited = outer_exec.schedule_blocking(move || {
23695          awaited_ran_tx.send(()).unwrap();
23696          17usize
23697        });
23698        let mut value = None;
23699        {
23700          let mut future = std::pin::pin!(async {
23701            value = Some(awaited.await.unwrap());
23702          });
23703          outer_exec.block_on(future.as_mut());
23704        }
23705        (sibling, value.unwrap())
23706      });
23707
23708      awaited_ran_rx
23709        .recv_timeout(Duration::from_secs(2))
23710        .expect("the exact awaited job must bypass the earlier detached sibling");
23711      let (sibling, value) = futures::executor::block_on(outer).unwrap();
23712      assert_eq!(value, 17);
23713      sibling_started_rx
23714        .recv_timeout(Duration::from_secs(2))
23715        .expect("the sibling must run after the owner releases its counted slot");
23716      release_sibling_tx.send(()).unwrap();
23717      futures::executor::block_on(sibling).unwrap();
23718      done_tx.send(()).unwrap();
23719    });
23720    done_rx
23721      .recv_timeout(Duration::from_secs(5))
23722      .expect("same-owner sibling selection deadlocked the exact awaited job");
23723    runner.join().unwrap();
23724  }
23725
23726  #[cfg(napi_runtime_os_threads)]
23727  #[test]
23728  fn blocking_dependency_propagates_through_async_join_lineage() {
23729    use std::sync::mpsc;
23730
23731    let (done_tx, done_rx) = mpsc::channel();
23732    let runner = std::thread::spawn(move || {
23733      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23734      let owner_controller = Arc::clone(&controller);
23735      let (child_tx, child_rx) = mpsc::channel();
23736      let outer = controller
23737        .try_spawn_blocking(move || {
23738          let async_controller = Arc::clone(&owner_controller);
23739          let async_child = owner_controller
23740            .try_spawn(async move {
23741              async_controller
23742                .try_spawn_blocking(move || {
23743                  child_tx.send(()).unwrap();
23744                  23usize
23745                })
23746                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23747                .await
23748                .unwrap()
23749            })
23750            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23751          let mut value = None;
23752          {
23753            let mut future = std::pin::pin!(async {
23754              value = Some(async_child.await.unwrap());
23755            });
23756            owner_controller.block_on(future.as_mut());
23757          }
23758          value.unwrap()
23759        })
23760        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23761
23762      child_rx
23763        .recv_timeout(Duration::from_secs(2))
23764        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23765      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23766      controller.shutdown().unwrap();
23767      done_tx.send(()).unwrap();
23768    });
23769    done_rx
23770      .recv_timeout(Duration::from_secs(5))
23771      .expect("blocking dependency lineage deadlocked");
23772    runner.join().unwrap();
23773  }
23774
23775  #[cfg(napi_runtime_os_threads)]
23776  #[test]
23777  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23778    use std::sync::mpsc;
23779
23780    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23781    let outer_controller = Arc::clone(&controller);
23782    let (child_queued_tx, child_queued_rx) = mpsc::channel();
23783    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23784    let (release_outer_tx, release_outer_rx) = oneshot::channel();
23785    let outer = controller
23786      .try_spawn_blocking(move || {
23787        let child_controller = Arc::clone(&outer_controller);
23788        let child = outer_controller
23789          .try_spawn(async move {
23790            let blocking = child_controller
23791              .try_spawn_blocking(move || {
23792                unrelated_ran_tx.send(()).unwrap();
23793                31usize
23794              })
23795              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23796            child_queued_tx.send(()).unwrap();
23797            blocking.await.unwrap()
23798          })
23799          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23800        let mut released = false;
23801        {
23802          let mut future = std::pin::pin!(async {
23803            release_outer_rx.await.unwrap();
23804            released = true;
23805          });
23806          outer_controller.block_on(future.as_mut());
23807        }
23808        assert!(released);
23809        child
23810      })
23811      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23812
23813    child_queued_rx
23814      .recv_timeout(Duration::from_secs(2))
23815      .expect("the cooperative driver must service the unrelated async runnable");
23816    assert!(
23817      unrelated_ran_rx
23818        .recv_timeout(Duration::from_millis(200))
23819        .is_err(),
23820      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23821    );
23822
23823    release_outer_tx.send(()).unwrap();
23824    let child = futures::executor::block_on(outer).unwrap();
23825    unrelated_ran_rx
23826      .recv_timeout(Duration::from_secs(2))
23827      .expect("the unrelated blocking job must run after the owner releases its counted slot");
23828    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23829    controller.shutdown().unwrap();
23830  }
23831
23832  #[cfg(napi_runtime_os_threads)]
23833  #[test]
23834  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23835    let parent = Arc::new(TaskDependency::default());
23836    let _context = TaskDependencyGuard::enter(&parent);
23837    let dependency = BlockingDependency {
23838      job: BlockingJobId(u64::MAX),
23839      owner: None,
23840    };
23841    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23842    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23843      receiver,
23844      dependency,
23845      published: PublishedBlockingDependencies::default(),
23846    });
23847    let waker = futures::task::noop_waker();
23848    let mut cx = Context::from_waker(&waker);
23849
23850    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23851    assert_eq!(parent.get_dependency(), Some(dependency));
23852    drop(handle);
23853    assert_eq!(
23854      parent.get_dependency(),
23855      None,
23856      "an abandoned JoinHandle must clear stale dependency lineage"
23857    );
23858    drop(sender);
23859  }
23860
23861  #[cfg(napi_runtime_os_threads)]
23862  #[test]
23863  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23864    let owner = BlockingOwnerToken {
23865      executor_id: u64::MAX - 40,
23866      frame: u64::MAX - 41,
23867    };
23868    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23869    let _context = TaskDependencyGuard::enter(&parent);
23870    let dependency = BlockingDependency {
23871      job: BlockingJobId(u64::MAX - 42),
23872      owner: None,
23873    };
23874    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23875    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23876      receiver,
23877      dependency,
23878      published: PublishedBlockingDependencies::default(),
23879    });
23880    let waker = futures::task::noop_waker();
23881    let mut cx = Context::from_waker(&waker);
23882
23883    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23884    assert_eq!(
23885      parent.get_dependency(),
23886      Some(BlockingDependency {
23887        owner: Some(owner),
23888        ..dependency
23889      }),
23890      "the ambient task owner must enrich an untagged direct blocking dependency"
23891    );
23892    drop(handle);
23893    assert_eq!(
23894      parent.get_dependency(),
23895      None,
23896      "detach cleanup must match the stable job id after owner enrichment"
23897    );
23898    drop(sender);
23899  }
23900
23901  #[cfg(napi_runtime_os_threads)]
23902  #[test]
23903  fn one_poll_preserves_every_observed_blocking_dependency() {
23904    let parent = Arc::new(TaskDependency::default());
23905    let _context = TaskDependencyGuard::enter(&parent);
23906    let first = BlockingDependency {
23907      job: BlockingJobId(u64::MAX - 60),
23908      owner: None,
23909    };
23910    let second = BlockingDependency {
23911      job: BlockingJobId(u64::MAX - 61),
23912      owner: None,
23913    };
23914    let (first_sender, first_receiver) =
23915      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23916    let (second_sender, second_receiver) =
23917      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23918    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23919      receiver: first_receiver,
23920      dependency: first,
23921      published: PublishedBlockingDependencies::default(),
23922    });
23923    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23924      receiver: second_receiver,
23925      dependency: second,
23926      published: PublishedBlockingDependencies::default(),
23927    });
23928    let waker = futures::task::noop_waker();
23929    let mut cx = Context::from_waker(&waker);
23930
23931    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23932    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23933    assert_eq!(
23934      parent.get_dependencies(),
23935      vec![first, second],
23936      "publishing a later dependency in one user poll must not replace an earlier dependency"
23937    );
23938
23939    drop(first_handle);
23940    drop(second_handle);
23941    drop(first_sender);
23942    drop(second_sender);
23943  }
23944
23945  #[cfg(napi_runtime_os_threads)]
23946  #[test]
23947  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23948    let owner = BlockingOwnerToken {
23949      executor_id: u64::MAX - 900,
23950      frame: u64::MAX - 901,
23951    };
23952    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23953    let _context = TaskDependencyGuard::enter(&parent);
23954    let waker = futures::task::noop_waker();
23955    let mut cx = Context::from_waker(&waker);
23956
23957    let j1 = BlockingDependency {
23958      job: BlockingJobId(u64::MAX - 910),
23959      owner: Some(owner),
23960    };
23961    let j2 = BlockingDependency {
23962      job: BlockingJobId(u64::MAX - 911),
23963      owner: Some(owner),
23964    };
23965
23966    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23967    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23968
23969    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23970      receiver: r1,
23971      dependency: j1,
23972      published: PublishedBlockingDependencies::default(),
23973    });
23974    // TWO Pending polls in one pass. Each publishes, so without coalescing the
23975    // handle leaves two live entries for J1 and can retire only the newer one.
23976    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23977    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23978    let entries_after_double_poll = parent
23979      .state
23980      .lock()
23981      .unwrap_or_else(std::sync::PoisonError::into_inner)
23982      .current
23983      .len();
23984    drop(h1);
23985
23986    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23987      receiver: r2,
23988      dependency: j2,
23989      published: PublishedBlockingDependencies::default(),
23990    });
23991    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23992
23993    assert_eq!(
23994      entries_after_double_poll, 1,
23995      "a repeat Pending poll must supersede the handle's own previous publication"
23996    );
23997    let selected = parent
23998      .select_for_owner(owner)
23999      .expect("a live dependency must be selectable");
24000    assert_eq!(
24001      selected.publication.dependency.job, j2.job,
24002      "a DETACHED job must not be selected for owner lending ahead of the live one"
24003    );
24004    drop((s1, s2));
24005  }
24006
24007  #[cfg(napi_runtime_os_threads)]
24008  #[test]
24009  fn handle_polled_under_a_nested_frame_retires_both_publications() {
24010    let owner = BlockingOwnerToken {
24011      executor_id: u64::MAX - 920,
24012      frame: u64::MAX - 921,
24013    };
24014    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24015    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24016    let waker = futures::task::noop_waker();
24017    let mut cx = Context::from_waker(&waker);
24018
24019    let j1 = BlockingDependency {
24020      job: BlockingJobId(u64::MAX - 930),
24021      owner: Some(owner),
24022    };
24023    let j2 = BlockingDependency {
24024      job: BlockingJobId(u64::MAX - 931),
24025      owner: Some(owner),
24026    };
24027    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24028    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24029    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24030      receiver: r1,
24031      dependency: j1,
24032      published: Default::default(),
24033    });
24034
24035    {
24036      let _outer_ctx = TaskDependencyGuard::enter(&outer);
24037      // A handle can publish into more than one frame: once here, and again
24038      // under a nested re-entrant frame. Detaching must retire BOTH, or the
24039      // forgotten entry keeps a detached job selectable for owner lending.
24040      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24041      {
24042        let _inner_ctx = TaskDependencyGuard::enter(&inner);
24043        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24044      }
24045      drop(h1);
24046      assert!(
24047        outer.get_dependencies().is_empty(),
24048        "detaching must retire the publication made in the outer frame too"
24049      );
24050      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24051        receiver: r2,
24052        dependency: j2,
24053        published: Default::default(),
24054      });
24055      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24056      let selected = outer.select_for_owner(owner).expect("selectable");
24057      assert_eq!(
24058        selected.publication.dependency.job, j2.job,
24059        "a detached job published in an outer frame must not outrank a live one"
24060      );
24061      drop(h2);
24062    }
24063    drop((s1, s2));
24064  }
24065
24066  #[cfg(napi_runtime_os_threads)]
24067  #[test]
24068  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
24069    const FRAME_COUNT: usize = 64;
24070    let waker = futures::task::noop_waker();
24071    let mut cx = Context::from_waker(&waker);
24072    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24073    let mut handle = JoinHandle(JoinHandleInner::Blocking {
24074      receiver,
24075      dependency: BlockingDependency {
24076        job: BlockingJobId(u64::MAX - 940),
24077        owner: None,
24078      },
24079      published: PublishedBlockingDependencies::default(),
24080    });
24081    let mut frames = Vec::new();
24082    for _ in 0..FRAME_COUNT {
24083      let frame = Arc::new(TaskDependency::default());
24084      frames.push(Arc::downgrade(&frame));
24085      let _context = TaskDependencyGuard::enter(&frame);
24086      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24087    }
24088    // A handle carried through a sequence of completed frames must not keep
24089    // any of them alive: it remembers each publication by `Weak`, and prunes
24090    // the expired tokens on its next publication, so the history stays at the
24091    // live frame count instead of growing with every frame ever seen.
24092    let retained = frames
24093      .iter()
24094      .filter(|frame| frame.strong_count() > 0)
24095      .count();
24096    assert_eq!(
24097      retained, 0,
24098      "a completed frame must not be kept alive by a pending handle's publication history"
24099    );
24100    drop(handle);
24101    drop(sender);
24102  }
24103
24104  #[cfg(napi_runtime_os_threads)]
24105  #[test]
24106  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
24107    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
24108    // sequence, so H1 must keep the position its FIRST poll gave it: if
24109    // re-polling appended a replacement at the tail, H2 would be lent first,
24110    // and an H2 that waits on H1 would deadlock the owner's single lane.
24111    let owner = BlockingOwnerToken {
24112      executor_id: u64::MAX - 950,
24113      frame: u64::MAX - 951,
24114    };
24115    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24116    let _context = TaskDependencyGuard::enter(&parent);
24117    let waker = futures::task::noop_waker();
24118    let mut cx = Context::from_waker(&waker);
24119
24120    let j1 = BlockingDependency {
24121      job: BlockingJobId(u64::MAX - 960),
24122      owner: Some(owner),
24123    };
24124    let j2 = BlockingDependency {
24125      job: BlockingJobId(u64::MAX - 961),
24126      owner: Some(owner),
24127    };
24128    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24129    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24130    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24131      receiver: r1,
24132      dependency: j1,
24133      published: PublishedBlockingDependencies::default(),
24134    });
24135    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24136      receiver: r2,
24137      dependency: j2,
24138      published: PublishedBlockingDependencies::default(),
24139    });
24140
24141    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24142    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24143    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24144
24145    let selected = parent
24146      .select_for_owner(owner)
24147      .expect("a live dependency must be selectable");
24148    assert_eq!(
24149      selected.publication.dependency.job, j1.job,
24150      "re-polling a handle must not push its dependency behind one published later"
24151    );
24152    drop((h1, h2, s1, s2));
24153  }
24154
24155  #[cfg(napi_runtime_os_threads)]
24156  #[test]
24157  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
24158    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
24159    // `TaskDependency`) but each drains `current` at the start of its poll,
24160    // which is what a real task poll does. A drained frame no longer holds the
24161    // entry, so its token stops standing and is dropped on the next
24162    // publication: the history tracks frames that still hold an entry, not
24163    // every frame the handle has ever been polled under.
24164    const FRAME_COUNT: usize = 256;
24165    let waker = futures::task::noop_waker();
24166    let mut cx = Context::from_waker(&waker);
24167    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24168    let mut handle = JoinHandle(JoinHandleInner::Blocking {
24169      receiver,
24170      dependency: BlockingDependency {
24171        job: BlockingJobId(u64::MAX - 970),
24172        owner: None,
24173      },
24174      published: PublishedBlockingDependencies::default(),
24175    });
24176    let mut retained = Vec::new();
24177    for _ in 0..FRAME_COUNT {
24178      let frame = Arc::new(TaskDependency::default());
24179      retained.push(Arc::clone(&frame));
24180      let _context = TaskDependencyGuard::enter(&frame);
24181      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24182      // The frame's next poll drains what the previous one published.
24183      frame.begin_poll();
24184    }
24185    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
24186      panic!("the handle must still be a blocking handle");
24187    };
24188    let remembered = usize::from(published.first.is_some()) + published.rest.len();
24189    assert!(
24190      remembered <= 2,
24191      "a drained frame must not stay in the publication history, remembered={remembered}"
24192    );
24193    drop(handle);
24194    drop(sender);
24195  }
24196
24197  #[cfg(napi_runtime_os_threads)]
24198  #[test]
24199  fn mass_detach_of_pending_blocking_handles_stays_linear() {
24200    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
24201    // poll pass -- so `begin_poll` never runs to drain `current` and all N
24202    // entries are simultaneously registered and still pending -- and are then
24203    // dropped together. Retiring a handle's own entry is O(1) and
24204    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
24205    //
24206    // This is the shape that made an earlier attempt at this optimisation
24207    // quadratic: it replaced the job index with a scan of `current` on every
24208    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
24209    // the dependency mutex.
24210    const HANDLE_COUNT: usize = 512;
24211
24212    let parent = Arc::new(TaskDependency::default());
24213    let _context = TaskDependencyGuard::enter(&parent);
24214    let waker = futures::task::noop_waker();
24215    let mut cx = Context::from_waker(&waker);
24216
24217    let mut senders = Vec::with_capacity(HANDLE_COUNT);
24218    let mut handles = Vec::with_capacity(HANDLE_COUNT);
24219    for index in 0..HANDLE_COUNT {
24220      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24221      senders.push(sender);
24222      handles.push(JoinHandle(JoinHandleInner::Blocking {
24223        receiver,
24224        dependency: BlockingDependency {
24225          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
24226          owner: None,
24227        },
24228        published: PublishedBlockingDependencies::default(),
24229      }));
24230    }
24231    for handle in &mut handles {
24232      assert!(Pin::new(handle).poll(&mut cx).is_pending());
24233    }
24234    assert_eq!(
24235      parent
24236        .state
24237        .lock()
24238        .unwrap_or_else(std::sync::PoisonError::into_inner)
24239        .current
24240        .len(),
24241      HANDLE_COUNT,
24242      "every handle must be registered and still pending before the detach"
24243    );
24244
24245    parent.reset_entry_visits();
24246    drop(handles);
24247
24248    assert!(
24249      parent.entry_visits() <= HANDLE_COUNT * 4,
24250      "mass-detach must visit O(n) entries across n handles, visits={}",
24251      parent.entry_visits()
24252    );
24253    assert_eq!(
24254      parent.get_dependency(),
24255      None,
24256      "every detached handle must have retired its own publication"
24257    );
24258    drop(senders);
24259  }
24260
24261  #[cfg(napi_runtime_os_threads)]
24262  #[test]
24263  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
24264    use std::sync::mpsc;
24265
24266    struct ReenterDependencyContext {
24267      dependency: Arc<TaskDependency>,
24268      woke: mpsc::Sender<()>,
24269    }
24270
24271    impl std::task::Wake for ReenterDependencyContext {
24272      fn wake(self: Arc<Self>) {
24273        let _context = TaskDependencyGuard::enter(&self.dependency);
24274        self.woke.send(()).unwrap();
24275      }
24276    }
24277
24278    let parent = Arc::new(TaskDependency::default());
24279    let nested = Arc::new(TaskDependency::default());
24280    let (woke_tx, woke_rx) = mpsc::channel();
24281    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
24282      dependency: Arc::clone(&nested),
24283      woke: woke_tx,
24284    }));
24285    parent.register_waiter(&waiter);
24286    let blocking = BlockingDependency {
24287      job: BlockingJobId(u64::MAX - 62),
24288      owner: None,
24289    };
24290
24291    let _context = TaskDependencyGuard::enter(&parent);
24292    let mut published = PublishedBlockingDependencies::default();
24293    record_blocking_dependency(&mut published, blocking);
24294    woke_rx
24295      .recv_timeout(Duration::from_secs(1))
24296      .expect("dependency notification must permit reentrant TLS context access");
24297
24298    parent.register_waiter(&waiter);
24299    clear_blocking_dependency(published);
24300    woke_rx
24301      .recv_timeout(Duration::from_secs(1))
24302      .expect("dependency clearing must permit reentrant TLS context access");
24303  }
24304
24305  #[cfg(napi_runtime_os_threads)]
24306  #[test]
24307  fn dependency_free_begin_poll_does_not_lock_state() {
24308    use std::sync::mpsc;
24309
24310    let dependency = Arc::new(TaskDependency::default());
24311    let state = dependency.state.lock().unwrap();
24312    let (completed_tx, completed_rx) = mpsc::channel();
24313    let polling_dependency = Arc::clone(&dependency);
24314    let polling = std::thread::spawn(move || {
24315      polling_dependency.begin_poll();
24316      completed_tx.send(()).unwrap();
24317    });
24318
24319    let completed_without_lock = completed_rx
24320      .recv_timeout(Duration::from_millis(200))
24321      .is_ok();
24322    drop(state);
24323    polling.join().unwrap();
24324    assert!(
24325      completed_without_lock,
24326      "a task with no published dependency must skip the dependency-state mutex"
24327    );
24328  }
24329
24330  #[cfg(not(target_family = "wasm"))]
24331  #[test]
24332  fn deep_dependency_liveness_chain_is_iterative() {
24333    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24334
24335    if std::env::var_os(CHILD_ENV).is_some() {
24336      const DEPTH: usize = 100_000;
24337
24338      let mut links = Vec::with_capacity(DEPTH + 1);
24339      links.push(DependencyLink::root());
24340      for _ in 0..DEPTH {
24341        links.push(DependencyLink::derived(links.last().unwrap()));
24342      }
24343      assert!(links.last().unwrap().is_live());
24344      links[0].cancel();
24345      assert!(!links.last().unwrap().is_live());
24346      return;
24347    }
24348
24349    let output = std::process::Command::new(std::env::current_exe().unwrap())
24350      .arg("--exact")
24351      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24352      .arg("--nocapture")
24353      .env(CHILD_ENV, "1")
24354      .output()
24355      .expect("the subprocess test must start");
24356    assert!(
24357      output.status.success(),
24358      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24359      output.status.code(),
24360      String::from_utf8_lossy(&output.stdout),
24361      String::from_utf8_lossy(&output.stderr)
24362    );
24363  }
24364
24365  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24366  #[test]
24367  fn task_dependency_set_contains_panicking_waiter() {
24368    struct PanicWake;
24369    impl std::task::Wake for PanicWake {
24370      fn wake(self: Arc<Self>) {
24371        panic!("intentional TaskDependency::set waiter panic");
24372      }
24373    }
24374
24375    let dependency = TaskDependency::default();
24376    let blocking = BlockingDependency {
24377      job: BlockingJobId(u64::MAX),
24378      owner: None,
24379    };
24380    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24381
24382    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24383    assert!(
24384      result.is_ok(),
24385      "set notification must not unwind its caller"
24386    );
24387    assert_eq!(
24388      dependency.get_dependency(),
24389      Some(blocking),
24390      "set must commit before notifying its waiter"
24391    );
24392  }
24393
24394  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24395  #[test]
24396  fn task_dependency_clear_contains_panicking_waiter() {
24397    struct PanicWake;
24398    impl std::task::Wake for PanicWake {
24399      fn wake(self: Arc<Self>) {
24400        panic!("intentional TaskDependency::clear waiter panic");
24401      }
24402    }
24403
24404    let dependency = TaskDependency::default();
24405    dependency.set_owned(BlockingDependency {
24406      job: BlockingJobId(u64::MAX),
24407      owner: None,
24408    });
24409    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24410
24411    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24412    assert!(
24413      result.is_ok(),
24414      "clear notification must not unwind its caller"
24415    );
24416    assert_eq!(
24417      dependency.get_dependency(),
24418      None,
24419      "clear must commit before notifying its waiter"
24420    );
24421  }
24422
24423  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24424  #[test]
24425  fn task_dependency_clear_if_contains_panicking_waiter() {
24426    struct PanicWake;
24427    impl std::task::Wake for PanicWake {
24428      fn wake(self: Arc<Self>) {
24429        panic!("intentional TaskDependency::clear_if waiter panic");
24430      }
24431    }
24432
24433    let dependency = TaskDependency::default();
24434    let blocking = BlockingDependency {
24435      job: BlockingJobId(u64::MAX),
24436      owner: None,
24437    };
24438    let sequence = dependency.set_owned(blocking);
24439    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24440
24441    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24442    assert!(
24443      result.is_ok(),
24444      "retire notification must not unwind its caller"
24445    );
24446    assert_eq!(
24447      dependency.get_dependency(),
24448      None,
24449      "retire must commit before notifying its waiter"
24450    );
24451  }
24452
24453  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24454  #[test]
24455  fn task_dependency_waiter_replacement_contains_panicking_drop() {
24456    use std::sync::mpsc;
24457
24458    const GENERATION: u64 = u64::MAX - 20;
24459
24460    struct PanicOnDropWake {
24461      dropped: mpsc::Sender<Option<u64>>,
24462    }
24463
24464    #[expect(
24465      clippy::manual_noop_waker,
24466      reason = "the test observes the waker payload's Drop"
24467    )]
24468    impl std::task::Wake for PanicOnDropWake {
24469      fn wake(self: Arc<Self>) {}
24470    }
24471
24472    impl Drop for PanicOnDropWake {
24473      fn drop(&mut self) {
24474        self
24475          .dropped
24476          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24477          .unwrap();
24478        panic!("intentional replaced dependency waiter destructor panic");
24479      }
24480    }
24481
24482    let dependency = TaskDependency::new(GENERATION, None);
24483    let (dropped_tx, dropped_rx) = mpsc::channel();
24484    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24485      dropped: dropped_tx,
24486    }));
24487    dependency.register_waiter(&waiter);
24488    drop(waiter);
24489
24490    let replacement = futures::task::noop_waker();
24491    let result = catch_unwind(AssertUnwindSafe(|| {
24492      dependency.register_waiter(&replacement)
24493    }));
24494    assert!(
24495      result.is_ok(),
24496      "replacing a hostile waiter must not unwind registration"
24497    );
24498    assert_eq!(
24499      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24500      Some(GENERATION),
24501      "the replaced waiter must be destroyed under its runtime generation"
24502    );
24503  }
24504
24505  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24506  #[test]
24507  fn task_dependency_retirement_contains_stored_waiter_drop() {
24508    use std::sync::mpsc;
24509
24510    const GENERATION: u64 = u64::MAX - 21;
24511
24512    struct PanicOnDropWake {
24513      dropped: mpsc::Sender<Option<u64>>,
24514    }
24515
24516    #[expect(
24517      clippy::manual_noop_waker,
24518      reason = "the test observes the waker payload's Drop"
24519    )]
24520    impl std::task::Wake for PanicOnDropWake {
24521      fn wake(self: Arc<Self>) {}
24522    }
24523
24524    impl Drop for PanicOnDropWake {
24525      fn drop(&mut self) {
24526        self
24527          .dropped
24528          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24529          .unwrap();
24530        panic!("intentional stored dependency waiter destructor panic");
24531      }
24532    }
24533
24534    let dependency = TaskDependency::new(GENERATION, None);
24535    let (dropped_tx, dropped_rx) = mpsc::channel();
24536    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24537      dropped: dropped_tx,
24538    }));
24539    dependency.register_waiter(&waiter);
24540    drop(waiter);
24541
24542    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24543    assert!(
24544      result.is_ok(),
24545      "retiring a hostile stored waiter must not unwind"
24546    );
24547    assert_eq!(
24548      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24549      Some(GENERATION),
24550      "the retained waiter must be destroyed under its runtime generation"
24551    );
24552  }
24553
24554  #[cfg(napi_runtime_os_threads)]
24555  #[test]
24556  fn detaching_task_clears_dependency_waiter_parent_retention() {
24557    struct RetainingWake {
24558      retained_parent: Arc<()>,
24559    }
24560
24561    impl std::task::Wake for RetainingWake {
24562      fn wake(self: Arc<Self>) {
24563        let _ = &self.retained_parent;
24564      }
24565    }
24566
24567    let dependency = Arc::new(TaskDependency::default());
24568    let retained_parent = Arc::new(());
24569    let retained_parent_weak = Arc::downgrade(&retained_parent);
24570    let waiter = Waker::from(Arc::new(RetainingWake {
24571      retained_parent: Arc::clone(&retained_parent),
24572    }));
24573    dependency.register_waiter(&waiter);
24574    drop(waiter);
24575    drop(retained_parent);
24576    assert!(
24577      retained_parent_weak.upgrade().is_some(),
24578      "the stored dependency waiter must retain its parent before detachment"
24579    );
24580
24581    let (runnable, task) = async_task::spawn(
24582      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24583      |_| {},
24584    );
24585    drop(JoinHandle(JoinHandleInner::Task {
24586      task: task.fallible(),
24587      awaiter: None,
24588      dependency: Arc::clone(&dependency),
24589    }));
24590    assert!(
24591      retained_parent_weak.upgrade().is_none(),
24592      "detachment must clear the dependency waiter instead of retaining its parent task"
24593    );
24594    drop(runnable);
24595  }
24596
24597  #[cfg(napi_runtime_os_threads)]
24598  #[test]
24599  fn detaching_task_clears_many_parent_publications_linearly() {
24600    const PUBLICATION_COUNT: usize = 1024;
24601
24602    struct CountWake(Arc<AtomicUsize>);
24603
24604    impl std::task::Wake for CountWake {
24605      fn wake(self: Arc<Self>) {
24606        self.0.fetch_add(1, Ordering::SeqCst);
24607      }
24608    }
24609
24610    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24611      dependency
24612        .state
24613        .lock()
24614        .unwrap_or_else(std::sync::PoisonError::into_inner)
24615        .current
24616        .back()
24617        .expect("the sentinel publication must be appended")
24618        .publication
24619        .clone()
24620    }
24621
24622    let owner = BlockingOwnerToken {
24623      executor_id: u64::MAX - 300,
24624      frame: u64::MAX - 301,
24625    };
24626    let other_owner = BlockingOwnerToken {
24627      executor_id: u64::MAX - 302,
24628      frame: u64::MAX - 303,
24629    };
24630    let child = Arc::new(TaskDependency::default());
24631    for index in 0..PUBLICATION_COUNT {
24632      child.set_owned(BlockingDependency {
24633        job: BlockingJobId(1_000_000 + index as u64),
24634        owner: (index % 2 == 0).then_some(owner),
24635      });
24636    }
24637    let child_publications = child.snapshot_all_live();
24638    let source = child_publications[0].clone();
24639
24640    let parent = Arc::new(TaskDependency::default());
24641    let _context = TaskDependencyGuard::enter(&parent);
24642    let (runnable, task) = async_task::spawn(
24643      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24644      |_| {},
24645    );
24646    let mut handle = JoinHandle(JoinHandleInner::Task {
24647      task: task.fallible(),
24648      awaiter: None,
24649      dependency: Arc::clone(&child),
24650    });
24651    let waker = futures::task::noop_waker();
24652    let mut cx = Context::from_waker(&waker);
24653    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24654    assert_eq!(
24655      parent.get_dependencies().len(),
24656      PUBLICATION_COUNT,
24657      "polling the child handle must propagate every current publication"
24658    );
24659
24660    let mut wrong_claim_source = source.clone();
24661    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24662    parent.set_borrowed(&wrong_claim_source);
24663    let wrong_claim_publication = latest_publication(&parent);
24664
24665    let mut wrong_job_source = source.clone();
24666    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24667    parent.set_borrowed(&wrong_job_source);
24668    let wrong_job_publication = latest_publication(&parent);
24669
24670    let wrong_immediate_source = source.derived(None);
24671    parent.set_borrowed(&wrong_immediate_source);
24672    let wrong_immediate_publication = latest_publication(&parent);
24673
24674    let wakes = Arc::new(AtomicUsize::new(0));
24675    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24676    parent.reset_entry_visits();
24677    drop(handle);
24678
24679    assert_eq!(
24680      wakes.load(Ordering::SeqCst),
24681      1,
24682      "one batched parent cleanup must emit one waiter notification"
24683    );
24684    assert!(
24685      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24686      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24687      parent.entry_visits()
24688    );
24689    assert!(
24690      wrong_claim_publication.is_live(),
24691      "claim identity must remain part of the match"
24692    );
24693    assert!(
24694      wrong_job_publication.is_live(),
24695      "job identity must remain part of the match"
24696    );
24697    assert!(
24698      wrong_immediate_publication.is_live(),
24699      "only an exact immediate-parent link may be retired"
24700    );
24701
24702    {
24703      let state = parent
24704        .state
24705        .lock()
24706        .unwrap_or_else(std::sync::PoisonError::into_inner);
24707      assert_eq!(state.current.len(), 3);
24708      // The `job_sequences` index is gone -- a blocking handle retires its own
24709      // entry by sequence -- so "the index retains no stale sequence" is
24710      // structurally impossible rather than something to assert. The
24711      // substantive property that assertion protected, that only the three
24712      // NON-matching sentinels survived the clear, is checked directly on the
24713      // authoritative structure.
24714      assert!(
24715        state
24716          .current
24717          .iter()
24718          .all(|entry| entry.publication.is_live()),
24719        "only the three non-matching sentinels may remain, all still live"
24720      );
24721      assert!(state.unowned_sequences.is_empty());
24722      assert_eq!(state.owner_sequences.len(), 1);
24723      assert_eq!(
24724        state.owner_sequences.get(&owner).map(VecDeque::len),
24725        Some(3)
24726      );
24727    }
24728    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24729    assert!(
24730      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24731      "cancelled unowned publications must not survive in availability predicates"
24732    );
24733    drop(runnable);
24734  }
24735
24736  #[cfg(not(target_family = "wasm"))]
24737  #[test]
24738  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24739    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24740
24741    if std::env::var_os(CHILD_ENV).is_some() {
24742      struct PanicWake;
24743      impl std::task::Wake for PanicWake {
24744        fn wake(self: Arc<Self>) {
24745          panic!("intentional dependency waker panic");
24746        }
24747      }
24748
24749      struct PanicOnDrop;
24750      impl Drop for PanicOnDrop {
24751        fn drop(&mut self) {
24752          panic!("intentional buffered blocking result destructor panic");
24753        }
24754      }
24755
24756      let parent = Arc::new(TaskDependency::default());
24757      let dependency = BlockingDependency {
24758        job: BlockingJobId(u64::MAX - 1),
24759        owner: None,
24760      };
24761      // The handle is built by hand rather than by polling, so hand it the
24762      // publication token a Pending poll would have recorded; detach retires
24763      // the entry by sequence.
24764      let sequence = parent.set_owned(dependency);
24765      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24766      let _context = TaskDependencyGuard::enter(&parent);
24767      let (sender, receiver) = oneshot::channel();
24768      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24769        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24770      ));
24771      drop(JoinHandle(JoinHandleInner::Blocking {
24772        receiver,
24773        dependency,
24774        published: PublishedBlockingDependencies {
24775          first: Some(PublishedBlockingDependency {
24776            state: Arc::downgrade(&parent),
24777            sequence,
24778          }),
24779          rest: Vec::new(),
24780        },
24781      }));
24782      assert_eq!(parent.get_dependency(), None);
24783      return;
24784    }
24785
24786    let output = std::process::Command::new(std::env::current_exe().unwrap())
24787      .arg("--exact")
24788      .arg(
24789        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24790      )
24791      .arg("--nocapture")
24792      .env(CHILD_ENV, "1")
24793      .output()
24794      .expect("the subprocess test must start");
24795    assert!(
24796      output.status.success(),
24797      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24798      output.status.code(),
24799      String::from_utf8_lossy(&output.stdout),
24800      String::from_utf8_lossy(&output.stderr)
24801    );
24802  }
24803
24804  #[cfg(napi_runtime_os_threads)]
24805  #[test]
24806  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24807    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
24808    // that does NOT own a counted blocking slot and re-enters `block_on`
24809    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
24810    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
24811    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
24812    // driver could use the over-cap fallback, the inner jobs would run while
24813    // both holders still occupy slots and push peak active blocking above 2.
24814    // The cap must be honored AND everything must still complete.
24815    use std::sync::{Barrier, mpsc};
24816    use std::time::Duration;
24817
24818    let (done_tx, done_rx) = mpsc::channel();
24819    let peak_out = Arc::new(Mutex::new(None));
24820    let peak_slot = Arc::clone(&peak_out);
24821    let runner = std::thread::spawn(move || {
24822      let metrics = Arc::new(RuntimeMetrics::default());
24823      let options = RuntimeOptions {
24824        flavor: RuntimeFlavor::MultiThread,
24825        worker_threads: 4,
24826        max_blocking_tasks: 2,
24827        thread_name_prefix: "rd1-cap".to_string(),
24828        park_deadline: None,
24829        drain_linger: DEFAULT_DRAIN_LINGER,
24830      };
24831      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24832
24833      let holder_count = 2usize; // saturates max_blocking_tasks
24834      let driver_count = 2usize;
24835
24836      // Holders signal once they hold a counted slot, then wait to be released.
24837      let (held_tx, held_rx) = mpsc::channel();
24838      let release = Arc::new(Barrier::new(holder_count + 1));
24839
24840      let mut holder_handles = Vec::new();
24841      for _ in 0..holder_count {
24842        let held_tx = held_tx.clone();
24843        let release = Arc::clone(&release);
24844        let handle = executor.schedule_blocking(move || {
24845          held_tx.send(()).unwrap();
24846          release.wait();
24847          0usize
24848        });
24849        holder_handles.push(handle);
24850      }
24851      // Wait until both holders hold their slots (cap fully saturated).
24852      for _ in 0..holder_count {
24853        held_rx.recv().unwrap();
24854      }
24855
24856      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
24857      // an inner spawn_blocking.
24858      let mut driver_tasks = Vec::new();
24859      for _ in 0..driver_count {
24860        let exec = Arc::clone(&executor);
24861        let body = async move {
24862          let inner_exec = Arc::clone(&exec);
24863          let mut inner = std::pin::pin!(async move {
24864            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24865            assert_eq!(v, 5);
24866          });
24867          exec.block_on(inner.as_mut());
24868        };
24869        let scheduler = Arc::clone(&executor);
24870        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24871        executor.schedule(runnable);
24872        driver_tasks.push(task);
24873      }
24874
24875      // Let the driver tasks reach block_on. They must park until a holder
24876      // frees a slot instead of running their inner jobs over the cap.
24877      std::thread::sleep(Duration::from_millis(300));
24878
24879      // Release the holders so the inner jobs can run within the cap.
24880      release.wait();
24881
24882      for task in driver_tasks {
24883        futures::executor::block_on(task);
24884      }
24885      for handle in holder_handles {
24886        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24887      }
24888
24889      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24890      let _ = done_tx.send(());
24891    });
24892
24893    match done_rx.recv_timeout(Duration::from_secs(10)) {
24894      Ok(()) => runner.join().unwrap(),
24895      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24896    }
24897    let peak = peak_out.lock().unwrap().expect("peak captured");
24898    assert!(
24899      peak <= 2,
24900      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24901    );
24902  }
24903
24904  #[cfg(napi_runtime_os_threads)]
24905  #[test]
24906  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24907    // Blocking-owner isolation invariant: a counted blocking OWNER that
24908    // re-enters cooperative `block_on` can drive a plain queued runnable via
24909    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
24910    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
24911    // the cap is saturated, the inner job must wait for a slot instead of
24912    // borrowing the owner's token.
24913    //
24914    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
24915    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
24916    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
24917    // drainers, no replacement drainer can be spawned, so O itself runs R via
24918    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
24919    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
24920    // runs within the cap and the peak remains 2. Everything must still
24921    // complete.
24922    use std::sync::{Barrier, mpsc};
24923    use std::time::Duration;
24924
24925    let (done_tx, done_rx) = mpsc::channel();
24926    let peak_out = Arc::new(Mutex::new(None));
24927    let peak_slot = Arc::clone(&peak_out);
24928    let runner = std::thread::spawn(move || {
24929      let metrics = Arc::new(RuntimeMetrics::default());
24930      let options = RuntimeOptions {
24931        flavor: RuntimeFlavor::MultiThread,
24932        worker_threads: 2,
24933        max_blocking_tasks: 2,
24934        thread_name_prefix: "rd1-owner-runnable".to_string(),
24935        park_deadline: None,
24936        drain_linger: DEFAULT_DRAIN_LINGER,
24937      };
24938      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24939
24940      let saturated = Arc::new(Barrier::new(2));
24941      let release = Arc::new(Barrier::new(2));
24942
24943      // Holder H2: holds the second counted slot until released. Not the driver.
24944      let h2_saturated = Arc::clone(&saturated);
24945      let h2_release = Arc::clone(&release);
24946      let h2 = executor.schedule_blocking(move || {
24947        h2_saturated.wait();
24948        h2_release.wait();
24949        0usize
24950      });
24951
24952      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
24953      // spawns plain runnable R and drives it cooperatively via `block_on`.
24954      let o_exec = Arc::clone(&executor);
24955      let o_saturated = Arc::clone(&saturated);
24956      let o = executor.schedule_blocking(move || {
24957        o_saturated.wait(); // both slots held -> cap saturated
24958
24959        let r_exec = Arc::clone(&o_exec);
24960        let body = async move {
24961          let inner_exec = Arc::clone(&r_exec);
24962          let mut inner = std::pin::pin!(async move {
24963            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24964            assert_eq!(v, 5);
24965          });
24966          r_exec.block_on(inner.as_mut());
24967        };
24968        let sched = Arc::clone(&o_exec);
24969        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24970        o_exec.schedule(runnable);
24971
24972        let mut wait_r = std::pin::pin!(async move {
24973          task.await;
24974        });
24975        o_exec.block_on(wait_r.as_mut());
24976        1usize
24977      });
24978
24979      // Give O time to drive R into its inner `block_on`; R must be parked
24980      // waiting for a slot.
24981      std::thread::sleep(Duration::from_millis(300));
24982      // Release H2 so the inner job can run within the cap.
24983      release.wait();
24984
24985      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24986      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24987
24988      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24989      let _ = done_tx.send(());
24990    });
24991
24992    match done_rx.recv_timeout(Duration::from_secs(10)) {
24993      Ok(()) => runner.join().unwrap(),
24994      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24995    }
24996    let peak = peak_out.lock().unwrap().expect("peak captured");
24997    assert!(
24998      peak <= 2,
24999      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
25000    );
25001  }
25002
25003  #[cfg(napi_runtime_os_threads)]
25004  #[test]
25005  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
25006    // The over-cap fallback must select the exact pending JoinHandle job, not
25007    // the front of the shared blocking queue.
25008    //
25009    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
25010    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
25011    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
25012    // The exact dependency must skip U and run D. Because U can only run once
25013    // a real slot frees after O completes, D must STRICTLY precede U.
25014    use std::sync::{Barrier, mpsc};
25015
25016    let (done_tx, done_rx) = mpsc::channel();
25017    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
25018    let order_read = Arc::clone(&order_out);
25019    let runner = std::thread::spawn(move || {
25020      let metrics = Arc::new(RuntimeMetrics::default());
25021      let options = RuntimeOptions {
25022        flavor: RuntimeFlavor::MultiThread,
25023        worker_threads: 2,
25024        max_blocking_tasks: 2,
25025        thread_name_prefix: "rd1-unrelated".to_string(),
25026        park_deadline: None,
25027        drain_linger: DEFAULT_DRAIN_LINGER,
25028      };
25029      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
25030
25031      // O and H signal once they each hold a counted slot (cap saturated), then
25032      // wait so U is queued before O nests. H stays held until the very end.
25033      let (held_tx, held_rx) = mpsc::channel();
25034      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
25035      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
25036
25037      // Holder H: holds slot 2 for the whole test.
25038      let h_held = held_tx.clone();
25039      let h_rel = Arc::clone(&h_release);
25040      let h = executor.schedule_blocking(move || {
25041        h_held.send(()).unwrap();
25042        h_rel.wait();
25043        0usize
25044      });
25045
25046      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
25047      // its OWN descendant D.
25048      let o_exec = Arc::clone(&executor);
25049      let o_ready = Arc::clone(&u_ready);
25050      let o_order = Arc::clone(&order_out);
25051      let o = executor.schedule_blocking(move || {
25052        held_tx.send(()).unwrap();
25053        o_ready.wait(); // wait until U is queued ahead of D
25054        let inner_exec = Arc::clone(&o_exec);
25055        let d_order = Arc::clone(&o_order);
25056        let mut inner = std::pin::pin!(async move {
25057          let v = inner_exec
25058            .schedule_blocking(move || {
25059              d_order.lock().unwrap().push("D");
25060              5usize
25061            })
25062            .await
25063            .unwrap();
25064          assert_eq!(v, 5);
25065        });
25066        o_exec.block_on(inner.as_mut());
25067        1usize
25068      });
25069
25070      // Wait until BOTH O and H hold their slots -> cap saturated.
25071      held_rx.recv().unwrap();
25072      held_rx.recv().unwrap();
25073
25074      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
25075      let u_order = Arc::clone(&order_out);
25076      let u = executor.schedule_blocking(move || {
25077        u_order.lock().unwrap().push("U");
25078        2usize
25079      });
25080
25081      // Release O to nest now that U is queued ahead of D.
25082      u_ready.wait();
25083
25084      // O makes progress by running D over cap; U only runs after O frees a slot.
25085      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
25086      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
25087
25088      // Release H and finish.
25089      h_release.wait();
25090      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
25091
25092      let _ = done_tx.send(());
25093    });
25094
25095    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
25096      Ok(()) => runner.join().unwrap(),
25097      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
25098    }
25099    let order = order_read.lock().unwrap().clone();
25100    assert_eq!(
25101      order,
25102      vec!["D", "U"],
25103      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
25104    );
25105  }
25106
25107  #[cfg(napi_runtime_os_threads)]
25108  #[test]
25109  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
25110    use std::sync::{Barrier, mpsc};
25111
25112    let opts = RuntimeOptions {
25113      flavor: RuntimeFlavor::MultiThread,
25114      worker_threads: 2,
25115      max_blocking_tasks: 2,
25116      thread_name_prefix: "rd1-scope".to_string(),
25117      park_deadline: None,
25118      drain_linger: DEFAULT_DRAIN_LINGER,
25119    };
25120    let exec1 =
25121      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25122    let exec2 =
25123      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25124    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25125
25126    let holder_count = opts.max_blocking_tasks;
25127    let (held_tx, held_rx) = mpsc::channel();
25128    let release = Arc::new(Barrier::new(holder_count + 1));
25129    let mut holder_handles = Vec::new();
25130    for _ in 0..holder_count {
25131      let held_tx = held_tx.clone();
25132      let release = Arc::clone(&release);
25133      holder_handles.push(exec2.schedule_blocking(move || {
25134        held_tx.send(()).unwrap();
25135        release.wait();
25136        0usize
25137      }));
25138    }
25139    for _ in 0..holder_count {
25140      held_rx.recv().unwrap();
25141    }
25142    assert_eq!(
25143      exec2.active_blocking.load(Ordering::Acquire),
25144      opts.max_blocking_tasks
25145    );
25146
25147    let exact = BlockingJobId(u64::MAX);
25148    let ran = Arc::new(AtomicBool::new(false));
25149    let ran_job = Arc::clone(&ran);
25150    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
25151      id: exact,
25152      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25153    });
25154    let dependency = TaskDependency::default();
25155
25156    let token1 = exec1.fresh_owner_token();
25157    {
25158      let _owner = BlockingOwnerGuard::enter(Some(token1));
25159      dependency.set_owned(BlockingDependency {
25160        job: exact,
25161        owner: Some(token1),
25162      });
25163      assert!(
25164        !exec2.try_owned_blocking_over_cap(&dependency),
25165        "a foreign-executor ambient token must fail the executor gate"
25166      );
25167    }
25168
25169    let token2 = exec2.fresh_owner_token();
25170    {
25171      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
25172      let _owner = BlockingOwnerGuard::enter(Some(token2));
25173      dependency.set_owned(BlockingDependency {
25174        job: BlockingJobId(u64::MAX - 1),
25175        owner: Some(token2),
25176      });
25177      assert!(
25178        !exec2.try_owned_blocking_over_cap(&dependency),
25179        "a foreign or unrelated dependency id must fail the exact-job gate"
25180      );
25181      dependency.set_owned(BlockingDependency {
25182        job: BlockingJobId(exact.0 - 2),
25183        owner: Some(token2),
25184      });
25185      assert!(
25186        !exec2.try_owned_blocking_over_cap(&dependency),
25187        "a different job id must not consume the queued dependency"
25188      );
25189      dependency.set_owned(BlockingDependency {
25190        job: exact,
25191        owner: Some(token2),
25192      });
25193      assert!(
25194        exec2.try_owned_blocking_over_cap(&dependency),
25195        "the exact awaited job must run over cap"
25196      );
25197    }
25198    assert!(
25199      ran.load(Ordering::SeqCst),
25200      "the exact dependency must have run"
25201    );
25202
25203    release.wait();
25204    for handle in holder_handles {
25205      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25206    }
25207  }
25208
25209  #[cfg(napi_runtime_os_threads)]
25210  #[test]
25211  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
25212    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
25213    let first_owner = executor.fresh_owner_token();
25214    let second_owner = executor.fresh_owner_token();
25215    let _first_owner =
25216      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
25217    let _second_owner =
25218      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
25219    let exact = BlockingJobId(u64::MAX - 30);
25220    let dependency = TaskDependency::default();
25221    dependency.set_owned(BlockingDependency {
25222      job: exact,
25223      owner: None,
25224    });
25225    let ran = Arc::new(AtomicBool::new(false));
25226    let ran_job = Arc::clone(&ran);
25227    executor
25228      .blocking_queue
25229      .lock()
25230      .unwrap()
25231      .push(QueuedBlocking {
25232        id: exact,
25233        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25234      });
25235
25236    assert!(
25237      !executor.try_owned_blocking_over_cap(&dependency),
25238      "an untagged driver must not choose between multiple active owner frames"
25239    );
25240    assert!(!ran.load(Ordering::SeqCst));
25241    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
25242    assert!(
25243      queued.is_some(),
25244      "ambiguous ownership must leave the exact job queued"
25245    );
25246  }
25247
25248  #[cfg(napi_runtime_os_threads)]
25249  #[test]
25250  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
25251    let executor = multi_thread_executor(2, None, "live-parked-dependency");
25252    let owner = executor.fresh_owner_token();
25253    let dependency = TaskDependency::new(u64::MAX, Some(owner));
25254    let stale = BlockingJobId(u64::MAX - 10);
25255    let live = BlockingJobId(u64::MAX - 9);
25256    dependency.set_owned(BlockingDependency {
25257      job: stale,
25258      owner: Some(owner),
25259    });
25260    dependency.begin_poll();
25261    dependency.set_owned(BlockingDependency {
25262      job: live,
25263      owner: Some(owner),
25264    });
25265
25266    let stale_ran = Arc::new(AtomicBool::new(false));
25267    let stale_job_ran = Arc::clone(&stale_ran);
25268    let live_ran = Arc::new(AtomicBool::new(false));
25269    let live_job_ran = Arc::clone(&live_ran);
25270    {
25271      let mut queue = executor
25272        .blocking_queue
25273        .lock()
25274        .unwrap_or_else(std::sync::PoisonError::into_inner);
25275      queue.push(QueuedBlocking {
25276        id: stale,
25277        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
25278      });
25279      queue.push(QueuedBlocking {
25280        id: live,
25281        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
25282      });
25283    }
25284
25285    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25286    let _owner = BlockingOwnerGuard::enter(Some(owner));
25287    assert!(
25288      executor.try_owned_blocking_over_cap(&dependency),
25289      "the owner lane must claim the currently published dependency"
25290    );
25291    assert!(
25292      live_ran.load(Ordering::SeqCst),
25293      "the live dependency must run"
25294    );
25295    assert!(
25296      !stale_ran.load(Ordering::SeqCst),
25297      "the stale registered id must remain queued"
25298    );
25299    let remaining = executor
25300      .blocking_queue
25301      .lock()
25302      .unwrap_or_else(std::sync::PoisonError::into_inner)
25303      .jobs
25304      .keys()
25305      .copied()
25306      .collect::<Vec<_>>();
25307    assert_eq!(remaining, vec![stale]);
25308  }
25309
25310  #[cfg(napi_runtime_os_threads)]
25311  #[test]
25312  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
25313    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
25314    let owner = executor.fresh_owner_token();
25315    let child = TaskDependency::new_task(u64::MAX);
25316    let parent = TaskDependency::new(u64::MAX, Some(owner));
25317    let exact = BlockingDependency {
25318      job: BlockingJobId(u64::MAX - 31),
25319      owner: Some(owner),
25320    };
25321
25322    child.set_owned(exact);
25323    let stale = child
25324      .get()
25325      .expect("the child must publish its direct dependency");
25326    child.begin_poll();
25327    parent.set_borrowed(&stale);
25328
25329    let ran = Arc::new(AtomicBool::new(false));
25330    let ran_job = Arc::clone(&ran);
25331    executor
25332      .blocking_queue
25333      .lock()
25334      .unwrap()
25335      .push(QueuedBlocking {
25336        id: exact.job,
25337        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25338      });
25339    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25340    let _owner = BlockingOwnerGuard::enter(Some(owner));
25341    assert!(
25342      !executor.try_owned_blocking_over_cap(&parent),
25343      "a publication copied before child cancellation must remain cancelled after parent install"
25344    );
25345    assert!(!ran.load(Ordering::SeqCst));
25346
25347    child.set_owned(exact);
25348    parent.set_borrowed(
25349      &child
25350        .get()
25351        .expect("the replacement publication must be live"),
25352    );
25353    assert!(
25354      executor.try_owned_blocking_over_cap(&parent),
25355      "a fresh child publication must still authorize its exact queued job"
25356    );
25357    assert!(ran.load(Ordering::SeqCst));
25358  }
25359
25360  #[cfg(napi_runtime_os_threads)]
25361  #[test]
25362  fn intermediate_repoll_does_not_cancel_child_source_claim() {
25363    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25364    let owner = executor.fresh_owner_token();
25365    let child = TaskDependency::new_task(u64::MAX);
25366    let intermediate = TaskDependency::new_task(u64::MAX);
25367    let parent = TaskDependency::new(u64::MAX, Some(owner));
25368    let exact = BlockingDependency {
25369      job: BlockingJobId(u64::MAX - 43),
25370      owner: Some(owner),
25371    };
25372
25373    child.set_owned(exact);
25374    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25375    parent.set_borrowed(
25376      &intermediate
25377        .get()
25378        .expect("the intermediate publication must be live"),
25379    );
25380
25381    intermediate.begin_poll();
25382    assert!(
25383      child.get().is_some(),
25384      "withdrawing an intermediate hop must not cancel the child's source claim"
25385    );
25386    assert!(
25387      parent.get().is_none(),
25388      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25389    );
25390
25391    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25392    parent.set_borrowed(
25393      &intermediate
25394        .get()
25395        .expect("the intermediate must republish the still-live source"),
25396    );
25397    let ran = Arc::new(AtomicBool::new(false));
25398    let ran_job = Arc::clone(&ran);
25399    executor
25400      .blocking_queue
25401      .lock()
25402      .unwrap()
25403      .push(QueuedBlocking {
25404        id: exact.job,
25405        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25406      });
25407
25408    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25409    let _owner = BlockingOwnerGuard::enter(Some(owner));
25410    assert!(
25411      executor.try_owned_blocking_over_cap(&parent),
25412      "a fresh chain through the repolled intermediate must retain the exact source claim"
25413    );
25414    assert!(ran.load(Ordering::SeqCst));
25415  }
25416
25417  #[cfg(napi_runtime_os_threads)]
25418  #[test]
25419  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25420    use std::sync::mpsc;
25421
25422    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25423    let owner = executor.fresh_owner_token();
25424    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25425    let child = Arc::new(TaskDependency::new_task(u64::MAX));
25426    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25427    let exact = BlockingDependency {
25428      job: BlockingJobId(u64::MAX - 44),
25429      owner: Some(owner),
25430    };
25431    child.set_owned(exact);
25432    parent.set_borrowed(&child.get().expect("the child source must be live"));
25433
25434    let ran = Arc::new(AtomicBool::new(false));
25435    let ran_job = Arc::clone(&ran);
25436    executor
25437      .blocking_queue
25438      .lock()
25439      .unwrap()
25440      .push(QueuedBlocking {
25441        id: exact.job,
25442        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25443      });
25444
25445    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25446    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25447    let claimant_executor = Arc::clone(&executor);
25448    let claimant_parent = Arc::clone(&parent);
25449    let claimant = std::thread::spawn(move || {
25450      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25451        *slot.borrow_mut() = Some(Box::new(move || {
25452          claim_entered_tx.send(()).unwrap();
25453          release_claim_rx.recv().unwrap();
25454        }));
25455      });
25456      let _owner = BlockingOwnerGuard::enter(Some(owner));
25457      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25458    });
25459    claim_entered_rx
25460      .recv_timeout(Duration::from_secs(2))
25461      .expect("the exact claimant must hold the shared transition lock");
25462
25463    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25464    let withdrawing_child = Arc::clone(&child);
25465    let withdrawal = std::thread::spawn(move || {
25466      withdrawing_child.begin_poll();
25467      withdrawn_tx.send(()).unwrap();
25468    });
25469    let withdrew_before_claim = withdrawn_rx
25470      .recv_timeout(Duration::from_millis(200))
25471      .is_ok();
25472    release_claim_tx.send(()).unwrap();
25473
25474    assert!(
25475      claimant.join().unwrap(),
25476      "the claim that linearized first must execute the exact job"
25477    );
25478    withdrawal.join().unwrap();
25479    assert!(
25480      !withdrew_before_claim,
25481      "source withdrawal must not interleave after claim validation but before claim commit"
25482    );
25483    assert!(ran.load(Ordering::SeqCst));
25484  }
25485
25486  #[cfg(napi_runtime_os_threads)]
25487  #[test]
25488  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25489    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
25490    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
25491    // executor whose worker set the marker. A worker of executor A that re-enters
25492    // `block_on` on executor B must take the PARKING branch instead -- it owns
25493    // none of B's accounting, so driving B's queue would make it an unaccounted
25494    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
25495    //
25496    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
25497    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
25498    // `block_on` on each executor with the SAME probe shape and observe whether
25499    // the cooperative `run_one` driver actually ran a queued runnable.
25500    //
25501    // The probe future returns `Pending` once (self-waking so the parking branch
25502    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
25503    // `run_one` at least once before the future completes. A single runnable is
25504    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
25505    // no background drainer is ever spawned to steal it). After `block_on`
25506    // returns: the cooperative branch will have driven that runnable
25507    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
25508    // (`runnable_polls == 0`, still queued).
25509    struct PendThenReady {
25510      polls: usize,
25511    }
25512    impl Future for PendThenReady {
25513      type Output = ();
25514      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25515        self.polls += 1;
25516        if self.polls >= 2 {
25517          Poll::Ready(())
25518        } else {
25519          // Self-wake so the parking branch (`futures::executor::block_on`)
25520          // re-polls to completion without any external waker; the cooperative
25521          // branch re-polls unconditionally each loop iteration regardless.
25522          cx.waker().wake_by_ref();
25523          Poll::Pending
25524        }
25525      }
25526    }
25527
25528    // Push one already-resolvable runnable directly onto `target`'s queue (no
25529    // `schedule`, so no drainer is spawned) and detach its task.
25530    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25531      let scheduler = Arc::clone(target);
25532      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25533      target.push_fifo_raw(runnable);
25534      task.detach();
25535    }
25536
25537    let opts = RuntimeOptions {
25538      flavor: RuntimeFlavor::MultiThread,
25539      worker_threads: 2,
25540      max_blocking_tasks: 2,
25541      thread_name_prefix: "rd-onpool-scope".to_string(),
25542      park_deadline: None,
25543      drain_linger: DEFAULT_DRAIN_LINGER,
25544    };
25545    let exec1 =
25546      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25547    let exec2 =
25548      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25549    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25550
25551    // Simulate being on exec1's pool worker for the whole scenario.
25552    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25553
25554    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
25555    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
25556    // touch its queue.
25557    enqueue_runnable(&exec2);
25558    {
25559      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25560      exec2.block_on(probe.as_mut());
25561    }
25562    assert_eq!(
25563      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25564      0,
25565      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25566    );
25567    assert_eq!(
25568      exec2.injector.len(),
25569      1,
25570      "the foreign call must leave exec2's runnable untouched on its queue"
25571    );
25572
25573    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
25574    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
25575    // runnable via `run_one`.
25576    enqueue_runnable(&exec1);
25577    {
25578      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25579      exec1.block_on(probe.as_mut());
25580    }
25581    assert_eq!(
25582      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25583      1,
25584      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25585    );
25586    assert!(
25587      exec1.injector.is_empty(),
25588      "the cooperative branch must have drained exec1's queued runnable"
25589    );
25590
25591    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
25592    // (steal-drain replaces the former `queue.lock().clear()`).
25593    while exec2.steal_one().is_some() {}
25594  }
25595
25596  #[cfg(napi_runtime_os_threads)]
25597  #[test]
25598  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25599    // A long-lived blocking consumer may consume the entire configured
25600    // blocking allowance, but it must not consume the reserve lane required
25601    // by the async producers that feed it.
25602    use std::sync::mpsc;
25603    use std::time::Duration;
25604
25605    const PRODUCER_COUNT: usize = 8;
25606
25607    fn schedule_pool_workload(
25608      executor: &Arc<MultiThreadExecutor>,
25609      tx: &mpsc::Sender<Option<usize>>,
25610      count: usize,
25611    ) {
25612      for i in 0..count {
25613        let tx = tx.clone();
25614        let scheduler = Arc::clone(executor);
25615        let (runnable, task) =
25616          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25617            scheduler.schedule(r);
25618          });
25619        executor.schedule(runnable);
25620        task.detach();
25621      }
25622      let tx = tx.clone();
25623      let scheduler = Arc::clone(executor);
25624      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25625        scheduler.schedule(r)
25626      });
25627      executor.schedule(runnable);
25628      task.detach();
25629    }
25630
25631    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25632    let (done_tx, done_rx) = mpsc::sync_channel(1);
25633    let runner = std::thread::spawn(move || {
25634      let options = RuntimeOptions {
25635        flavor: RuntimeFlavor::MultiThread,
25636        worker_threads: 1,
25637        max_blocking_tasks: 1,
25638        thread_name_prefix: "rd2-pre-fix".to_string(),
25639        park_deadline: None,
25640        drain_linger: DEFAULT_DRAIN_LINGER,
25641      }
25642      .validate()
25643      .expect("production MultiThread options must provide a reserve lane");
25644      let executor =
25645        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25646
25647      let (tx, rx) = mpsc::channel::<Option<usize>>();
25648      let (started_tx, started_rx) = mpsc::sync_channel(1);
25649      let handle = executor.schedule_blocking(move || {
25650        started_tx.send(()).unwrap();
25651        let mut sum = 0usize;
25652        while let Ok(msg) = rx.recv() {
25653          match msg {
25654            Some(value) => sum += value,
25655            None => break,
25656          }
25657        }
25658        sum
25659      });
25660      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25661      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25662      let sum = futures::executor::block_on(handle).unwrap();
25663      done_tx.send(sum).unwrap();
25664    });
25665
25666    match done_rx.recv_timeout(Duration::from_secs(10)) {
25667      Ok(sum) => {
25668        runner.join().unwrap();
25669        assert_eq!(sum, expected_sum);
25670      }
25671      Err(error) => {
25672        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25673      }
25674    }
25675  }
25676
25677  #[test]
25678  fn current_thread_blocking_work_completes_inline() {
25679    let metrics = Arc::new(RuntimeMetrics::default());
25680    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25681      CurrentThreadExecutor::new(Arc::clone(&metrics)),
25682    )));
25683    let registration = backend.work.try_register_work().unwrap();
25684
25685    let value =
25686      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25687        .expect("blocking task should complete");
25688
25689    assert_eq!(value, 7);
25690    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25691    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25692  }
25693
25694  #[cfg(napi_runtime_os_threads)]
25695  #[test]
25696  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25697    use std::sync::mpsc;
25698
25699    let controller = Arc::new(current_thread_controller(
25700      "current-thread-blocking-admission",
25701    ));
25702    let backend = controller.backend();
25703    let (first_started_tx, first_started_rx) = mpsc::channel();
25704    let (release_first_tx, release_first_rx) = mpsc::channel();
25705    let first_controller = Arc::clone(&controller);
25706    let first = std::thread::spawn(move || {
25707      let handle = first_controller
25708        .try_spawn_blocking(move || {
25709          first_started_tx.send(()).unwrap();
25710          release_first_rx.recv().unwrap();
25711          1usize
25712        })
25713        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25714      futures::executor::block_on(handle).unwrap()
25715    });
25716    first_started_rx
25717      .recv_timeout(Duration::from_secs(1))
25718      .unwrap();
25719
25720    let (second_started_tx, second_started_rx) = mpsc::channel();
25721    let second_controller = Arc::clone(&controller);
25722    let second = std::thread::spawn(move || {
25723      let handle = second_controller
25724        .try_spawn_blocking(move || {
25725          second_started_tx.send(()).unwrap();
25726          2usize
25727        })
25728        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25729      futures::executor::block_on(handle).unwrap()
25730    });
25731
25732    wait_until("both CurrentThread blocking calls to be accepted", || {
25733      backend
25734        .work
25735        .state
25736        .lock()
25737        .unwrap_or_else(std::sync::PoisonError::into_inner)
25738        .active
25739        == 2
25740    });
25741    assert!(
25742      second_started_rx
25743        .recv_timeout(Duration::from_millis(100))
25744        .is_err(),
25745      "CurrentThread must not execute two blocking closures concurrently"
25746    );
25747
25748    release_first_tx.send(()).unwrap();
25749    second_started_rx
25750      .recv_timeout(Duration::from_secs(1))
25751      .expect("the waiting blocking call must acquire the released lane");
25752    assert_eq!(first.join().unwrap(), 1);
25753    assert_eq!(second.join().unwrap(), 2);
25754    assert_eq!(
25755      controller
25756        .metrics
25757        .blocking_tasks_started
25758        .load(Ordering::Relaxed),
25759      2
25760    );
25761    assert_eq!(
25762      controller
25763        .metrics
25764        .blocking_tasks_completed
25765        .load(Ordering::Relaxed),
25766      2
25767    );
25768    assert_eq!(
25769      controller
25770        .metrics
25771        .active_blocking_tasks
25772        .load(Ordering::Relaxed),
25773      0
25774    );
25775    assert_eq!(
25776      controller
25777        .metrics
25778        .max_active_blocking_tasks
25779        .load(Ordering::Relaxed),
25780      1,
25781      "the public active-lane metric must remain within CurrentThread's reported capacity"
25782    );
25783    controller.shutdown().unwrap();
25784  }
25785
25786  #[cfg(napi_runtime_os_threads)]
25787  #[test]
25788  fn current_thread_nested_blocking_reuses_the_active_lane() {
25789    use std::sync::mpsc;
25790
25791    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25792    let (done_tx, done_rx) = mpsc::channel();
25793    let runner_controller = Arc::clone(&controller);
25794    let runner = std::thread::spawn(move || {
25795      let nested_controller = Arc::clone(&runner_controller);
25796      let outer = runner_controller
25797        .try_spawn_blocking(move || {
25798          let inner = nested_controller
25799            .try_spawn_blocking(|| 41usize)
25800            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25801          futures::executor::block_on(inner).unwrap() + 1
25802        })
25803        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25804      done_tx
25805        .send(futures::executor::block_on(outer).unwrap())
25806        .unwrap();
25807    });
25808
25809    assert_eq!(
25810      done_rx
25811        .recv_timeout(Duration::from_secs(2))
25812        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25813      42
25814    );
25815    runner.join().unwrap();
25816    assert_eq!(
25817      controller
25818        .metrics
25819        .blocking_tasks_started
25820        .load(Ordering::Relaxed),
25821      2
25822    );
25823    assert_eq!(
25824      controller
25825        .metrics
25826        .blocking_tasks_completed
25827        .load(Ordering::Relaxed),
25828      2
25829    );
25830    assert_eq!(
25831      controller
25832        .metrics
25833        .active_blocking_tasks
25834        .load(Ordering::Relaxed),
25835      0
25836    );
25837    assert_eq!(
25838      controller
25839        .metrics
25840        .max_active_blocking_tasks
25841        .load(Ordering::Relaxed),
25842      1,
25843      "nested work borrows the outer lane instead of reporting a second physical lane"
25844    );
25845    controller.shutdown().unwrap();
25846  }
25847
25848  #[cfg(napi_runtime_os_threads)]
25849  #[test]
25850  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25851    use std::sync::mpsc;
25852
25853    struct HotFuture {
25854      stop: Arc<AtomicBool>,
25855    }
25856
25857    impl Future for HotFuture {
25858      type Output = ();
25859
25860      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25861        if self.stop.load(Ordering::SeqCst) {
25862          Poll::Ready(())
25863        } else {
25864          cx.waker().wake_by_ref();
25865          Poll::Pending
25866        }
25867      }
25868    }
25869
25870    let stop = Arc::new(AtomicBool::new(false));
25871    let (ready_tx, ready_rx) = mpsc::channel();
25872    let (exact_tx, exact_rx) = mpsc::channel();
25873    let (done_tx, done_rx) = mpsc::channel();
25874    let runner_stop = Arc::clone(&stop);
25875    let runner = std::thread::spawn(move || {
25876      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25877        RuntimeMetrics::default(),
25878      )));
25879      let owner = executor.fresh_owner_token();
25880      let job = BlockingJobId(u64::MAX - 70);
25881      let (result_tx, result_rx) = oneshot::channel();
25882      {
25883        let mut state = executor
25884          .blocking_admission
25885          .state
25886          .lock()
25887          .unwrap_or_else(std::sync::PoisonError::into_inner);
25888        state.owner = Some(owner);
25889        let exact_stop = Arc::clone(&runner_stop);
25890        state.queue.push(QueuedBlocking {
25891          id: job,
25892          run: Box::new(move || {
25893            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25894            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25895          }),
25896        });
25897      }
25898      let exact = JoinHandle(JoinHandleInner::Blocking {
25899        receiver: result_rx,
25900        dependency: BlockingDependency {
25901          job,
25902          owner: Some(owner),
25903        },
25904        published: PublishedBlockingDependencies::default(),
25905      });
25906      let _owner = executor.enter_blocking_owner(owner, false);
25907
25908      let scheduler = Arc::clone(&executor);
25909      let hot_stop = Arc::clone(&runner_stop);
25910      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25911        scheduler.schedule(runnable);
25912      });
25913      executor.schedule(runnable);
25914      ready_tx.send(()).unwrap();
25915
25916      let mut value = None;
25917      {
25918        let mut future = std::pin::pin!(async {
25919          value = Some(exact.await.unwrap());
25920          hot_task.await;
25921        });
25922        assert_eq!(
25923          executor.block_on(future.as_mut()),
25924          BlockOnOutcome::Completed
25925        );
25926      }
25927      executor
25928        .blocking_admission
25929        .state
25930        .lock()
25931        .unwrap_or_else(std::sync::PoisonError::into_inner)
25932        .owner = None;
25933      done_tx.send(value.unwrap()).unwrap();
25934    });
25935
25936    ready_rx
25937      .recv_timeout(Duration::from_secs(2))
25938      .expect("the fairness driver must start");
25939    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25940    stop.store(true, Ordering::SeqCst);
25941    assert_eq!(
25942      done_rx
25943        .recv_timeout(Duration::from_secs(2))
25944        .expect("the CurrentThread driver must retire"),
25945      41
25946    );
25947    runner.join().unwrap();
25948    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25949    assert_eq!(
25950      exact_before_stop,
25951      Some(true),
25952      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25953    );
25954  }
25955
25956  #[cfg(napi_runtime_os_threads)]
25957  #[test]
25958  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25959    use std::sync::mpsc;
25960
25961    let controller = Arc::new(current_thread_controller(
25962      "current-thread-cross-driver-blocking",
25963    ));
25964    let backend = controller.backend();
25965    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25966      panic!("configured CurrentThread backend must create a CurrentThread executor");
25967    };
25968    let executor = Arc::clone(executor);
25969    drop(backend);
25970
25971    let (release_driver_tx, release_driver_rx) = oneshot::channel();
25972    let (driver_started_tx, driver_started_rx) = mpsc::channel();
25973    let driver_controller = Arc::clone(&controller);
25974    let driver = std::thread::spawn(move || {
25975      driver_started_tx.send(std::thread::current().id()).unwrap();
25976      block_on_with_controller(&driver_controller, async {
25977        release_driver_rx
25978          .await
25979          .expect("driver release sender must remain alive");
25980      });
25981    });
25982    let driver_thread = driver_started_rx
25983      .recv_timeout(Duration::from_secs(1))
25984      .unwrap();
25985    wait_until("the secondary CurrentThread driver to park", || {
25986      executor
25987        .stop
25988        .parkers
25989        .lock()
25990        .unwrap_or_else(std::sync::PoisonError::into_inner)
25991        .iter()
25992        .filter_map(Weak::upgrade)
25993        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25994    });
25995
25996    let (child_polled_tx, child_polled_rx) = mpsc::channel();
25997    let (release_child_tx, release_child_rx) = mpsc::channel();
25998    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25999    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
26000    let (owner_done_tx, owner_done_rx) = mpsc::channel();
26001    let owner_controller = Arc::clone(&controller);
26002    let owner = std::thread::spawn(move || {
26003      let owner_thread = std::thread::current().id();
26004      let await_controller = Arc::clone(&owner_controller);
26005      let child_controller = Arc::clone(&owner_controller);
26006      let outer = owner_controller
26007        .try_spawn_blocking(move || {
26008          let spawn_controller = Arc::clone(&child_controller);
26009          let child = spawn_controller
26010            .try_spawn(async move {
26011              child_polled_tx.send(std::thread::current().id()).unwrap();
26012              release_child_rx.recv().unwrap();
26013              child_controller
26014                .try_spawn_blocking(move || {
26015                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
26016                  41usize
26017                })
26018                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
26019                .await
26020                .unwrap()
26021                + 1
26022            })
26023            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
26024
26025          assert_eq!(
26026            child_polled_rx
26027              .recv_timeout(Duration::from_secs(1))
26028              .unwrap(),
26029            driver_thread,
26030            "the secondary driver must own the child poll that reaches blocking admission"
26031          );
26032          owner_awaiting_tx.send(owner_thread).unwrap();
26033          block_on_with_controller(&await_controller, child).unwrap()
26034        })
26035        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
26036      owner_done_tx
26037        .send(futures::executor::block_on(outer))
26038        .unwrap();
26039    });
26040
26041    let owner_thread = owner_awaiting_rx
26042      .recv_timeout(Duration::from_secs(1))
26043      .unwrap();
26044    wait_until(
26045      "the blocking owner to park while awaiting the cross-driver child",
26046      || {
26047        executor
26048          .stop
26049          .parkers
26050          .lock()
26051          .unwrap_or_else(std::sync::PoisonError::into_inner)
26052          .iter()
26053          .filter_map(Weak::upgrade)
26054          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
26055      },
26056    );
26057    release_child_tx.send(()).unwrap();
26058
26059    assert_eq!(
26060      blocking_ran_rx
26061        .recv_timeout(Duration::from_secs(2))
26062        .unwrap(),
26063      owner_thread,
26064      "the exact descendant must be serviced by the lane-owning driver"
26065    );
26066    assert_eq!(
26067      owner_done_rx
26068        .recv_timeout(Duration::from_secs(2))
26069        .expect("cross-driver blocking dependency must not deadlock")
26070        .unwrap(),
26071      42
26072    );
26073    release_driver_tx.send(()).unwrap();
26074    owner.join().unwrap();
26075    driver.join().unwrap();
26076    controller.shutdown().unwrap();
26077    assert_eq!(
26078      controller
26079        .metrics
26080        .blocking_tasks_started
26081        .load(Ordering::Relaxed),
26082      2
26083    );
26084    assert_eq!(
26085      controller
26086        .metrics
26087        .blocking_tasks_completed
26088        .load(Ordering::Relaxed),
26089      2
26090    );
26091    assert_eq!(
26092      controller
26093        .metrics
26094        .active_blocking_tasks
26095        .load(Ordering::Relaxed),
26096      0
26097    );
26098    assert_eq!(
26099      controller
26100        .metrics
26101        .max_active_blocking_tasks
26102        .load(Ordering::Relaxed),
26103      1,
26104      "cross-driver dependency service must reuse the owner's physical lane"
26105    );
26106  }
26107
26108  #[cfg(napi_runtime_os_threads)]
26109  #[test]
26110  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
26111    use std::sync::mpsc;
26112
26113    let controller = Arc::new(current_thread_controller(
26114      "current-thread-blocking-stop-race",
26115    ));
26116    let backend = controller.backend();
26117    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26118      panic!("configured CurrentThread backend must create a CurrentThread executor");
26119    };
26120    let executor = Arc::clone(executor);
26121    drop(backend);
26122
26123    let ran = Arc::new(AtomicBool::new(false));
26124    let blocking_ran = Arc::clone(&ran);
26125    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
26126    let (release_admission_tx, release_admission_rx) = mpsc::channel();
26127    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
26128    let submit_controller = Arc::clone(&controller);
26129    let submitter = std::thread::spawn(move || {
26130      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26131        *slot.borrow_mut() = Some(Box::new(move || {
26132          admission_locked_tx.send(()).unwrap();
26133          release_admission_rx.recv().unwrap();
26134        }));
26135      });
26136      let handle = submit_controller
26137        .try_spawn_blocking(move || {
26138          blocking_ran.store(true, Ordering::SeqCst);
26139        })
26140        .unwrap_or_else(|_| {
26141          panic!("the submission linearized before shutdown and must be accepted")
26142        });
26143      blocking_result_tx
26144        .send(futures::executor::block_on(handle))
26145        .unwrap();
26146    });
26147    admission_locked_rx
26148      .recv_timeout(Duration::from_secs(2))
26149      .expect("the blocking submitter must pause while holding the admission lock");
26150
26151    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
26152    let shutdown_controller = Arc::clone(&controller);
26153    let shutdown = std::thread::spawn(move || {
26154      shutdown_result_tx
26155        .send(shutdown_controller.shutdown())
26156        .unwrap();
26157    });
26158    wait_until("CurrentThread shutdown to publish generation stop", || {
26159      executor.stop.is_stopping()
26160    });
26161    release_admission_tx.send(()).unwrap();
26162
26163    let blocking_error = blocking_result_rx
26164      .recv_timeout(Duration::from_secs(2))
26165      .expect("the admitted blocking handle must resolve")
26166      .expect_err("stop publication must reject the blocking lane claim");
26167    assert_eq!(
26168      blocking_error.to_string(),
26169      "the async runtime stopped before the blocking task could start"
26170    );
26171    assert!(
26172      !ran.load(Ordering::SeqCst),
26173      "blocking work must not start after stop publication"
26174    );
26175    shutdown_result_rx
26176      .recv_timeout(Duration::from_secs(2))
26177      .expect("shutdown must finish after the rejected admission retires")
26178      .unwrap();
26179    submitter.join().unwrap();
26180    shutdown.join().unwrap();
26181  }
26182
26183  #[cfg(napi_runtime_os_threads)]
26184  #[test]
26185  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
26186    use std::sync::mpsc;
26187
26188    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26189      RuntimeMetrics::default(),
26190    )));
26191    let ran = Arc::new(AtomicBool::new(false));
26192    let job_ran = Arc::clone(&ran);
26193    {
26194      let mut state = executor
26195        .blocking_admission
26196        .state
26197        .lock()
26198        .unwrap_or_else(std::sync::PoisonError::into_inner);
26199      state.queue.push(QueuedBlocking {
26200        id: BlockingJobId(u64::MAX - 71),
26201        run: Box::new(move || {
26202          job_ran.store(true, Ordering::SeqCst);
26203        }),
26204      });
26205    }
26206
26207    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26208    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26209    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26210    let claim_executor = Arc::clone(&executor);
26211    let claimant = std::thread::spawn(move || {
26212      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26213        *slot.borrow_mut() = Some(Box::new(move || {
26214          claim_locked_tx.send(()).unwrap();
26215          release_claim_rx.recv().unwrap();
26216        }));
26217      });
26218      claim_result_tx
26219        .send(claim_executor.run_one_blocking())
26220        .unwrap();
26221    });
26222    claim_locked_rx
26223      .recv_timeout(Duration::from_secs(2))
26224      .expect("the FIFO claimant must pause while holding the admission lock");
26225
26226    executor.stop.begin_shutdown();
26227    release_claim_tx.send(()).unwrap();
26228    assert!(
26229      !claim_result_rx
26230        .recv_timeout(Duration::from_secs(2))
26231        .expect("the stopped FIFO claim must finish"),
26232      "stop publication must reject the queued FIFO claim"
26233    );
26234    claimant.join().unwrap();
26235    assert!(
26236      !ran.load(Ordering::SeqCst),
26237      "queued FIFO work must not start after stop publication"
26238    );
26239    assert!(
26240      !executor
26241        .blocking_admission
26242        .state
26243        .lock()
26244        .unwrap_or_else(std::sync::PoisonError::into_inner)
26245        .queue
26246        .is_empty(),
26247      "the rejected FIFO job must remain queued for shutdown cancellation"
26248    );
26249
26250    executor.close_blocking_admission();
26251    assert!(
26252      executor
26253        .blocking_admission
26254        .state
26255        .lock()
26256        .unwrap_or_else(std::sync::PoisonError::into_inner)
26257        .queue
26258        .is_empty()
26259    );
26260  }
26261
26262  #[cfg(napi_runtime_os_threads)]
26263  #[test]
26264  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26265    use std::sync::mpsc;
26266
26267    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26268      RuntimeMetrics::default(),
26269    )));
26270    let ran = Arc::new(AtomicBool::new(false));
26271    let job_ran = Arc::clone(&ran);
26272    {
26273      let mut state = executor
26274        .blocking_admission
26275        .state
26276        .lock()
26277        .unwrap_or_else(std::sync::PoisonError::into_inner);
26278      state.queue.push(QueuedBlocking {
26279        id: BlockingJobId(u64::MAX - 73),
26280        run: Box::new(move || {
26281          job_ran.store(true, Ordering::SeqCst);
26282        }),
26283      });
26284    }
26285
26286    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26287    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26288    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26289    let claim_executor = Arc::clone(&executor);
26290    let claimant = std::thread::spawn(move || {
26291      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26292        *slot.borrow_mut() = Some(Box::new(move || {
26293          claim_locked_tx.send(()).unwrap();
26294          release_claim_rx.recv().unwrap();
26295        }));
26296      });
26297      claim_result_tx
26298        .send(claim_executor.run_one_blocking())
26299        .unwrap();
26300    });
26301    claim_locked_rx
26302      .recv_timeout(Duration::from_secs(2))
26303      .expect("the FIFO claimant must pause while holding the admission lock");
26304
26305    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26306    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26307    let cancelling_executor = Arc::clone(&executor);
26308    let cancellation = std::thread::spawn(move || {
26309      let cancelling = cancelling_executor
26310        .begin_failed_dispatch_cancellation()
26311        .expect("the test must publish terminal dispatch cancellation");
26312      cancellation_published_tx.send(()).unwrap();
26313      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26314      cancellation_done_tx.send(()).unwrap();
26315    });
26316    cancellation_published_rx
26317      .recv_timeout(Duration::from_secs(2))
26318      .expect("terminal dispatch cancellation must publish before queue cancellation");
26319    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26320    assert!(
26321      matches!(
26322        cancellation_done_rx.try_recv(),
26323        Err(mpsc::TryRecvError::Empty)
26324      ),
26325      "terminal cancellation must wait for the blocking-admission lock"
26326    );
26327
26328    release_claim_tx.send(()).unwrap();
26329    assert!(
26330      !claim_result_rx
26331        .recv_timeout(Duration::from_secs(2))
26332        .expect("the cancelled FIFO claim must finish"),
26333      "terminal cancellation publication must reject the queued FIFO claim"
26334    );
26335    cancellation_done_rx
26336      .recv_timeout(Duration::from_secs(2))
26337      .expect("terminal cancellation must drain the rejected FIFO job");
26338    claimant.join().unwrap();
26339    cancellation.join().unwrap();
26340
26341    assert!(
26342      !ran.load(Ordering::SeqCst),
26343      "terminally cancelled FIFO work must not start"
26344    );
26345    assert!(
26346      executor
26347        .blocking_admission
26348        .state
26349        .lock()
26350        .unwrap_or_else(std::sync::PoisonError::into_inner)
26351        .queue
26352        .is_empty(),
26353      "terminal cancellation must drain the rejected FIFO job"
26354    );
26355    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26356  }
26357
26358  #[cfg(napi_runtime_os_threads)]
26359  #[test]
26360  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26361    use std::sync::mpsc;
26362
26363    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26364      RuntimeMetrics::default(),
26365    )));
26366    let owner = executor.fresh_owner_token();
26367    let job = BlockingJobId(u64::MAX - 72);
26368    let ran = Arc::new(AtomicBool::new(false));
26369    let job_ran = Arc::clone(&ran);
26370    {
26371      let mut state = executor
26372        .blocking_admission
26373        .state
26374        .lock()
26375        .unwrap_or_else(std::sync::PoisonError::into_inner);
26376      state.owner = Some(owner);
26377      state.queue.push(QueuedBlocking {
26378        id: job,
26379        run: Box::new(move || {
26380          job_ran.store(true, Ordering::SeqCst);
26381        }),
26382      });
26383    }
26384    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26385    dependency.set_owned(BlockingDependency {
26386      job,
26387      owner: Some(owner),
26388    });
26389
26390    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26391    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26392    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26393    let claim_executor = Arc::clone(&executor);
26394    let claim_dependency = Arc::clone(&dependency);
26395    let claimant = std::thread::spawn(move || {
26396      let _owner = claim_executor.enter_blocking_owner(owner, false);
26397      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26398        *slot.borrow_mut() = Some(Box::new(move || {
26399          claim_locked_tx.send(()).unwrap();
26400          release_claim_rx.recv().unwrap();
26401        }));
26402      });
26403      claim_result_tx
26404        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26405        .unwrap();
26406    });
26407    claim_locked_rx
26408      .recv_timeout(Duration::from_secs(2))
26409      .expect("the owner claimant must pause while holding the admission lock");
26410
26411    executor.stop.begin_shutdown();
26412    release_claim_tx.send(()).unwrap();
26413    assert!(
26414      !claim_result_rx
26415        .recv_timeout(Duration::from_secs(2))
26416        .expect("the stopped owner claim must finish"),
26417      "stop publication must reject the exact-owner claim"
26418    );
26419    claimant.join().unwrap();
26420    assert!(
26421      !ran.load(Ordering::SeqCst),
26422      "queued exact-owner work must not start after stop publication"
26423    );
26424    assert!(
26425      !executor
26426        .blocking_admission
26427        .state
26428        .lock()
26429        .unwrap_or_else(std::sync::PoisonError::into_inner)
26430        .queue
26431        .is_empty(),
26432      "the rejected exact-owner job must remain queued for shutdown cancellation"
26433    );
26434
26435    executor
26436      .blocking_admission
26437      .state
26438      .lock()
26439      .unwrap_or_else(std::sync::PoisonError::into_inner)
26440      .owner = None;
26441    executor.close_blocking_admission();
26442    assert!(
26443      executor
26444        .blocking_admission
26445        .state
26446        .lock()
26447        .unwrap_or_else(std::sync::PoisonError::into_inner)
26448        .queue
26449        .is_empty()
26450    );
26451  }
26452
26453  #[cfg(napi_runtime_os_threads)]
26454  #[test]
26455  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26456    use std::sync::mpsc;
26457
26458    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26459      RuntimeMetrics::default(),
26460    )));
26461    let owner = executor.fresh_owner_token();
26462    let job = BlockingJobId(u64::MAX - 74);
26463    let ran = Arc::new(AtomicBool::new(false));
26464    let job_ran = Arc::clone(&ran);
26465    {
26466      let mut state = executor
26467        .blocking_admission
26468        .state
26469        .lock()
26470        .unwrap_or_else(std::sync::PoisonError::into_inner);
26471      state.owner = Some(owner);
26472      state.queue.push(QueuedBlocking {
26473        id: job,
26474        run: Box::new(move || {
26475          job_ran.store(true, Ordering::SeqCst);
26476        }),
26477      });
26478    }
26479    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26480    dependency.set_owned(BlockingDependency {
26481      job,
26482      owner: Some(owner),
26483    });
26484
26485    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26486    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26487    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26488    let claim_executor = Arc::clone(&executor);
26489    let claim_dependency = Arc::clone(&dependency);
26490    let claimant = std::thread::spawn(move || {
26491      let _owner = claim_executor.enter_blocking_owner(owner, false);
26492      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26493        *slot.borrow_mut() = Some(Box::new(move || {
26494          claim_locked_tx.send(()).unwrap();
26495          release_claim_rx.recv().unwrap();
26496        }));
26497      });
26498      claim_result_tx
26499        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26500        .unwrap();
26501    });
26502    claim_locked_rx
26503      .recv_timeout(Duration::from_secs(2))
26504      .expect("the owner claimant must pause while holding the admission lock");
26505
26506    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26507    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26508    let cancelling_executor = Arc::clone(&executor);
26509    let cancellation = std::thread::spawn(move || {
26510      let cancelling = cancelling_executor
26511        .begin_failed_dispatch_cancellation()
26512        .expect("the test must publish terminal dispatch cancellation");
26513      cancellation_published_tx.send(()).unwrap();
26514      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26515      cancellation_done_tx.send(()).unwrap();
26516    });
26517    cancellation_published_rx
26518      .recv_timeout(Duration::from_secs(2))
26519      .expect("terminal dispatch cancellation must publish before queue cancellation");
26520    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26521    assert!(
26522      matches!(
26523        cancellation_done_rx.try_recv(),
26524        Err(mpsc::TryRecvError::Empty)
26525      ),
26526      "terminal cancellation must wait for the blocking-admission lock"
26527    );
26528
26529    release_claim_tx.send(()).unwrap();
26530    assert!(
26531      !claim_result_rx
26532        .recv_timeout(Duration::from_secs(2))
26533        .expect("the cancelled owner claim must finish"),
26534      "terminal cancellation publication must reject the exact-owner claim"
26535    );
26536    cancellation_done_rx
26537      .recv_timeout(Duration::from_secs(2))
26538      .expect("terminal cancellation must drain the rejected exact-owner job");
26539    claimant.join().unwrap();
26540    cancellation.join().unwrap();
26541
26542    assert!(
26543      !ran.load(Ordering::SeqCst),
26544      "terminally cancelled exact-owner work must not start"
26545    );
26546    assert!(
26547      executor
26548        .blocking_admission
26549        .state
26550        .lock()
26551        .unwrap_or_else(std::sync::PoisonError::into_inner)
26552        .queue
26553        .is_empty(),
26554      "terminal cancellation must drain the rejected exact-owner job"
26555    );
26556    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26557  }
26558
26559  #[cfg(napi_runtime_os_threads)]
26560  #[test]
26561  fn current_thread_shutdown_cancels_waiting_blocking_work() {
26562    use std::sync::mpsc;
26563
26564    struct DropSignal(mpsc::Sender<()>);
26565
26566    impl Drop for DropSignal {
26567      fn drop(&mut self) {
26568        let _ = self.0.send(());
26569      }
26570    }
26571
26572    let controller = Arc::new(current_thread_controller(
26573      "current-thread-blocking-shutdown",
26574    ));
26575    let backend = controller.backend();
26576    let (first_started_tx, first_started_rx) = mpsc::channel();
26577    let (release_first_tx, release_first_rx) = mpsc::channel();
26578    let first_controller = Arc::clone(&controller);
26579    let first = std::thread::spawn(move || {
26580      let handle = first_controller
26581        .try_spawn_blocking(move || {
26582          first_started_tx.send(()).unwrap();
26583          release_first_rx.recv().unwrap();
26584        })
26585        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26586      futures::executor::block_on(handle)
26587    });
26588    first_started_rx
26589      .recv_timeout(Duration::from_secs(1))
26590      .unwrap();
26591
26592    let waiting_ran = Arc::new(AtomicBool::new(false));
26593    let waiting_ran_task = Arc::clone(&waiting_ran);
26594    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26595    let waiting_drop = DropSignal(waiting_dropped_tx);
26596    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26597    let waiting_controller = Arc::clone(&controller);
26598    let waiting = std::thread::spawn(move || {
26599      let handle = waiting_controller
26600        .try_spawn_blocking(move || {
26601          let _waiting_drop = waiting_drop;
26602          waiting_ran_task.store(true, Ordering::SeqCst);
26603        })
26604        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26605      waiting_result_tx
26606        .send(futures::executor::block_on(handle))
26607        .unwrap();
26608    });
26609    wait_until("both CurrentThread blocking calls to be accepted", || {
26610      backend
26611        .work
26612        .state
26613        .lock()
26614        .unwrap_or_else(std::sync::PoisonError::into_inner)
26615        .active
26616        == 2
26617    });
26618
26619    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26620    let shutdown_controller = Arc::clone(&controller);
26621    let shutdown = std::thread::spawn(move || {
26622      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26623    });
26624    waiting_dropped_rx
26625      .recv_timeout(Duration::from_secs(1))
26626      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26627    assert!(
26628      !waiting_ran.load(Ordering::SeqCst),
26629      "waiting blocking work must not start"
26630    );
26631    assert!(
26632      waiting_result_rx
26633        .recv_timeout(Duration::from_secs(1))
26634        .unwrap()
26635        .is_err(),
26636      "the waiting blocking handle must resolve as cancelled"
26637    );
26638    assert!(
26639      shutdown_rx
26640        .recv_timeout(Duration::from_millis(100))
26641        .is_err(),
26642      "shutdown must still wait for the blocking closure that already owns the lane"
26643    );
26644
26645    release_first_tx.send(()).unwrap();
26646    first.join().unwrap().unwrap();
26647    waiting.join().unwrap();
26648    shutdown_rx
26649      .recv_timeout(Duration::from_secs(1))
26650      .unwrap()
26651      .unwrap();
26652    shutdown.join().unwrap();
26653    assert_eq!(
26654      controller
26655        .metrics
26656        .blocking_tasks_started
26657        .load(Ordering::Relaxed),
26658      1
26659    );
26660    assert_eq!(
26661      controller
26662        .metrics
26663        .blocking_tasks_completed
26664        .load(Ordering::Relaxed),
26665      1
26666    );
26667    assert_eq!(
26668      controller
26669        .metrics
26670        .active_blocking_tasks
26671        .load(Ordering::Relaxed),
26672      0
26673    );
26674  }
26675
26676  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26677  #[test]
26678  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26679    use std::{panic::panic_any, sync::mpsc};
26680
26681    struct ReentrantPayload {
26682      controller: Arc<RuntimeController>,
26683      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26684    }
26685
26686    impl Drop for ReentrantPayload {
26687      fn drop(&mut self) {
26688        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26689        let shutdown = self.controller.shutdown();
26690        self.result.send((generation, shutdown)).unwrap();
26691      }
26692    }
26693
26694    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26695
26696    impl Drop for PanicPayloadOnDrop {
26697      fn drop(&mut self) {
26698        panic_any(self.0.take().expect("the result must panic only once"));
26699      }
26700    }
26701
26702    let controller = Arc::new(current_thread_controller(
26703      "current-thread-panic-payload-generation",
26704    ));
26705    let backend = controller.backend();
26706    let generation = backend.generation();
26707    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26708      panic!("configured CurrentThread backend must create a CurrentThread executor");
26709    };
26710    let executor = Arc::clone(executor);
26711    drop(backend);
26712
26713    // Hold the physical lane long enough to force scheduler-owned delivery.
26714    // Dropping the receiver then makes the queued sender destroy the result.
26715    let owner = executor.fresh_owner_token();
26716    executor
26717      .blocking_admission
26718      .state
26719      .lock()
26720      .unwrap_or_else(std::sync::PoisonError::into_inner)
26721      .owner = Some(owner);
26722    let (result_tx, result_rx) = mpsc::channel();
26723    let payload_controller = Arc::clone(&controller);
26724    let handle = controller
26725      .try_spawn_blocking(move || {
26726        PanicPayloadOnDrop(Some(ReentrantPayload {
26727          controller: payload_controller,
26728          result: result_tx,
26729        }))
26730      })
26731      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26732    drop(handle);
26733    executor
26734      .blocking_admission
26735      .state
26736      .lock()
26737      .unwrap_or_else(std::sync::PoisonError::into_inner)
26738      .owner = None;
26739    let dispatch = current_thread_dispatch(&executor);
26740    controller.drive_current_thread_dispatch(dispatch, false);
26741
26742    let (observed_generation, shutdown) = result_rx
26743      .recv_timeout(Duration::from_secs(1))
26744      .expect("the caught result panic payload must be destroyed in the scheduler turn");
26745    assert_eq!(observed_generation, Some(generation));
26746    let error = shutdown.expect_err("payload destruction is still work in the active generation");
26747    assert!(error.to_string().contains("work running on that runtime"));
26748    controller.shutdown().unwrap();
26749  }
26750
26751  #[cfg(panic = "unwind")]
26752  #[test]
26753  fn current_thread_detached_blocking_result_drop_is_contained() {
26754    use std::sync::mpsc;
26755
26756    struct PanicOnDrop(mpsc::Sender<()>);
26757
26758    impl Drop for PanicOnDrop {
26759      fn drop(&mut self) {
26760        self.0.send(()).unwrap();
26761        panic!("intentional CurrentThread blocking result destructor panic");
26762      }
26763    }
26764
26765    let controller = current_thread_controller("current-thread-blocking-result-drop");
26766    let (dropped_tx, dropped_rx) = mpsc::channel();
26767    let handle = controller
26768      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26769      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26770
26771    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26772    dropped_rx
26773      .recv_timeout(Duration::from_secs(1))
26774      .expect("dropping the detached handle must reclaim its blocking result");
26775    assert!(
26776      result.is_ok(),
26777      "the blocking result destructor panic must be contained"
26778    );
26779    controller.shutdown().unwrap();
26780  }
26781
26782  // ---- Runtime option validation and configuration immutability -------------
26783  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
26784  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
26785
26786  #[test]
26787  fn validate_rejects_zero_worker_threads() {
26788    let error = RuntimeOptions {
26789      flavor: RuntimeFlavor::MultiThread,
26790      worker_threads: 0,
26791      max_blocking_tasks: 1,
26792      thread_name_prefix: "rd8".to_string(),
26793      park_deadline: None,
26794      drain_linger: DEFAULT_DRAIN_LINGER,
26795    }
26796    .validate()
26797    .expect_err("worker_threads == 0 must be rejected");
26798    assert_eq!(
26799      error.to_string(),
26800      "worker_threads must be greater than zero"
26801    );
26802  }
26803
26804  #[test]
26805  fn validate_rejects_zero_max_blocking_tasks() {
26806    let error = RuntimeOptions {
26807      flavor: RuntimeFlavor::MultiThread,
26808      worker_threads: 2,
26809      max_blocking_tasks: 0,
26810      thread_name_prefix: "rd8".to_string(),
26811      park_deadline: None,
26812      drain_linger: DEFAULT_DRAIN_LINGER,
26813    }
26814    .validate()
26815    .expect_err("max_blocking_tasks == 0 must be rejected");
26816    assert_eq!(
26817      error.to_string(),
26818      "max_blocking_tasks must be greater than zero"
26819    );
26820  }
26821
26822  #[test]
26823  fn validate_current_thread_coerces_worker_threads_to_one() {
26824    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
26825    // clamped to `.min(worker_threads)` == 1.
26826    let validated = RuntimeOptions {
26827      flavor: RuntimeFlavor::CurrentThread,
26828      worker_threads: 8,
26829      max_blocking_tasks: 8,
26830      thread_name_prefix: "rd8".to_string(),
26831      park_deadline: None,
26832      drain_linger: DEFAULT_DRAIN_LINGER,
26833    }
26834    .validate()
26835    .expect("CurrentThread options must validate");
26836    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26837    assert_eq!(validated.worker_threads, 1);
26838    assert_eq!(validated.max_blocking_tasks, 1);
26839  }
26840
26841  // `validate()` rejects `MultiThread` on builds that cannot create OS threads
26842  // (after applying the clamp), so this success assertion is valid only where
26843  // `napi_runtime_os_threads` holds: native and `wasm32-wasip1-threads`.
26844  #[cfg(napi_runtime_os_threads)]
26845  #[test]
26846  fn validate_reserves_one_worker_from_blocking_admission() {
26847    let validated = RuntimeOptions {
26848      flavor: RuntimeFlavor::MultiThread,
26849      worker_threads: 2,
26850      max_blocking_tasks: 8,
26851      thread_name_prefix: "rd8".to_string(),
26852      park_deadline: None,
26853      drain_linger: DEFAULT_DRAIN_LINGER,
26854    }
26855    .validate()
26856    .expect("MultiThread options must validate");
26857    assert_eq!(validated.worker_threads, 2);
26858    assert_eq!(validated.max_blocking_tasks, 1);
26859  }
26860
26861  #[cfg(napi_runtime_os_threads)]
26862  #[test]
26863  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26864    let validated = RuntimeOptions {
26865      flavor: RuntimeFlavor::MultiThread,
26866      worker_threads: 1,
26867      max_blocking_tasks: 8,
26868      thread_name_prefix: "rd8-minimum".to_string(),
26869      park_deadline: None,
26870      drain_linger: DEFAULT_DRAIN_LINGER,
26871    }
26872    .validate()
26873    .expect("MultiThread options must validate");
26874    assert_eq!(validated.worker_threads, 2);
26875    assert_eq!(validated.max_blocking_tasks, 1);
26876  }
26877
26878  #[cfg(napi_runtime_os_threads)]
26879  #[test]
26880  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26881    let simulated_32_bit = RuntimeOptions {
26882      flavor: RuntimeFlavor::MultiThread,
26883      worker_threads: 256,
26884      max_blocking_tasks: 256,
26885      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26886      park_deadline: None,
26887      drain_linger: DEFAULT_DRAIN_LINGER,
26888    }
26889    .validate_with_rayon_max_threads(Some(255))
26890    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26891    assert_eq!(simulated_32_bit.worker_threads, 255);
26892    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26893
26894    let rayon_max_threads = max_num_threads();
26895    let requested_threads = rayon_max_threads
26896      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26897      .checked_add(1)
26898      .expect("supported worker counts must leave room for an oversized request");
26899    let controller = RuntimeController::new();
26900    controller
26901      .configure(RuntimeOptions {
26902        flavor: RuntimeFlavor::MultiThread,
26903        worker_threads: requested_threads,
26904        max_blocking_tasks: requested_threads,
26905        thread_name_prefix: "rd8-rayon-cap".to_string(),
26906        park_deadline: None,
26907        drain_linger: DEFAULT_DRAIN_LINGER,
26908      })
26909      .expect("an oversized request must clamp to the production worker ceiling");
26910
26911    let reported = controller.options();
26912    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26913    assert_eq!(reported.worker_threads, expected_workers);
26914    assert_eq!(
26915      reported.max_blocking_tasks,
26916      expected_workers - 1,
26917      "the blocking cap must reserve a lane from the physically realizable worker count"
26918    );
26919  }
26920
26921  #[cfg(napi_runtime_os_threads)]
26922  #[test]
26923  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26924    // u64::MAX microseconds (the exact value an unvalidated
26925    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
26926    // via the removed OnceLock) must clamp to the ceiling everywhere a
26927    // budget enters the system: full options validation, a partial patch
26928    // through the controller, and the executor's defensive constructor
26929    // clamp for direct unvalidated test construction.
26930    let extreme = Duration::from_micros(u64::MAX);
26931    let validated = RuntimeOptions {
26932      flavor: RuntimeFlavor::MultiThread,
26933      worker_threads: 2,
26934      max_blocking_tasks: 1,
26935      thread_name_prefix: "linger-clamp".to_string(),
26936      park_deadline: None,
26937      drain_linger: extreme,
26938    }
26939    .validate_with_rayon_max_threads(Some(2))
26940    .expect("an extreme linger budget must clamp, not fail validation");
26941    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26942
26943    let controller = RuntimeController::new();
26944    controller
26945      .configure_partial(RuntimeOptionsPatch {
26946        drain_linger: Some(extreme),
26947        ..RuntimeOptionsPatch::default()
26948      })
26949      .expect("an extreme linger patch must clamp, not fail validation");
26950    assert_eq!(
26951      controller.options().drain_linger,
26952      MAX_DRAIN_LINGER,
26953      "introspection must report the clamped budget, not the requested one"
26954    );
26955    controller
26956      .configure_partial(RuntimeOptionsPatch {
26957        drain_linger: Some(Duration::ZERO),
26958        ..RuntimeOptionsPatch::default()
26959      })
26960      .expect("ZERO (lingering disabled) must remain a valid budget");
26961    assert_eq!(controller.options().drain_linger, Duration::ZERO);
26962
26963    let metrics = Arc::new(RuntimeMetrics::default());
26964    let unvalidated = RuntimeOptions {
26965      flavor: RuntimeFlavor::MultiThread,
26966      worker_threads: 2,
26967      max_blocking_tasks: 2,
26968      thread_name_prefix: "linger-clamp-exec".to_string(),
26969      park_deadline: None,
26970      drain_linger: extreme,
26971    };
26972    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26973      .expect("the executor must construct with an extreme unvalidated budget");
26974    assert_eq!(
26975      executor.drain_linger,
26976      Some(MAX_DRAIN_LINGER),
26977      "the constructor's defensive clamp must bound unvalidated budgets"
26978    );
26979
26980    let metrics = Arc::new(RuntimeMetrics::default());
26981    let disabled = RuntimeOptions {
26982      flavor: RuntimeFlavor::MultiThread,
26983      worker_threads: 2,
26984      max_blocking_tasks: 2,
26985      thread_name_prefix: "linger-clamp-off".to_string(),
26986      park_deadline: None,
26987      drain_linger: Duration::ZERO,
26988    };
26989    let executor = MultiThreadExecutor::new(&disabled, metrics)
26990      .expect("the executor must construct with lingering disabled");
26991    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26992  }
26993
26994  #[cfg(not(target_family = "wasm"))]
26995  #[test]
26996  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26997    let controller = RuntimeController::new();
26998    let options = controller.options();
26999    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
27000    assert_eq!(
27001      options.max_blocking_tasks,
27002      options.worker_threads.saturating_sub(1).max(1),
27003      "the unconfigured Rust API must use the same validated reserve as the binding"
27004    );
27005  }
27006
27007  #[cfg(napi_runtime_os_threads)]
27008  #[test]
27009  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
27010    use std::{sync::mpsc, time::Duration};
27011
27012    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
27013    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
27014    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
27015    let first_controller = Arc::clone(&controller);
27016    let first = std::thread::spawn(move || {
27017      first_controller
27018        .configure_partial_inner(
27019          RuntimeOptionsPatch {
27020            max_blocking_tasks: Some(3),
27021            ..RuntimeOptionsPatch::default()
27022          },
27023          |candidate| {
27024            assert_eq!(
27025              (candidate.worker_threads, candidate.max_blocking_tasks),
27026              (8, 3)
27027            );
27028            first_merged_tx
27029              .send(())
27030              .expect("the race coordinator must still be listening");
27031            release_first_rx
27032              .recv_timeout(Duration::from_secs(5))
27033              .expect("the first partial configuration must be released");
27034          },
27035        )
27036        .expect("the first partial configuration must commit");
27037    });
27038    first_merged_rx
27039      .recv_timeout(Duration::from_secs(5))
27040      .expect("the first partial configuration must hold the controller lock after merging");
27041
27042    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
27043    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
27044    let second_controller = Arc::clone(&controller);
27045    let second = std::thread::spawn(move || {
27046      second_started_tx
27047        .send(())
27048        .expect("the race coordinator must still be listening");
27049      second_controller
27050        .configure_partial_inner(
27051          RuntimeOptionsPatch {
27052            worker_threads: Some(6),
27053            ..RuntimeOptionsPatch::default()
27054          },
27055          |candidate| {
27056            second_candidate_tx
27057              .send((candidate.worker_threads, candidate.max_blocking_tasks))
27058              .expect("the race coordinator must still be listening");
27059          },
27060        )
27061        .expect("the second partial configuration must commit");
27062    });
27063    second_started_rx
27064      .recv_timeout(Duration::from_secs(5))
27065      .expect("the second partial configuration thread must start");
27066    release_first_tx
27067      .send(())
27068      .expect("the first partial configuration must still be waiting");
27069
27070    first
27071      .join()
27072      .expect("the first configuration thread must not panic");
27073    second
27074      .join()
27075      .expect("the second configuration thread must not panic");
27076    assert_eq!(
27077      second_candidate_rx
27078        .recv_timeout(Duration::from_secs(5))
27079        .expect("the second partial configuration must expose its merged candidate"),
27080      (6, 3),
27081      "the second caller must merge against the first caller's committed options"
27082    );
27083
27084    let options = controller.options();
27085    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
27086    assert_eq!(options.thread_name_prefix, "partial-config-race");
27087  }
27088
27089  #[cfg(napi_runtime_os_threads)]
27090  #[test]
27091  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
27092    use std::{sync::mpsc, time::Duration};
27093
27094    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
27095    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
27096    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
27097    let invalid_controller = Arc::clone(&controller);
27098    let invalid = std::thread::spawn(move || {
27099      invalid_controller
27100        .configure_partial_inner(
27101          RuntimeOptionsPatch {
27102            worker_threads: Some(0),
27103            ..RuntimeOptionsPatch::default()
27104          },
27105          |candidate| {
27106            assert_eq!(
27107              (candidate.worker_threads, candidate.max_blocking_tasks),
27108              (0, 2)
27109            );
27110            invalid_merged_tx
27111              .send(())
27112              .expect("the race coordinator must still be listening");
27113            release_invalid_rx
27114              .recv_timeout(Duration::from_secs(5))
27115              .expect("the invalid partial configuration must be released");
27116          },
27117        )
27118        .expect_err("the invalid partial configuration must be rejected")
27119        .to_string()
27120    });
27121    invalid_merged_rx
27122      .recv_timeout(Duration::from_secs(5))
27123      .expect("the invalid partial configuration must hold the lock after merging");
27124
27125    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
27126    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
27127    let valid_controller = Arc::clone(&controller);
27128    let valid = std::thread::spawn(move || {
27129      valid_started_tx
27130        .send(())
27131        .expect("the race coordinator must still be listening");
27132      valid_controller
27133        .configure_partial_inner(
27134          RuntimeOptionsPatch {
27135            max_blocking_tasks: Some(4),
27136            ..RuntimeOptionsPatch::default()
27137          },
27138          |candidate| {
27139            valid_candidate_tx
27140              .send((candidate.worker_threads, candidate.max_blocking_tasks))
27141              .expect("the race coordinator must still be listening");
27142          },
27143        )
27144        .expect("the valid partial configuration must commit");
27145    });
27146    valid_started_rx
27147      .recv_timeout(Duration::from_secs(5))
27148      .expect("the valid partial configuration thread must start");
27149    release_invalid_tx
27150      .send(())
27151      .expect("the invalid partial configuration must still be waiting");
27152
27153    assert_eq!(
27154      invalid
27155        .join()
27156        .expect("the invalid configuration thread must not panic"),
27157      "worker_threads must be greater than zero"
27158    );
27159    valid
27160      .join()
27161      .expect("the valid configuration thread must not panic");
27162    assert_eq!(
27163      valid_candidate_rx
27164        .recv_timeout(Duration::from_secs(5))
27165        .expect("the valid partial configuration must expose its merged candidate"),
27166      (6, 4),
27167      "the valid caller must merge against the last committed options, not the rejected candidate"
27168    );
27169
27170    let options = controller.options();
27171    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
27172    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
27173  }
27174
27175  #[test]
27176  fn configure_after_backend_started_is_rejected() {
27177    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
27178    // so starting it is cheap and spawns no OS threads.
27179    let controller = RuntimeController::new();
27180    controller
27181      .configure(RuntimeOptions {
27182        flavor: RuntimeFlavor::CurrentThread,
27183        worker_threads: 1,
27184        max_blocking_tasks: 1,
27185        thread_name_prefix: "rd8".to_string(),
27186        park_deadline: None,
27187        drain_linger: DEFAULT_DRAIN_LINGER,
27188      })
27189      .expect("first configure before the backend exists must succeed");
27190
27191    // Materialize the backend; further configuration must now be refused.
27192    let _backend = controller.backend();
27193
27194    let error = controller
27195      .configure(RuntimeOptions::default())
27196      .expect_err("configure after the backend started must be rejected");
27197    assert_eq!(
27198      error.to_string(),
27199      "the async runtime configuration is frozen; configure it before the first async call"
27200    );
27201  }
27202
27203  #[test]
27204  fn partial_configuration_after_backend_started_is_rejected() {
27205    let controller = current_thread_controller("partial-config-frozen");
27206    let _backend = controller.backend();
27207
27208    let error = controller
27209      .configure_partial(RuntimeOptionsPatch {
27210        worker_threads: Some(2),
27211        ..RuntimeOptionsPatch::default()
27212      })
27213      .expect_err("partial configuration after the backend started must be rejected");
27214    assert_eq!(
27215      error.to_string(),
27216      "the async runtime configuration is frozen; configure it before the first async call"
27217    );
27218  }
27219
27220  fn multi_thread_options(worker_threads: usize) -> RuntimeOptions {
27221    RuntimeOptions {
27222      flavor: RuntimeFlavor::MultiThread,
27223      worker_threads,
27224      max_blocking_tasks: worker_threads,
27225      thread_name_prefix: "pool-workers".to_string(),
27226      park_deadline: None,
27227      drain_linger: DEFAULT_DRAIN_LINGER,
27228    }
27229  }
27230
27231  #[test]
27232  fn pool_workers_follow_the_validated_flavor_and_worker_count() {
27233    let validate = |options: RuntimeOptions| {
27234      options
27235        .validate_with_rayon_max_threads(Some(MAX_ASYNC_RUNTIME_WORKER_THREADS))
27236        .expect("options must validate")
27237    };
27238    assert_eq!(pool_workers_for(&validate(multi_thread_options(3))), 3);
27239    // MultiThread is clamped to at least two lanes.
27240    assert_eq!(pool_workers_for(&validate(multi_thread_options(1))), 2);
27241    // CurrentThread runs on the host's thread: no pool Worker.
27242    assert_eq!(
27243      pool_workers_for(&validate(RuntimeOptions {
27244        flavor: RuntimeFlavor::CurrentThread,
27245        ..multi_thread_options(8)
27246      })),
27247      0
27248    );
27249  }
27250
27251  #[cfg(napi_runtime_os_threads)]
27252  #[test]
27253  fn pool_workers_mirror_every_successful_configure() {
27254    let controller = RuntimeController::new();
27255    assert_eq!(
27256      controller.pool_workers(),
27257      pool_workers_for(&controller.options()),
27258      "the initial value mirrors the default options"
27259    );
27260
27261    controller
27262      .configure(multi_thread_options(3))
27263      .expect("configure before the backend exists must succeed");
27264    assert_eq!(controller.pool_workers(), 3);
27265
27266    controller
27267      .configure_partial(RuntimeOptionsPatch {
27268        worker_threads: Some(5),
27269        ..RuntimeOptionsPatch::default()
27270      })
27271      .expect("partial configure before the backend exists must succeed");
27272    assert_eq!(controller.pool_workers(), 5);
27273
27274    controller
27275      .configure_partial(RuntimeOptionsPatch {
27276        worker_threads: Some(1),
27277        ..RuntimeOptionsPatch::default()
27278      })
27279      .expect("a MultiThread count below two is clamped, not rejected");
27280    assert_eq!(controller.pool_workers(), 2);
27281
27282    controller
27283      .configure_partial(RuntimeOptionsPatch {
27284        flavor: Some(RuntimeFlavor::CurrentThread),
27285        ..RuntimeOptionsPatch::default()
27286      })
27287      .expect("switching to CurrentThread must succeed");
27288    assert_eq!(controller.pool_workers(), 0);
27289
27290    controller
27291      .configure(multi_thread_options(4))
27292      .expect("configure before the backend exists must succeed");
27293    assert_eq!(controller.pool_workers(), 4);
27294
27295    // A configure that fails validation changes nothing.
27296    controller
27297      .configure(multi_thread_options(0))
27298      .expect_err("zero workers must be rejected");
27299    controller
27300      .configure_partial(RuntimeOptionsPatch {
27301        worker_threads: Some(0),
27302        ..RuntimeOptionsPatch::default()
27303      })
27304      .expect_err("zero workers must be rejected");
27305    assert_eq!(controller.pool_workers(), 4);
27306  }
27307
27308  #[test]
27309  fn pool_workers_survive_a_frozen_configure() {
27310    let controller = current_thread_controller("pool-workers-frozen");
27311    assert_eq!(controller.pool_workers(), 0);
27312    let _backend = controller.backend();
27313
27314    controller
27315      .configure(multi_thread_options(3))
27316      .expect_err("configure after the backend started must be rejected");
27317    controller
27318      .configure_partial(RuntimeOptionsPatch {
27319        flavor: Some(RuntimeFlavor::MultiThread),
27320        worker_threads: Some(3),
27321        ..RuntimeOptionsPatch::default()
27322      })
27323      .expect_err("partial configuration after the backend started must be rejected");
27324    assert_eq!(controller.pool_workers(), 0);
27325    assert_eq!(controller.options().flavor, RuntimeFlavor::CurrentThread);
27326  }
27327
27328  #[cfg(napi_runtime_os_threads)]
27329  #[test]
27330  fn pool_workers_drop_to_zero_once_the_backend_started() {
27331    let controller = RuntimeController::new();
27332    controller
27333      .configure(multi_thread_options(3))
27334      .expect("configure before the backend exists must succeed");
27335    assert_eq!(controller.pool_workers(), 3);
27336
27337    // Building the backend spawns all three pool threads.
27338    drop(controller.backend());
27339    assert_eq!(controller.pool_workers(), 0);
27340
27341    // A restart from `Stopped` builds a new backend and spawns its threads.
27342    controller
27343      .shutdown()
27344      .expect("the first generation must stop");
27345    assert_eq!(controller.pool_workers(), 0);
27346    controller.start().expect("the runtime must restart");
27347    assert_eq!(controller.pool_workers(), 0);
27348    controller
27349      .shutdown()
27350      .expect("the restarted runtime must stop");
27351  }
27352
27353  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
27354    let controller = RuntimeController::new();
27355    controller
27356      .configure(RuntimeOptions {
27357        flavor: RuntimeFlavor::CurrentThread,
27358        worker_threads: 1,
27359        max_blocking_tasks: 1,
27360        thread_name_prefix: thread_name_prefix.to_string(),
27361        park_deadline: None,
27362        drain_linger: DEFAULT_DRAIN_LINGER,
27363      })
27364      .expect("CurrentThread test configuration must be accepted");
27365    controller
27366  }
27367
27368  #[cfg(napi_runtime_os_threads)]
27369  fn multi_thread_controller(
27370    thread_name_prefix: &str,
27371    worker_threads: usize,
27372    max_blocking_tasks: usize,
27373  ) -> RuntimeController {
27374    let controller = RuntimeController::new();
27375    controller
27376      .configure(RuntimeOptions {
27377        flavor: RuntimeFlavor::MultiThread,
27378        worker_threads,
27379        max_blocking_tasks,
27380        thread_name_prefix: thread_name_prefix.to_string(),
27381        park_deadline: None,
27382        drain_linger: DEFAULT_DRAIN_LINGER,
27383      })
27384      .expect("MultiThread test configuration must be accepted");
27385    controller
27386  }
27387
27388  #[test]
27389  fn blocking_backend_creation_failure_preserves_diagnostic() {
27390    let controller = current_thread_controller("blocking-backend-creation-failure");
27391    let ran = Arc::new(AtomicBool::new(false));
27392    let work_ran = Arc::clone(&ran);
27393
27394    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27395    let Err((error, work)) = controller.try_spawn_blocking(move || {
27396      work_ran.store(true, Ordering::SeqCst);
27397      17usize
27398    }) else {
27399      panic!("the injected backend creation failure must reject blocking work");
27400    };
27401    assert_eq!(
27402      error.to_string(),
27403      "injected async runtime backend creation failure"
27404    );
27405    assert!(
27406      !ran.load(Ordering::SeqCst),
27407      "rejected blocking work must not run"
27408    );
27409    assert_eq!(
27410      work(),
27411      17,
27412      "the rejected blocking closure must remain usable"
27413    );
27414    assert!(ran.load(Ordering::SeqCst));
27415
27416    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27417    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
27418      .expect_err("infallible blocking submission must return a failed join handle");
27419    assert_eq!(
27420      error.to_string(),
27421      "injected async runtime backend creation failure"
27422    );
27423
27424    let handle = controller
27425      .try_spawn_blocking(|| 31usize)
27426      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
27427    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27428    controller.shutdown().unwrap();
27429  }
27430
27431  #[cfg(not(target_family = "wasm"))]
27432  #[test]
27433  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
27434    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
27435
27436    if std::env::var_os(CHILD_ENV).is_some() {
27437      struct PollProbe {
27438        polls: Arc<AtomicUsize>,
27439      }
27440
27441      impl Future for PollProbe {
27442        type Output = ();
27443
27444        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27445          self.polls.fetch_add(1, Ordering::SeqCst);
27446          Poll::Ready(())
27447        }
27448      }
27449
27450      shutdown().expect("the isolated runtime must stop before block_on admission");
27451      let polls = Arc::new(AtomicUsize::new(0));
27452      let mut future = std::pin::pin!(PollProbe {
27453        polls: Arc::clone(&polls)
27454      });
27455      let error = try_block_on_dyn(future.as_mut())
27456        .expect_err("the stopped runtime must reject the borrowed future");
27457      assert_eq!(
27458        error.to_string(),
27459        "the async runtime is stopped; call start before submitting work"
27460      );
27461      assert_eq!(
27462        polls.load(Ordering::SeqCst),
27463        0,
27464        "rejected block_on must not poll its future"
27465      );
27466
27467      start().expect("the isolated runtime must restart");
27468      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27469      assert_eq!(polls.load(Ordering::SeqCst), 1);
27470      shutdown().expect("the restarted isolated runtime must stop");
27471      return;
27472    }
27473
27474    let output = std::process::Command::new(std::env::current_exe().unwrap())
27475      .arg("--exact")
27476      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27477      .arg("--nocapture")
27478      .env(CHILD_ENV, "1")
27479      .output()
27480      .expect("the stopped block_on subprocess must start");
27481    assert!(
27482      output.status.success(),
27483      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27484      output.status.code(),
27485      String::from_utf8_lossy(&output.stdout),
27486      String::from_utf8_lossy(&output.stderr)
27487    );
27488  }
27489
27490  #[cfg(not(target_family = "wasm"))]
27491  #[test]
27492  fn try_block_on_dyn_reports_concurrent_shutdown() {
27493    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27494
27495    if std::env::var_os(CHILD_ENV).is_some() {
27496      use std::sync::mpsc;
27497
27498      struct PendingProbe {
27499        entered: Option<mpsc::Sender<()>>,
27500        complete: Arc<AtomicBool>,
27501      }
27502
27503      impl Future for PendingProbe {
27504        type Output = ();
27505
27506        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27507          if let Some(entered) = self.entered.take() {
27508            entered.send(()).unwrap();
27509          }
27510          if self.complete.load(Ordering::SeqCst) {
27511            Poll::Ready(())
27512          } else {
27513            Poll::Pending
27514          }
27515        }
27516      }
27517
27518      configure(RuntimeOptions {
27519        flavor: RuntimeFlavor::CurrentThread,
27520        worker_threads: 1,
27521        max_blocking_tasks: 1,
27522        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27523        park_deadline: None,
27524        drain_linger: DEFAULT_DRAIN_LINGER,
27525      })
27526      .expect("the isolated runtime must accept CurrentThread configuration");
27527      start().expect("the isolated runtime must start");
27528
27529      let (entered_tx, entered_rx) = mpsc::channel();
27530      let (error_tx, error_rx) = mpsc::channel();
27531      let (retry_tx, retry_rx) = mpsc::channel();
27532      let complete = Arc::new(AtomicBool::new(false));
27533      let future_complete = Arc::clone(&complete);
27534      let runner = std::thread::spawn(move || {
27535        let mut future = std::pin::pin!(PendingProbe {
27536          entered: Some(entered_tx),
27537          complete: future_complete
27538        });
27539        let error = try_block_on_dyn(future.as_mut())
27540          .expect_err("concurrent shutdown must interrupt the borrowed future")
27541          .to_string();
27542        error_tx.send(error).unwrap();
27543        retry_rx.recv().unwrap();
27544        try_block_on_dyn(future.as_mut())
27545          .expect("restart must accept the same interrupted borrowed future");
27546      });
27547      entered_rx
27548        .recv_timeout(Duration::from_secs(2))
27549        .expect("the borrowed future must be polled before shutdown");
27550      shutdown().expect("shutdown must wait for the active block_on scope to retire");
27551      assert_eq!(
27552        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27553        "the async runtime stopped before block_on completed its future"
27554      );
27555      start().expect("the isolated runtime must restart after the interrupted drive");
27556      complete.store(true, Ordering::SeqCst);
27557      retry_tx.send(()).unwrap();
27558      runner.join().unwrap();
27559      shutdown().expect("the retried isolated runtime must stop");
27560      return;
27561    }
27562
27563    let output = std::process::Command::new(std::env::current_exe().unwrap())
27564      .arg("--exact")
27565      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27566      .arg("--nocapture")
27567      .env(CHILD_ENV, "1")
27568      .output()
27569      .expect("the concurrent block_on shutdown subprocess must start");
27570    assert!(
27571      output.status.success(),
27572      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27573      output.status.code(),
27574      String::from_utf8_lossy(&output.stdout),
27575      String::from_utf8_lossy(&output.stderr)
27576    );
27577  }
27578
27579  #[test]
27580  fn initial_lifecycle_start_preserves_lazy_configuration() {
27581    let controller = RuntimeController::new();
27582    controller
27583      .start()
27584      .expect("initial lifecycle start must succeed");
27585    controller
27586      .configure(RuntimeOptions {
27587        flavor: RuntimeFlavor::CurrentThread,
27588        worker_threads: 1,
27589        max_blocking_tasks: 1,
27590        thread_name_prefix: "lifecycle-initial-start".to_string(),
27591        park_deadline: None,
27592        drain_linger: DEFAULT_DRAIN_LINGER,
27593      })
27594      .expect("module-registration start must not consume the configuration window");
27595
27596    let state = controller
27597      .state
27598      .lock()
27599      .unwrap_or_else(std::sync::PoisonError::into_inner);
27600    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27601  }
27602
27603  #[test]
27604  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27605    let controller = RuntimeController::new();
27606
27607    for _ in 0..3 {
27608      controller
27609        .start()
27610        .expect("zero-work lifecycle start must succeed");
27611      assert!(matches!(
27612        &controller
27613          .state
27614          .lock()
27615          .unwrap_or_else(std::sync::PoisonError::into_inner)
27616          .lifecycle,
27617        RuntimeLifecycle::Initial
27618      ));
27619
27620      controller
27621        .shutdown()
27622        .expect("zero-work lifecycle shutdown must succeed");
27623      assert!(matches!(
27624        &controller
27625          .state
27626          .lock()
27627          .unwrap_or_else(std::sync::PoisonError::into_inner)
27628          .lifecycle,
27629        RuntimeLifecycle::StoppedBeforeFirstUse
27630      ));
27631    }
27632
27633    controller
27634      .start()
27635      .expect("a later zero-work restart must remain lazy");
27636    controller
27637      .configure(RuntimeOptions {
27638        flavor: RuntimeFlavor::CurrentThread,
27639        worker_threads: 1,
27640        max_blocking_tasks: 1,
27641        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27642        park_deadline: None,
27643        drain_linger: DEFAULT_DRAIN_LINGER,
27644      })
27645      .expect("zero-work lifecycle cycles must not consume the configuration window");
27646
27647    let state = controller
27648      .state
27649      .lock()
27650      .unwrap_or_else(std::sync::PoisonError::into_inner);
27651    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27652  }
27653
27654  #[cfg(napi_runtime_os_threads)]
27655  #[test]
27656  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27657    use std::sync::mpsc;
27658
27659    struct BlockingDropFuture {
27660      entered: mpsc::Sender<()>,
27661      release: mpsc::Receiver<()>,
27662    }
27663
27664    impl Future for BlockingDropFuture {
27665      type Output = ();
27666
27667      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27668        Poll::Pending
27669      }
27670    }
27671
27672    impl Drop for BlockingDropFuture {
27673      fn drop(&mut self) {
27674        self.entered.send(()).unwrap();
27675        self.release.recv().unwrap();
27676      }
27677    }
27678
27679    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27680    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27681    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27682    let submit_controller = Arc::clone(&controller);
27683    let submitter = std::thread::spawn(move || {
27684      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27685      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27686        entered: drop_entered_tx,
27687        release: release_drop_rx,
27688      }));
27689      assert!(
27690        result.is_err(),
27691        "the injected backend creation failure must reject the submission"
27692      );
27693    });
27694    drop_entered_rx
27695      .recv_timeout(Duration::from_secs(2))
27696      .expect("the rejected future destructor must start");
27697
27698    let retry_ran = Arc::new(AtomicBool::new(false));
27699    let retry_ran_task = Arc::clone(&retry_ran);
27700    let Err(retry) = controller.try_spawn_detached(async move {
27701      retry_ran_task.store(true, Ordering::SeqCst);
27702    }) else {
27703      panic!("backend creation must remain closed during rejected destruction");
27704    };
27705    drop(retry);
27706    assert!(!retry_ran.load(Ordering::SeqCst));
27707
27708    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27709    let shutdown_controller = Arc::clone(&controller);
27710    let shutdown = std::thread::spawn(move || {
27711      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27712    });
27713    wait_until("initial shutdown to close runtime admission", || {
27714      matches!(
27715        controller
27716          .state
27717          .lock()
27718          .unwrap_or_else(std::sync::PoisonError::into_inner)
27719          .lifecycle,
27720        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27721      )
27722    });
27723    assert!(
27724      shutdown_rx
27725        .recv_timeout(Duration::from_millis(200))
27726        .is_err(),
27727      "initial shutdown must wait for rejected user state destruction"
27728    );
27729    assert!(
27730      controller.try_spawn_detached(async {}).is_err(),
27731      "shutdown must keep first-generation admission closed while it waits"
27732    );
27733
27734    let (start_tx, start_rx) = mpsc::channel();
27735    let start_controller = Arc::clone(&controller);
27736    let starter = std::thread::spawn(move || {
27737      start_tx.send(start_controller.start()).unwrap();
27738    });
27739    assert!(
27740      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27741      "start must wait for the zero-backend shutdown transition to finish"
27742    );
27743
27744    release_drop_tx.send(()).unwrap();
27745    submitter.join().unwrap();
27746    shutdown_rx
27747      .recv_timeout(Duration::from_secs(2))
27748      .unwrap()
27749      .unwrap();
27750    shutdown.join().unwrap();
27751    start_rx
27752      .recv_timeout(Duration::from_secs(2))
27753      .unwrap()
27754      .unwrap();
27755    starter.join().unwrap();
27756
27757    let ran = Arc::new(AtomicBool::new(false));
27758    let ran_task = Arc::clone(&ran);
27759    controller
27760      .try_spawn_detached(async move {
27761        ran_task.store(true, Ordering::SeqCst);
27762      })
27763      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27764    let dispatch = controller_current_thread_dispatch(&controller);
27765    controller.drive_current_thread_dispatch(dispatch, false);
27766    assert!(ran.load(Ordering::SeqCst));
27767    controller.shutdown().unwrap();
27768  }
27769
27770  #[cfg(napi_runtime_os_threads)]
27771  #[test]
27772  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27773    use std::sync::mpsc;
27774
27775    struct BlockingDropFuture {
27776      entered: mpsc::Sender<()>,
27777      release: mpsc::Receiver<()>,
27778    }
27779
27780    impl Future for BlockingDropFuture {
27781      type Output = ();
27782
27783      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27784        Poll::Pending
27785      }
27786    }
27787
27788    impl Drop for BlockingDropFuture {
27789      fn drop(&mut self) {
27790        self.entered.send(()).unwrap();
27791        self.release.recv().unwrap();
27792      }
27793    }
27794
27795    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27796    let old_generation = controller.backend().generation();
27797    controller.shutdown().unwrap();
27798
27799    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27800    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27801    let submit_controller = Arc::clone(&controller);
27802    let submitter = std::thread::spawn(move || {
27803      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27804        entered: drop_entered_tx,
27805        release: release_drop_rx,
27806      }));
27807      assert!(
27808        result.is_err(),
27809        "the stopped runtime must reject the submission"
27810      );
27811    });
27812    drop_entered_rx
27813      .recv_timeout(Duration::from_secs(2))
27814      .unwrap();
27815
27816    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27817    let shutdown_controller = Arc::clone(&controller);
27818    let shutdown = std::thread::spawn(move || {
27819      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27820    });
27821    wait_until(
27822      "stopped shutdown to enter its zero-backend transition",
27823      || {
27824        matches!(
27825          controller
27826            .state
27827            .lock()
27828            .unwrap_or_else(std::sync::PoisonError::into_inner)
27829            .lifecycle,
27830          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27831        )
27832      },
27833    );
27834
27835    let (start_tx, start_rx) = mpsc::channel();
27836    let start_controller = Arc::clone(&controller);
27837    let starter = std::thread::spawn(move || {
27838      start_tx.send(start_controller.start()).unwrap();
27839    });
27840    assert!(
27841      shutdown_rx
27842        .recv_timeout(Duration::from_millis(200))
27843        .is_err(),
27844      "stopped shutdown must wait for rejected destruction"
27845    );
27846    assert!(
27847      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27848      "restart must wait for the stopped zero-backend transition"
27849    );
27850
27851    release_drop_tx.send(()).unwrap();
27852    submitter.join().unwrap();
27853    shutdown_rx
27854      .recv_timeout(Duration::from_secs(2))
27855      .unwrap()
27856      .unwrap();
27857    shutdown.join().unwrap();
27858    start_rx
27859      .recv_timeout(Duration::from_secs(2))
27860      .unwrap()
27861      .unwrap();
27862    starter.join().unwrap();
27863
27864    let new_generation = controller.backend().generation();
27865    assert_ne!(new_generation, old_generation);
27866    controller.shutdown().unwrap();
27867  }
27868
27869  #[test]
27870  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27871    use std::sync::mpsc;
27872
27873    struct ShutdownOnDrop {
27874      controller: Arc<RuntimeController>,
27875      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27876    }
27877
27878    impl Future for ShutdownOnDrop {
27879      type Output = ();
27880
27881      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27882        Poll::Pending
27883      }
27884    }
27885
27886    impl Drop for ShutdownOnDrop {
27887      fn drop(&mut self) {
27888        self.result.send(self.controller.shutdown()).unwrap();
27889      }
27890    }
27891
27892    let controller = Arc::new(current_thread_controller(
27893      "failed-initial-reentrant-shutdown",
27894    ));
27895    let (result_tx, result_rx) = mpsc::channel();
27896    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27897    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27898      controller: Arc::clone(&controller),
27899      result: result_tx,
27900    }));
27901    assert!(rejected.is_err());
27902
27903    let error = result_rx
27904      .recv_timeout(Duration::from_secs(1))
27905      .expect("the rejected destructor must not self-deadlock")
27906      .expect_err("rejected destruction cannot wait for its own shutdown");
27907    assert!(error.to_string().contains("rejected submission"));
27908    {
27909      let state = controller
27910        .state
27911        .lock()
27912        .unwrap_or_else(std::sync::PoisonError::into_inner);
27913      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27914      assert_eq!(state.rejected_drops, 0);
27915    }
27916
27917    let handle = controller
27918      .try_spawn(async { 23usize })
27919      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27920    let dispatch = controller_current_thread_dispatch(&controller);
27921    controller.drive_current_thread_dispatch(dispatch, false);
27922    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27923    controller.shutdown().unwrap();
27924  }
27925
27926  #[cfg(napi_runtime_os_threads)]
27927  #[test]
27928  fn failed_initial_submission_destructor_cannot_reenter_start() {
27929    use std::sync::mpsc;
27930
27931    struct StartOnDrop {
27932      controller: Arc<RuntimeController>,
27933      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27934      release: mpsc::Receiver<()>,
27935    }
27936
27937    impl Future for StartOnDrop {
27938      type Output = ();
27939
27940      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27941        Poll::Pending
27942      }
27943    }
27944
27945    impl Drop for StartOnDrop {
27946      fn drop(&mut self) {
27947        self.result.send(self.controller.start()).unwrap();
27948        self.release.recv().unwrap();
27949      }
27950    }
27951
27952    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27953    let (result_tx, result_rx) = mpsc::channel();
27954    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27955    let (submission_tx, submission_rx) = mpsc::channel();
27956    let submit_controller = Arc::clone(&controller);
27957    let submitter = std::thread::spawn(move || {
27958      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27959      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27960        controller: Arc::clone(&submit_controller),
27961        result: result_tx,
27962        release: release_drop_rx,
27963      }));
27964      submission_tx.send(rejected).unwrap();
27965    });
27966
27967    let error = result_rx
27968      .recv_timeout(Duration::from_secs(1))
27969      .expect("the rejected destructor must not self-deadlock")
27970      .expect_err("rejected destruction must not report a successful runtime start");
27971    assert!(error.to_string().contains("rejected submission"));
27972    {
27973      let state = controller
27974        .state
27975        .lock()
27976        .unwrap_or_else(std::sync::PoisonError::into_inner);
27977      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27978      assert_eq!(state.rejected_drops, 1);
27979    }
27980
27981    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27982    let (external_start_tx, external_start_rx) = mpsc::channel();
27983    let start_controller = Arc::clone(&controller);
27984    let starter = std::thread::spawn(move || {
27985      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27986        *slot.borrow_mut() = Some(Box::new(move || {
27987          wait_entered_tx.send(()).unwrap();
27988        }));
27989      });
27990      external_start_tx.send(start_controller.start()).unwrap();
27991    });
27992    wait_entered_rx
27993      .recv_timeout(Duration::from_secs(1))
27994      .expect("external start must reach the Initial rejected-destruction wait");
27995    assert!(
27996      external_start_rx.try_recv().is_err(),
27997      "external start must not return while rejected destruction remains active"
27998    );
27999
28000    release_drop_tx.send(()).unwrap();
28001    assert!(
28002      submission_rx
28003        .recv_timeout(Duration::from_secs(2))
28004        .unwrap()
28005        .is_err()
28006    );
28007    submitter.join().unwrap();
28008    external_start_rx
28009      .recv_timeout(Duration::from_secs(2))
28010      .unwrap()
28011      .unwrap();
28012    starter.join().unwrap();
28013
28014    {
28015      let state = controller
28016        .state
28017        .lock()
28018        .unwrap_or_else(std::sync::PoisonError::into_inner);
28019      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
28020      assert_eq!(state.rejected_drops, 0);
28021    }
28022
28023    let handle = controller
28024      .try_spawn(async { 31usize })
28025      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
28026    let dispatch = controller_current_thread_dispatch(&controller);
28027    controller.drive_current_thread_dispatch(dispatch, false);
28028    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
28029    controller.shutdown().unwrap();
28030  }
28031
28032  #[cfg(panic = "unwind")]
28033  #[test]
28034  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
28035    use std::sync::mpsc;
28036
28037    struct BlockingDropFuture {
28038      controller: Arc<RuntimeController>,
28039      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28040      release: mpsc::Receiver<()>,
28041    }
28042
28043    impl Future for BlockingDropFuture {
28044      type Output = ();
28045
28046      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28047        Poll::Pending
28048      }
28049    }
28050
28051    impl Drop for BlockingDropFuture {
28052      fn drop(&mut self) {
28053        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
28054        self
28055          .entered
28056          .send((generation, self.controller.shutdown()))
28057          .unwrap();
28058        self.release.recv().unwrap();
28059      }
28060    }
28061
28062    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
28063    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28064    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28065    let (runner_done_tx, runner_done_rx) = mpsc::channel();
28066    let runner_controller = Arc::clone(&controller);
28067    let runner = std::thread::spawn(move || {
28068      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
28069      let result = catch_unwind(AssertUnwindSafe(|| {
28070        block_on_with_controller(
28071          &runner_controller,
28072          BlockingDropFuture {
28073            controller: Arc::clone(&runner_controller),
28074            entered: drop_entered_tx,
28075            release: release_drop_rx,
28076          },
28077        );
28078      }));
28079      runner_done_tx.send(result.is_err()).unwrap();
28080    });
28081
28082    let (generation, reentrant_shutdown) = drop_entered_rx
28083      .recv_timeout(Duration::from_secs(2))
28084      .expect("failed block_on acquisition must destroy its owned future");
28085    assert_eq!(generation, None, "no runtime generation was created");
28086    let error = reentrant_shutdown
28087      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
28088    assert!(error.to_string().contains("rejected submission"));
28089
28090    let Err(retry_error) = controller.try_backend() else {
28091      panic!("backend retry must remain closed during rejected block_on destruction");
28092    };
28093    assert!(retry_error.to_string().contains("rejected submission"));
28094
28095    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28096    let shutdown_controller = Arc::clone(&controller);
28097    let shutdown = std::thread::spawn(move || {
28098      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28099    });
28100    assert!(
28101      shutdown_rx
28102        .recv_timeout(Duration::from_millis(200))
28103        .is_err(),
28104      "initial shutdown must wait for rejected block_on destruction"
28105    );
28106
28107    release_drop_tx.send(()).unwrap();
28108    assert!(
28109      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28110      "block_on must preserve its infallible panic contract after contained cleanup"
28111    );
28112    runner.join().unwrap();
28113    shutdown_rx
28114      .recv_timeout(Duration::from_secs(2))
28115      .unwrap()
28116      .unwrap();
28117    shutdown.join().unwrap();
28118
28119    controller
28120      .start()
28121      .expect("zero-generation shutdown must remain restartable");
28122    let ran = Arc::new(AtomicBool::new(false));
28123    let ran_task = Arc::clone(&ran);
28124    controller
28125      .try_spawn_detached(async move {
28126        ran_task.store(true, Ordering::SeqCst);
28127      })
28128      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
28129    let dispatch = controller_current_thread_dispatch(&controller);
28130    controller.drive_current_thread_dispatch(dispatch, false);
28131    assert!(ran.load(Ordering::SeqCst));
28132    controller.shutdown().unwrap();
28133  }
28134
28135  #[cfg(not(target_family = "wasm"))]
28136  #[test]
28137  fn public_block_on_contains_poll_and_drop_double_panic() {
28138    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
28139
28140    if std::env::var_os(CHILD_ENV).is_some() {
28141      struct PanicInPollAndDrop;
28142
28143      impl Future for PanicInPollAndDrop {
28144        type Output = ();
28145
28146        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28147          panic!("intentional block_on poll panic");
28148        }
28149      }
28150
28151      impl Drop for PanicInPollAndDrop {
28152        fn drop(&mut self) {
28153          panic!("intentional block_on future destructor panic");
28154        }
28155      }
28156
28157      let controller = current_thread_controller("block-on-double-panic");
28158      assert!(
28159        catch_unwind(AssertUnwindSafe(|| {
28160          block_on_with_controller(&controller, PanicInPollAndDrop);
28161        }))
28162        .is_err(),
28163        "the original poll panic must still reach the caller"
28164      );
28165      controller
28166        .shutdown()
28167        .expect("contained future destruction must not strand the runtime");
28168      return;
28169    }
28170
28171    let output = std::process::Command::new(std::env::current_exe().unwrap())
28172      .arg("--exact")
28173      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
28174      .arg("--nocapture")
28175      .env(CHILD_ENV, "1")
28176      .output()
28177      .expect("the subprocess test must start");
28178    assert!(
28179      output.status.success(),
28180      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
28181      output.status.code(),
28182      String::from_utf8_lossy(&output.stdout),
28183      String::from_utf8_lossy(&output.stderr)
28184    );
28185  }
28186
28187  #[test]
28188  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
28189    let controller = RuntimeController::new();
28190    controller
28191      .start()
28192      .expect("initial lifecycle start must succeed");
28193    controller
28194      .shutdown()
28195      .expect("zero-work lifecycle shutdown must succeed");
28196    controller
28197      .start()
28198      .expect("zero-work lifecycle restart must remain lazy");
28199    controller
28200      .configure(RuntimeOptions {
28201        flavor: RuntimeFlavor::CurrentThread,
28202        worker_threads: 1,
28203        max_blocking_tasks: 1,
28204        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
28205        park_deadline: None,
28206        drain_linger: DEFAULT_DRAIN_LINGER,
28207      })
28208      .expect("configuration before first real async use must succeed");
28209
28210    let first_generation = controller.backend().generation();
28211    controller
28212      .shutdown()
28213      .expect("the first real generation must stop");
28214    assert!(matches!(
28215      &controller
28216        .state
28217        .lock()
28218        .unwrap_or_else(std::sync::PoisonError::into_inner)
28219        .lifecycle,
28220      RuntimeLifecycle::Stopped
28221    ));
28222    let error = controller
28223      .configure(RuntimeOptions::default())
28224      .expect_err("a real generation must permanently freeze configuration");
28225    assert_eq!(
28226      error.to_string(),
28227      "the async runtime configuration is frozen; configure it before the first async call"
28228    );
28229
28230    controller
28231      .start()
28232      .expect("the stopped real generation must restart");
28233    let restarted_generation = {
28234      let state = controller
28235        .state
28236        .lock()
28237        .unwrap_or_else(std::sync::PoisonError::into_inner);
28238      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
28239        panic!("a real generation restart must eagerly create its replacement backend");
28240      };
28241      backend.generation()
28242    };
28243    assert_ne!(
28244      restarted_generation, first_generation,
28245      "a real generation restart must not reuse the retired backend"
28246    );
28247    controller
28248      .shutdown()
28249      .expect("the replacement generation must stop");
28250  }
28251
28252  #[test]
28253  fn runtime_controller_rejects_submissions_after_shutdown() {
28254    let controller = current_thread_controller("lifecycle-reject");
28255    controller.start().expect("runtime must start");
28256    drop(controller.backend());
28257    controller.shutdown().expect("runtime must stop");
28258
28259    let async_ran = Arc::new(AtomicBool::new(false));
28260    let async_ran_task = Arc::clone(&async_ran);
28261    let Err(rejected_future) = controller.try_spawn_detached(async move {
28262      async_ran_task.store(true, Ordering::SeqCst);
28263    }) else {
28264      panic!("shutdown must reject async work");
28265    };
28266    assert!(
28267      !async_ran.load(Ordering::SeqCst),
28268      "a rejected future must not be polled"
28269    );
28270    futures::executor::block_on(rejected_future);
28271    assert!(
28272      async_ran.load(Ordering::SeqCst),
28273      "the original future must remain usable"
28274    );
28275
28276    let blocking_ran = Arc::new(AtomicBool::new(false));
28277    let blocking_ran_task = Arc::clone(&blocking_ran);
28278    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
28279      blocking_ran_task.store(true, Ordering::SeqCst);
28280      7
28281    }) else {
28282      panic!("shutdown must reject blocking work");
28283    };
28284    assert_eq!(
28285      blocking_error.to_string(),
28286      "the async runtime is stopped; call start before submitting work"
28287    );
28288    assert!(
28289      !blocking_ran.load(Ordering::SeqCst),
28290      "rejected blocking work must not run"
28291    );
28292    assert_eq!(
28293      rejected_work(),
28294      7,
28295      "the original closure must remain usable"
28296    );
28297
28298    let config_error = controller
28299      .configure(RuntimeOptions::default())
28300      .expect_err("shutdown must not make runtime configuration mutable again");
28301    assert_eq!(
28302      config_error.to_string(),
28303      "the async runtime configuration is frozen; configure it before the first async call"
28304    );
28305
28306    let state = controller
28307      .state
28308      .lock()
28309      .unwrap_or_else(std::sync::PoisonError::into_inner);
28310    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
28311  }
28312
28313  #[test]
28314  fn rejected_future_can_be_resubmitted_after_restart() {
28315    let controller = current_thread_controller("lifecycle-retry-future");
28316    controller.start().expect("runtime must start");
28317    drop(controller.backend());
28318    controller.shutdown().expect("runtime must stop");
28319
28320    let runs = Arc::new(AtomicUsize::new(0));
28321    let runs_task = Arc::clone(&runs);
28322    let future = async move {
28323      runs_task.fetch_add(1, Ordering::SeqCst);
28324      23
28325    };
28326    let Err((error, future)) = controller.try_spawn(future) else {
28327      panic!("stopped runtime must reject the future");
28328    };
28329    assert!(error.to_string().contains("runtime is stopped"));
28330    assert_eq!(runs.load(Ordering::SeqCst), 0);
28331
28332    controller.start().expect("runtime must restart");
28333    let handle = controller
28334      .try_spawn(future)
28335      .unwrap_or_else(|_| panic!("restart must accept retained future"));
28336    let dispatch = controller_current_thread_dispatch(&controller);
28337    controller.drive_current_thread_dispatch(dispatch, false);
28338    assert_eq!(
28339      futures::executor::block_on(handle).expect("retained future must complete"),
28340      23
28341    );
28342    assert_eq!(runs.load(Ordering::SeqCst), 1);
28343    controller
28344      .shutdown()
28345      .expect("replacement runtime must stop");
28346  }
28347
28348  #[test]
28349  fn runtime_controller_start_after_shutdown_accepts_work() {
28350    let controller = current_thread_controller("lifecycle-restart");
28351    controller.start().expect("runtime must start");
28352    drop(controller.backend());
28353    controller.shutdown().expect("runtime must stop");
28354    controller.start().expect("runtime must restart");
28355
28356    let async_ran = Arc::new(AtomicBool::new(false));
28357    let async_ran_task = Arc::clone(&async_ran);
28358    controller
28359      .try_spawn_detached(async move {
28360        async_ran_task.store(true, Ordering::SeqCst);
28361      })
28362      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
28363    assert!(
28364      !async_ran.load(Ordering::SeqCst),
28365      "hostless CurrentThread submission must remain enqueue-only"
28366    );
28367    let dispatch = controller_current_thread_dispatch(&controller);
28368    controller.drive_current_thread_dispatch(dispatch, false);
28369    assert!(
28370      async_ran.load(Ordering::SeqCst),
28371      "restarted runtime must poll submitted work"
28372    );
28373
28374    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
28375      panic!("restarted runtime must accept blocking work");
28376    };
28377    assert_eq!(
28378      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
28379      11
28380    );
28381  }
28382
28383  #[test]
28384  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
28385    let controller = current_thread_controller("stale-host-generation");
28386    let old_generation = controller.backend().generation();
28387    let old_dispatch = controller_current_thread_dispatch(&controller);
28388    controller.shutdown().unwrap();
28389    controller.start().unwrap();
28390    let new_generation = controller.backend().generation();
28391    assert_ne!(new_generation, old_generation);
28392
28393    let ran = Arc::new(AtomicBool::new(false));
28394    let ran_task = Arc::clone(&ran);
28395    controller
28396      .try_spawn_detached(async move {
28397        ran_task.store(true, Ordering::SeqCst);
28398      })
28399      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
28400    let new_dispatch = controller_current_thread_dispatch(&controller);
28401    assert_ne!(new_dispatch, old_dispatch);
28402
28403    controller.drive_current_thread_dispatch(old_dispatch, false);
28404    assert!(
28405      !ran.load(Ordering::SeqCst),
28406      "a callback from the retired generation must not touch the restarted executor"
28407    );
28408    controller.drive_current_thread_dispatch(new_dispatch, false);
28409    assert!(ran.load(Ordering::SeqCst));
28410    controller.shutdown().unwrap();
28411  }
28412
28413  #[test]
28414  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
28415    let controller = current_thread_controller("stale-host-cancellation");
28416    let old_generation = controller.backend().generation();
28417    let old_dispatch = controller_current_thread_dispatch(&controller);
28418    controller.shutdown().unwrap();
28419    controller.start().unwrap();
28420    let backend = controller.backend();
28421    let new_generation = backend.generation();
28422    assert_ne!(new_generation, old_generation);
28423    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28424      panic!("configured CurrentThread backend must create a CurrentThread executor");
28425    };
28426    let new_dispatch = current_thread_dispatch(executor);
28427    assert_ne!(new_dispatch, old_dispatch);
28428
28429    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
28430    assert_eq!(
28431      executor.dispatch_pending.load(Ordering::Acquire),
28432      new_dispatch,
28433      "a delayed failure from the retired generation must not release the replacement latch"
28434    );
28435
28436    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
28437    assert_eq!(
28438      executor.dispatch_pending.load(Ordering::Acquire),
28439      0,
28440      "the live generation must be able to cancel its accepted dispatch"
28441    );
28442    drop(backend);
28443    controller.shutdown().unwrap();
28444  }
28445
28446  #[cfg(napi_runtime_os_threads)]
28447  #[test]
28448  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
28449    use std::sync::mpsc;
28450
28451    type ProbeResult = (
28452      &'static str,
28453      Option<u64>,
28454      Result<(), String>,
28455      Result<(), String>,
28456      Result<(), String>,
28457    );
28458
28459    struct StaleGenerationProbe {
28460      label: &'static str,
28461      controller: Arc<RuntimeController>,
28462      result: mpsc::Sender<ProbeResult>,
28463    }
28464
28465    impl Drop for StaleGenerationProbe {
28466      fn drop(&mut self) {
28467        let generation = active_runtime_generation();
28468        let submission = match self.controller.try_spawn(async {}) {
28469          Ok(handle) => {
28470            drop(handle);
28471            Ok(())
28472          }
28473          Err((error, future)) => {
28474            drop(future);
28475            Err(error.to_string())
28476          }
28477        };
28478        let start = self.controller.start().map_err(|error| error.to_string());
28479        let shutdown = self
28480          .controller
28481          .shutdown()
28482          .map_err(|error| error.to_string());
28483        self
28484          .result
28485          .send((self.label, generation, submission, start, shutdown))
28486          .unwrap();
28487      }
28488    }
28489
28490    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28491    let old_generation = controller.backend().generation();
28492    let (result_tx, result_rx) = mpsc::channel();
28493
28494    let async_controller = Arc::clone(&controller);
28495    let async_result = result_tx.clone();
28496    let async_handle = controller
28497      .try_spawn(async move {
28498        StaleGenerationProbe {
28499          label: "async",
28500          controller: async_controller,
28501          result: async_result,
28502        }
28503      })
28504      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28505    let old_dispatch = controller_current_thread_dispatch(&controller);
28506    controller.drive_current_thread_dispatch(old_dispatch, false);
28507
28508    let ready_controller = Arc::clone(&controller);
28509    let ready_handle = controller
28510      .try_spawn_blocking(move || StaleGenerationProbe {
28511        label: "ready",
28512        controller: ready_controller,
28513        result: result_tx,
28514      })
28515      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28516
28517    controller.shutdown().unwrap();
28518    controller.start().unwrap();
28519    let new_generation = controller.backend().generation();
28520    assert_ne!(new_generation, old_generation);
28521
28522    drop(async_handle);
28523    let async_result = result_rx
28524      .recv_timeout(Duration::from_secs(2))
28525      .expect("the retained async output must be destroyed without waiting on the replacement");
28526    drop(ready_handle);
28527    let ready_result = result_rx
28528      .recv_timeout(Duration::from_secs(2))
28529      .expect("the retained immediate output must be destroyed without touching the replacement");
28530
28531    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28532      let (label, generation, submission, start, shutdown) = result;
28533      assert_eq!(label, expected_label);
28534      assert_eq!(generation, Some(old_generation));
28535      for error in [submission, start, shutdown] {
28536        assert!(
28537          error
28538            .expect_err("retired-generation output destruction must be rejected")
28539            .contains("retired generation")
28540        );
28541      }
28542    }
28543
28544    let survivor = controller
28545      .try_spawn(async { 29usize })
28546      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28547    let new_dispatch = controller_current_thread_dispatch(&controller);
28548    controller.drive_current_thread_dispatch(new_dispatch, false);
28549    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28550    controller.shutdown().unwrap();
28551  }
28552
28553  #[cfg(napi_runtime_os_threads)]
28554  #[test]
28555  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28556    use std::sync::mpsc;
28557
28558    struct ShutdownOnDrop {
28559      controller: Arc<RuntimeController>,
28560      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28561    }
28562
28563    impl Drop for ShutdownOnDrop {
28564      fn drop(&mut self) {
28565        self
28566          .result
28567          .send((active_runtime_generation(), self.controller.shutdown()))
28568          .unwrap();
28569      }
28570    }
28571
28572    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28573    let old_generation = controller.backend().generation();
28574    let (result_tx, result_rx) = mpsc::channel();
28575    let old_handle = controller
28576      .try_spawn_blocking({
28577        let controller = Arc::clone(&controller);
28578        move || ShutdownOnDrop {
28579          controller,
28580          result: result_tx,
28581        }
28582      })
28583      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28584    wait_until(
28585      "the first-generation blocking result to be buffered",
28586      || {
28587        controller
28588          .metrics
28589          .blocking_tasks_completed
28590          .load(Ordering::Acquire)
28591          == 1
28592      },
28593    );
28594    controller.shutdown().unwrap();
28595    controller.start().unwrap();
28596    let new_generation = controller.backend().generation();
28597    assert_ne!(new_generation, old_generation);
28598
28599    let (holder_started_tx, holder_started_rx) = mpsc::channel();
28600    let (release_holder_tx, release_holder_rx) = mpsc::channel();
28601    let holder = controller
28602      .try_spawn_blocking(move || {
28603        holder_started_tx.send(()).unwrap();
28604        release_holder_rx.recv().unwrap();
28605      })
28606      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28607    holder_started_rx
28608      .recv_timeout(Duration::from_secs(2))
28609      .unwrap();
28610
28611    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28612    let shutdown_controller = Arc::clone(&controller);
28613    let shutdown = std::thread::spawn(move || {
28614      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28615    });
28616    wait_until("the replacement generation to enter Stopping", || {
28617      matches!(
28618        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28619        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28620      )
28621    });
28622
28623    let dropper = std::thread::spawn(move || drop(old_handle));
28624    let (observed_generation, reentrant_shutdown) =
28625      match result_rx.recv_timeout(Duration::from_secs(2)) {
28626        Ok(result) => result,
28627        Err(error) => {
28628          release_holder_tx.send(()).unwrap();
28629          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28630          let _ = dropper.join();
28631          let _ = shutdown.join();
28632          panic!("retained first-generation output waited on replacement shutdown: {error}");
28633        }
28634      };
28635    assert_eq!(observed_generation, Some(old_generation));
28636    let error = reentrant_shutdown
28637      .expect_err("a retained first-generation output must not control replacement shutdown");
28638    assert!(error.to_string().contains("retired generation"));
28639
28640    release_holder_tx.send(()).unwrap();
28641    shutdown_rx
28642      .recv_timeout(Duration::from_secs(2))
28643      .unwrap()
28644      .unwrap();
28645    dropper.join().unwrap();
28646    shutdown.join().unwrap();
28647    futures::executor::block_on(holder).unwrap();
28648  }
28649
28650  #[cfg(napi_runtime_os_threads)]
28651  #[test]
28652  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28653    use std::sync::mpsc;
28654
28655    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28656    let generation = controller.backend().generation();
28657    let dispatch = controller_current_thread_dispatch(&controller);
28658    let (admitted_tx, admitted_rx) = mpsc::channel();
28659    let (release_tx, release_rx) = mpsc::channel();
28660    let driver_controller = Arc::clone(&controller);
28661    let driver = std::thread::spawn(move || {
28662      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28663        *slot.borrow_mut() = Some(Box::new(move || {
28664          admitted_tx.send(()).unwrap();
28665          release_rx.recv().unwrap();
28666        }));
28667      });
28668      driver_controller.drive_current_thread_dispatch(dispatch, false);
28669    });
28670    admitted_rx
28671      .recv_timeout(Duration::from_secs(2))
28672      .expect("the host turn must claim its scheduler role before execution");
28673
28674    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28675    let shutdown_controller = Arc::clone(&controller);
28676    let shutdown = std::thread::spawn(move || {
28677      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28678    });
28679    wait_until(
28680      "shutdown to close the admitted CurrentThread generation",
28681      || {
28682        let state = controller
28683          .state
28684          .lock()
28685          .unwrap_or_else(std::sync::PoisonError::into_inner);
28686        matches!(
28687          &state.lifecycle,
28688          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28689        )
28690      },
28691    );
28692    assert!(
28693      shutdown_rx
28694        .recv_timeout(Duration::from_millis(200))
28695        .is_err(),
28696      "shutdown must wait for an admitted host callback even before it starts draining"
28697    );
28698
28699    release_tx.send(()).unwrap();
28700    driver.join().unwrap();
28701    shutdown_rx
28702      .recv_timeout(Duration::from_secs(2))
28703      .unwrap()
28704      .unwrap();
28705    shutdown.join().unwrap();
28706  }
28707
28708  #[cfg(napi_runtime_os_threads)]
28709  #[test]
28710  fn spawn_racing_shutdown_cannot_recreate_backend() {
28711    use std::sync::mpsc;
28712
28713    let controller = Arc::new(current_thread_controller("lifecycle-race"));
28714    controller.start().expect("runtime must start");
28715    drop(controller.backend());
28716
28717    let (at_hook_tx, at_hook_rx) = mpsc::channel();
28718    let (release_tx, release_rx) = mpsc::channel();
28719    let ran = Arc::new(AtomicBool::new(false));
28720    let ran_task = Arc::clone(&ran);
28721    let worker_controller = Arc::clone(&controller);
28722    let worker = std::thread::spawn(move || {
28723      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28724        *slot.borrow_mut() = Some(Box::new(move || {
28725          at_hook_tx
28726            .send(())
28727            .expect("test coordinator must still be listening");
28728          release_rx
28729            .recv()
28730            .expect("test coordinator must release the submission");
28731        }));
28732      });
28733
28734      let future = async move {
28735        ran_task.store(true, Ordering::SeqCst);
28736      };
28737      match worker_controller.try_spawn_detached(future) {
28738        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28739        Err(future) => drop(future),
28740      }
28741    });
28742
28743    at_hook_rx
28744      .recv_timeout(Duration::from_secs(5))
28745      .expect("submitting thread must stop before taking the controller lock");
28746    controller.shutdown().expect("runtime must stop");
28747    release_tx
28748      .send(())
28749      .expect("submitting thread must still be waiting");
28750    worker.join().expect("submitting thread must not panic");
28751
28752    assert!(
28753      !ran.load(Ordering::SeqCst),
28754      "the rejected task must never run"
28755    );
28756    let state = controller
28757      .state
28758      .lock()
28759      .unwrap_or_else(std::sync::PoisonError::into_inner);
28760    assert!(
28761      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28762      "the rejected submission must not lazily recreate a backend"
28763    );
28764  }
28765
28766  #[cfg(napi_runtime_os_threads)]
28767  #[test]
28768  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28769    use std::sync::mpsc;
28770
28771    struct BlockingDropFuture {
28772      work: Arc<GenerationWork>,
28773      entered: mpsc::Sender<(Option<u64>, usize)>,
28774      release: mpsc::Receiver<()>,
28775    }
28776
28777    impl Future for BlockingDropFuture {
28778      type Output = ();
28779
28780      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28781        Poll::Pending
28782      }
28783    }
28784
28785    impl Drop for BlockingDropFuture {
28786      fn drop(&mut self) {
28787        let active = self
28788          .work
28789          .state
28790          .lock()
28791          .unwrap_or_else(std::sync::PoisonError::into_inner)
28792          .active;
28793        self
28794          .entered
28795          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28796          .unwrap();
28797        self.release.recv().unwrap();
28798      }
28799    }
28800
28801    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28802    let backend = controller.backend();
28803    let old_generation = backend.generation();
28804    let work = Arc::clone(&backend.work);
28805    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28806      panic!("configured CurrentThread backend must create a CurrentThread executor");
28807    };
28808    let executor = Arc::clone(executor);
28809    drop(backend);
28810
28811    let (registered_tx, registered_rx) = mpsc::channel();
28812    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28813    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28814    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28815    let submit_controller = Arc::clone(&controller);
28816    let submit_work = Arc::clone(&work);
28817    let submitter = std::thread::spawn(move || {
28818      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28819        *slot.borrow_mut() = Some(Box::new(move || {
28820          registered_tx.send(()).unwrap();
28821          release_submission_rx.recv().unwrap();
28822        }));
28823      });
28824      submit_controller
28825        .try_spawn_detached(BlockingDropFuture {
28826          work: submit_work,
28827          entered: drop_entered_tx,
28828          release: release_drop_rx,
28829        })
28830        .unwrap_or_else(|_| {
28831          panic!("the submission linearized before shutdown and must be accepted")
28832        });
28833    });
28834    registered_rx
28835      .recv_timeout(Duration::from_secs(2))
28836      .expect("the submitting thread must register generation work before shutdown");
28837
28838    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28839    let shutdown_controller = Arc::clone(&controller);
28840    let shutdown = std::thread::spawn(move || {
28841      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28842    });
28843    wait_until("CurrentThread shutdown to close the runnable queue", || {
28844      executor
28845        .queue
28846        .lock()
28847        .unwrap_or_else(std::sync::PoisonError::into_inner)
28848        .closed
28849    });
28850    release_submission_tx.send(()).unwrap();
28851
28852    let (drop_generation, active_during_drop) = drop_entered_rx
28853      .recv_timeout(Duration::from_secs(2))
28854      .expect("the closed queue must destroy the accepted unpolled future");
28855    assert_eq!(drop_generation, Some(old_generation));
28856    assert_eq!(
28857      active_during_drop, 1,
28858      "accepted generation work must remain registered through user future destruction"
28859    );
28860
28861    let (start_tx, start_rx) = mpsc::channel();
28862    let start_controller = Arc::clone(&controller);
28863    let starter = std::thread::spawn(move || {
28864      start_tx.send(start_controller.start()).unwrap();
28865    });
28866    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28867    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28868    let shutdown_overlapped = early_shutdown.is_some();
28869    let restart_overlapped = early_start.is_some();
28870
28871    release_drop_tx.send(()).unwrap();
28872    submitter.join().unwrap();
28873    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28874      shutdown_rx
28875        .recv_timeout(Duration::from_secs(2))
28876        .expect("shutdown must finish after drop")
28877    });
28878    shutdown_result.unwrap();
28879    shutdown.join().unwrap();
28880    let start_result = early_start.unwrap_or_else(|| {
28881      start_rx
28882        .recv_timeout(Duration::from_secs(2))
28883        .expect("restart must follow shutdown")
28884    });
28885    start_result.unwrap();
28886    starter.join().unwrap();
28887
28888    assert!(
28889      !shutdown_overlapped,
28890      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28891    );
28892    assert!(
28893      !restart_overlapped,
28894      "restart must not create a new generation while an accepted old-generation future is being destroyed"
28895    );
28896    assert_ne!(controller.backend().generation(), old_generation);
28897    controller.shutdown().unwrap();
28898  }
28899
28900  #[cfg(napi_runtime_os_threads)]
28901  #[test]
28902  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28903    use std::sync::mpsc;
28904
28905    struct BlockingDrop {
28906      work: Arc<GenerationWork>,
28907      entered: mpsc::Sender<(Option<u64>, usize)>,
28908      release: mpsc::Receiver<()>,
28909    }
28910
28911    impl Drop for BlockingDrop {
28912      fn drop(&mut self) {
28913        let active = self
28914          .work
28915          .state
28916          .lock()
28917          .unwrap_or_else(std::sync::PoisonError::into_inner)
28918          .active;
28919        self
28920          .entered
28921          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28922          .unwrap();
28923        self.release.recv().unwrap();
28924      }
28925    }
28926
28927    let controller = Arc::new(multi_thread_controller(
28928      "accepted-blocking-submission-drop",
28929      2,
28930      1,
28931    ));
28932    let backend = controller.backend();
28933    let old_generation = backend.generation();
28934    let work = Arc::clone(&backend.work);
28935    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28936      panic!("configured MultiThread backend must create a MultiThread executor");
28937    };
28938    let executor = Arc::downgrade(executor);
28939    drop(backend);
28940
28941    let (registered_tx, registered_rx) = mpsc::channel();
28942    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28943    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28944    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28945    let submit_controller = Arc::clone(&controller);
28946    let submitter = std::thread::spawn(move || {
28947      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28948        *slot.borrow_mut() = Some(Box::new(move || {
28949          registered_tx.send(()).unwrap();
28950          release_submission_rx.recv().unwrap();
28951        }));
28952      });
28953      let drop_signal = BlockingDrop {
28954        work,
28955        entered: drop_entered_tx,
28956        release: release_drop_rx,
28957      };
28958      let handle = submit_controller
28959        .try_spawn_blocking(move || {
28960          let _drop_signal = drop_signal;
28961        })
28962        .unwrap_or_else(|_| {
28963          panic!("the submission linearized before shutdown and must be accepted")
28964        });
28965      assert!(
28966        futures::executor::block_on(handle).is_err(),
28967        "the executor must cancel work that reaches its queue after shutdown closed it"
28968      );
28969    });
28970    registered_rx
28971      .recv_timeout(Duration::from_secs(2))
28972      .expect("the submitting thread must register generation work before shutdown");
28973
28974    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28975    let shutdown_controller = Arc::clone(&controller);
28976    let shutdown = std::thread::spawn(move || {
28977      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28978    });
28979    wait_until("MultiThread shutdown to close the blocking queue", || {
28980      executor
28981        .upgrade()
28982        .expect("the retiring executor must remain alive until shutdown completes")
28983        .blocking_queue
28984        .lock()
28985        .unwrap_or_else(std::sync::PoisonError::into_inner)
28986        .closed
28987    });
28988    release_submission_tx.send(()).unwrap();
28989
28990    let (drop_generation, active_during_drop) = drop_entered_rx
28991      .recv_timeout(Duration::from_secs(2))
28992      .expect("the closed queue must destroy the accepted blocking closure");
28993    assert_eq!(drop_generation, Some(old_generation));
28994    assert_eq!(
28995      active_during_drop, 1,
28996      "accepted generation work must remain registered through blocking closure destruction"
28997    );
28998
28999    let (start_tx, start_rx) = mpsc::channel();
29000    let start_controller = Arc::clone(&controller);
29001    let starter = std::thread::spawn(move || {
29002      start_tx.send(start_controller.start()).unwrap();
29003    });
29004    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29005    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
29006    let shutdown_overlapped = early_shutdown.is_some();
29007    let restart_overlapped = early_start.is_some();
29008
29009    release_drop_tx.send(()).unwrap();
29010    submitter.join().unwrap();
29011    let shutdown_result = early_shutdown.unwrap_or_else(|| {
29012      shutdown_rx
29013        .recv_timeout(Duration::from_secs(2))
29014        .expect("shutdown must finish after drop")
29015    });
29016    shutdown_result.unwrap();
29017    shutdown.join().unwrap();
29018    let start_result = early_start.unwrap_or_else(|| {
29019      start_rx
29020        .recv_timeout(Duration::from_secs(2))
29021        .expect("restart must follow shutdown")
29022    });
29023    start_result.unwrap();
29024    starter.join().unwrap();
29025
29026    assert!(
29027      !shutdown_overlapped,
29028      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
29029    );
29030    assert!(
29031      !restart_overlapped,
29032      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
29033    );
29034    assert_ne!(controller.backend().generation(), old_generation);
29035    controller.shutdown().unwrap();
29036  }
29037
29038  #[cfg(napi_runtime_os_threads)]
29039  #[test]
29040  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
29041    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
29042    let options = controller.options();
29043    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
29044
29045    let backend = controller.backend();
29046    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29047      panic!("configured MultiThread backend must create a Rayon executor");
29048    };
29049    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
29050    let lifecycle = Arc::clone(&executor.worker_lifecycle);
29051    drop(backend);
29052
29053    controller.shutdown().expect("runtime must stop");
29054    assert_eq!(
29055      lifecycle.remaining(),
29056      0,
29057      "shutdown must observe every physical worker exit"
29058    );
29059  }
29060
29061  #[cfg(napi_runtime_os_threads)]
29062  #[test]
29063  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
29064    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
29065    // includes worker TLS-destructor completion -- `wait_for_all_workers`
29066    // joins the OS threads, and an OS thread only becomes joinable after its
29067    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
29068    // worker's destructor and sampling the counter on the shutdown thread at
29069    // the very instant `shutdown()` returns: a barrier that excluded the
29070    // destructors would race that sample and fail loudly.
29071    //
29072    // The destructor must never do more than record. The previous shape
29073    // parked it on a condvar until the harness released it -- on Windows TLS
29074    // destructors run under the process loader lock, so the first parked
29075    // worker blocked the second from even ENTERING its destructor, the
29076    // release condition (both entered) became unsatisfiable, and the parked
29077    // destructor froze every thread spawn/exit in the process: the
29078    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
29079    // `WorkerTlsDropAction`).
29080    use std::sync::mpsc;
29081
29082    if !platform_runs_thread_local_destructors() {
29083      eprintln!(
29084        "skipping: std does not run thread_local! destructors at thread exit on this target \
29085         (rust-lang/rust#163748), so there is no destructor completion for the join barrier to include"
29086      );
29087      return;
29088    }
29089
29090    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
29091    let backend = controller.backend();
29092    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29093      panic!("configured MultiThread backend must create a Rayon executor");
29094    };
29095    let worker_count = executor.pool.current_num_threads();
29096    let completed = Arc::new(AtomicUsize::new(0));
29097    let probe_completed = Arc::clone(&completed);
29098    executor.pool.broadcast(move |_| {
29099      WORKER_TLS_DROP_PROBE.with(|slot| {
29100        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
29101          action: Some(WorkerTlsDropAction::Record {
29102            completed: Arc::clone(&probe_completed),
29103          }),
29104        });
29105        assert!(
29106          previous.is_none(),
29107          "each worker must install exactly one TLS drop probe"
29108        );
29109      });
29110    });
29111    drop(backend);
29112
29113    assert_eq!(
29114      completed.load(Ordering::SeqCst),
29115      0,
29116      "no worker TLS destructor may run before shutdown retires the workers"
29117    );
29118
29119    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29120    let shutdown_controller = Arc::clone(&controller);
29121    let sample_completed = Arc::clone(&completed);
29122    let shutdown = std::thread::spawn(move || {
29123      let result = shutdown_controller.shutdown();
29124      // Sample on the SAME thread, before any further synchronization could
29125      // mask a barrier gap: at this instant every destructor must already
29126      // have recorded itself.
29127      let seen_at_return = sample_completed.load(Ordering::SeqCst);
29128      shutdown_tx.send((result, seen_at_return)).unwrap();
29129    });
29130    let (shutdown_result, seen_at_return) = shutdown_rx
29131      .recv_timeout(Duration::from_secs(10))
29132      .expect("shutdown must finish once worker TLS destructors have run");
29133    shutdown_result.unwrap();
29134    // On a std that never runs TLS destructors at thread exit there is no
29135    // completion to order; see `std_runs_tls_destructors_before_join`.
29136    #[cfg(not(napi_runtime_wasi_threads))]
29137    let destructors_run = true;
29138    #[cfg(napi_runtime_wasi_threads)]
29139    let destructors_run = std_runs_tls_destructors_before_join();
29140    if destructors_run {
29141      assert_eq!(
29142        seen_at_return, worker_count,
29143        "every worker TLS destructor must have completed before shutdown() returned: \
29144         the worker-join barrier must include TLS-destructor completion"
29145      );
29146    }
29147    join_within(
29148      "the shutdown thread exits after teardown",
29149      shutdown,
29150      Duration::from_secs(30),
29151    );
29152  }
29153
29154  #[cfg(not(target_family = "wasm"))]
29155  #[test]
29156  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
29157    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
29158
29159    if std::env::var_os(CHILD_ENV).is_some() {
29160      use std::sync::mpsc;
29161
29162      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
29163      let backend = controller.backend();
29164      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29165        panic!("configured MultiThread backend must create a Rayon executor");
29166      };
29167      let installed = Arc::new(AtomicBool::new(false));
29168      let (completed_tx, completed_rx) = mpsc::channel();
29169      let probe_controller = Arc::clone(&controller);
29170      let probe_installed = Arc::clone(&installed);
29171      executor.pool.broadcast(move |_| {
29172        if probe_installed.swap(true, Ordering::SeqCst) {
29173          return;
29174        }
29175        WORKER_TLS_DROP_PROBE.with(|slot| {
29176          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
29177            action: Some(WorkerTlsDropAction::ReenterLifecycle {
29178              controller: Arc::clone(&probe_controller),
29179              completed: completed_tx.clone(),
29180            }),
29181          });
29182          assert!(
29183            previous.is_none(),
29184            "the selected worker must have no prior TLS probe"
29185          );
29186        });
29187      });
29188      assert!(
29189        installed.load(Ordering::SeqCst),
29190        "one worker must install the lifecycle probe"
29191      );
29192      drop(backend);
29193
29194      controller
29195        .shutdown()
29196        .expect("external shutdown must join the retiring workers");
29197      let (start, shutdown) = completed_rx
29198        .recv_timeout(Duration::from_secs(2))
29199        .expect("the TLS destructor must complete both lifecycle reentry attempts");
29200      assert_eq!(
29201        start.unwrap_err(),
29202        "cannot restart the async runtime from work in the generation being stopped"
29203      );
29204      assert_eq!(
29205        shutdown.unwrap_err(),
29206        "cannot wait for async runtime shutdown from work in the generation being stopped"
29207      );
29208      return;
29209    }
29210
29211    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
29212      .arg("--exact")
29213      .arg(
29214        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
29215      )
29216      .arg("--nocapture")
29217      .env(CHILD_ENV, "1")
29218      .stdout(std::process::Stdio::piped())
29219      .stderr(std::process::Stdio::piped())
29220      .spawn()
29221      .expect("the TLS lifecycle-reentry subprocess must start");
29222    let deadline = Instant::now() + Duration::from_secs(5);
29223    let status = loop {
29224      if let Some(status) = child
29225        .try_wait()
29226        .expect("the subprocess status must be readable")
29227      {
29228        break status;
29229      }
29230      if Instant::now() >= deadline {
29231        let _ = child.kill();
29232        let output = child
29233          .wait_with_output()
29234          .expect("the timed-out subprocess must be reaped");
29235        panic!(
29236          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
29237          String::from_utf8_lossy(&output.stdout),
29238          String::from_utf8_lossy(&output.stderr)
29239        );
29240      }
29241      std::thread::sleep(Duration::from_millis(10));
29242    };
29243    let output = child
29244      .wait_with_output()
29245      .expect("the completed subprocess output must be read");
29246    assert!(
29247      status.success(),
29248      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
29249      status.code(),
29250      String::from_utf8_lossy(&output.stdout),
29251      String::from_utf8_lossy(&output.stderr)
29252    );
29253  }
29254
29255  #[cfg(napi_runtime_os_threads)]
29256  #[test]
29257  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
29258    use std::sync::mpsc;
29259
29260    struct DropSignal(mpsc::Sender<()>);
29261    impl Drop for DropSignal {
29262      fn drop(&mut self) {
29263        let _ = self.0.send(());
29264      }
29265    }
29266
29267    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
29268    let (polled_tx, polled_rx) = mpsc::channel();
29269    let (dropped_tx, dropped_rx) = mpsc::channel();
29270    controller
29271      .try_spawn_detached(async move {
29272        let _drop_signal = DropSignal(dropped_tx);
29273        polled_tx.send(()).unwrap();
29274        std::future::pending::<()>().await;
29275      })
29276      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
29277    polled_rx
29278      .recv_timeout(Duration::from_secs(5))
29279      .expect("the pending task must be polled before shutdown");
29280
29281    controller
29282      .shutdown()
29283      .expect("shutdown must cancel accepted async work");
29284    dropped_rx
29285      .recv_timeout(Duration::from_secs(5))
29286      .expect("shutdown must drop the accepted task future before returning");
29287  }
29288
29289  #[cfg(napi_runtime_os_threads)]
29290  #[test]
29291  fn current_thread_shutdown_outranks_perpetual_self_wake() {
29292    use std::sync::mpsc;
29293
29294    struct SelfWakingPending {
29295      entered: Option<mpsc::Sender<()>>,
29296    }
29297
29298    impl Future for SelfWakingPending {
29299      type Output = ();
29300
29301      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29302        if let Some(entered) = self.entered.take() {
29303          entered.send(()).unwrap();
29304        }
29305        cx.waker().wake_by_ref();
29306        Poll::Pending
29307      }
29308    }
29309
29310    let controller = Arc::new(current_thread_controller(
29311      "current-thread-self-wake-shutdown",
29312    ));
29313    let (entered_tx, entered_rx) = mpsc::channel();
29314    let (outcome_tx, outcome_rx) = mpsc::channel();
29315    let block_on_controller = Arc::clone(&controller);
29316    let runner = std::thread::spawn(move || {
29317      let mut future = std::pin::pin!(SelfWakingPending {
29318        entered: Some(entered_tx)
29319      });
29320      outcome_tx
29321        .send(block_on_controller.block_on(future.as_mut()))
29322        .unwrap();
29323    });
29324    entered_rx
29325      .recv_timeout(Duration::from_secs(2))
29326      .expect("the self-waking future must be polled before shutdown");
29327
29328    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29329    let shutdown_controller = Arc::clone(&controller);
29330    let shutdown = std::thread::spawn(move || {
29331      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29332    });
29333    shutdown_rx
29334      .recv_timeout(Duration::from_secs(2))
29335      .expect("shutdown must outrank a stored self-wake permit")
29336      .unwrap();
29337    assert_eq!(
29338      outcome_rx
29339        .recv_timeout(Duration::from_secs(2))
29340        .expect("the raw block_on driver must report shutdown"),
29341      BlockOnOutcome::Stopped
29342    );
29343    runner.join().unwrap();
29344    shutdown.join().unwrap();
29345  }
29346
29347  #[cfg(napi_runtime_os_threads)]
29348  #[test]
29349  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
29350    use std::sync::mpsc;
29351
29352    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
29353    let backend = controller.backend();
29354    let executor = match &backend.executor {
29355      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29356      RuntimeExecutor::CurrentThread(_) => unreachable!(),
29357    };
29358    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
29359    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
29360    for _ in 0..2 {
29361      let worker_ready_tx = worker_ready_tx.clone();
29362      let release_workers = Arc::clone(&release_workers);
29363      executor.pool.spawn(move || {
29364        worker_ready_tx.send(()).unwrap();
29365        let (released, changed) = &*release_workers;
29366        let mut released = released
29367          .lock()
29368          .unwrap_or_else(std::sync::PoisonError::into_inner);
29369        while !*released {
29370          released = changed
29371            .wait(released)
29372            .unwrap_or_else(std::sync::PoisonError::into_inner);
29373        }
29374      });
29375    }
29376    drop(worker_ready_tx);
29377    for _ in 0..2 {
29378      worker_ready_rx
29379        .recv_timeout(Duration::from_secs(5))
29380        .expect("both workers must be occupied before the FIFO runnable is submitted");
29381    }
29382    drop(executor);
29383    drop(backend);
29384
29385    let polls = Arc::new(AtomicUsize::new(0));
29386    let handle = controller.spawn(StopPollProbe {
29387      polls: Arc::clone(&polls),
29388      first_poll: None,
29389    });
29390    wait_until("the FIFO runnable to be queued", || {
29391      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
29392    });
29393
29394    let observed_queue = Arc::new(Mutex::new(None));
29395    let hook_observed_queue = Arc::clone(&observed_queue);
29396    let hook_metrics = Arc::clone(&controller.metrics);
29397    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29398      *slot.borrow_mut() = Some(Box::new(move || {
29399        let (released, changed) = &*release_workers;
29400        *released
29401          .lock()
29402          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29403        changed.notify_all();
29404        wait_until("the stopped FIFO runnable claim", || {
29405          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
29406        });
29407        *hook_observed_queue
29408          .lock()
29409          .unwrap_or_else(std::sync::PoisonError::into_inner) =
29410          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
29411      }));
29412    });
29413
29414    controller
29415      .shutdown()
29416      .expect("shutdown must cancel the stopped FIFO claim");
29417    assert_eq!(polls.load(Ordering::SeqCst), 0);
29418    assert_eq!(
29419      *observed_queue
29420        .lock()
29421        .unwrap_or_else(std::sync::PoisonError::into_inner),
29422      Some(0)
29423    );
29424    assert_eq!(
29425      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29426      0
29427    );
29428    drop(handle);
29429  }
29430
29431  #[cfg(napi_runtime_os_threads)]
29432  #[test]
29433  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
29434    use std::sync::mpsc;
29435
29436    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
29437    let backend = controller.backend();
29438    let executor = match &backend.executor {
29439      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29440      RuntimeExecutor::CurrentThread(_) => unreachable!(),
29441    };
29442    let polls = Arc::new(AtomicUsize::new(0));
29443    let scheduler = Arc::downgrade(&executor);
29444    let (runnable, task) = async_task::spawn(
29445      StopPollProbe {
29446        polls: Arc::clone(&polls),
29447        first_poll: None,
29448      },
29449      move |runnable| {
29450        if let Some(executor) = scheduler.upgrade() {
29451          executor.schedule(runnable);
29452        }
29453      },
29454    );
29455    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
29456    let (release_slot_tx, release_slot_rx) = mpsc::channel();
29457    let (claim_done_tx, claim_done_rx) = mpsc::channel();
29458    let worker_executor = Arc::clone(&executor);
29459    let worker_metrics = Arc::clone(&controller.metrics);
29460    executor.pool.spawn(move || {
29461      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
29462      worker_executor.schedule(runnable);
29463      slot_ready_tx.send(()).unwrap();
29464      release_slot_rx.recv().unwrap();
29465      let claimed = worker_executor.run_one_runnable();
29466      claim_done_tx
29467        .send((
29468          claimed,
29469          worker_metrics.queued_runnables.load(Ordering::SeqCst),
29470        ))
29471        .unwrap();
29472    });
29473    slot_ready_rx
29474      .recv_timeout(Duration::from_secs(5))
29475      .expect("the worker must publish the LIFO runnable before shutdown");
29476    assert_eq!(
29477      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29478      1
29479    );
29480    drop(executor);
29481    drop(backend);
29482
29483    let observed_claim = Arc::new(Mutex::new(None));
29484    let hook_observed_claim = Arc::clone(&observed_claim);
29485    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29486      *slot.borrow_mut() = Some(Box::new(move || {
29487        release_slot_tx.send(()).unwrap();
29488        let result = claim_done_rx
29489          .recv_timeout(Duration::from_secs(5))
29490          .expect("the stopped LIFO runnable claim must complete before abort");
29491        *hook_observed_claim
29492          .lock()
29493          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29494      }));
29495    });
29496
29497    controller
29498      .shutdown()
29499      .expect("shutdown must cancel the stopped LIFO claim");
29500    assert_eq!(polls.load(Ordering::SeqCst), 0);
29501    assert_eq!(
29502      *observed_claim
29503        .lock()
29504        .unwrap_or_else(std::sync::PoisonError::into_inner),
29505      Some((true, 0))
29506    );
29507    assert_eq!(
29508      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29509      0
29510    );
29511    drop(task);
29512  }
29513
29514  #[cfg(napi_runtime_os_threads)]
29515  #[test]
29516  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29517    use std::sync::mpsc;
29518
29519    struct BlockingDrop {
29520      entered: mpsc::Sender<()>,
29521      release: mpsc::Receiver<()>,
29522    }
29523
29524    impl Drop for BlockingDrop {
29525      fn drop(&mut self) {
29526        self.entered.send(()).unwrap();
29527        self.release.recv().unwrap();
29528      }
29529    }
29530
29531    struct TwoPollProbe {
29532      first_poll: Option<mpsc::Sender<()>>,
29533      release_first_poll: mpsc::Receiver<()>,
29534      second_poll: mpsc::Sender<()>,
29535    }
29536
29537    impl Future for TwoPollProbe {
29538      type Output = ();
29539
29540      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29541        if let Some(first_poll) = self.first_poll.take() {
29542          first_poll.send(()).unwrap();
29543          self.release_first_poll.recv().unwrap();
29544        } else {
29545          self.second_poll.send(()).unwrap();
29546        }
29547        cx.waker().wake_by_ref();
29548        Poll::Pending
29549      }
29550    }
29551
29552    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29553    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29554    let (release_drop_tx, release_drop_rx) = mpsc::channel();
29555    let blocking_drop = BlockingDrop {
29556      entered: drop_entered_tx,
29557      release: release_drop_rx,
29558    };
29559    controller
29560      .try_spawn_detached(async move {
29561        let _drop = blocking_drop;
29562        std::future::pending::<()>().await;
29563      })
29564      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29565
29566    let (first_poll_tx, first_poll_rx) = mpsc::channel();
29567    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29568    let (second_poll_tx, second_poll_rx) = mpsc::channel();
29569    let (outcome_tx, outcome_rx) = mpsc::channel();
29570    let runner_controller = Arc::clone(&controller);
29571    let runner = std::thread::spawn(move || {
29572      let mut future = std::pin::pin!(TwoPollProbe {
29573        first_poll: Some(first_poll_tx),
29574        release_first_poll: release_first_poll_rx,
29575        second_poll: second_poll_tx,
29576      });
29577      outcome_tx
29578        .send(runner_controller.block_on(future.as_mut()))
29579        .unwrap();
29580    });
29581    first_poll_rx
29582      .recv_timeout(Duration::from_secs(2))
29583      .expect("the active block_on poll must enter before shutdown");
29584
29585    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29586    let shutdown_controller = Arc::clone(&controller);
29587    let shutdown = std::thread::spawn(move || {
29588      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29589    });
29590    drop_entered_rx
29591      .recv_timeout(Duration::from_secs(2))
29592      .expect("shutdown must begin destroying queued work");
29593
29594    release_first_poll_tx.send(()).unwrap();
29595    let polled_again = second_poll_rx
29596      .recv_timeout(Duration::from_millis(200))
29597      .is_ok();
29598    release_drop_tx.send(()).unwrap();
29599
29600    assert!(
29601      !polled_again,
29602      "an active block_on driver must observe generation stop before queued user cleanup blocks"
29603    );
29604    assert_eq!(
29605      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29606      BlockOnOutcome::Stopped
29607    );
29608    shutdown_rx
29609      .recv_timeout(Duration::from_secs(2))
29610      .unwrap()
29611      .unwrap();
29612    runner.join().unwrap();
29613    shutdown.join().unwrap();
29614  }
29615
29616  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29617  #[test]
29618  fn shutdown_cancels_nested_block_on_within_deadline() {
29619    use std::sync::mpsc;
29620
29621    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29622    let task_controller = Arc::clone(&controller);
29623    let (entered_tx, entered_rx) = mpsc::channel();
29624    let task = controller
29625      .try_spawn(async move {
29626        entered_tx.send(()).unwrap();
29627        block_on_with_controller(&task_controller, std::future::pending::<()>());
29628      })
29629      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29630    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29631
29632    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29633    let shutdown_controller = Arc::clone(&controller);
29634    let shutdown = std::thread::spawn(move || {
29635      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29636    });
29637    shutdown_rx
29638      .recv_timeout(Duration::from_secs(2))
29639      .expect("shutdown must wake and cancel an active nested block_on")
29640      .unwrap();
29641    shutdown.join().unwrap();
29642    assert!(
29643      futures::executor::block_on(task).is_err(),
29644      "the cancelled nested block_on task must resolve as stopped"
29645    );
29646  }
29647
29648  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29649  #[test]
29650  fn stopped_public_block_on_drops_future_before_generation_retirement() {
29651    use std::sync::mpsc;
29652
29653    struct PendingDrop {
29654      entered: Option<mpsc::Sender<()>>,
29655      dropped: mpsc::Sender<Option<u64>>,
29656      release: mpsc::Receiver<()>,
29657    }
29658
29659    impl Future for PendingDrop {
29660      type Output = ();
29661
29662      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29663        if let Some(entered) = self.entered.take() {
29664          entered.send(()).unwrap();
29665        }
29666        Poll::Pending
29667      }
29668    }
29669
29670    impl Drop for PendingDrop {
29671      fn drop(&mut self) {
29672        self
29673          .dropped
29674          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29675          .unwrap();
29676        self.release.recv().unwrap();
29677      }
29678    }
29679
29680    let controller = Arc::new(current_thread_controller(
29681      "public-block-on-generation-destruction",
29682    ));
29683    let generation = controller.backend().generation();
29684    let (entered_tx, entered_rx) = mpsc::channel();
29685    let (dropped_tx, dropped_rx) = mpsc::channel();
29686    let (release_tx, release_rx) = mpsc::channel();
29687    let (runner_done_tx, runner_done_rx) = mpsc::channel();
29688    let block_controller = Arc::clone(&controller);
29689    let runner = std::thread::spawn(move || {
29690      let result = catch_unwind(AssertUnwindSafe(|| {
29691        block_on_with_controller(
29692          &block_controller,
29693          PendingDrop {
29694            entered: Some(entered_tx),
29695            dropped: dropped_tx,
29696            release: release_rx,
29697          },
29698        );
29699      }));
29700      runner_done_tx.send(result.is_err()).unwrap();
29701    });
29702    entered_rx
29703      .recv_timeout(Duration::from_secs(2))
29704      .expect("the public block_on future must be polled before shutdown");
29705
29706    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29707    let shutdown_controller = Arc::clone(&controller);
29708    let shutdown = std::thread::spawn(move || {
29709      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29710    });
29711
29712    assert_eq!(
29713      dropped_rx
29714        .recv_timeout(Duration::from_secs(2))
29715        .expect("shutdown must wake block_on and begin destroying its future"),
29716      Some(generation),
29717      "the stopped future must be destroyed under its retiring generation identity"
29718    );
29719    assert!(
29720      shutdown_rx
29721        .recv_timeout(Duration::from_millis(200))
29722        .is_err(),
29723      "shutdown must not publish Stopped while the public block_on future destructor is running"
29724    );
29725
29726    release_tx.send(()).unwrap();
29727    assert!(
29728      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29729      "the public helper must retain its existing early-stop panic behavior"
29730    );
29731    shutdown_rx
29732      .recv_timeout(Duration::from_secs(2))
29733      .unwrap()
29734      .unwrap();
29735    runner.join().unwrap();
29736    shutdown.join().unwrap();
29737  }
29738
29739  #[cfg(not(target_family = "wasm"))]
29740  #[test]
29741  fn shutdown_contains_panicking_nested_block_on_future_drop() {
29742    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29743
29744    if std::env::var_os(CHILD_ENV).is_some() {
29745      use std::sync::mpsc;
29746
29747      struct PendingPanicOnDrop {
29748        entered: Option<mpsc::Sender<()>>,
29749      }
29750
29751      impl Future for PendingPanicOnDrop {
29752        type Output = ();
29753
29754        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29755          if let Some(entered) = self.entered.take() {
29756            entered.send(()).unwrap();
29757          }
29758          Poll::Pending
29759        }
29760      }
29761
29762      impl Drop for PendingPanicOnDrop {
29763        fn drop(&mut self) {
29764          panic!("intentional nested block_on future destructor panic");
29765        }
29766      }
29767
29768      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29769      let task_controller = Arc::clone(&controller);
29770      let (entered_tx, entered_rx) = mpsc::channel();
29771      let task = controller
29772        .try_spawn(async move {
29773          block_on_with_controller(
29774            &task_controller,
29775            PendingPanicOnDrop {
29776              entered: Some(entered_tx),
29777            },
29778          );
29779        })
29780        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29781      entered_rx
29782        .recv_timeout(Duration::from_secs(2))
29783        .expect("the nested block_on future must be polled before shutdown");
29784
29785      controller
29786        .shutdown()
29787        .expect("shutdown must contain nested future destruction");
29788      assert!(
29789        futures::executor::block_on(task).is_err(),
29790        "shutdown cancellation must still surface as a task error"
29791      );
29792      return;
29793    }
29794
29795    let output = std::process::Command::new(std::env::current_exe().unwrap())
29796      .arg("--exact")
29797      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29798      .arg("--nocapture")
29799      .env(CHILD_ENV, "1")
29800      .output()
29801      .expect("the subprocess test must start");
29802    assert!(
29803      output.status.success(),
29804      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29805      output.status.code(),
29806      String::from_utf8_lossy(&output.stdout),
29807      String::from_utf8_lossy(&output.stderr)
29808    );
29809  }
29810
29811  #[cfg(not(target_family = "wasm"))]
29812  #[test]
29813  fn panicking_future_drop_is_contained_before_async_task_drop() {
29814    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29815
29816    if std::env::var_os(CHILD_ENV).is_some() {
29817      struct PanicOnDrop;
29818      impl Drop for PanicOnDrop {
29819        fn drop(&mut self) {
29820          panic!("intentional future destructor panic");
29821        }
29822      }
29823
29824      let controller = current_thread_controller("future-drop-subprocess");
29825      let bomb = PanicOnDrop;
29826      controller
29827        .try_spawn_detached(async move {
29828          let _bomb = bomb;
29829          std::future::pending::<()>().await;
29830        })
29831        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29832      controller
29833        .shutdown()
29834        .expect("shutdown must contain the future destructor panic");
29835      return;
29836    }
29837
29838    let output = std::process::Command::new(std::env::current_exe().unwrap())
29839      .arg("--exact")
29840      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29841      .arg("--nocapture")
29842      .env(CHILD_ENV, "1")
29843      .output()
29844      .expect("the subprocess test must start");
29845    assert!(
29846      output.status.success(),
29847      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29848      output.status.code(),
29849      String::from_utf8_lossy(&output.stdout),
29850      String::from_utf8_lossy(&output.stderr)
29851    );
29852  }
29853
29854  #[cfg(not(target_family = "wasm"))]
29855  #[test]
29856  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29857    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29858
29859    if std::env::var_os(CHILD_ENV).is_some() {
29860      struct PanicOnDrop;
29861      impl Drop for PanicOnDrop {
29862        fn drop(&mut self) {
29863          panic!("intentional detached output destructor panic");
29864        }
29865      }
29866
29867      let controller = current_thread_controller("output-drop-subprocess");
29868      let handle = controller
29869        .try_spawn(async { PanicOnDrop })
29870        .unwrap_or_else(|_| panic!("runtime must accept the task"));
29871      drop(handle);
29872      let dispatch = controller_current_thread_dispatch(&controller);
29873      controller.drive_current_thread_dispatch(dispatch, false);
29874      controller.shutdown().unwrap();
29875      return;
29876    }
29877
29878    let output = std::process::Command::new(std::env::current_exe().unwrap())
29879      .arg("--exact")
29880      .arg(
29881        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29882      )
29883      .arg("--nocapture")
29884      .env(CHILD_ENV, "1")
29885      .output()
29886      .expect("the subprocess test must start");
29887    assert!(
29888      output.status.success(),
29889      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29890      output.status.code(),
29891      String::from_utf8_lossy(&output.stdout),
29892      String::from_utf8_lossy(&output.stderr)
29893    );
29894  }
29895
29896  #[cfg(napi_runtime_os_threads)]
29897  #[test]
29898  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29899    use std::sync::mpsc;
29900
29901    struct BlockingDrop {
29902      controller: Arc<RuntimeController>,
29903      entered: mpsc::Sender<Option<u64>>,
29904      release: mpsc::Receiver<()>,
29905      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29906    }
29907
29908    impl Drop for BlockingDrop {
29909      fn drop(&mut self) {
29910        self
29911          .entered
29912          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29913          .unwrap();
29914        self.release.recv().unwrap();
29915        self
29916          .reentrant_shutdown
29917          .send(self.controller.shutdown())
29918          .unwrap();
29919      }
29920    }
29921
29922    let controller = Arc::new(current_thread_controller(
29923      "current-thread-host-drain-output-retirement",
29924    ));
29925    let old_generation = controller.backend().generation();
29926    let (entered_tx, entered_rx) = mpsc::channel();
29927    let (release_tx, release_rx) = mpsc::channel();
29928    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29929    let output_controller = Arc::clone(&controller);
29930    let handle = controller
29931      .try_spawn(async move {
29932        BlockingDrop {
29933          controller: output_controller,
29934          entered: entered_tx,
29935          release: release_rx,
29936          reentrant_shutdown: reentrant_tx,
29937        }
29938      })
29939      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29940    drop(handle);
29941    let dispatch = controller_current_thread_dispatch(&controller);
29942
29943    let driver_controller = Arc::clone(&controller);
29944    let driver =
29945      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29946    assert_eq!(
29947      entered_rx
29948        .recv_timeout(Duration::from_secs(2))
29949        .expect("the host turn must begin destroying the detached output"),
29950      Some(old_generation),
29951      "detached output destruction must retain the generation that produced it"
29952    );
29953
29954    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29955    let shutdown_controller = Arc::clone(&controller);
29956    let shutdown = std::thread::spawn(move || {
29957      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29958    });
29959    wait_until(
29960      "CurrentThread shutdown to enter the old generation's stopping state",
29961      || {
29962        let state = controller
29963          .state
29964          .lock()
29965          .unwrap_or_else(std::sync::PoisonError::into_inner);
29966        matches!(
29967          &state.lifecycle,
29968          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29969        )
29970      },
29971    );
29972
29973    let (restart_tx, restart_rx) = mpsc::channel();
29974    let restart_controller = Arc::clone(&controller);
29975    let restart = std::thread::spawn(move || {
29976      restart_controller.start().unwrap();
29977      restart_tx
29978        .send(restart_controller.backend().generation())
29979        .unwrap();
29980    });
29981
29982    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29983    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29984    let shutdown_overlapped = early_shutdown.is_some();
29985    let restart_overlapped = early_restart.is_some();
29986    release_tx.send(()).unwrap();
29987
29988    let reentrant_error = reentrant_rx
29989      .recv_timeout(Duration::from_secs(2))
29990      .expect("the retiring output destructor must finish its lifecycle check")
29991      .expect_err("old-generation output destruction must not shut down a replacement runtime");
29992    assert!(
29993      reentrant_error
29994        .to_string()
29995        .contains("generation being stopped"),
29996      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29997    );
29998
29999    let shutdown_result = early_shutdown.unwrap_or_else(|| {
30000      shutdown_rx
30001        .recv_timeout(Duration::from_secs(2))
30002        .expect("shutdown must finish after detached output destruction retires")
30003    });
30004    shutdown_result.unwrap();
30005    let new_generation = early_restart.unwrap_or_else(|| {
30006      restart_rx
30007        .recv_timeout(Duration::from_secs(2))
30008        .expect("restart must finish after the old host turn retires")
30009    });
30010
30011    driver.join().unwrap();
30012    shutdown.join().unwrap();
30013    restart.join().unwrap();
30014    assert!(
30015      !shutdown_overlapped,
30016      "shutdown must not publish Stopped while a host turn is destroying detached output"
30017    );
30018    assert!(
30019      !restart_overlapped,
30020      "restart must not overlap a host turn from the retiring generation"
30021    );
30022    assert_ne!(new_generation, old_generation);
30023    controller.shutdown().unwrap();
30024  }
30025
30026  #[cfg(napi_runtime_os_threads)]
30027  #[test]
30028  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
30029    use std::sync::mpsc;
30030
30031    struct ShutdownOnDrop {
30032      controller: Arc<RuntimeController>,
30033      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
30034    }
30035
30036    impl Drop for ShutdownOnDrop {
30037      fn drop(&mut self) {
30038        let _ = self.result.send(self.controller.shutdown());
30039      }
30040    }
30041
30042    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
30043    let (running_tx, running_rx) = mpsc::channel();
30044    let (release_tx, release_rx) = mpsc::channel();
30045    let running = controller
30046      .try_spawn_blocking(move || {
30047        running_tx.send(()).unwrap();
30048        release_rx.recv().unwrap();
30049      })
30050      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30051    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30052
30053    let (reentrant_tx, reentrant_rx) = mpsc::channel();
30054    let bomb = ShutdownOnDrop {
30055      controller: Arc::clone(&controller),
30056      result: reentrant_tx,
30057    };
30058    let queued = controller
30059      .try_spawn_blocking(move || {
30060        let _bomb = &bomb;
30061      })
30062      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
30063
30064    let (shutdown_tx, shutdown_rx) = mpsc::channel();
30065    let shutdown_controller = Arc::clone(&controller);
30066    let shutdown = std::thread::spawn(move || {
30067      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30068    });
30069    let error = reentrant_rx
30070      .recv_timeout(Duration::from_secs(2))
30071      .expect("queued destruction must not self-deadlock")
30072      .expect_err("the retiring generation must reject reentrant shutdown");
30073    assert!(error.to_string().contains("generation being stopped"));
30074    release_tx.send(()).unwrap();
30075    shutdown_rx
30076      .recv_timeout(Duration::from_secs(2))
30077      .unwrap()
30078      .unwrap();
30079    shutdown.join().unwrap();
30080    futures::executor::block_on(running).unwrap();
30081    assert!(futures::executor::block_on(queued).is_err());
30082  }
30083
30084  #[cfg(napi_runtime_os_threads)]
30085  #[test]
30086  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
30087    use std::sync::mpsc;
30088
30089    struct ShutdownAfterRelease {
30090      controller: Arc<RuntimeController>,
30091      entered: mpsc::Sender<()>,
30092      release: mpsc::Receiver<()>,
30093      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
30094    }
30095
30096    impl Drop for ShutdownAfterRelease {
30097      fn drop(&mut self) {
30098        self.entered.send(()).unwrap();
30099        self.release.recv().unwrap();
30100        self.result.send(self.controller.shutdown()).unwrap();
30101      }
30102    }
30103
30104    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
30105    let (holder_started_tx, holder_started_rx) = mpsc::channel();
30106    let (release_holder_tx, release_holder_rx) = mpsc::channel();
30107    let holder = controller
30108      .try_spawn_blocking(move || {
30109        holder_started_tx.send(()).unwrap();
30110        release_holder_rx.recv().unwrap();
30111      })
30112      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30113    holder_started_rx
30114      .recv_timeout(Duration::from_secs(2))
30115      .unwrap();
30116
30117    let (shutdown_tx, shutdown_rx) = mpsc::channel();
30118    let shutdown_controller = Arc::clone(&controller);
30119    let shutdown = std::thread::spawn(move || {
30120      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30121    });
30122    wait_until("the runtime entered Stopping", || {
30123      matches!(
30124        controller
30125          .state
30126          .lock()
30127          .unwrap_or_else(std::sync::PoisonError::into_inner)
30128          .lifecycle,
30129        RuntimeLifecycle::Stopping(_)
30130      )
30131    });
30132
30133    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
30134    let (release_drop_tx, release_drop_rx) = mpsc::channel();
30135    let (reentrant_tx, reentrant_rx) = mpsc::channel();
30136    let rejected_controller = Arc::clone(&controller);
30137    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
30138    let rejected = std::thread::spawn(move || {
30139      let bomb = ShutdownAfterRelease {
30140        controller: Arc::clone(&rejected_controller),
30141        entered: drop_entered_tx,
30142        release: release_drop_rx,
30143        result: reentrant_tx,
30144      };
30145      let handle = rejected_controller.spawn(async move {
30146        let _bomb = bomb;
30147        std::future::pending::<()>().await;
30148      });
30149      rejected_done_tx
30150        .send(futures::executor::block_on(handle))
30151        .unwrap();
30152    });
30153    drop_entered_rx
30154      .recv_timeout(Duration::from_secs(2))
30155      .unwrap();
30156
30157    release_holder_tx.send(()).unwrap();
30158    assert!(
30159      shutdown_rx
30160        .recv_timeout(Duration::from_millis(200))
30161        .is_err(),
30162      "shutdown crossed into Stopped before rejected destruction finished"
30163    );
30164
30165    let (start_tx, start_rx) = mpsc::channel();
30166    let start_controller = Arc::clone(&controller);
30167    let starter = std::thread::spawn(move || {
30168      start_tx.send(start_controller.start()).unwrap();
30169    });
30170    assert!(
30171      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
30172      "restart crossed into a new generation before rejected destruction finished"
30173    );
30174
30175    release_drop_tx.send(()).unwrap();
30176    let error = reentrant_rx
30177      .recv_timeout(Duration::from_secs(2))
30178      .expect("the rejected destructor must complete")
30179      .expect_err("the retiring generation must reject the destructor's shutdown");
30180    assert!(error.to_string().contains("generation being stopped"));
30181    assert!(
30182      rejected_done_rx
30183        .recv_timeout(Duration::from_secs(2))
30184        .unwrap()
30185        .is_err()
30186    );
30187    rejected.join().unwrap();
30188
30189    shutdown_rx
30190      .recv_timeout(Duration::from_secs(2))
30191      .unwrap()
30192      .unwrap();
30193    shutdown.join().unwrap();
30194    start_rx
30195      .recv_timeout(Duration::from_secs(2))
30196      .unwrap()
30197      .unwrap();
30198    starter.join().unwrap();
30199    futures::executor::block_on(holder).unwrap();
30200    controller.shutdown().unwrap();
30201  }
30202
30203  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30204  #[test]
30205  fn shutdown_contains_hostile_caught_panic_payload() {
30206    use std::{panic::panic_any, sync::mpsc};
30207
30208    struct PanicAgain;
30209    impl Drop for PanicAgain {
30210      fn drop(&mut self) {
30211        panic!("caught panic payload destructor escaped");
30212      }
30213    }
30214
30215    struct PanicPayloadOnDrop(mpsc::Sender<()>);
30216    impl Drop for PanicPayloadOnDrop {
30217      fn drop(&mut self) {
30218        let _ = self.0.send(());
30219        panic_any(PanicAgain);
30220      }
30221    }
30222
30223    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
30224    let (running_tx, running_rx) = mpsc::channel();
30225    let (release_tx, release_rx) = mpsc::channel();
30226    let running = controller
30227      .try_spawn_blocking(move || {
30228        running_tx.send(()).unwrap();
30229        release_rx.recv().unwrap();
30230      })
30231      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
30232    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30233
30234    let (drop_tx, drop_rx) = mpsc::channel();
30235    let payload = PanicPayloadOnDrop(drop_tx);
30236    let queued = controller
30237      .try_spawn_blocking(move || {
30238        let _payload = &payload;
30239      })
30240      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
30241
30242    let (shutdown_tx, shutdown_rx) = mpsc::channel();
30243    let shutdown_controller = Arc::clone(&controller);
30244    let shutdown = std::thread::spawn(move || {
30245      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
30246    });
30247    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
30248    release_tx.send(()).unwrap();
30249    shutdown_rx
30250      .recv_timeout(Duration::from_secs(2))
30251      .expect("hostile panic payload destruction must remain contained")
30252      .unwrap();
30253    shutdown
30254      .join()
30255      .expect("the caught payload must not panic during destruction");
30256    futures::executor::block_on(running).unwrap();
30257    assert!(futures::executor::block_on(queued).is_err());
30258  }
30259
30260  #[cfg(napi_runtime_os_threads)]
30261  #[test]
30262  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
30263    use std::sync::mpsc;
30264
30265    struct DropSignal(mpsc::Sender<()>);
30266    impl Drop for DropSignal {
30267      fn drop(&mut self) {
30268        let _ = self.0.send(());
30269      }
30270    }
30271
30272    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
30273    let (running_tx, running_rx) = mpsc::channel();
30274    let (release_tx, release_rx) = mpsc::channel();
30275    let running = controller
30276      .try_spawn_blocking(move || {
30277        running_tx.send(()).unwrap();
30278        release_rx.recv().unwrap();
30279        7usize
30280      })
30281      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30282    running_rx
30283      .recv_timeout(Duration::from_secs(5))
30284      .expect("the first blocking job must hold the only blocking slot");
30285
30286    let queued_ran = Arc::new(AtomicBool::new(false));
30287    let queued_ran_work = Arc::clone(&queued_ran);
30288    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
30289    let queued_drop_signal = DropSignal(queued_dropped_tx);
30290    let queued = controller
30291      .try_spawn_blocking(move || {
30292        let _drop_signal = queued_drop_signal;
30293        queued_ran_work.store(true, Ordering::SeqCst);
30294        11usize
30295      })
30296      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30297
30298    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30299    let shutdown_controller = Arc::clone(&controller);
30300    let shutdown = std::thread::spawn(move || {
30301      shutdown_controller.shutdown().unwrap();
30302      shutdown_done_tx.send(()).unwrap();
30303    });
30304
30305    queued_dropped_rx
30306      .recv_timeout(Duration::from_secs(5))
30307      .expect("shutdown must drop queued blocking work");
30308    assert!(
30309      !queued_ran.load(Ordering::SeqCst),
30310      "queued blocking work must not execute"
30311    );
30312    assert!(
30313      shutdown_done_rx.try_recv().is_err(),
30314      "shutdown must wait while an accepted blocking closure is still running"
30315    );
30316
30317    release_tx.send(()).unwrap();
30318    shutdown_done_rx
30319      .recv_timeout(Duration::from_secs(5))
30320      .expect("shutdown must finish after running blocking work retires");
30321    shutdown.join().unwrap();
30322    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
30323    assert!(
30324      futures::executor::block_on(queued).is_err(),
30325      "the queued blocking handle must resolve as cancelled"
30326    );
30327  }
30328
30329  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30330  #[test]
30331  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
30332    use std::sync::mpsc;
30333
30334    struct PanicOnDrop(mpsc::Sender<()>);
30335    impl Drop for PanicOnDrop {
30336      fn drop(&mut self) {
30337        let _ = self.0.send(());
30338        panic!("intentional queued blocking destructor panic");
30339      }
30340    }
30341
30342    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
30343    let (running_tx, running_rx) = mpsc::channel();
30344    let (release_tx, release_rx) = mpsc::channel();
30345    let running = controller
30346      .try_spawn_blocking(move || {
30347        running_tx.send(()).unwrap();
30348        release_rx.recv().unwrap();
30349      })
30350      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30351    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30352
30353    let (drop_tx, drop_rx) = mpsc::channel();
30354    let panic_on_drop = PanicOnDrop(drop_tx);
30355    let queued = controller
30356      .try_spawn_blocking(move || {
30357        let _keep_captured_until_run = &panic_on_drop;
30358      })
30359      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30360
30361    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30362    let shutdown_controller = Arc::clone(&controller);
30363    let shutdown = std::thread::spawn(move || {
30364      shutdown_controller.shutdown().unwrap();
30365      shutdown_done_tx.send(()).unwrap();
30366    });
30367
30368    drop_rx
30369      .recv_timeout(Duration::from_secs(5))
30370      .expect("shutdown must attempt to drop the queued closure");
30371    release_tx.send(()).unwrap();
30372    shutdown_done_rx
30373      .recv_timeout(Duration::from_secs(5))
30374      .expect("a panicking queued destructor must not strand shutdown");
30375    shutdown
30376      .join()
30377      .expect("shutdown must contain the destructor panic");
30378    futures::executor::block_on(running).unwrap();
30379    assert!(futures::executor::block_on(queued).is_err());
30380
30381    controller
30382      .start()
30383      .expect("the stopped controller must restart");
30384    let restarted = controller
30385      .try_spawn_blocking(|| 17usize)
30386      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30387    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
30388    controller.shutdown().unwrap();
30389  }
30390
30391  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30392  #[test]
30393  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
30394    use std::{panic::panic_any, sync::mpsc};
30395
30396    struct ReentrantPayload {
30397      controller: Arc<RuntimeController>,
30398      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
30399    }
30400
30401    impl Drop for ReentrantPayload {
30402      fn drop(&mut self) {
30403        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
30404        let shutdown = self.controller.shutdown();
30405        self.result.send((generation, shutdown)).unwrap();
30406      }
30407    }
30408
30409    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
30410
30411    impl Drop for PanicPayloadOnDrop {
30412      fn drop(&mut self) {
30413        panic_any(self.0.take().expect("the result must panic only once"));
30414      }
30415    }
30416
30417    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
30418    let generation = controller.backend().generation();
30419    let (started_tx, started_rx) = mpsc::channel();
30420    let (release_tx, release_rx) = mpsc::channel();
30421    let (result_tx, result_rx) = mpsc::channel();
30422    let payload_controller = Arc::clone(&controller);
30423    let handle = controller
30424      .try_spawn_blocking(move || {
30425        started_tx.send(()).unwrap();
30426        release_rx.recv().unwrap();
30427        PanicPayloadOnDrop(Some(ReentrantPayload {
30428          controller: payload_controller,
30429          result: result_tx,
30430        }))
30431      })
30432      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30433    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30434
30435    // Detach the receiver before the result is delivered. Sending then drops
30436    // the result on the worker, whose destructor intentionally panics.
30437    drop(handle);
30438    release_tx.send(()).unwrap();
30439    let (observed_generation, reentrant_shutdown) = result_rx
30440      .recv_timeout(Duration::from_secs(5))
30441      .expect("the dropped result panic payload must not self-deadlock");
30442    assert_eq!(observed_generation, Some(generation));
30443    let error =
30444      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
30445    assert!(error.to_string().contains("work running on that runtime"));
30446
30447    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30448    let shutdown_controller = Arc::clone(&controller);
30449    let shutdown = std::thread::spawn(move || {
30450      shutdown_controller.shutdown().unwrap();
30451      shutdown_done_tx.send(()).unwrap();
30452    });
30453    shutdown_done_rx
30454      .recv_timeout(Duration::from_secs(5))
30455      .expect("a dropped-result destructor panic must not strand shutdown");
30456    shutdown
30457      .join()
30458      .expect("shutdown must contain the destructor panic");
30459
30460    controller
30461      .start()
30462      .expect("the stopped controller must restart");
30463    let restarted = controller
30464      .try_spawn_blocking(|| 19usize)
30465      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30466    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
30467    controller.shutdown().unwrap();
30468  }
30469
30470  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30471  #[test]
30472  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
30473    use std::sync::mpsc;
30474
30475    struct PanicOnDrop(mpsc::Sender<()>);
30476
30477    impl Drop for PanicOnDrop {
30478      fn drop(&mut self) {
30479        self.0.send(()).unwrap();
30480        panic!("intentional buffered blocking result destructor panic");
30481      }
30482    }
30483
30484    let options = RuntimeOptions {
30485      flavor: RuntimeFlavor::MultiThread,
30486      worker_threads: 2,
30487      max_blocking_tasks: 1,
30488      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30489      park_deadline: None,
30490      drain_linger: DEFAULT_DRAIN_LINGER,
30491    };
30492    let executor =
30493      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30494    let (started_tx, started_rx) = mpsc::channel();
30495    let (dropped_tx, dropped_rx) = mpsc::channel();
30496    let handle = executor.schedule_blocking(move || {
30497      started_tx.send(()).unwrap();
30498      PanicOnDrop(dropped_tx)
30499    });
30500    started_rx
30501      .recv_timeout(Duration::from_secs(2))
30502      .expect("the blocking closure must run");
30503    wait_until("the completed blocking result to be buffered", || {
30504      executor.active_blocking.load(Ordering::Acquire) == 0
30505    });
30506    assert!(
30507      dropped_rx.try_recv().is_err(),
30508      "the buffered result must remain owned by the live receiver"
30509    );
30510
30511    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30512    assert!(
30513      dropped.is_ok(),
30514      "dropping the buffered result must contain its destructor panic"
30515    );
30516    dropped_rx
30517      .recv_timeout(Duration::from_secs(2))
30518      .expect("dropping the receiver must reclaim the buffered result");
30519  }
30520
30521  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30522  #[test]
30523  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30524    use std::sync::mpsc;
30525
30526    struct PanicOnDrop(mpsc::Sender<()>);
30527    impl Drop for PanicOnDrop {
30528      fn drop(&mut self) {
30529        let _ = self.0.send(());
30530        panic!("intentional late blocking destructor panic");
30531      }
30532    }
30533
30534    let executor = multi_thread_executor(2, None, "closed-queue-drop");
30535    executor.begin_shutdown();
30536
30537    let (drop_tx, drop_rx) = mpsc::channel();
30538    let panic_on_drop = PanicOnDrop(drop_tx);
30539    let handle = executor.schedule_blocking(move || {
30540      let _keep_captured_until_run = &panic_on_drop;
30541    });
30542
30543    drop_rx
30544      .recv_timeout(Duration::from_secs(5))
30545      .expect("the closed queue must retire the rejected closure");
30546    assert!(
30547      futures::executor::block_on(handle).is_err(),
30548      "the rejected blocking handle must resolve as cancelled"
30549    );
30550  }
30551
30552  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30553  #[test]
30554  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30555    struct PanicWake;
30556    impl std::task::Wake for PanicWake {
30557      fn wake(self: Arc<Self>) {
30558        panic!("intentional timer waker panic");
30559      }
30560    }
30561
30562    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30563    let backend = controller.backend();
30564    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30565      panic!("configured MultiThread backend must create a Rayon executor");
30566    };
30567    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30568    let waker = Waker::from(Arc::new(PanicWake));
30569    let mut cx = Context::from_waker(&waker);
30570    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30571    drop(backend);
30572
30573    controller
30574      .shutdown()
30575      .expect("shutdown must contain the timer waker panic");
30576    controller
30577      .start()
30578      .expect("the stopped controller must restart");
30579    let restarted = controller
30580      .try_spawn_blocking(|| 23usize)
30581      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30582    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30583    controller.shutdown().unwrap();
30584  }
30585
30586  #[cfg(napi_runtime_os_threads)]
30587  #[test]
30588  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30589    use std::sync::mpsc;
30590
30591    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30592    let backend = controller.backend();
30593    let old_generation = backend.generation();
30594    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30595      panic!("configured MultiThread backend must create a Rayon executor");
30596    };
30597    let old_workers = Arc::clone(&executor.worker_lifecycle);
30598    drop(backend);
30599
30600    let (running_tx, running_rx) = mpsc::channel();
30601    let (release_tx, release_rx) = mpsc::channel();
30602    let running = controller
30603      .try_spawn_blocking(move || {
30604        running_tx.send(()).unwrap();
30605        release_rx.recv().unwrap();
30606      })
30607      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30608    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30609
30610    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30611    let shutdown_controller = Arc::clone(&controller);
30612    let shutdown = std::thread::spawn(move || {
30613      shutdown_controller.shutdown().unwrap();
30614      shutdown_done_tx.send(()).unwrap();
30615    });
30616    wait_until("controller entered Stopping", || {
30617      let state = controller
30618        .state
30619        .lock()
30620        .unwrap_or_else(std::sync::PoisonError::into_inner);
30621      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30622    });
30623
30624    let (start_done_tx, start_done_rx) = mpsc::channel();
30625    let start_controller = Arc::clone(&controller);
30626    let starter = std::thread::spawn(move || {
30627      start_controller.start().unwrap();
30628      start_done_tx.send(()).unwrap();
30629    });
30630    assert!(
30631      start_done_rx.try_recv().is_err(),
30632      "start must wait while shutdown is quiescing"
30633    );
30634    assert!(
30635      old_workers.remaining() > 0,
30636      "the held old generation must still own workers"
30637    );
30638
30639    release_tx.send(()).unwrap();
30640    futures::executor::block_on(running).unwrap();
30641    shutdown_done_rx
30642      .recv_timeout(Duration::from_secs(5))
30643      .unwrap();
30644    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30645    shutdown.join().unwrap();
30646    starter.join().unwrap();
30647    assert_eq!(
30648      old_workers.remaining(),
30649      0,
30650      "restart may begin only after every old worker exits"
30651    );
30652
30653    let backend = controller.backend();
30654    assert_ne!(backend.generation(), old_generation);
30655    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30656      panic!("restarted backend must remain MultiThread");
30657    };
30658    assert_eq!(
30659      executor.pool.current_num_threads(),
30660      controller.options().worker_threads
30661    );
30662    drop(backend);
30663    controller.shutdown().unwrap();
30664  }
30665
30666  #[cfg(napi_runtime_os_threads)]
30667  #[test]
30668  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30669    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30670    let task_controller = Arc::clone(&controller);
30671    let handle = controller
30672      .try_spawn(async move {
30673        task_controller
30674          .shutdown()
30675          .expect_err("runtime work must not wait for its own generation")
30676          .to_string()
30677      })
30678      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30679    let message = futures::executor::block_on(handle).unwrap();
30680    assert!(message.contains("cannot shut down"));
30681    controller.shutdown().unwrap();
30682  }
30683
30684  // ---- two-phase shutdown handshake ----------------------------------------
30685  // `shutdown` is `begin_shutdown` + `finish_shutdown`, so every existing
30686  // shutdown test above also covers the composition. These cover the window
30687  // the split opens: phase 1 must return while work is still running, the
30688  // poll must eventually answer `false`, a re-entered phase 1 must never
30689  // wait, and every OPENED phase 1 must pair with exactly one
30690  // `finish_shutdown` -- no more (the extra one is the orphan error) and no
30691  // fewer (a racing `start` in the window must not turn a completed shutdown
30692  // into an error).
30693
30694  #[cfg(napi_runtime_os_threads)]
30695  #[test]
30696  fn two_phase_shutdown_matches_single_call() {
30697    // The threaded WASI artifact enters shutdown from the JS thread, so phase
30698    // 1 has to come back while a blocking closure is still running; phase 2
30699    // then does exactly what the single call does once the closure retires.
30700    let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30701    let entered = Arc::new(AtomicBool::new(false));
30702    let release = Arc::new(AtomicBool::new(false));
30703    let job_entered = Arc::clone(&entered);
30704    let job_release = Arc::clone(&release);
30705    let handle = controller
30706      .try_spawn_blocking(move || {
30707        job_entered.store(true, Ordering::SeqCst);
30708        while !job_release.load(Ordering::SeqCst) {
30709          std::hint::spin_loop();
30710        }
30711        7usize
30712      })
30713      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30714    while !entered.load(Ordering::SeqCst) {
30715      std::thread::yield_now();
30716    }
30717
30718    assert!(
30719      controller
30720        .begin_shutdown()
30721        .expect("phase 1 on a running generation must be accepted"),
30722      "phase 1 must report the still-running blocking closure instead of waiting for it"
30723    );
30724    assert!(
30725      controller.runtime_work_pending(),
30726      "the parked closure holds its generation guard"
30727    );
30728    // Admission is closed by `backend_locked` the moment `Stopping` is
30729    // published; phase 1 adds no admission code of its own.
30730    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30731      panic!("a submission between the phases must be rejected");
30732    };
30733    assert_eq!(
30734      error.to_string(),
30735      "the async runtime is stopped; call start before submitting work"
30736    );
30737
30738    release.store(true, Ordering::SeqCst);
30739    let deadline = Instant::now() + Duration::from_secs(5);
30740    while controller.runtime_work_pending() {
30741      assert!(
30742        Instant::now() < deadline,
30743        "the released closure must retire and the poll must answer false"
30744      );
30745      std::thread::yield_now();
30746    }
30747    assert_eq!(
30748      futures::executor::block_on(handle).unwrap(),
30749      7usize,
30750      "the closure that phase 1 refused to wait for still delivers its result"
30751    );
30752
30753    controller
30754      .finish_shutdown()
30755      .expect("phase 2 must complete the outstanding phase 1");
30756    {
30757      let state = controller
30758        .state
30759        .lock()
30760        .unwrap_or_else(std::sync::PoisonError::into_inner);
30761      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30762      assert!(
30763        state.draining.is_none(),
30764        "phase 2 must consume the phase-1 handoff"
30765      );
30766    }
30767
30768    // The split leaves the restart path exactly as the single call does.
30769    controller.start().expect("a stopped runtime must restart");
30770    drop(controller.backend());
30771    controller
30772      .shutdown()
30773      .expect("the single call must still shut the restarted runtime down");
30774  }
30775
30776  #[cfg(napi_runtime_os_threads)]
30777  #[test]
30778  fn concurrent_shutdown_during_two_phase_still_reports_success() {
30779    // A `shutdown` from a second thread must still answer `Ok(())` while a
30780    // handshake is in flight, and neither thread may wedge the other. THIS
30781    // test pins the interleaving where the phase-1 OWNER drains: the
30782    // concurrent call is started from the owner's own
30783    // `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e. after the handoff is out
30784    // of the state and before the owner waits, so the second thread provably
30785    // arrives at an already-taken handshake and must report the owner's
30786    // publication. The mirror image (the concurrent call drains, the owner
30787    // finishes afterwards) is
30788    // `phase_one_owner_finishes_after_a_concurrent_shutdown_drains`.
30789    let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30790    let entered = Arc::new(AtomicBool::new(false));
30791    let committed = Arc::new(AtomicBool::new(false));
30792    let job_entered = Arc::clone(&entered);
30793    let job_committed = Arc::clone(&committed);
30794    // The closure retires only once the concurrent call has committed, so the
30795    // owner's drain cannot run to completion before the second thread is in
30796    // flight: both are always overlapped, as before.
30797    let handle = controller
30798      .try_spawn_blocking(move || {
30799        job_entered.store(true, Ordering::SeqCst);
30800        while !job_committed.load(Ordering::SeqCst) {
30801          std::hint::spin_loop();
30802        }
30803        3usize
30804      })
30805      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30806    wait_until("the blocking closure to park", || {
30807      entered.load(Ordering::SeqCst)
30808    });
30809    assert!(
30810      controller
30811        .begin_shutdown()
30812        .expect("phase 1 must be accepted")
30813    );
30814
30815    let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30816    let hook_controller = Arc::clone(&controller);
30817    let hook_committed = Arc::clone(&committed);
30818    let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30819    let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30820    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30821      *slot.borrow_mut() = Some(Box::new(move || {
30822        hook_took_the_drain.store(true, Ordering::SeqCst);
30823        let concurrent = std::thread::spawn(move || {
30824          hook_committed.store(true, Ordering::SeqCst);
30825          hook_controller.shutdown()
30826        });
30827        concurrent_tx
30828          .send(concurrent)
30829          .expect("the test thread must still be waiting for the concurrent handle");
30830      }));
30831    });
30832
30833    controller
30834      .finish_shutdown()
30835      .expect("phase 2 must complete the handshake");
30836    assert!(
30837      owner_took_the_drain.load(Ordering::SeqCst),
30838      "the phase-1 owner must be the thread that took the handoff"
30839    );
30840    concurrent_rx
30841      .recv()
30842      .expect("the hook must have started the concurrent shutdown")
30843      .join()
30844      .expect("the concurrent shutdown thread must not panic")
30845      .expect("a concurrent single-call shutdown must still succeed");
30846
30847    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30848    let state = controller
30849      .state
30850      .lock()
30851      .unwrap_or_else(std::sync::PoisonError::into_inner);
30852    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30853    assert!(state.draining.is_none());
30854  }
30855
30856  #[cfg(napi_runtime_os_threads)]
30857  #[test]
30858  fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30859    // The handoff slot must never go empty while the join is still running.
30860    // `finish_shutdown` takes the backend out of the state and only then
30861    // waits; for the whole of that window the lifecycle still reads
30862    // `Stopping`. If the slot were left `None` there, a re-entering phase 1
30863    // would fall through to the lifecycle loop and PARK on `lifecycle_changed`
30864    // until the join it is racing publishes `Stopped` -- breaking the
30865    // documented "a `begin_shutdown` while one is outstanding never waits"
30866    // contract -- and `runtime_work_pending` would answer `false` about work
30867    // that is demonstrably still parked. On a host that drives both calls from
30868    // the same thread pool that the drained work needs, that park is a
30869    // deadlock.
30870    //
30871    // The probe is started from `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e.
30872    // provably after the handoff has left the slot and before the finisher
30873    // waits, and the closure it must see is released only once the probe has
30874    // answered (or a bounded watchdog gives up), so a parking probe FAILS the
30875    // test instead of hanging it.
30876    let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30877    let entered = Arc::new(AtomicBool::new(false));
30878    let release = Arc::new(AtomicBool::new(false));
30879    let job_entered = Arc::clone(&entered);
30880    let job_release = Arc::clone(&release);
30881    let handle = controller
30882      .try_spawn_blocking(move || {
30883        job_entered.store(true, Ordering::SeqCst);
30884        while !job_release.load(Ordering::SeqCst) {
30885          std::hint::spin_loop();
30886        }
30887        11usize
30888      })
30889      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30890    wait_until("the blocking closure to park", || {
30891      entered.load(Ordering::SeqCst)
30892    });
30893    assert!(
30894      controller
30895        .begin_shutdown()
30896        .expect("phase 1 must be accepted"),
30897      "the parked closure must be reported as live"
30898    );
30899
30900    let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30901    let hook_controller = Arc::clone(&controller);
30902    let hook_release = Arc::clone(&release);
30903    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30904      *slot.borrow_mut() = Some(Box::new(move || {
30905        let probe_controller = Arc::clone(&hook_controller);
30906        let probe = std::thread::spawn(move || {
30907          let pending = probe_controller.runtime_work_pending();
30908          (pending, probe_controller.begin_shutdown())
30909        });
30910        let watchdog = std::thread::spawn(move || {
30911          let deadline = Instant::now() + Duration::from_secs(5);
30912          while !probe.is_finished() && Instant::now() < deadline {
30913            std::thread::yield_now();
30914          }
30915          // Whether the probe answered BEFORE the drained closure retired is
30916          // the whole verdict: releasing afterwards would let a parked probe
30917          // be woken by the `Stopped` publication and look healthy.
30918          let answered_during_the_drain = probe.is_finished();
30919          hook_release.store(true, Ordering::SeqCst);
30920          (answered_during_the_drain, probe.join())
30921        });
30922        probe_tx
30923          .send(watchdog)
30924          .expect("the test thread must still be waiting for the probe handle");
30925      }));
30926    });
30927
30928    controller
30929      .finish_shutdown()
30930      .expect("phase 2 must complete the outstanding phase 1");
30931    let (answered_during_the_drain, probe) = probe_rx
30932      .recv()
30933      .expect("the hook must have started the probe")
30934      .join()
30935      .expect("the watchdog thread must not panic");
30936    let (pending, verdict) = probe.expect("the probe thread must not panic");
30937    assert!(
30938      answered_during_the_drain,
30939      "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30940    );
30941    assert!(
30942      pending,
30943      "the poll must still see the parked closure while the drain that is waiting for it runs"
30944    );
30945    assert!(
30946      verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30947      "the re-entering phase 1 must report the parked closure as live"
30948    );
30949
30950    assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30951    {
30952      let state = controller
30953        .state
30954        .lock()
30955        .unwrap_or_else(std::sync::PoisonError::into_inner);
30956      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30957      assert!(
30958        state.draining.is_none(),
30959        "publishing `Stopped` must retire the in-flight drain marker too"
30960      );
30961    }
30962    assert!(
30963      !controller.runtime_work_pending(),
30964      "a stopped runtime has no pending work"
30965    );
30966
30967    controller.start().expect("a stopped runtime must restart");
30968    drop(controller.backend());
30969    controller
30970      .shutdown()
30971      .expect("the single call must still shut the restarted runtime down");
30972  }
30973
30974  #[cfg(napi_runtime_os_threads)]
30975  #[test]
30976  fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30977    // The other half of the race above, hand-sequenced by a join: a
30978    // concurrent single-call `shutdown` takes the outstanding handoff AND
30979    // completes the whole shutdown, and only then does the thread that opened
30980    // phase 1 call its own phase 2. The runtime is already `Stopped`, so the
30981    // join that caller is owed has happened -- phase 2 owes it `Ok(())`, not
30982    // "shutdown was not started", whoever performed that join.
30983    let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30984    let entered = Arc::new(AtomicBool::new(false));
30985    let release = Arc::new(AtomicBool::new(false));
30986    let job_entered = Arc::clone(&entered);
30987    let job_release = Arc::clone(&release);
30988    let handle = controller
30989      .try_spawn_blocking(move || {
30990        job_entered.store(true, Ordering::SeqCst);
30991        while !job_release.load(Ordering::SeqCst) {
30992          std::hint::spin_loop();
30993        }
30994        3usize
30995      })
30996      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30997    wait_until("the blocking closure to park", || {
30998      entered.load(Ordering::SeqCst)
30999    });
31000    assert!(
31001      controller
31002        .begin_shutdown()
31003        .expect("phase 1 must be accepted"),
31004      "the parked closure must be reported as live"
31005    );
31006
31007    let single_call = Arc::clone(&controller);
31008    let concurrent = std::thread::spawn(move || single_call.shutdown());
31009    release.store(true, Ordering::SeqCst);
31010    concurrent
31011      .join()
31012      .expect("the concurrent shutdown thread must not panic")
31013      .expect("a concurrent single-call shutdown must still succeed");
31014    {
31015      let state = controller
31016        .state
31017        .lock()
31018        .unwrap_or_else(std::sync::PoisonError::into_inner);
31019      assert!(
31020        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31021        "the concurrent call must have published the stop"
31022      );
31023      assert!(
31024        state.draining.is_none(),
31025        "the concurrent call must have consumed the phase-1 handoff"
31026      );
31027    }
31028
31029    controller
31030      .finish_shutdown()
31031      .expect("the thread that began phase 1 must still be told its shutdown completed");
31032    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
31033
31034    // Flipped with the idempotent model (it used to expect the orphan error
31035    // here): a phase 2 after a restart finds nothing outstanding, so it is a
31036    // no-op `Ok(())` -- and it must NOT adopt the generation the restart
31037    // published.
31038    controller.start().expect("a stopped runtime must restart");
31039    drop(controller.backend());
31040    controller
31041      .finish_shutdown()
31042      .expect("phase 2 after a restart is a no-op, not an error");
31043    {
31044      let state = controller
31045        .state
31046        .lock()
31047        .unwrap_or_else(std::sync::PoisonError::into_inner);
31048      assert!(
31049        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31050        "the no-op phase 2 must not stop the restarted generation"
31051      );
31052    }
31053    controller
31054      .shutdown()
31055      .expect("the single call must still shut the restarted runtime down");
31056  }
31057
31058  #[test]
31059  fn begin_shutdown_twice_does_not_block() {
31060    // The host thread may re-enter the handshake from a turn it took between
31061    // the phases. A second phase 1 must answer from the outstanding one --
31062    // taking the `Stopping` arm instead would park this thread on the condvar
31063    // forever, so a regression hangs this test rather than failing it. It
31064    // also OPENS nothing -- and, flipped with the idempotent model, the second
31065    // phase 2 is no longer an orphan error: the stop it was asked to complete
31066    // has been completed, so it answers `Ok(())`.
31067    let controller = current_thread_controller("two-phase-reentrant-begin");
31068    drop(controller.backend());
31069
31070    assert!(
31071      !controller
31072        .begin_shutdown()
31073        .expect("phase 1 on an idle generation must be accepted"),
31074      "an idle generation has nothing for the host to wait for"
31075    );
31076    {
31077      let state = controller
31078        .state
31079        .lock()
31080        .unwrap_or_else(std::sync::PoisonError::into_inner);
31081      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
31082    }
31083    assert!(
31084      !controller
31085        .begin_shutdown()
31086        .expect("a second phase 1 must answer immediately"),
31087      "the second phase 1 must report the same verdict"
31088    );
31089
31090    controller
31091      .finish_shutdown()
31092      .expect("one phase 2 completes the single outstanding phase 1");
31093    {
31094      let state = controller
31095        .state
31096        .lock()
31097        .unwrap_or_else(std::sync::PoisonError::into_inner);
31098      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31099      assert!(state.draining.is_none());
31100    }
31101
31102    controller
31103      .finish_shutdown()
31104      .expect("a second phase 2 repeats the completed stop instead of erroring");
31105    {
31106      let state = controller
31107        .state
31108        .lock()
31109        .unwrap_or_else(std::sync::PoisonError::into_inner);
31110      assert!(
31111        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31112        "the repeated phase 2 must change nothing"
31113      );
31114    }
31115  }
31116
31117  #[test]
31118  fn current_thread_two_phase_is_idle_immediately() {
31119    // No cfg gate: this is the threadless `wasm32-wasip1` shape, where the
31120    // handshake must degrade to two cheap calls.
31121    let controller = current_thread_controller("two-phase-current-thread");
31122    drop(controller.backend());
31123    assert!(
31124      !controller.runtime_work_pending(),
31125      "an idle running generation has no pending work"
31126    );
31127
31128    assert!(
31129      !controller
31130        .begin_shutdown()
31131        .expect("phase 1 must be accepted"),
31132      "phase 1 on an idle generation must report nothing to wait for"
31133    );
31134    assert!(
31135      !controller.runtime_work_pending(),
31136      "the poll must stay false across the window"
31137    );
31138    controller.finish_shutdown().expect("phase 2 must complete");
31139    {
31140      let state = controller
31141        .state
31142        .lock()
31143        .unwrap_or_else(std::sync::PoisonError::into_inner);
31144      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31145    }
31146
31147    controller.start().expect("a stopped runtime must restart");
31148    drop(controller.backend());
31149    controller
31150      .shutdown()
31151      .expect("the single call must still work");
31152  }
31153
31154  #[test]
31155  fn finish_shutdown_without_begin_is_an_error() {
31156    let controller = current_thread_controller("two-phase-orphan-finish");
31157    drop(controller.backend());
31158    let error = controller
31159      .finish_shutdown()
31160      .expect_err("phase 2 without an outstanding phase 1 must be rejected");
31161    assert_eq!(
31162      error.to_string(),
31163      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
31164    );
31165    controller
31166      .shutdown()
31167      .expect("the rejected phase 2 must leave the runtime shuttable");
31168  }
31169
31170  #[test]
31171  fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
31172    // A phase 1 on an already stopped runtime settles with no backend. The
31173    // `start` that follows publishes a FRESH generation, which owes nothing
31174    // to a handshake opened against the previous one. If the settled handoff
31175    // survived that `start`, the next `shutdown` would short-circuit on it --
31176    // phase 1 answering from the stale handoff, phase 2 consuming it -- and
31177    // report success while this generation was still `Running`, still holding
31178    // its backend and still accepting work.
31179    let controller = current_thread_controller("two-phase-stale-settled");
31180    drop(controller.backend());
31181    controller
31182      .shutdown()
31183      .expect("the first shutdown must complete");
31184
31185    assert!(
31186      !controller
31187        .begin_shutdown()
31188        .expect("phase 1 on a stopped runtime must be accepted"),
31189      "a stopped runtime has no work for the host to wait for"
31190    );
31191    controller.start().expect("a stopped runtime must restart");
31192    {
31193      let state = controller
31194        .state
31195        .lock()
31196        .unwrap_or_else(std::sync::PoisonError::into_inner);
31197      assert!(
31198        state.draining.is_none(),
31199        "a fresh generation must not inherit the settled handoff"
31200      );
31201    }
31202    drop(controller.backend());
31203
31204    controller
31205      .shutdown()
31206      .expect("the second shutdown must stop the restarted generation");
31207    {
31208      let state = controller
31209        .state
31210        .lock()
31211        .unwrap_or_else(std::sync::PoisonError::into_inner);
31212      assert!(
31213        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31214        "the restarted generation must really be stopped, not reported stopped"
31215      );
31216      assert!(state.draining.is_none());
31217    }
31218    assert!(
31219      controller.current_backend.load().is_none(),
31220      "the stopped generation's backend must be gone"
31221    );
31222    assert!(
31223      !controller.runtime_work_pending(),
31224      "a stopped runtime has no pending work"
31225    );
31226    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31227      panic!("a submission after the second shutdown must be rejected");
31228    };
31229    assert_eq!(
31230      error.to_string(),
31231      "the async runtime is stopped; call start before submitting work"
31232    );
31233  }
31234
31235  #[test]
31236  fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
31237    // Renamed and flipped again with the idempotent model (it counted phases
31238    // before, and errored before that): a `start` between the phases DROPS the
31239    // settled handoff -- so it can never answer for the new generation -- but
31240    // the stop that was announced still happened, so EVERY phase 2 that
31241    // follows reports it, and none of them touches the restarted generation.
31242    // Without this, a single-call `shutdown` racing a `start` reports `Err`
31243    // for a shutdown it performed.
31244    let controller = current_thread_controller("two-phase-settled-idempotent");
31245    drop(controller.backend());
31246    controller
31247      .shutdown()
31248      .expect("the first shutdown must complete");
31249    assert!(
31250      !controller
31251        .begin_shutdown()
31252        .expect("phase 1 on a stopped runtime must be accepted")
31253    );
31254    controller.start().expect("a stopped runtime must restart");
31255    {
31256      let state = controller
31257        .state
31258        .lock()
31259        .unwrap_or_else(std::sync::PoisonError::into_inner);
31260      assert!(
31261        state.draining.is_none(),
31262        "the restart must drop the settled handoff"
31263      );
31264      assert!(
31265        state.phase_ever_opened,
31266        "the restart must not erase the fact that a stop was announced"
31267      );
31268    }
31269
31270    controller
31271      .finish_shutdown()
31272      .expect("the announced stop must be reported to the caller that opened it");
31273    controller
31274      .finish_shutdown()
31275      .expect("and to every phase 2 after it -- the call is idempotent");
31276    {
31277      let state = controller
31278        .state
31279        .lock()
31280        .unwrap_or_else(std::sync::PoisonError::into_inner);
31281      assert!(
31282        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31283        "neither phase 2 may stop the generation the restart published"
31284      );
31285    }
31286    drop(controller.backend());
31287    controller
31288      .shutdown()
31289      .expect("the no-op phase 2s must leave the runtime shuttable");
31290  }
31291
31292  #[test]
31293  fn shutdown_racing_a_start_still_reports_ok() {
31294    // The defect this whole rule exists for. The single call takes
31295    // the lifecycle lock twice; on an already stopped runtime phase 1 settles
31296    // with no backend and publishes no `Stopping` for a `start` to wait in,
31297    // so a `start` can land in the window and drop the parked handoff. Phase 2
31298    // then finds an empty slot and a `Running` lifecycle. `phase_ever_opened`
31299    // is what lets it answer `Ok(())` for the shutdown this call really did
31300    // perform, instead of "shutdown was not started".
31301    //
31302    // Deterministic, not stressed: the hook runs the `start` inside the window
31303    // itself. In the wild the same interleaving shows up as a flaky
31304    // `NO_PHASE_ERROR` from a `shutdown` racing a restart on another thread.
31305    let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
31306    drop(controller.backend());
31307    controller
31308      .shutdown()
31309      .expect("the first shutdown must complete");
31310
31311    let start_controller = Arc::clone(&controller);
31312    let started = Arc::new(AtomicBool::new(false));
31313    let hook_started = Arc::clone(&started);
31314    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31315      *slot.borrow_mut() = Some(Box::new(move || {
31316        start_controller
31317          .start()
31318          .expect("a stopped runtime must restart");
31319        hook_started.store(true, Ordering::SeqCst);
31320      }));
31321    });
31322    controller
31323      .shutdown()
31324      .expect("a shutdown must report the phase it opened, even across a racing start");
31325    assert!(
31326      started.load(Ordering::SeqCst),
31327      "the hook must have run the start inside the window"
31328    );
31329
31330    {
31331      let state = controller
31332        .state
31333        .lock()
31334        .unwrap_or_else(std::sync::PoisonError::into_inner);
31335      assert!(
31336        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31337        "the racing start wins the window: its generation is live, not stopped"
31338      );
31339      assert!(
31340        state.draining.is_none(),
31341        "the start must have dropped the settled handoff"
31342      );
31343    }
31344
31345    // The generation the start published is a real one: it accepts work, and
31346    // the next shutdown really stops it.
31347    let ran = Arc::new(AtomicBool::new(false));
31348    let ran_task = Arc::clone(&ran);
31349    controller
31350      .try_spawn_detached(async move {
31351        ran_task.store(true, Ordering::SeqCst);
31352      })
31353      .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
31354    let dispatch = controller_current_thread_dispatch(&controller);
31355    controller.drive_current_thread_dispatch(dispatch, false);
31356    assert!(ran.load(Ordering::SeqCst));
31357    controller
31358      .shutdown()
31359      .expect("the restarted generation must still shut down");
31360    let state = controller
31361      .state
31362      .lock()
31363      .unwrap_or_else(std::sync::PoisonError::into_inner);
31364    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31365    assert!(state.draining.is_none());
31366  }
31367
31368  // The runtime FLAVOR under test is CurrentThread -- that is where the race
31369  // this pins was 8.2% -- but the probe itself needs a second OS thread, so it
31370  // cannot run on the threadless `wasm32-wasip1` lane.
31371  #[cfg(napi_runtime_os_threads)]
31372  #[test]
31373  fn concurrent_split_finish_racing_a_single_call_shutdown() {
31374    // The interleaving the counting model got wrong (8.2% on CurrentThread in
31375    // a 100k-iteration probe, and the reason phases are no longer counted): a
31376    // single call opens the phase and a SECOND thread, using the documented
31377    // split pair, completes it. With a count, the split thread's finish spent
31378    // the opener's credit and the single call was then told "shutdown was not
31379    // started" -- for a stop it had already published.
31380    //
31381    // Deterministic, not stressed: the hook parks the single call between its
31382    // halves and runs the split pair to completion there.
31383    let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
31384    drop(controller.backend());
31385
31386    let split_controller = Arc::clone(&controller);
31387    let (tx, rx) = std::sync::mpsc::channel();
31388    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31389      *slot.borrow_mut() = Some(Box::new(move || {
31390        let split = std::thread::spawn(move || {
31391          let pending = split_controller
31392            .begin_shutdown()
31393            .expect("a second phase 1 must re-report the outstanding handoff");
31394          split_controller
31395            .finish_shutdown()
31396            .expect("an in-protocol phase 2 must complete the outstanding stop");
31397          pending
31398        });
31399        tx.send(
31400          split
31401            .join()
31402            .expect("the split shutdown thread must not panic"),
31403        )
31404        .expect("the test thread must still be waiting for the split result");
31405      }));
31406    });
31407
31408    controller
31409      .shutdown()
31410      .expect("the single call must report the stop it announced, whoever joined it");
31411    assert!(
31412      !rx
31413        .try_recv()
31414        .expect("the hook must have run the split pair inside the window"),
31415      "an idle generation has nothing for the host to wait for"
31416    );
31417
31418    let state = controller
31419      .state
31420      .lock()
31421      .unwrap_or_else(std::sync::PoisonError::into_inner);
31422    assert!(
31423      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31424      "the generation must really be stopped, not reported stopped"
31425    );
31426    assert!(state.draining.is_none());
31427  }
31428
31429  #[test]
31430  fn repeated_begin_start_pairs_leave_no_residue() {
31431    // The other half of what the count got wrong: `k x (begin, start)` parked
31432    // `k` credits that nothing ever drained, so the next `k` stray phase 2s
31433    // reported success off a leaked count. A sticky bool cannot accumulate --
31434    // the behaviour is the same at every `k`.
31435    const PAIRS: usize = 50;
31436    let controller = current_thread_controller("begin-start-residue");
31437    drop(controller.backend());
31438    for _ in 0..PAIRS {
31439      controller
31440        .shutdown()
31441        .expect("each shutdown must stop the live generation");
31442      assert!(
31443        !controller
31444          .begin_shutdown()
31445          .expect("phase 1 on a stopped runtime must be accepted"),
31446        "a stopped runtime has no work for the host to wait for"
31447      );
31448      controller
31449        .start()
31450        .expect("a zero-backend window must let a restart through");
31451      let state = controller
31452        .state
31453        .lock()
31454        .unwrap_or_else(std::sync::PoisonError::into_inner);
31455      assert!(
31456        state.draining.is_none(),
31457        "the restart must drop the settled handoff"
31458      );
31459      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
31460    }
31461
31462    controller
31463      .finish_shutdown()
31464      .expect("a phase 2 with nothing outstanding is a no-op Ok");
31465    {
31466      let state = controller
31467        .state
31468        .lock()
31469        .unwrap_or_else(std::sync::PoisonError::into_inner);
31470      assert!(
31471        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31472        "the no-op phase 2 must not stop the generation the last restart published"
31473      );
31474    }
31475
31476    controller
31477      .shutdown()
31478      .expect("a full shutdown after the begin/start pairs must still work");
31479    let state = controller
31480      .state
31481      .lock()
31482      .unwrap_or_else(std::sync::PoisonError::into_inner);
31483    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31484    assert!(state.draining.is_none());
31485  }
31486
31487  #[cfg(napi_runtime_os_threads)]
31488  #[test]
31489  fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31490    // The MultiThread half of the same rule: the worker join lives in phase
31491    // 2, so a stale settled handoff would let `shutdown` return while this
31492    // generation's workers were still alive.
31493    let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31494    drop(controller.backend());
31495    controller
31496      .shutdown()
31497      .expect("the first shutdown must complete");
31498    assert!(
31499      !controller
31500        .begin_shutdown()
31501        .expect("phase 1 on a stopped runtime must be accepted")
31502    );
31503    controller.start().expect("a stopped runtime must restart");
31504
31505    let backend = controller.backend();
31506    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31507      panic!("configured MultiThread backend must create a Rayon executor");
31508    };
31509    let workers = Arc::clone(&executor.worker_lifecycle);
31510    drop(backend);
31511    let handle = controller
31512      .try_spawn_blocking(|| 5usize)
31513      .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31514    assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31515
31516    controller
31517      .shutdown()
31518      .expect("the second shutdown must stop the restarted generation");
31519    assert_eq!(
31520      workers.remaining(),
31521      0,
31522      "phase 2 must join the restarted generation's workers"
31523    );
31524    {
31525      let state = controller
31526        .state
31527        .lock()
31528        .unwrap_or_else(std::sync::PoisonError::into_inner);
31529      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31530      assert!(state.draining.is_none());
31531    }
31532    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31533      panic!("a submission after the second shutdown must be rejected");
31534    };
31535    assert_eq!(
31536      error.to_string(),
31537      "the async runtime is stopped; call start before submitting work"
31538    );
31539  }
31540
31541  /// A rejected submission whose destructor is parked until `release` is sent.
31542  /// The counted window (`rejected_drops`) stays open for the whole of that
31543  /// `Drop`, which is the state phase 1 must refuse to wait on.
31544  #[cfg(napi_runtime_os_threads)]
31545  struct ParkedRejectedDropFuture {
31546    entered: std::sync::mpsc::Sender<()>,
31547    release: std::sync::mpsc::Receiver<()>,
31548  }
31549
31550  #[cfg(napi_runtime_os_threads)]
31551  impl Future for ParkedRejectedDropFuture {
31552    type Output = ();
31553
31554    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31555      Poll::Pending
31556    }
31557  }
31558
31559  #[cfg(napi_runtime_os_threads)]
31560  impl Drop for ParkedRejectedDropFuture {
31561    fn drop(&mut self) {
31562      self.entered.send(()).unwrap();
31563      self.release.recv().unwrap();
31564    }
31565  }
31566
31567  /// Reject one submission on a spawned thread and return once its destructor
31568  /// has been entered, so the caller runs with `rejected_drops != 0` held open
31569  /// by a thread that is not it.
31570  #[cfg(napi_runtime_os_threads)]
31571  fn park_a_rejected_destructor(
31572    controller: &Arc<RuntimeController>,
31573    fail_backend_creation: bool,
31574  ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31575    use std::sync::mpsc;
31576
31577    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31578    let (release_drop_tx, release_drop_rx) = mpsc::channel();
31579    let submit_controller = Arc::clone(controller);
31580    let submitter = std::thread::spawn(move || {
31581      if fail_backend_creation {
31582        FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31583      }
31584      let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31585        entered: drop_entered_tx,
31586        release: release_drop_rx,
31587      }));
31588      assert!(
31589        result.is_err(),
31590        "the submission must be rejected, not accepted"
31591      );
31592    });
31593    drop_entered_rx
31594      .recv_timeout(Duration::from_secs(2))
31595      .expect("the rejected future destructor must start");
31596    (submitter, release_drop_tx)
31597  }
31598
31599  /// Run phase 1 on a spawned thread and answer within `timeout`.
31600  ///
31601  /// A regression HANGS inside `begin_shutdown` instead of returning a wrong
31602  /// value, so the call cannot be made inline: the `recv_timeout` is what
31603  /// turns the hang into a failure.
31604  #[cfg(napi_runtime_os_threads)]
31605  fn begin_shutdown_within(
31606    controller: &Arc<RuntimeController>,
31607    what: &str,
31608    timeout: Duration,
31609  ) -> Result<bool, RuntimeConfigError> {
31610    use std::sync::mpsc;
31611
31612    let (tx, rx) = mpsc::channel();
31613    let begin_controller = Arc::clone(controller);
31614    let beginner = std::thread::spawn(move || {
31615      tx.send(begin_controller.begin_shutdown()).unwrap();
31616    });
31617    let answer = rx
31618      .recv_timeout(timeout)
31619      .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31620    join_within(what, beginner, Duration::from_secs(2));
31621    answer
31622  }
31623
31624  #[cfg(napi_runtime_os_threads)]
31625  #[test]
31626  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31627    // Phase 1 never waits -- including for a rejected submission whose
31628    // destructor is running on ANOTHER thread. That destructor is arbitrary
31629    // user `Drop` code; on the threaded WASI artifact the thread running it
31630    // may be waiting for the very host turn the phase-1 caller can only give
31631    // by returning, so waiting here is the deadlock the split API exists to
31632    // avoid. The wait belongs to phase 2, and the window must report itself
31633    // as pending work so the host keeps turning its loop.
31634    use std::sync::mpsc;
31635
31636    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31637    drop(controller.backend());
31638    controller
31639      .shutdown()
31640      .expect("the first shutdown must complete");
31641
31642    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31643
31644    assert!(
31645      begin_shutdown_within(
31646        &controller,
31647        "phase 1 on a stopped runtime with a rejected destructor outstanding",
31648        Duration::from_secs(2),
31649      )
31650      .expect("phase 1 on a stopped runtime must be accepted"),
31651      "the outstanding rejected destruction is a wait phase 2 still owes"
31652    );
31653    assert!(
31654      controller.runtime_work_pending(),
31655      "the poll must report the destruction, or the host stops turning the loop that frees it"
31656    );
31657    {
31658      let state = controller
31659        .state
31660        .lock()
31661        .unwrap_or_else(std::sync::PoisonError::into_inner);
31662      assert!(matches!(
31663        &state.lifecycle,
31664        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31665      ));
31666      assert!(matches!(
31667        &state.draining,
31668        Some(ShutdownDrain::ZeroBackend(
31669          ZeroBackendShutdownTarget::Stopped
31670        ))
31671      ));
31672    }
31673
31674    assert!(
31675      begin_shutdown_within(
31676        &controller,
31677        "a second phase 1 inside the zero-backend window",
31678        Duration::from_secs(2),
31679      )
31680      .expect("a second phase 1 must answer from the outstanding handoff"),
31681      "the second phase 1 must repeat the same verdict"
31682    );
31683
31684    let (finish_tx, finish_rx) = mpsc::channel();
31685    let finish_controller = Arc::clone(&controller);
31686    let finisher = std::thread::spawn(move || {
31687      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31688    });
31689    assert!(
31690      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31691      "phase 2 owns the wait phase 1 refused"
31692    );
31693
31694    release_drop_tx.send(()).unwrap();
31695    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31696    finish_rx
31697      .recv_timeout(Duration::from_secs(2))
31698      .expect("phase 2 must return once the destructor retires")
31699      .expect("phase 2 must complete the announced stop");
31700    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31701
31702    {
31703      let state = controller
31704        .state
31705        .lock()
31706        .unwrap_or_else(std::sync::PoisonError::into_inner);
31707      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31708      assert!(state.draining.is_none());
31709    }
31710  }
31711
31712  #[cfg(napi_runtime_os_threads)]
31713  #[test]
31714  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31715    // The `Initial` twin of the test above: a backend creation that fails
31716    // leaves the lifecycle `Initial` with the destructor of the rejected
31717    // submission still running, and phase 1 must publish and return there
31718    // too. The target it parks is `StoppedBeforeFirstUse`, and phase 2 is
31719    // what publishes it.
31720    use std::sync::mpsc;
31721
31722    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31723    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31724
31725    assert!(
31726      begin_shutdown_within(
31727        &controller,
31728        "phase 1 on an initial runtime with a rejected destructor outstanding",
31729        Duration::from_secs(2),
31730      )
31731      .expect("phase 1 on an initial runtime must be accepted"),
31732      "the outstanding rejected destruction is a wait phase 2 still owes"
31733    );
31734    assert!(controller.runtime_work_pending());
31735    {
31736      let state = controller
31737        .state
31738        .lock()
31739        .unwrap_or_else(std::sync::PoisonError::into_inner);
31740      assert!(matches!(
31741        &state.lifecycle,
31742        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31743      ));
31744      assert!(matches!(
31745        &state.draining,
31746        Some(ShutdownDrain::ZeroBackend(
31747          ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31748        ))
31749      ));
31750    }
31751    assert!(
31752      controller.try_spawn_detached(async {}).is_err(),
31753      "the announced stop must keep first-generation admission closed"
31754    );
31755
31756    let (finish_tx, finish_rx) = mpsc::channel();
31757    let finish_controller = Arc::clone(&controller);
31758    let finisher = std::thread::spawn(move || {
31759      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31760    });
31761    assert!(
31762      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31763      "phase 2 owns the wait phase 1 refused"
31764    );
31765
31766    release_drop_tx.send(()).unwrap();
31767    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31768    finish_rx
31769      .recv_timeout(Duration::from_secs(2))
31770      .expect("phase 2 must return once the destructor retires")
31771      .expect("phase 2 must complete the announced stop");
31772    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31773
31774    {
31775      let state = controller
31776        .state
31777        .lock()
31778        .unwrap_or_else(std::sync::PoisonError::into_inner);
31779      assert!(
31780        matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31781        "a generation that never had a backend must stop before first use"
31782      );
31783      assert!(state.draining.is_none());
31784    }
31785  }
31786
31787  #[cfg(napi_runtime_os_threads)]
31788  #[test]
31789  fn start_waits_out_a_zero_backend_phase_one() {
31790    // Phase 1 no longer waits, so the `StoppingWithoutBackend` window now
31791    // lasts until phase 2 publishes instead of only for the length of phase
31792    // 1's internal wait. `start` must wait that whole window out: the
31793    // destructor still runs under the RETIRED generation's guard, so a
31794    // restart admitted before phase 2 would publish a fresh backend on top of
31795    // it. The existing coverage only drives the single-call `shutdown`; this
31796    // pins the invariant on the split API.
31797    use std::sync::mpsc;
31798
31799    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31800    let old_generation = controller.backend().generation();
31801    controller
31802      .shutdown()
31803      .expect("the first shutdown must complete");
31804
31805    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31806    assert!(
31807      begin_shutdown_within(
31808        &controller,
31809        "phase 1 before the restart",
31810        Duration::from_secs(2),
31811      )
31812      .expect("phase 1 on a stopped runtime must be accepted")
31813    );
31814
31815    let (start_tx, start_rx) = mpsc::channel();
31816    let start_controller = Arc::clone(&controller);
31817    let starter = std::thread::spawn(move || {
31818      start_tx.send(start_controller.start()).unwrap();
31819    });
31820    assert!(
31821      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31822      "a restart must wait for the announced zero-backend stop to be published"
31823    );
31824
31825    let (finish_tx, finish_rx) = mpsc::channel();
31826    let finish_controller = Arc::clone(&controller);
31827    let finisher = std::thread::spawn(move || {
31828      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31829    });
31830    assert!(
31831      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31832      "phase 2 must still be waiting on the destructor"
31833    );
31834
31835    release_drop_tx.send(()).unwrap();
31836    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31837    finish_rx
31838      .recv_timeout(Duration::from_secs(2))
31839      .expect("phase 2 must return once the destructor retires")
31840      .expect("phase 2 must complete the announced stop");
31841    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31842    start_rx
31843      .recv_timeout(Duration::from_secs(2))
31844      .expect("the restart must be admitted once phase 2 has published")
31845      .expect("a stopped runtime must restart after phase 2");
31846    join_within("the restart thread", starter, Duration::from_secs(2));
31847
31848    let new_generation = controller.backend().generation();
31849    assert_ne!(
31850      new_generation, old_generation,
31851      "the restart must publish a fresh generation"
31852    );
31853    controller
31854      .shutdown()
31855      .expect("the restarted generation must still shut down");
31856  }
31857
31858  #[cfg(napi_runtime_os_threads)]
31859  #[test]
31860  fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31861    // The order the production WASI loader actually runs, which the three
31862    // tests above never do: `begin` -> turn the host loop, polling
31863    // `runtime_work_pending` until it answers `false` -> only THEN call
31864    // `finish`. They park phase 2 on a thread BEFORE releasing the
31865    // destructor, so they prove the wait; this proves the way out of it. The
31866    // poll must stop reporting work once the rejected destructor retires, and
31867    // the `finish` entered after that must not block -- a loader that polled
31868    // to zero and then found phase 2 parked would wedge the disposal on the
31869    // JavaScript thread, which is the whole failure the split API avoids.
31870    use std::sync::mpsc;
31871
31872    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31873    drop(controller.backend());
31874    controller
31875      .shutdown()
31876      .expect("the first shutdown must complete");
31877
31878    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31879    assert!(
31880      begin_shutdown_within(
31881        &controller,
31882        "phase 1 before the loader's poll loop",
31883        Duration::from_secs(2),
31884      )
31885      .expect("phase 1 on a stopped runtime must be accepted"),
31886      "the outstanding rejected destruction is a wait phase 2 still owes"
31887    );
31888    assert!(
31889      controller.runtime_work_pending(),
31890      "the loader gates its poll loop on this, so it must report the destruction first"
31891    );
31892
31893    release_drop_tx.send(()).unwrap();
31894    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31895
31896    // The host's turns, bounded: a handoff that never clears fails here
31897    // instead of spinning for the length of the CI step.
31898    let deadline = std::time::Instant::now() + Duration::from_secs(5);
31899    while controller.runtime_work_pending() {
31900      assert!(
31901        std::time::Instant::now() < deadline,
31902        "the poll must stop reporting work once the rejected destructor retires"
31903      );
31904      std::thread::sleep(Duration::from_millis(1));
31905    }
31906
31907    let (finish_tx, finish_rx) = mpsc::channel();
31908    let finish_controller = Arc::clone(&controller);
31909    let finisher = std::thread::spawn(move || {
31910      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31911    });
31912    finish_rx
31913      .recv_timeout(Duration::from_millis(200))
31914      .expect("phase 2 entered after the poll cleared must not block")
31915      .expect("phase 2 must complete the announced stop");
31916    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31917
31918    {
31919      let state = controller
31920        .state
31921        .lock()
31922        .unwrap_or_else(std::sync::PoisonError::into_inner);
31923      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31924      assert!(state.draining.is_none());
31925    }
31926  }
31927
31928  /// Poll `runtime_work_pending` between the phases of an idle generation's
31929  /// shutdown while `hold` keeps a lock the poll reads under held for good,
31930  /// and require the answer "pending" -- an exact read would say idle.
31931  ///
31932  /// `hold` forgets the guard: that is the pool thread that trapped while
31933  /// holding the lock on `wasm32-wasip1-threads`, where a trap unwinds
31934  /// nothing, so the lock never comes back. The loader polls from the
31935  /// JavaScript thread, so the poll runs on another thread here too, and a
31936  /// regression HANGS in it: the `recv_timeout` turns that into a failure.
31937  /// Nothing can take the lock again, so the runtime is leaked, not shut down.
31938  #[cfg(napi_runtime_os_threads)]
31939  fn assert_poll_reports_pending_while_held(
31940    controller: RuntimeController,
31941    what: &str,
31942    hold: impl FnOnce(&RuntimeController, &RuntimeBackend),
31943  ) {
31944    use std::sync::mpsc;
31945
31946    let controller = Arc::new(controller);
31947    let backend = controller.backend();
31948    assert!(
31949      !controller
31950        .begin_shutdown()
31951        .expect("phase 1 on an idle generation must be accepted"),
31952      "an idle generation has nothing for the host to wait for"
31953    );
31954    assert!(
31955      !controller.runtime_work_pending(),
31956      "with every lock free the poll must read the idle generation"
31957    );
31958
31959    hold(&controller, &backend);
31960    let (pending_tx, pending_rx) = mpsc::channel();
31961    let poll_controller = Arc::clone(&controller);
31962    let poller = std::thread::spawn(move || {
31963      pending_tx
31964        .send(poll_controller.runtime_work_pending())
31965        .unwrap();
31966    });
31967    let pending = pending_rx
31968      .recv_timeout(Duration::from_secs(5))
31969      .unwrap_or_else(|_| panic!("the work-pending poll must not wait for {what}"));
31970    join_within(what, poller, Duration::from_secs(2));
31971    assert!(
31972      pending,
31973      "a poll that cannot read {what} must answer pending, not idle"
31974    );
31975
31976    std::mem::forget(backend);
31977    std::mem::forget(controller);
31978  }
31979
31980  #[cfg(napi_runtime_os_threads)]
31981  fn current_thread_executor(backend: &RuntimeBackend) -> &CurrentThreadExecutor {
31982    match &backend.executor {
31983      RuntimeExecutor::CurrentThread(executor) => executor,
31984      RuntimeExecutor::MultiThread(_) => panic!("expected a CurrentThread backend"),
31985    }
31986  }
31987
31988  #[cfg(napi_runtime_os_threads)]
31989  #[test]
31990  fn work_pending_poll_does_not_wait_for_a_held_controller_lock() {
31991    for controller in [
31992      current_thread_controller("poll-held-controller-lock-ct"),
31993      multi_thread_controller("poll-held-controller-lock-mt", 2, 1),
31994    ] {
31995      assert_poll_reports_pending_while_held(
31996        controller,
31997        "the controller state lock",
31998        |controller, _| {
31999          std::mem::forget(controller.state.lock().unwrap());
32000        },
32001      );
32002    }
32003  }
32004
32005  #[cfg(napi_runtime_os_threads)]
32006  #[test]
32007  fn work_pending_poll_does_not_wait_for_a_held_generation_lock() {
32008    for controller in [
32009      current_thread_controller("poll-held-generation-lock-ct"),
32010      multi_thread_controller("poll-held-generation-lock-mt", 2, 1),
32011    ] {
32012      assert_poll_reports_pending_while_held(
32013        controller,
32014        "the generation state lock",
32015        |_, backend| {
32016          std::mem::forget(backend.work.state.lock().unwrap());
32017        },
32018      );
32019    }
32020  }
32021
32022  #[cfg(napi_runtime_os_threads)]
32023  #[test]
32024  fn current_thread_work_pending_poll_does_not_wait_for_a_held_queue_lock() {
32025    assert_poll_reports_pending_while_held(
32026      current_thread_controller("poll-held-queue-lock"),
32027      "the CurrentThread queue lock",
32028      |_, backend| {
32029        std::mem::forget(current_thread_executor(backend).queue.lock().unwrap());
32030      },
32031    );
32032  }
32033
32034  #[cfg(napi_runtime_os_threads)]
32035  #[test]
32036  fn current_thread_work_pending_poll_does_not_wait_for_a_held_scheduler_lock() {
32037    assert_poll_reports_pending_while_held(
32038      current_thread_controller("poll-held-scheduler-lock"),
32039      "the CurrentThread scheduler lock",
32040      |_, backend| {
32041        std::mem::forget(
32042          current_thread_executor(backend)
32043            .scheduler_idle_lock
32044            .lock()
32045            .unwrap(),
32046        );
32047      },
32048    );
32049  }
32050
32051  /// Run a shutdown phase on a thread standing in for the host's JavaScript
32052  /// thread, after the caller left the runtime the way a pool thread that
32053  /// trapped on `wasm32-wasip1-threads` leaves it: a lock held for good (a
32054  /// forgotten guard), or work that never retires. The phase must keep
32055  /// waiting while no thread has crashed, and must give up once the host's
32056  /// crash flag is up -- a trap on wasm, `GaveUpAfterThreadCrash` here. A
32057  /// regression HANGS in the phase: the `recv_timeout` turns that into a
32058  /// failure. Nothing can release what the caller left behind, so the runtime
32059  /// is leaked, not shut down.
32060  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32061  fn assert_shutdown_phase_gives_up_after_a_thread_crash(
32062    what: &str,
32063    controller: Arc<RuntimeController>,
32064    phase: impl FnOnce(&RuntimeController) + Send + 'static,
32065  ) {
32066    use std::sync::mpsc;
32067
32068    let crashed = Arc::new(AtomicBool::new(false));
32069    let (outcome_tx, outcome_rx) = mpsc::channel();
32070    let js_thread = {
32071      let controller = Arc::clone(&controller);
32072      let crashed = Arc::clone(&crashed);
32073      std::thread::spawn(move || {
32074        crate::sync::watch_crash_flag_for_test(crashed);
32075        let outcome = match catch_unwind(AssertUnwindSafe(|| phase(&controller))) {
32076          Ok(()) => "returned",
32077          Err(payload) if payload.is::<crate::sync::GaveUpAfterThreadCrash>() => "gave up",
32078          Err(_) => "panicked",
32079        };
32080        outcome_tx.send(outcome).unwrap();
32081      })
32082    };
32083    assert!(
32084      outcome_rx.recv_timeout(Duration::from_millis(200)).is_err(),
32085      "{what}: with no thread crashed, the phase must keep waiting"
32086    );
32087    crashed.store(true, Ordering::SeqCst);
32088    let outcome = outcome_rx
32089      .recv_timeout(Duration::from_secs(5))
32090      .unwrap_or_else(|_| panic!("{what}: the phase must stop waiting once a thread crashed"));
32091    join_within(what, js_thread, Duration::from_secs(2));
32092    assert_eq!(outcome, "gave up", "{what}");
32093    std::mem::forget(controller);
32094  }
32095
32096  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32097  #[test]
32098  fn begin_shutdown_gives_up_on_a_held_lock_after_a_thread_crash() {
32099    type Hold = fn(&RuntimeController, &RuntimeBackend);
32100    let holds: [(&str, Hold); 2] = [
32101      ("the controller state lock", |controller, _| {
32102        std::mem::forget(controller.state.lock().unwrap());
32103      }),
32104      // `close_and_abort` takes it first thing.
32105      ("the generation state lock", |_, backend| {
32106        std::mem::forget(backend.work.state.lock().unwrap());
32107      }),
32108    ];
32109    for (what, hold) in holds {
32110      for controller in [
32111        current_thread_controller("crash-begin-ct"),
32112        multi_thread_controller("crash-begin-mt", 2, 1),
32113      ] {
32114        let controller = Arc::new(controller);
32115        let backend = controller.backend();
32116        hold(&controller, &backend);
32117        assert_shutdown_phase_gives_up_after_a_thread_crash(
32118          &format!("begin_shutdown on {what}"),
32119          controller,
32120          |controller| {
32121            let _ = controller.begin_shutdown();
32122          },
32123        );
32124        std::mem::forget(backend);
32125      }
32126    }
32127  }
32128
32129  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32130  #[test]
32131  fn finish_shutdown_gives_up_after_a_thread_crash() {
32132    for controller in [
32133      current_thread_controller("crash-finish-work-ct"),
32134      multi_thread_controller("crash-finish-work-mt", 2, 1),
32135    ] {
32136      // A task on the thread that trapped: its guard never drops, so the
32137      // generation never goes idle and `wait_until_idle` never wakes.
32138      let controller = Arc::new(controller);
32139      let backend = controller.backend();
32140      let work = backend
32141        .work
32142        .try_register_work()
32143        .expect("an open generation must accept work");
32144      assert!(
32145        controller
32146          .begin_shutdown()
32147          .expect("phase 1 must be accepted"),
32148        "phase 1 must report the registered work"
32149      );
32150      std::mem::forget(work);
32151      assert_shutdown_phase_gives_up_after_a_thread_crash(
32152        "finish_shutdown on work that never retires",
32153        controller,
32154        |controller| {
32155          let _ = controller.finish_shutdown();
32156        },
32157      );
32158      std::mem::forget(backend);
32159    }
32160    for controller in [
32161      current_thread_controller("crash-finish-lock-ct"),
32162      multi_thread_controller("crash-finish-lock-mt", 2, 1),
32163    ] {
32164      let controller = Arc::new(controller);
32165      let backend = controller.backend();
32166      assert!(
32167        !controller
32168          .begin_shutdown()
32169          .expect("phase 1 on an idle generation must be accepted"),
32170        "an idle generation has nothing for the host to wait for"
32171      );
32172      std::mem::forget(backend.work.state.lock().unwrap());
32173      assert_shutdown_phase_gives_up_after_a_thread_crash(
32174        "finish_shutdown on the generation state lock",
32175        controller,
32176        |controller| {
32177          let _ = controller.finish_shutdown();
32178        },
32179      );
32180      std::mem::forget(backend);
32181    }
32182  }
32183
32184  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
32185  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
32186  // global `RUNTIME`) and run the workload on a child thread guarded by a
32187  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
32188  // wedging the whole suite.
32189
32190  #[cfg(napi_runtime_os_threads)]
32191  #[test]
32192  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
32193    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
32194    // blocking job while `active_blocking >= max_blocking`. The guard is
32195    // exercised deterministically (no sleeps, no incidental worker overlap):
32196    //
32197    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
32198    //      occupies a counted slot (`take_blocking` increments `active_blocking`
32199    //      BEFORE running the closure), signals `held`, then parks on a release
32200    //      barrier -- so all slots are provably occupied before we proceed.
32201    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
32202    //      With the cap intact `take_blocking` returns `None` for them, so the
32203    //      idle workers cannot pop them: no extra may signal `extra_started`.
32204    //   3. We release the holders, then assert EVERY job (holders + extras)
32205    //      completes with the right result and the recorded peak never exceeded
32206    //      the cap.
32207    //
32208    // Decisive property: if the `active >= self.max_blocking` guard were removed,
32209    // the two idle workers would pop the extras while both holders are still
32210    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
32211    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
32212    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
32213    // can never self-deadlock on the over-cap extras.
32214    use std::sync::{Barrier, mpsc};
32215    use std::time::Duration;
32216
32217    let (done_tx, done_rx) = mpsc::channel();
32218    let peak_out = Arc::new(Mutex::new(None));
32219    let peak_slot = Arc::clone(&peak_out);
32220    let runner = std::thread::spawn(move || {
32221      let metrics = Arc::new(RuntimeMetrics::default());
32222      let options = RuntimeOptions {
32223        flavor: RuntimeFlavor::MultiThread,
32224        worker_threads: 4,
32225        max_blocking_tasks: 2,
32226        thread_name_prefix: "rd9-blk-cap".to_string(),
32227        park_deadline: None,
32228        drain_linger: DEFAULT_DRAIN_LINGER,
32229      };
32230      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32231
32232      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
32233      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
32234
32235      // Holders: signal once they hold a counted slot, then park until released.
32236      let (held_tx, held_rx) = mpsc::channel();
32237      let release = Arc::new(Barrier::new(holder_count + 1));
32238      let mut holder_handles = Vec::new();
32239      for _ in 0..holder_count {
32240        let held_tx = held_tx.clone();
32241        let release = Arc::clone(&release);
32242        holder_handles.push(executor.schedule_blocking(move || {
32243          held_tx.send(()).unwrap();
32244          release.wait();
32245          0usize
32246        }));
32247      }
32248      // Wait until every slot is provably occupied (cap fully saturated).
32249      for _ in 0..holder_count {
32250        held_rx.recv().unwrap();
32251      }
32252
32253      // Queue the beyond-cap extras. Each signals the instant it begins running.
32254      let (extra_started_tx, extra_started_rx) = mpsc::channel();
32255      let mut extra_handles = Vec::new();
32256      for i in 0..extra_count {
32257        let extra_started_tx = extra_started_tx.clone();
32258        extra_handles.push(executor.schedule_blocking(move || {
32259          extra_started_tx.send(i).unwrap();
32260          i * 2
32261        }));
32262      }
32263
32264      // With the cap intact, `take_blocking` returns `None` while both slots are
32265      // held, so no extra can have started running yet.
32266      assert!(
32267        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
32268        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
32269      );
32270
32271      // Release the holders so the queued extras can run within the cap.
32272      release.wait();
32273
32274      for handle in holder_handles {
32275        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
32276      }
32277      // Each handle owns its own oneshot receiver, so awaiting in order returns
32278      // each extra's own result regardless of the order the pool ran them.
32279      for (i, handle) in extra_handles.into_iter().enumerate() {
32280        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
32281      }
32282
32283      // `blocking_tasks_started` is incremented at job entry, so once every result
32284      // is in, all holder + extra starts are recorded.
32285      assert_eq!(
32286        metrics.blocking_tasks_started.load(Ordering::Relaxed),
32287        (holder_count + extra_count) as u64
32288      );
32289
32290      *peak_slot
32291        .lock()
32292        .unwrap_or_else(std::sync::PoisonError::into_inner) =
32293        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
32294      let _ = done_tx.send(());
32295    });
32296
32297    match done_rx.recv_timeout(Duration::from_secs(10)) {
32298      Ok(()) => runner.join().unwrap(),
32299      Err(error) => panic!("blocking-cap test did not complete ({error})"),
32300    }
32301    let peak = peak_out
32302      .lock()
32303      .unwrap_or_else(std::sync::PoisonError::into_inner)
32304      .expect("peak captured");
32305    assert!(
32306      peak >= 1,
32307      "at least one blocking job must have run (peak {peak})"
32308    );
32309    assert!(
32310      peak <= 2,
32311      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
32312    );
32313  }
32314
32315  #[cfg(napi_runtime_os_threads)]
32316  #[test]
32317  fn multi_thread_drain_budget_completes_large_runnable_batch() {
32318    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
32319    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
32320    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
32321    // still complete every task, with the runnable counters settling back to 0.
32322    use std::sync::mpsc;
32323    use std::time::{Duration, Instant};
32324
32325    let (done_tx, done_rx) = mpsc::channel();
32326    let runner = std::thread::spawn(move || {
32327      let metrics = Arc::new(RuntimeMetrics::default());
32328      let options = RuntimeOptions {
32329        flavor: RuntimeFlavor::MultiThread,
32330        worker_threads: 2,
32331        max_blocking_tasks: 2,
32332        thread_name_prefix: "rd9-budget".to_string(),
32333        park_deadline: None,
32334        drain_linger: DEFAULT_DRAIN_LINGER,
32335      };
32336      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32337
32338      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
32339      let completed = Arc::new(AtomicU64::new(0));
32340      let mut tasks = Vec::new();
32341      for _ in 0..task_count {
32342        let completed = Arc::clone(&completed);
32343        let scheduler = Arc::clone(&executor);
32344        let (runnable, task) = async_task::spawn(
32345          async move {
32346            completed.fetch_add(1, Ordering::Relaxed);
32347          },
32348          move |r| scheduler.schedule(r),
32349        );
32350        executor.schedule(runnable);
32351        tasks.push(task);
32352      }
32353      for task in tasks {
32354        futures::executor::block_on(task);
32355      }
32356
32357      // Every immediately-ready future is polled exactly once; `runnable_polls`
32358      // (incremented at poll entry, before completion) is therefore exact here.
32359      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
32360      assert_eq!(
32361        metrics.runnable_polls.load(Ordering::Relaxed),
32362        task_count as u64
32363      );
32364
32365      // The active-runnable guard for the LAST task drops just after its Task
32366      // resolves (i.e. just after `block_on` returns), so give the counters a
32367      // brief, bounded moment to settle to 0 rather than asserting them racily.
32368      let deadline = Instant::now() + Duration::from_secs(5);
32369      loop {
32370        let active = metrics.active_runnables.load(Ordering::Relaxed);
32371        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
32372        if active == 0 && queued == 0 {
32373          break;
32374        }
32375        assert!(
32376          Instant::now() < deadline,
32377          "runnable counters did not settle to 0: active={active} queued={queued}"
32378        );
32379        std::thread::yield_now();
32380      }
32381      let _ = done_tx.send(());
32382    });
32383
32384    match done_rx.recv_timeout(Duration::from_secs(15)) {
32385      Ok(()) => runner.join().unwrap(),
32386      Err(error) => panic!("drain-budget test did not complete ({error})"),
32387    }
32388  }
32389
32390  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32391  #[test]
32392  fn multi_thread_spawned_panic_surfaces_as_join_error() {
32393    // A panicking spawned future must surface as `Err(JoinError)` carrying the
32394    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
32395    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
32396    // drive the resulting `JoinHandle` on this executor -- without touching the
32397    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
32398    // `panic!("x")` message round-trips as `"x"`.
32399    use std::sync::mpsc;
32400    use std::time::Duration;
32401
32402    let (done_tx, done_rx) = mpsc::channel();
32403    let runner = std::thread::spawn(move || {
32404      let metrics = Arc::new(RuntimeMetrics::default());
32405      let options = RuntimeOptions {
32406        flavor: RuntimeFlavor::MultiThread,
32407        worker_threads: 2,
32408        max_blocking_tasks: 2,
32409        thread_name_prefix: "rd9-panic".to_string(),
32410        park_deadline: None,
32411        drain_linger: DEFAULT_DRAIN_LINGER,
32412      };
32413      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
32414
32415      let scheduler = Arc::clone(&executor);
32416      let wrapped = async {
32417        AssertUnwindSafe(async { panic!("x") })
32418          .catch_unwind()
32419          .await
32420          .map(|()| ContainedTaskOutput::new((), u64::MAX))
32421          .map_err(panic_payload_to_join_error)
32422      };
32423      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
32424      executor.schedule(runnable);
32425
32426      let result: Result<(), JoinError> =
32427        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
32428          task: task.fallible(),
32429          awaiter: None,
32430          dependency: Arc::new(TaskDependency::default()),
32431        }));
32432      let _ = done_tx.send(result);
32433    });
32434
32435    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
32436      Ok(result) => {
32437        runner.join().unwrap();
32438        result
32439      }
32440      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
32441    };
32442    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
32443    assert_eq!(error.to_string(), "x");
32444  }
32445
32446  #[cfg(napi_runtime_os_threads)]
32447  #[test]
32448  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
32449    use std::sync::mpsc;
32450
32451    let metrics = Arc::new(RuntimeMetrics::default());
32452    metrics.tasks_spawned.store(1, Ordering::Relaxed);
32453    metrics.tasks_completed.store(2, Ordering::Relaxed);
32454    metrics.tasks_panicked.store(3, Ordering::Relaxed);
32455    metrics.runnable_schedules.store(4, Ordering::Relaxed);
32456    metrics.runnable_polls.store(5, Ordering::Relaxed);
32457    metrics
32458      .blocking_tasks_scheduled
32459      .store(14, Ordering::Relaxed);
32460    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
32461    metrics
32462      .blocking_tasks_completed
32463      .store(11, Ordering::Relaxed);
32464    metrics.runnable_scheduled();
32465    let (guards_live_tx, guards_live_rx) = mpsc::channel();
32466    let (release_tx, release_rx) = mpsc::channel();
32467    let guard_metrics = Arc::clone(&metrics);
32468    let guard_thread = std::thread::spawn(move || {
32469      let runnable_guard = guard_metrics.runnable_started();
32470      let blocking_guard = guard_metrics.blocking_started(true);
32471      guards_live_tx.send(()).unwrap();
32472      release_rx.recv().unwrap();
32473      drop(runnable_guard);
32474      drop(blocking_guard);
32475    });
32476    guards_live_rx.recv().unwrap();
32477    let fingerprint_before_reset = metrics.progress_fingerprint();
32478
32479    metrics.reset();
32480
32481    assert!(
32482      metrics.progress_fingerprint() != fingerprint_before_reset,
32483      "a reset must advance the deadlock detector generation even if counters later repeat"
32484    );
32485    assert_eq!(
32486      metrics.tasks_spawned.load(Ordering::Relaxed),
32487      0,
32488      "tasks_spawned"
32489    );
32490    assert_eq!(
32491      metrics.tasks_completed.load(Ordering::Relaxed),
32492      0,
32493      "tasks_completed"
32494    );
32495    assert_eq!(
32496      metrics.tasks_panicked.load(Ordering::Relaxed),
32497      0,
32498      "tasks_panicked"
32499    );
32500    assert_eq!(
32501      metrics.runnable_schedules.load(Ordering::Relaxed),
32502      0,
32503      "runnable_schedules"
32504    );
32505    assert_eq!(
32506      metrics.runnable_polls.load(Ordering::Relaxed),
32507      0,
32508      "runnable_polls"
32509    );
32510    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
32511    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
32512    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
32513    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
32514    assert_eq!(
32515      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
32516      0,
32517      "blocking_tasks_scheduled"
32518    );
32519    assert_eq!(
32520      metrics.blocking_tasks_started.load(Ordering::Relaxed),
32521      0,
32522      "blocking_tasks_started"
32523    );
32524    assert_eq!(
32525      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
32526      0,
32527      "blocking_tasks_completed"
32528    );
32529    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
32530    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
32531
32532    release_tx.send(()).unwrap();
32533    guard_thread.join().unwrap();
32534    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
32535    assert_eq!(
32536      metrics.active_blocking_tasks.load(Ordering::Relaxed),
32537      0,
32538      "a live blocking guard completed after reset without unsigned underflow"
32539    );
32540    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
32541  }
32542
32543  #[cfg(napi_runtime_os_threads)]
32544  #[test]
32545  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
32546    use std::sync::mpsc;
32547
32548    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
32549      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
32550        *slot.borrow_mut() = Some(Box::new(move || {
32551          entered.send(()).unwrap();
32552          release.recv().unwrap();
32553        }));
32554      });
32555    }
32556
32557    let options = RuntimeOptions {
32558      flavor: RuntimeFlavor::CurrentThread,
32559      worker_threads: 1,
32560      max_blocking_tasks: 1,
32561      thread_name_prefix: "metrics-snapshot".to_string(),
32562      park_deadline: None,
32563      drain_linger: DEFAULT_DRAIN_LINGER,
32564    };
32565
32566    let queued_metrics = Arc::new(RuntimeMetrics::default());
32567    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
32568    let (queued_release_tx, queued_release_rx) = mpsc::channel();
32569    let queued_writer_metrics = Arc::clone(&queued_metrics);
32570    let queued_writer = std::thread::spawn(move || {
32571      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
32572      queued_writer_metrics.runnable_scheduled();
32573    });
32574    queued_entered_rx.recv().unwrap();
32575    assert_eq!(
32576      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
32577      0
32578    );
32579    let snapshot = queued_metrics.snapshot(&options);
32580    assert_eq!(snapshot.queued_runnables, 1);
32581    assert_eq!(snapshot.max_queued_runnables, 1);
32582    queued_release_tx.send(()).unwrap();
32583    queued_writer.join().unwrap();
32584    queued_metrics.runnable_cancelled();
32585
32586    let active_metrics = Arc::new(RuntimeMetrics::default());
32587    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
32588    let (active_entered_tx, active_entered_rx) = mpsc::channel();
32589    let (active_release_tx, active_release_rx) = mpsc::channel();
32590    let active_writer_metrics = Arc::clone(&active_metrics);
32591    let active_writer = std::thread::spawn(move || {
32592      pause_next_high_water_update(active_entered_tx, active_release_rx);
32593      let _active = active_writer_metrics.runnable_started();
32594    });
32595    active_entered_rx.recv().unwrap();
32596    assert_eq!(
32597      active_metrics.max_active_runnables.load(Ordering::Relaxed),
32598      0
32599    );
32600    let snapshot = active_metrics.snapshot(&options);
32601    assert_eq!(snapshot.active_runnables, 1);
32602    assert_eq!(snapshot.max_active_runnables, 1);
32603    active_release_tx.send(()).unwrap();
32604    active_writer.join().unwrap();
32605
32606    let blocking_metrics = Arc::new(RuntimeMetrics::default());
32607    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
32608    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
32609    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
32610    let blocking_writer = std::thread::spawn(move || {
32611      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
32612      let _active = blocking_writer_metrics.blocking_started(true);
32613    });
32614    blocking_entered_rx.recv().unwrap();
32615    assert_eq!(
32616      blocking_metrics
32617        .max_active_blocking_tasks
32618        .load(Ordering::Relaxed),
32619      0
32620    );
32621    let snapshot = blocking_metrics.snapshot(&options);
32622    assert_eq!(snapshot.active_blocking_tasks, 1);
32623    assert_eq!(snapshot.max_active_blocking_tasks, 1);
32624    blocking_release_tx.send(()).unwrap();
32625    blocking_writer.join().unwrap();
32626  }
32627
32628  #[cfg(panic = "unwind")]
32629  #[test]
32630  fn runtime_metrics_reset_fails_before_generation_wraparound() {
32631    let metrics = RuntimeMetrics::default();
32632    metrics
32633      .reset_generation
32634      .store(u64::MAX - 1, Ordering::SeqCst);
32635    metrics.tasks_spawned.store(7, Ordering::Relaxed);
32636
32637    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
32638    assert!(exhausted.is_err());
32639    assert!(!metrics.reset_lock.is_poisoned());
32640    assert_eq!(
32641      metrics.reset_generation.load(Ordering::SeqCst),
32642      u64::MAX - 1
32643    );
32644    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
32645  }
32646
32647  // ---- Self-detecting block_on deadlocks -------------------------------------
32648  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
32649  // park decision with no pending wake panics with the typed diagnostic; (2) a
32650  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
32651  // lives at the PARK DECISION, not at Pending-return; (3) an
32652  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
32653  // progress RESETS the deadline instead of firing; (5) the
32654  // foreign/napi whole-build park is excluded from the deadline entirely.
32655
32656  /// A future that never completes and never wakes anyone: once its driver
32657  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
32658  struct NeverReady;
32659  impl Future for NeverReady {
32660    type Output = ();
32661    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32662      Poll::Pending
32663    }
32664  }
32665
32666  #[cfg(napi_runtime_os_threads)]
32667  struct ReadyAfterOwnerLanePublication {
32668    dependency: BlockingDependency,
32669    ready: Arc<AtomicBool>,
32670  }
32671
32672  #[cfg(napi_runtime_os_threads)]
32673  impl Future for ReadyAfterOwnerLanePublication {
32674    type Output = ();
32675
32676    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32677      if self.ready.load(Ordering::SeqCst) {
32678        Poll::Ready(())
32679      } else {
32680        // This shape publishes on every pending poll and never retires; the
32681        // owner-lane assertions read the state, not the token.
32682        let mut published = PublishedBlockingDependencies::default();
32683        record_blocking_dependency(&mut published, self.dependency);
32684        Poll::Pending
32685      }
32686    }
32687  }
32688
32689  #[cfg(panic = "unwind")]
32690  #[test]
32691  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
32692    // Shape (1): on a threadless build a CT park decision with an empty queue
32693    // and no wake token pending is a PROVABLE deadlock (no other thread exists
32694    // to deliver the wake) -- `block_on` must panic immediately with the typed
32695    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
32696    // runs on a child thread bounded by recv_timeout so a missing detection
32697    // (a hang) fails the test rather than wedging the suite.
32698    use std::sync::mpsc;
32699
32700    let (done_tx, done_rx) = mpsc::channel();
32701    let runner = std::thread::spawn(move || {
32702      let metrics = Arc::new(RuntimeMetrics::default());
32703      // Native stand-in for the threadless wasm build; no deadline because the
32704      // certain case is not timing-based.
32705      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32706
32707      let payload = catch_unwind(AssertUnwindSafe(|| {
32708        let mut future = std::pin::pin!(NeverReady);
32709        executor.block_on(future.as_mut());
32710      }))
32711      .expect_err("a threadless park with an empty queue and no pending wake must panic");
32712      let diagnostic = payload
32713        .downcast_ref::<BlockOnDeadlock>()
32714        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
32715      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
32716      assert_eq!(
32717        diagnostic.park_deadline, None,
32718        "the certain case carries no deadline"
32719      );
32720      let message = diagnostic.to_string();
32721      assert!(
32722        message.contains("block_on") && message.contains("JS"),
32723        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
32724      );
32725      let _ = done_tx.send(());
32726    });
32727
32728    match done_rx.recv_timeout(Duration::from_secs(10)) {
32729      Ok(()) => runner.join().unwrap(),
32730      Err(error) => panic!(
32731        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
32732      ),
32733    }
32734  }
32735
32736  #[cfg(napi_runtime_os_threads)]
32737  #[test]
32738  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32739    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
32740    // `Pending` but wakes itself synchronously during the poll leaves
32741    // a wake token pending, so the loop must consume the token and re-poll --
32742    // NOT panic -- even on a threadless build. "Pending returned" alone is
32743    // never a deadlock.
32744    use std::sync::mpsc;
32745
32746    struct PendThenReady {
32747      polls: usize,
32748    }
32749    impl Future for PendThenReady {
32750      type Output = ();
32751      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32752        self.polls += 1;
32753        if self.polls >= 2 {
32754          Poll::Ready(())
32755        } else {
32756          cx.waker().wake_by_ref();
32757          Poll::Pending
32758        }
32759      }
32760    }
32761
32762    let (done_tx, done_rx) = mpsc::channel();
32763    let runner = std::thread::spawn(move || {
32764      let metrics = Arc::new(RuntimeMetrics::default());
32765      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32766      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32767      executor.block_on(future.as_mut());
32768      assert_eq!(
32769        future.polls, 2,
32770        "the self-woken future must have been re-polled to completion"
32771      );
32772      let _ = done_tx.send(());
32773    });
32774
32775    match done_rx.recv_timeout(Duration::from_secs(10)) {
32776      Ok(()) => runner.join().unwrap(),
32777      Err(error) => panic!(
32778        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32779      ),
32780    }
32781  }
32782
32783  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32784  #[test]
32785  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32786    // Threaded CT is NOT provably dead at the park decision (another OS thread
32787    // could deliver a wake later), so detection there is deadline-based and
32788    // opt-in. A park that outlives the armed deadline with ZERO runtime
32789    // progress must panic with the typed diagnostic.
32790    use std::sync::mpsc;
32791
32792    let (done_tx, done_rx) = mpsc::channel();
32793    let runner = std::thread::spawn(move || {
32794      let metrics = Arc::new(RuntimeMetrics::default());
32795      let executor = Arc::new(CurrentThreadExecutor::with_detection(
32796        metrics,
32797        false,
32798        Some(Duration::from_millis(50)),
32799      ));
32800
32801      let payload = catch_unwind(AssertUnwindSafe(|| {
32802        let mut future = std::pin::pin!(NeverReady);
32803        executor.block_on(future.as_mut());
32804      }))
32805      .expect_err("a dead native CT park must fire the armed deadline");
32806      let diagnostic = payload
32807        .downcast_ref::<BlockOnDeadlock>()
32808        .expect("typed payload");
32809      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32810      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32811      // Teardown inside the bounded window: a wedged executor drop must fail
32812      // the recv below loudly, not hang the final join silently.
32813      drop(executor);
32814      let _ = done_tx.send(());
32815    });
32816
32817    match done_rx.recv_timeout(Duration::from_secs(10)) {
32818      Ok(()) => join_within(
32819        "the CT deadline-fire runner exits after teardown",
32820        runner,
32821        Duration::from_secs(30),
32822      ),
32823      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32824    }
32825  }
32826
32827  #[cfg(napi_runtime_os_threads)]
32828  #[test]
32829  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32830    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
32831    // runtime is alive (e.g. a slow JS plugin with other
32832    // work proceeding), so the deadline must re-arm instead of firing.
32833    // Progress is injected by advancing the very counter the reset reads --
32834    // the exact seam, isolated from wake delivery (production advances it via
32835    // real runnable polls).
32836    //
32837    // Deadline/cadence ratio: the reset is only exercised when at least one
32838    // tick lands inside EVERY armed window, so the window must dwarf the
32839    // injector thread's worst-case scheduling stall. On starved CI hosts
32840    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
32841    // can overshoot past a 60ms window, leaving one window with zero
32842    // progress -- which IS a deadline fire by design. Ticks every ~25ms
32843    // against a 1s window tolerate a ~975ms stall while the park is still
32844    // held across multiple full windows, so the repeated RE-ARM (not merely
32845    // the first arm) is what keeps the park alive.
32846    use std::sync::mpsc;
32847
32848    let deadline = Duration::from_millis(1000);
32849    let metrics = Arc::new(RuntimeMetrics::default());
32850    let executor = Arc::new(CurrentThreadExecutor::with_detection(
32851      Arc::clone(&metrics),
32852      false,
32853      Some(deadline),
32854    ));
32855
32856    let (value_tx, value_rx) = oneshot::channel::<usize>();
32857    let (done_tx, done_rx) = mpsc::channel();
32858    let runner = std::thread::spawn(move || {
32859      let mut output = None;
32860      {
32861        let mut future = std::pin::pin!(async {
32862          output = Some(value_rx.await.unwrap());
32863        });
32864        executor.block_on(future.as_mut());
32865      }
32866      assert_eq!(output, Some(9));
32867      // Teardown inside the bounded window (see `join_within`).
32868      drop(executor);
32869      let _ = done_tx.send(());
32870    });
32871
32872    // Hold the park across several full deadline windows, every one
32873    // containing many injected progress ticks, then deliver the (legitimately
32874    // late) wake. The loop is elapsed-based so an oversleeping iteration
32875    // extends the hold (more windows) instead of thinning the tick supply,
32876    // and the final tick lands at most one iteration before the wake.
32877    let hold_until = Instant::now() + deadline * 5 / 2;
32878    while Instant::now() < hold_until {
32879      std::thread::sleep(Duration::from_millis(25));
32880      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32881    }
32882    value_tx.send(9).unwrap();
32883
32884    match done_rx.recv_timeout(Duration::from_secs(30)) {
32885      Ok(()) => join_within(
32886        "the CT deadline-reset runner exits after teardown",
32887        runner,
32888        Duration::from_secs(30),
32889      ),
32890      Err(error) => panic!(
32891        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32892      ),
32893    }
32894  }
32895
32896  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32897  #[test]
32898  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32899    // Shape (3): the MT deadline applies to the DriverParker inside
32900    // `cooperative_block_on`. Run the cooperative branch directly on this
32901    // thread with the test-only on-pool marker so the panic is observable as
32902    // a typed payload without rayon in between.
32903    use std::sync::mpsc;
32904
32905    let (done_tx, done_rx) = mpsc::channel();
32906    let runner = std::thread::spawn(move || {
32907      let options = RuntimeOptions {
32908        flavor: RuntimeFlavor::MultiThread,
32909        worker_threads: 1,
32910        max_blocking_tasks: 1,
32911        thread_name_prefix: "deadline-fire".to_string(),
32912        park_deadline: Some(Duration::from_millis(50)),
32913        drain_linger: DEFAULT_DRAIN_LINGER,
32914      };
32915      let executor =
32916        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32917
32918      let payload = {
32919        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32920        catch_unwind(AssertUnwindSafe(|| {
32921          let mut future = std::pin::pin!(NeverReady);
32922          executor.block_on(future.as_mut());
32923        }))
32924        .expect_err("a dead cooperative park must fire the armed deadline")
32925      };
32926      let diagnostic = payload
32927        .downcast_ref::<BlockOnDeadlock>()
32928        .expect("typed payload");
32929      assert_eq!(
32930        diagnostic.kind,
32931        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32932      );
32933      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32934      let message = diagnostic.to_string();
32935      assert!(
32936        message.contains("block_on"),
32937        "diagnostic must name the hazard, got: {message}"
32938      );
32939      assert_eq!(
32940        executor.parked_drivers.count.load(Ordering::SeqCst),
32941        0,
32942        "the firing driver must deregister itself before panicking"
32943      );
32944      // Teardown inside the bounded window (see `join_within`).
32945      drop(executor);
32946      let _ = done_tx.send(());
32947    });
32948
32949    match done_rx.recv_timeout(Duration::from_secs(10)) {
32950      Ok(()) => join_within(
32951        "the MT deadline-fire runner exits after executor teardown",
32952        runner,
32953        Duration::from_secs(30),
32954      ),
32955      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32956    }
32957  }
32958
32959  #[cfg(napi_runtime_os_threads)]
32960  #[test]
32961  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32962    // Shape (4), MT side of the progress reset: a legitimately slow wake under
32963    // an armed deadline must NOT fire while the executor's
32964    // metrics keep advancing. Topology: the sole worker is parked as a
32965    // cooperative driver (real pool thread, real DriverParker park); the test
32966    // injects progress ticks across several full deadline windows, then
32967    // delivers the late wake.
32968    //
32969    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
32970    // so the window must dwarf the injector thread's worst-case scheduling
32971    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
32972    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
32973    // and a full window with zero progress IS a deadline fire by design.
32974    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
32975    // still holding the park across multiple full windows, proving the
32976    // repeated RE-ARM rather than a single lucky first window.
32977    use std::sync::mpsc;
32978
32979    let (done_tx, done_rx) = mpsc::channel();
32980    let runner = std::thread::spawn(move || {
32981      let deadline = Duration::from_millis(1000);
32982      let metrics = Arc::new(RuntimeMetrics::default());
32983      let options = RuntimeOptions {
32984        flavor: RuntimeFlavor::MultiThread,
32985        worker_threads: 1,
32986        max_blocking_tasks: 1,
32987        thread_name_prefix: "deadline-reset".to_string(),
32988        park_deadline: Some(deadline),
32989        drain_linger: DEFAULT_DRAIN_LINGER,
32990      };
32991      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32992
32993      // Park the sole worker as a cooperative driver awaiting a gate (same
32994      // topology as the miss-compensation test).
32995      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32996      let td_exec = Arc::clone(&executor);
32997      let completed = Arc::new(AtomicBool::new(false));
32998      let td_flag = Arc::clone(&completed);
32999      let td_body = async move {
33000        let mut inner = std::pin::pin!(async move {
33001          assert_eq!(gate_rx.await.unwrap(), 9);
33002        });
33003        td_exec.block_on(inner.as_mut());
33004        td_flag.store(true, Ordering::SeqCst);
33005      };
33006      let sched = Arc::clone(&executor);
33007      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
33008      executor.schedule(td_runnable);
33009      wait_until("the sole driver parked", || {
33010        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
33011      });
33012
33013      // Outlive the armed deadline several times over, with every window
33014      // containing many injected progress ticks (the counter the reset
33015      // reads). Elapsed-based so an oversleeping iteration extends the hold
33016      // instead of thinning the tick supply, and the final tick lands at
33017      // most one iteration before the late wake below.
33018      let hold_until = Instant::now() + deadline * 5 / 2;
33019      while Instant::now() < hold_until {
33020        std::thread::sleep(Duration::from_millis(25));
33021        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
33022      }
33023      assert!(
33024        !completed.load(Ordering::SeqCst),
33025        "the driver must still be parked awaiting its late wake"
33026      );
33027      gate_tx.send(9).unwrap();
33028      futures::executor::block_on(td_task);
33029      assert!(
33030        completed.load(Ordering::SeqCst),
33031        "the late wake must complete the parked driver"
33032      );
33033      // Teardown inside the bounded window (see `join_within`).
33034      drop(executor);
33035      let _ = done_tx.send(());
33036    });
33037
33038    match done_rx.recv_timeout(Duration::from_secs(30)) {
33039      Ok(()) => join_within(
33040        "the MT deadline-reset runner exits after executor teardown",
33041        runner,
33042        Duration::from_secs(30),
33043      ),
33044      Err(error) => panic!(
33045        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
33046      ),
33047    }
33048  }
33049
33050  #[cfg(napi_runtime_os_threads)]
33051  #[test]
33052  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
33053    // Shape (5): the foreign/napi caller thread parks in
33054    // `futures::executor::block_on` for the ENTIRE build -- the normal
33055    // production shape. Even with an aggressively short deadline armed and
33056    // ZERO executor progress for many windows, that park must never fire.
33057    use std::sync::mpsc;
33058
33059    let (done_tx, done_rx) = mpsc::channel();
33060    let runner = std::thread::spawn(move || {
33061      let options = RuntimeOptions {
33062        flavor: RuntimeFlavor::MultiThread,
33063        worker_threads: 2,
33064        max_blocking_tasks: 2,
33065        thread_name_prefix: "foreign-exempt".to_string(),
33066        park_deadline: Some(Duration::from_millis(40)),
33067        drain_linger: DEFAULT_DRAIN_LINGER,
33068      };
33069      let executor =
33070        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33071
33072      let (value_tx, value_rx) = oneshot::channel::<usize>();
33073      let waker_thread = std::thread::spawn(move || {
33074        // Well past several deadline windows, with zero executor progress.
33075        std::thread::sleep(Duration::from_millis(300));
33076        let _ = value_tx.send(7);
33077      });
33078
33079      let started = Instant::now();
33080      let mut output = None;
33081      {
33082        let mut future = std::pin::pin!(async {
33083          output = Some(value_rx.await.unwrap());
33084        });
33085        // This (non-pool) thread takes the parking branch: exempt by design.
33086        executor.block_on(future.as_mut());
33087      }
33088      assert_eq!(output, Some(7));
33089      assert!(
33090        started.elapsed() >= Duration::from_millis(250),
33091        "the park must genuinely have outlived the 40ms deadline many times over"
33092      );
33093      waker_thread.join().unwrap();
33094      // Teardown inside the bounded window: this test was one of the silent
33095      // wedges on the Windows CI runners -- every assertion above had
33096      // passed, so the hang lived in the unbounded residue (executor drop /
33097      // thread teardown). Keep that phase inside the loud bounds.
33098      drop(executor);
33099      let _ = done_tx.send(());
33100    });
33101
33102    match done_rx.recv_timeout(Duration::from_secs(10)) {
33103      Ok(()) => join_within(
33104        "the exempt foreign-park runner exits after executor teardown",
33105        runner,
33106        Duration::from_secs(30),
33107      ),
33108      Err(error) => {
33109        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
33110      }
33111    }
33112  }
33113
33114  #[cfg(napi_runtime_os_threads)]
33115  #[test]
33116  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
33117    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
33118    // hook instead of a wall-clock lottery. Interleaving:
33119    //   (1) the cooperative park's deadline expires with genuinely zero
33120    //       progress (nothing else touches the executor before the park);
33121    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
33122    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
33123    //       pops this parker, stores the permit and reports the wake as
33124    //       DELIVERED (so no drainer is spawned). The wake was counted as
33125    //       delivered TO US, so the post-deregister permit re-check is the
33126    //       first-line catch; queued work and the enqueue counters in the
33127    //       fingerprint are further backstops;
33128    //   (3) the expiry decision runs. It must treat the permit as a wake, run
33129    //       the job (which sends the gate), and complete the awaited future
33130    //       instead of reporting `BlockOnDeadlock`.
33131    use std::sync::mpsc;
33132
33133    let (done_tx, done_rx) = mpsc::channel();
33134    let runner = std::thread::spawn(move || {
33135      let options = RuntimeOptions {
33136        flavor: RuntimeFlavor::MultiThread,
33137        worker_threads: 1,
33138        max_blocking_tasks: 1,
33139        thread_name_prefix: "edge-wake".to_string(),
33140        park_deadline: Some(Duration::from_millis(40)),
33141        drain_linger: DEFAULT_DRAIN_LINGER,
33142      };
33143      let executor =
33144        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33145
33146      // Until the racing job sends this gate, the awaited future is Pending.
33147      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
33148      // (registered_before_wake, registered_after_wake) observed by the hook.
33149      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
33150
33151      let hook_exec = Arc::clone(&executor);
33152      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33153        *slot.borrow_mut() = Some(Box::new(move || {
33154          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
33155          // The racing schedule: pushes the job, then `wake_one` pops the
33156          // still-registered parker (permit stored, wake counted as
33157          // delivered, no drainer).
33158          let handle = hook_exec.schedule_blocking(move || {
33159            let _ = gate_tx.send(5usize);
33160            0usize
33161          });
33162          drop(handle); // result unobserved; the gate send is the signal
33163          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
33164          hook_saw_tx.send((before, after)).unwrap();
33165        }));
33166      });
33167
33168      // The test-only on-pool marker runs the cooperative branch and its
33169      // thread-local hook on THIS thread.
33170      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33171      let mut output = None;
33172      {
33173        let mut future = std::pin::pin!(async {
33174          output = Some(gate_rx.await.unwrap());
33175        });
33176        executor.block_on(future.as_mut());
33177      }
33178      // Clear the hook slot BEFORE any assert can panic: a hook left
33179      // installed captures an executor Arc, and dropping the last executor
33180      // Arc from a TLS destructor at thread exit is loader-lock hostile on
33181      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
33182      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
33183      assert_eq!(
33184        output,
33185        Some(5),
33186        "the edge-delivered wake's job must have run and completed the future"
33187      );
33188
33189      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
33190      assert_eq!(
33191        before, 1,
33192        "the hook must observe the parker STILL registered (race window)"
33193      );
33194      assert_eq!(
33195        after, 0,
33196        "the racing wake_one must have popped the parker (wake DELIVERED)"
33197      );
33198      // Teardown inside the bounded window (see `join_within`).
33199      drop(executor);
33200      let _ = done_tx.send(());
33201    });
33202
33203    match done_rx.recv_timeout(Duration::from_secs(10)) {
33204      Ok(()) => join_within(
33205        "the expiry-edge-wake runner exits after executor teardown",
33206        runner,
33207        Duration::from_secs(30),
33208      ),
33209      Err(error) => panic!(
33210        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
33211      ),
33212    }
33213  }
33214
33215  #[cfg(napi_runtime_os_threads)]
33216  #[test]
33217  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
33218    // Enqueue/fingerprint invariant: both runnable and blocking submissions
33219    // must advance the progress fingerprint at ENQUEUE. A blocking submission
33220    // landing after `has_queued_work()` but before the final fingerprint read
33221    // has already pushed deliverable work, while its wake targets an EMPTY
33222    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
33223    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
33224    // fires in this window on the driver thread:
33225    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
33226    //     job on the real worker -- `blocking_tasks_started` provably cannot
33227    //     advance behind our back;
33228    //   * the enqueue counter must therefore save the healthy park.
33229    use std::sync::mpsc;
33230
33231    let (done_tx, done_rx) = mpsc::channel();
33232    let runner = std::thread::spawn(move || {
33233      let options = RuntimeOptions {
33234        flavor: RuntimeFlavor::MultiThread,
33235        worker_threads: 1,
33236        max_blocking_tasks: 1,
33237        thread_name_prefix: "verdict-blocking".to_string(),
33238        park_deadline: Some(Duration::from_millis(40)),
33239        drain_linger: DEFAULT_DRAIN_LINGER,
33240      };
33241      let executor =
33242        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33243      executor
33244        .active_drainers
33245        .store(executor.max_drainers, Ordering::SeqCst);
33246
33247      // Until the racing job sends this gate, the awaited future is Pending.
33248      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
33249      // Registry size observed by the hook (must be 0: already deregistered,
33250      // so no permit can possibly be delivered for this enqueue).
33251      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
33252
33253      let hook_exec = Arc::clone(&executor);
33254      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
33255        *slot.borrow_mut() = Some(Box::new(move || {
33256          let handle = hook_exec.schedule_blocking(move || {
33257            let _ = gate_tx.send(11usize);
33258            0usize
33259          });
33260          drop(handle); // result unobserved; the gate send is the signal
33261          hook_saw_tx
33262            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
33263            .unwrap();
33264        }));
33265      });
33266
33267      // The test-only on-pool marker runs the cooperative branch and its
33268      // thread-local hook on THIS thread.
33269      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33270      let mut output = None;
33271      {
33272        let mut future = std::pin::pin!(async {
33273          output = Some(gate_rx.await.unwrap());
33274        });
33275        executor.block_on(future.as_mut());
33276      }
33277      assert_eq!(
33278        output,
33279        Some(11),
33280        "the enqueued job must have been run by the surviving driver via run_one"
33281      );
33282
33283      let registered_at_hook = hook_saw_rx
33284        .recv()
33285        .expect("the verdict hook must have fired");
33286      assert_eq!(
33287        registered_at_hook, 0,
33288        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
33289      );
33290      executor.active_drainers.store(0, Ordering::SeqCst);
33291      let _ = done_tx.send(());
33292    });
33293
33294    match done_rx.recv_timeout(Duration::from_secs(10)) {
33295      Ok(()) => runner.join().unwrap(),
33296      Err(error) => panic!(
33297        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
33298      ),
33299    }
33300  }
33301
33302  #[cfg(napi_runtime_os_threads)]
33303  #[test]
33304  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
33305    // Publish only through the deadlock-admission protocol after every legacy
33306    // final check. No runnable, blocking job, permit, timer, or metrics
33307    // fingerprint changes, so only the synchronized publication epoch can
33308    // prevent the false verdict. The future observes a separate test flag on
33309    // its next poll; that flag is deliberately not a scheduler wake source.
33310    use std::sync::mpsc;
33311
33312    struct ReadyAfterPublication(Arc<AtomicBool>);
33313
33314    impl Future for ReadyAfterPublication {
33315      type Output = ();
33316
33317      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
33318        if self.0.load(Ordering::SeqCst) {
33319          Poll::Ready(())
33320        } else {
33321          Poll::Pending
33322        }
33323      }
33324    }
33325
33326    let (done_tx, done_rx) = mpsc::channel();
33327    let runner = std::thread::spawn(move || {
33328      let options = RuntimeOptions {
33329        flavor: RuntimeFlavor::MultiThread,
33330        worker_threads: 1,
33331        max_blocking_tasks: 1,
33332        thread_name_prefix: "final-verdict-publication".to_string(),
33333        park_deadline: Some(Duration::from_millis(40)),
33334        drain_linger: DEFAULT_DRAIN_LINGER,
33335      };
33336      let executor =
33337        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33338
33339      let ready = Arc::new(AtomicBool::new(false));
33340      let baseline_fingerprint = executor.metrics.progress_fingerprint();
33341      let baseline_publications = executor.deadlock_state.publications();
33342      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
33343      let hook_exec = Arc::clone(&executor);
33344      let hook_ready = Arc::clone(&ready);
33345      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33346        *slot.borrow_mut() = Some(Box::new(move || {
33347          let publication_executor = Arc::clone(&hook_exec);
33348          std::thread::spawn(move || {
33349            let mut admission = publication_executor
33350              .begin_deadlock_admission()
33351              .expect("the deadline detector must enable admission accounting");
33352            hook_ready.store(true, Ordering::SeqCst);
33353            admission.mark_published();
33354          })
33355          .join()
33356          .unwrap();
33357          hook_saw_tx
33358            .send((
33359              hook_exec.deadlock_state.publications(),
33360              !hook_exec.has_queued_runnable(),
33361              hook_exec
33362                .blocking_queue
33363                .lock()
33364                .unwrap_or_else(std::sync::PoisonError::into_inner)
33365                .is_empty(),
33366              hook_exec.next_timer_deadline().is_none(),
33367              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
33368              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
33369            ))
33370            .unwrap();
33371        }));
33372      });
33373
33374      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33375      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
33376      assert_eq!(
33377        executor.block_on(future.as_mut()),
33378        BlockOnOutcome::Completed,
33379        "the publication epoch must veto the first verdict and permit the next poll"
33380      );
33381      let (
33382        publications,
33383        runnable_queue_empty,
33384        blocking_queue_empty,
33385        timer_heap_empty,
33386        fingerprint_unchanged,
33387        parked_drivers,
33388      ) = hook_saw_rx
33389        .recv()
33390        .expect("the final-verdict hook must have fired");
33391      assert_eq!(
33392        publications,
33393        baseline_publications
33394          .checked_add(1)
33395          .expect("test publication epoch must not overflow"),
33396        "the cross-thread admission must publish exactly one epoch"
33397      );
33398      assert!(
33399        runnable_queue_empty,
33400        "the publication test must not queue a runnable"
33401      );
33402      assert!(
33403        blocking_queue_empty,
33404        "the publication test must not queue blocking work"
33405      );
33406      assert!(
33407        timer_heap_empty,
33408        "the publication test must not register a timer"
33409      );
33410      assert!(
33411        fingerprint_unchanged,
33412        "ordinary metrics progress must not veto the publication-only verdict"
33413      );
33414      assert_eq!(
33415        parked_drivers, 0,
33416        "the publication must land after the cooperative parker was deregistered"
33417      );
33418      let _ = done_tx.send(());
33419    });
33420
33421    match done_rx.recv_timeout(Duration::from_secs(10)) {
33422      Ok(()) => runner.join().unwrap(),
33423      Err(error) => panic!(
33424        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
33425      ),
33426    }
33427  }
33428
33429  #[cfg(napi_runtime_os_threads)]
33430  #[test]
33431  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
33432    // The exact owner lane is saturated and reserved while the cooperative
33433    // driver parks. At expiry, release selects that driver and publishes its
33434    // handoff, then pauses before unpark. The driver reaches the gated verdict
33435    // with no queue, timer, metric, or ordinary publication change. The
33436    // exact-owner publication boundary must serialize the final check. The
33437    // hook then consumes the ordinary permit and renews the lane reservation,
33438    // leaving only the pending-handoff predicate to veto the false deadlock.
33439    use std::sync::mpsc;
33440
33441    let executor = multi_thread_executor(
33442      1,
33443      Some(Duration::from_millis(40)),
33444      "deadline-selected-handoff",
33445    );
33446    let owner = executor.fresh_owner_token();
33447    executor.active_blocking_owners.register(owner);
33448    executor
33449      .active_blocking
33450      .store(executor.max_blocking, Ordering::SeqCst);
33451
33452    let ready = Arc::new(AtomicBool::new(false));
33453    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33454    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33455    let (selected_tx, selected_rx) = mpsc::channel();
33456    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
33457    let (released_tx, released_rx) = mpsc::channel();
33458    let (renewed_tx, renewed_rx) = mpsc::channel();
33459    let release_executor = Arc::clone(&executor);
33460    let renewal_executor = Arc::clone(&executor);
33461    let release_ready = Arc::clone(&ready);
33462    let releaser = std::thread::spawn(move || {
33463      let reservation =
33464        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33465          .expect("the synthetic exact-owner lane must start reserved");
33466      reservation_ready_tx.send(()).unwrap();
33467      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
33468        *slot.borrow_mut() = Some(Box::new(move || {
33469          selected_tx.send(()).unwrap();
33470          allow_unpark_rx.recv().unwrap();
33471          let renewed = renewal_executor
33472            .active_blocking_owners
33473            .reserve(owner)
33474            .expect("the released exact-owner lane must be reservable before unpark");
33475          release_ready.store(true, Ordering::SeqCst);
33476          renewed_tx.send(renewed).unwrap();
33477        }));
33478      });
33479      release_reservation_rx.recv().unwrap();
33480      released_tx
33481        .send(release_executor.release_owner_lane_and_wake(reservation))
33482        .unwrap();
33483    });
33484    reservation_ready_rx
33485      .recv_timeout(Duration::from_secs(2))
33486      .expect("the exact-owner reservation must be established before the park");
33487
33488    let baseline_fingerprint = executor.metrics.progress_fingerprint();
33489    let baseline_publications = executor.deadlock_state.publications();
33490    let expiry_executor = Arc::clone(&executor);
33491    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33492      *slot.borrow_mut() = Some(Box::new(move || {
33493        release_reservation_tx.send(()).unwrap();
33494        selected_rx
33495          .recv_timeout(Duration::from_secs(2))
33496          .expect("reservation release must select the still-registered exact-owner driver");
33497        assert_eq!(
33498          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
33499          0,
33500          "handoff selection must remove the driver before pausing ahead of unpark"
33501        );
33502      }));
33503    });
33504
33505    let verdict_executor = Arc::clone(&executor);
33506    let verdict_ready = Arc::clone(&ready);
33507    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33508      *slot.borrow_mut() = Some(Box::new(move || {
33509        assert!(
33510          verdict_executor.active_blocking_owners.is_available(owner),
33511          "reservation release must publish lane availability before handoff selection"
33512        );
33513        assert!(!verdict_executor.has_queued_runnable());
33514        assert!(
33515          verdict_executor
33516            .blocking_queue
33517            .lock()
33518            .unwrap_or_else(std::sync::PoisonError::into_inner)
33519            .is_empty()
33520        );
33521        assert!(verdict_executor.next_timer_deadline().is_none());
33522        assert!(
33523          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33524          "ordinary metrics progress must not save the selected-handoff verdict"
33525        );
33526        assert_eq!(
33527          verdict_executor.deadlock_state.publications(),
33528          baseline_publications
33529        );
33530        assert!(
33531          !verdict_ready.load(Ordering::SeqCst),
33532          "the selected handoff must still be paused before unpark at the gated verdict"
33533        );
33534        allow_unpark_tx.send(()).unwrap();
33535      }));
33536    });
33537    let exact_owner_executor = Arc::clone(&executor);
33538    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
33539      *slot.borrow_mut() = Some(Box::new(move |parker| {
33540        assert!(
33541          parker.consume_permit(),
33542          "the selected handoff must unpark before exact-owner final predicates run"
33543        );
33544        assert!(
33545          parker.has_owner_handoff(),
33546          "consuming the ordinary permit must leave the exact-owner handoff pending"
33547        );
33548        assert!(
33549          !exact_owner_executor
33550            .active_blocking_owners
33551            .is_available(owner),
33552          "the renewed reservation must prevent lane availability from saving the verdict"
33553        );
33554      }));
33555    });
33556
33557    {
33558      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33559      let _owner = BlockingOwnerGuard::enter(Some(owner));
33560      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33561        dependency: BlockingDependency {
33562          job: BlockingJobId(u64::MAX - 80),
33563          owner: Some(owner)
33564        },
33565        ready: Arc::clone(&ready),
33566      });
33567      assert_eq!(
33568        executor.block_on(future.as_mut()),
33569        BlockOnOutcome::Completed
33570      );
33571    }
33572    assert!(
33573      released_rx
33574        .recv_timeout(Duration::from_secs(2))
33575        .expect("the selected handoff publication must finish"),
33576      "reservation release must have selected the cooperative driver"
33577    );
33578    let renewed = renewed_rx
33579      .recv_timeout(Duration::from_secs(2))
33580      .expect("the selected handoff must renew the owner reservation before unpark");
33581    releaser.join().unwrap();
33582
33583    executor.active_blocking_owners.release(owner, renewed);
33584    executor.active_blocking.store(0, Ordering::SeqCst);
33585    executor.active_blocking_owners.unregister(owner);
33586    executor.begin_shutdown();
33587    executor.wait_until_scheduler_idle();
33588  }
33589
33590  #[cfg(napi_runtime_os_threads)]
33591  #[test]
33592  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
33593    // Release begins only after the deadline path has deregistered its parker
33594    // and completed every legacy final check. No targeted wake or ordinary
33595    // scheduler publication can save the verdict; the synchronized exact-owner
33596    // availability predicate must observe the released lane.
33597    use std::sync::mpsc;
33598
33599    let executor = multi_thread_executor(
33600      1,
33601      Some(Duration::from_millis(40)),
33602      "deadline-owner-no-parker",
33603    );
33604    let owner = executor.fresh_owner_token();
33605    executor.active_blocking_owners.register(owner);
33606    executor
33607      .active_blocking
33608      .store(executor.max_blocking, Ordering::SeqCst);
33609
33610    let ready = Arc::new(AtomicBool::new(false));
33611    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33612    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33613    let (released_tx, released_rx) = mpsc::channel();
33614    let release_executor = Arc::clone(&executor);
33615    let release_ready = Arc::clone(&ready);
33616    let releaser = std::thread::spawn(move || {
33617      let reservation =
33618        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33619          .expect("the synthetic exact-owner lane must start reserved");
33620      reservation_ready_tx.send(()).unwrap();
33621      release_reservation_rx.recv().unwrap();
33622      let woke = release_executor.release_owner_lane_and_wake(reservation);
33623      release_ready.store(true, Ordering::SeqCst);
33624      released_tx.send(woke).unwrap();
33625    });
33626    reservation_ready_rx
33627      .recv_timeout(Duration::from_secs(2))
33628      .expect("the exact-owner reservation must be established before the park");
33629
33630    let baseline_fingerprint = executor.metrics.progress_fingerprint();
33631    let baseline_publications = executor.deadlock_state.publications();
33632    let verdict_executor = Arc::clone(&executor);
33633    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33634      *slot.borrow_mut() = Some(Box::new(move || {
33635        assert_eq!(
33636          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
33637          0,
33638          "the release must begin only after deadline deregistration"
33639        );
33640        release_reservation_tx.send(()).unwrap();
33641        assert!(
33642          !released_rx
33643            .recv_timeout(Duration::from_secs(2))
33644            .expect("the no-parker owner release must finish"),
33645          "no registered parker may receive the released owner lane"
33646        );
33647        assert!(verdict_executor.active_blocking_owners.is_available(owner));
33648        assert!(!verdict_executor.has_queued_runnable());
33649        assert!(
33650          verdict_executor
33651            .blocking_queue
33652            .lock()
33653            .unwrap_or_else(std::sync::PoisonError::into_inner)
33654            .is_empty()
33655        );
33656        assert!(verdict_executor.next_timer_deadline().is_none());
33657        assert!(
33658          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33659          "ordinary metrics progress must not save the no-parker release verdict"
33660        );
33661        assert_eq!(
33662          verdict_executor.deadlock_state.publications(),
33663          baseline_publications
33664        );
33665      }));
33666    });
33667
33668    {
33669      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33670      let _owner = BlockingOwnerGuard::enter(Some(owner));
33671      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33672        dependency: BlockingDependency {
33673          job: BlockingJobId(u64::MAX - 81),
33674          owner: Some(owner)
33675        },
33676        ready: Arc::clone(&ready),
33677      });
33678      assert_eq!(
33679        executor.block_on(future.as_mut()),
33680        BlockOnOutcome::Completed
33681      );
33682    }
33683    releaser.join().unwrap();
33684
33685    executor.active_blocking.store(0, Ordering::SeqCst);
33686    executor.active_blocking_owners.unregister(owner);
33687    executor.begin_shutdown();
33688    executor.wait_until_scheduler_idle();
33689  }
33690
33691  #[cfg(napi_runtime_os_threads)]
33692  #[test]
33693  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
33694    // Pin the shutdown race after the final verdict gate has closed:
33695    //   1. an accepted task is pending, so shutdown abort must wake it;
33696    //   2. a cooperative block_on reaches its deadline and pauses while
33697    //      holding the closed verdict gate;
33698    //   3. shutdown publishes generation stop, transitions
33699    //      Running -> Stopping, then its abort wake blocks entering scheduler
33700    //      admission behind that gate;
33701    //   4. the gated final verdict must observe stop and return Stopped,
33702    //      reopening the gate so the abort wake and shutdown can finish.
33703    //
33704    // Before stop was published by the controller transition, step 3 blocked
33705    // inside abort before executor shutdown could set it. The driver then saw
33706    // empty queues and emitted a false BlockOnDeadlock.
33707    use std::sync::mpsc;
33708
33709    let controller = Arc::new(RuntimeController::new());
33710    controller
33711      .configure(RuntimeOptions {
33712        flavor: RuntimeFlavor::MultiThread,
33713        worker_threads: 2,
33714        max_blocking_tasks: 1,
33715        thread_name_prefix: "shutdown-gated-verdict".to_string(),
33716        park_deadline: Some(Duration::from_millis(40)),
33717        drain_linger: DEFAULT_DRAIN_LINGER,
33718      })
33719      .expect("the shutdown race test configuration must be accepted");
33720
33721    let backend = controller.backend();
33722    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33723      panic!("the configured backend must be MultiThread");
33724    };
33725    let executor_id = executor.id;
33726    let weak_executor = Arc::downgrade(executor);
33727    drop(backend);
33728
33729    let (task_polled_tx, task_polled_rx) = mpsc::channel();
33730    let task = controller
33731      .try_spawn(async move {
33732        task_polled_tx.send(()).unwrap();
33733        std::future::pending::<()>().await;
33734      })
33735      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33736    task_polled_rx
33737      .recv_timeout(Duration::from_secs(2))
33738      .expect("the accepted task must publish an abortable pending waker");
33739
33740    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33741    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33742    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33743    let driver_controller = Arc::clone(&controller);
33744    let driver = std::thread::spawn(move || {
33745      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33746        *slot.borrow_mut() = Some(Box::new(move || {
33747          gate_closed_tx.send(()).unwrap();
33748          release_verdict_rx.recv().unwrap();
33749        }));
33750      });
33751
33752      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33753      let mut pending = std::pin::pin!(NeverReady);
33754      let result = catch_unwind(AssertUnwindSafe(|| {
33755        driver_controller.block_on(pending.as_mut())
33756      }));
33757      let result = match result {
33758        Ok(outcome) => Ok(outcome),
33759        Err(payload) => {
33760          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33761          discard_panic_payload(payload);
33762          Err(was_deadlock)
33763        }
33764      };
33765      driver_result_tx.send(result).unwrap();
33766    });
33767
33768    gate_closed_rx
33769      .recv_timeout(Duration::from_secs(2))
33770      .expect("the cooperative driver must pause with the verdict gate closed");
33771
33772    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33773    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33774    let shutdown_controller = Arc::clone(&controller);
33775    let shutdown = std::thread::spawn(move || {
33776      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33777        *slot.borrow_mut() = Some(Box::new(move || {
33778          abort_blocked_tx.send(()).unwrap();
33779        }));
33780      });
33781      shutdown_result_tx
33782        .send(shutdown_controller.shutdown())
33783        .unwrap();
33784    });
33785
33786    abort_blocked_rx
33787      .recv_timeout(Duration::from_secs(2))
33788      .expect("the accepted task's abort wake must reach the closed verdict gate");
33789    let lifecycle_is_stopping = {
33790      let state = controller
33791        .state
33792        .lock()
33793        .unwrap_or_else(std::sync::PoisonError::into_inner);
33794      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33795    };
33796    let stop_was_published = weak_executor
33797      .upgrade()
33798      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33799
33800    release_verdict_tx.send(()).unwrap();
33801
33802    let driver_result = driver_result_rx
33803      .recv_timeout(Duration::from_secs(2))
33804      .expect("the gated driver must resolve after observing shutdown");
33805    let shutdown_result = shutdown_result_rx
33806      .recv_timeout(Duration::from_secs(5))
33807      .expect("shutdown must finish after the verdict gate reopens");
33808    // Both threads have sent their results; what remains is teardown (and on
33809    // the shutdown thread, the controller's `wait_for_all_workers` already
33810    // completed inside `shutdown()`). This test was one of the silent wedges
33811    // on the Windows CI runners -- bound the residue loudly.
33812    join_within(
33813      "the gated cooperative driver exits after teardown",
33814      driver,
33815      Duration::from_secs(30),
33816    );
33817    join_within(
33818      "the shutdown thread exits after teardown",
33819      shutdown,
33820      Duration::from_secs(30),
33821    );
33822
33823    assert!(
33824      lifecycle_is_stopping,
33825      "the abort wake must occur after Running -> Stopping"
33826    );
33827    assert!(
33828      stop_was_published,
33829      "generation stop must be visible before shutdown invokes accepted-task aborts"
33830    );
33831    assert_eq!(
33832      driver_result,
33833      Ok(BlockOnOutcome::Stopped),
33834      "shutdown must not be reported as a gated BlockOnDeadlock"
33835    );
33836    shutdown_result.unwrap();
33837    assert!(
33838      futures::executor::block_on(task).is_err(),
33839      "the accepted pending task must resolve as cancelled"
33840    );
33841  }
33842
33843  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33844  #[test]
33845  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33846    // Complement the publication-wins race above. The verdict owns both its
33847    // admission gate and the stop-publication mutex before shutdown starts.
33848    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
33849    // Running -> Stopping until the verdict finishes its synchronized checks.
33850    use std::sync::mpsc;
33851
33852    let controller = Arc::new(RuntimeController::new());
33853    controller
33854      .configure(RuntimeOptions {
33855        flavor: RuntimeFlavor::MultiThread,
33856        worker_threads: 2,
33857        max_blocking_tasks: 1,
33858        thread_name_prefix: "verdict-before-shutdown".to_string(),
33859        park_deadline: Some(Duration::from_millis(40)),
33860        drain_linger: DEFAULT_DRAIN_LINGER,
33861      })
33862      .expect("the verdict-first test configuration must be accepted");
33863
33864    let backend = controller.backend();
33865    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33866      panic!("the configured backend must be MultiThread");
33867    };
33868    let executor_id = executor.id;
33869    drop(backend);
33870
33871    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33872    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33873    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33874    let driver_controller = Arc::clone(&controller);
33875    let driver = std::thread::spawn(move || {
33876      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33877        *slot.borrow_mut() = Some(Box::new(move || {
33878          verdict_locked_tx.send(()).unwrap();
33879          release_verdict_rx.recv().unwrap();
33880        }));
33881      });
33882
33883      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33884      let mut pending = std::pin::pin!(NeverReady);
33885      let result = catch_unwind(AssertUnwindSafe(|| {
33886        driver_controller.block_on(pending.as_mut())
33887      }));
33888      let result = match result {
33889        Ok(outcome) => Ok(outcome),
33890        Err(payload) => {
33891          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33892          discard_panic_payload(payload);
33893          Err(was_deadlock)
33894        }
33895      };
33896      driver_result_tx.send(result).unwrap();
33897    });
33898
33899    verdict_locked_rx
33900      .recv_timeout(Duration::from_secs(2))
33901      .expect("the verdict must hold the synchronized stop-publication guard");
33902
33903    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33904    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33905    let shutdown_controller = Arc::clone(&controller);
33906    let shutdown = std::thread::spawn(move || {
33907      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33908        *slot.borrow_mut() = Some(Box::new(move || {
33909          shutdown_waiting_tx.send(()).unwrap();
33910        }));
33911      });
33912      shutdown_result_tx
33913        .send(shutdown_controller.shutdown())
33914        .unwrap();
33915    });
33916
33917    shutdown_waiting_rx
33918      .recv_timeout(Duration::from_secs(2))
33919      .expect("shutdown must reach the lifecycle-to-publication lock edge");
33920    let early_shutdown = shutdown_result_rx
33921      .recv_timeout(Duration::from_millis(100))
33922      .ok();
33923    let shutdown_was_blocked = early_shutdown.is_none();
33924    release_verdict_tx.send(()).unwrap();
33925
33926    let driver_result = driver_result_rx
33927      .recv_timeout(Duration::from_secs(2))
33928      .expect("the verdict-first driver must finish after release");
33929    let shutdown_result = early_shutdown.unwrap_or_else(|| {
33930      shutdown_result_rx
33931        .recv_timeout(Duration::from_secs(5))
33932        .expect("shutdown must finish after the verdict releases publication")
33933    });
33934    driver.join().unwrap();
33935    shutdown.join().unwrap();
33936
33937    assert!(
33938      shutdown_was_blocked,
33939      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33940    );
33941    assert_eq!(
33942      driver_result,
33943      Err(true),
33944      "a verdict that linearizes before shutdown must retain the typed deadlock"
33945    );
33946    shutdown_result.unwrap();
33947  }
33948
33949  #[cfg(not(target_family = "wasm"))]
33950  #[test]
33951  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33952    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33953
33954    if std::env::var_os(CHILD_ENV).is_some() {
33955      let executor =
33956        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33957      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
33958      // hook only needs to prove that scheduler admission returns.
33959      executor
33960        .active_drainers
33961        .store(executor.max_drainers, Ordering::SeqCst);
33962
33963      let hook_returned = Arc::new(AtomicBool::new(false));
33964      let hook_flag = Arc::clone(&hook_returned);
33965      let hook_executor = Arc::clone(&executor);
33966      let previous_hook = std::panic::take_hook();
33967      std::panic::set_hook(Box::new(move |_| {
33968        let handle = hook_executor.schedule_blocking(|| ());
33969        drop(handle);
33970        hook_flag.store(true, Ordering::SeqCst);
33971      }));
33972
33973      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33974      let result = catch_unwind(AssertUnwindSafe(|| {
33975        let mut pending = std::pin::pin!(std::future::pending::<()>());
33976        executor.block_on(pending.as_mut());
33977      }));
33978      std::panic::set_hook(previous_hook);
33979
33980      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33981      assert!(
33982        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33983        "the deadline must retain its structured diagnostic"
33984      );
33985      assert!(
33986        hook_returned.load(Ordering::SeqCst),
33987        "the panic hook's scheduler submission must return before unwind"
33988      );
33989
33990      executor.active_drainers.store(0, Ordering::SeqCst);
33991      executor.begin_shutdown();
33992      executor.wait_until_scheduler_idle();
33993      return;
33994    }
33995
33996    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33997      .arg("--exact")
33998      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33999      .arg("--nocapture")
34000      .env(CHILD_ENV, "1")
34001      .stdout(std::process::Stdio::piped())
34002      .stderr(std::process::Stdio::piped())
34003      .spawn()
34004      .expect("the panic-hook subprocess must start");
34005    let deadline = Instant::now() + Duration::from_secs(5);
34006    let status = loop {
34007      if let Some(status) = child
34008        .try_wait()
34009        .expect("the subprocess status must be readable")
34010      {
34011        break status;
34012      }
34013      if Instant::now() >= deadline {
34014        let _ = child.kill();
34015        let output = child
34016          .wait_with_output()
34017          .expect("the timed-out subprocess must be reaped");
34018        panic!(
34019          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
34020          String::from_utf8_lossy(&output.stdout),
34021          String::from_utf8_lossy(&output.stderr)
34022        );
34023      }
34024      std::thread::sleep(Duration::from_millis(10));
34025    };
34026    let output = child
34027      .wait_with_output()
34028      .expect("the completed subprocess output must be read");
34029    assert!(
34030      status.success(),
34031      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
34032      status.code(),
34033      String::from_utf8_lossy(&output.stdout),
34034      String::from_utf8_lossy(&output.stderr)
34035    );
34036  }
34037
34038  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
34039  // the embedding host's env-resolution pipeline together with the read
34040  // itself; its tests live there.
34041
34042  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
34043  // The tests cover both deadlock-detection interactions. All tests build
34044  // executors LOCALLY (never the global RUNTIME or the process-global host
34045  // driver) and run hang-prone workloads on a child thread bounded by
34046  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
34047
34048  #[cfg(panic = "unwind")]
34049  #[test]
34050  fn contained_waker_separates_wake_and_drop_panics() {
34051    struct PanicWakeAndDrop;
34052
34053    impl std::task::Wake for PanicWakeAndDrop {
34054      fn wake(self: Arc<Self>) {
34055        panic!("intentional consuming wake panic");
34056      }
34057
34058      fn wake_by_ref(self: &Arc<Self>) {
34059        panic!("intentional borrowed wake panic");
34060      }
34061    }
34062
34063    impl Drop for PanicWakeAndDrop {
34064      fn drop(&mut self) {
34065        panic!("intentional RawWaker destructor panic");
34066      }
34067    }
34068
34069    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
34070  }
34071
34072  struct TestHostileRawWakerState {
34073    clones: AtomicUsize,
34074    drop_panics: AtomicUsize,
34075    panic_on_clone: Option<usize>,
34076  }
34077
34078  struct TestHostileRawWakerToken {
34079    state: Arc<TestHostileRawWakerState>,
34080    panic_on_drop: bool,
34081  }
34082
34083  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
34084    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
34085    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
34086    assert_ne!(
34087      token.state.panic_on_clone,
34088      Some(clone),
34089      "intentional RawWaker clone panic"
34090    );
34091    let clone = Box::new(TestHostileRawWakerToken {
34092      state: Arc::clone(&token.state),
34093      panic_on_drop: true,
34094    });
34095    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
34096  }
34097
34098  unsafe fn wake_test_hostile_waker(data: *const ()) {
34099    unsafe { drop_test_hostile_waker(data) };
34100  }
34101
34102  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
34103
34104  unsafe fn drop_test_hostile_waker(data: *const ()) {
34105    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
34106    if token.panic_on_drop {
34107      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
34108      panic!("intentional cloned RawWaker destructor panic");
34109    }
34110  }
34111
34112  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
34113    clone_test_hostile_waker,
34114    wake_test_hostile_waker,
34115    wake_test_hostile_waker_by_ref,
34116    drop_test_hostile_waker,
34117  );
34118
34119  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
34120    let token = Box::new(TestHostileRawWakerToken {
34121      state,
34122      panic_on_drop: false,
34123    });
34124    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
34125    unsafe { Waker::from_raw(raw) }
34126  }
34127
34128  /// Waker that only counts. For polling a raw `Sleep` without an executor.
34129  struct CountingWake(AtomicUsize);
34130  impl std::task::Wake for CountingWake {
34131    fn wake(self: Arc<Self>) {
34132      self.0.fetch_add(1, Ordering::SeqCst);
34133    }
34134  }
34135
34136  /// One armed stub timer: the latest waker plus a cancellation flag.
34137  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
34138
34139  /// Test-local host driver: fires each registered timer from a helper OS
34140  /// thread at its deadline (re-registration under the same id refreshes the
34141  /// waker without arming a second thread, per the `TimerDriver` contract).
34142  struct StubHostTimerDriver {
34143    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
34144  }
34145
34146  impl StubHostTimerDriver {
34147    fn new() -> Self {
34148      Self {
34149        timers: Mutex::new(FxHashMap::default()),
34150      }
34151    }
34152  }
34153
34154  impl TimerDriver for StubHostTimerDriver {
34155    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
34156      let mut timers = self.timers.lock().unwrap();
34157      if let Some(cell) = timers.get(&id) {
34158        *cell.0.lock().unwrap() = waker;
34159        return;
34160      }
34161      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
34162      timers.insert(id, Arc::clone(&cell));
34163      std::thread::spawn(move || {
34164        let now = Instant::now();
34165        if deadline > now {
34166          std::thread::sleep(deadline - now);
34167        }
34168        if !cell.1.load(Ordering::SeqCst) {
34169          cell.0.lock().unwrap().wake_by_ref();
34170        }
34171      });
34172    }
34173
34174    fn cancel(&self, id: TimerId) {
34175      let removed = self.timers.lock().unwrap().remove(&id);
34176      if let Some(cell) = removed {
34177        cell.1.store(true, Ordering::SeqCst);
34178      }
34179    }
34180  }
34181
34182  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
34183  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
34184  /// per timer id), never fires on its own, and reports the controllable
34185  /// `live` flag (a killed stub plays a driver whose owning host context --
34186  /// napi env in the binding -- was torn down).
34187  struct ManualStubDriver {
34188    live: AtomicBool,
34189    registers: Mutex<Vec<(TimerId, Instant)>>,
34190    cancels: Mutex<Vec<TimerId>>,
34191    pending: Mutex<FxHashMap<TimerId, Waker>>,
34192    swept: AtomicBool,
34193  }
34194
34195  impl ManualStubDriver {
34196    fn new() -> Arc<Self> {
34197      Arc::new(Self {
34198        live: AtomicBool::new(true),
34199        registers: Mutex::new(Vec::new()),
34200        cancels: Mutex::new(Vec::new()),
34201        pending: Mutex::new(FxHashMap::default()),
34202        swept: AtomicBool::new(false),
34203      })
34204    }
34205
34206    fn kill(&self) {
34207      self.live.store(false, Ordering::SeqCst);
34208    }
34209  }
34210
34211  impl TimerDriver for ManualStubDriver {
34212    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
34213      self.registers.lock().unwrap().push((id, deadline));
34214      self.pending.lock().unwrap().insert(id, waker);
34215    }
34216
34217    fn cancel(&self, id: TimerId) {
34218      self.cancels.lock().unwrap().push(id);
34219      self.pending.lock().unwrap().remove(&id);
34220    }
34221
34222    fn is_live(&self) -> bool {
34223      self.live.load(Ordering::SeqCst)
34224    }
34225
34226    fn on_swept(&self) {
34227      // The binding shape: drain the pending map and wake everything armed
34228      // here so those sleeps re-poll onto the next live registrant.
34229      self.swept.store(true, Ordering::SeqCst);
34230      let wakers: Vec<Waker> = self
34231        .pending
34232        .lock()
34233        .unwrap()
34234        .drain()
34235        .map(|(_, w)| w)
34236        .collect();
34237      for waker in wakers {
34238        waker.wake();
34239      }
34240    }
34241  }
34242
34243  struct PanickingTimerDriver {
34244    swept: AtomicBool,
34245  }
34246
34247  impl TimerDriver for PanickingTimerDriver {
34248    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34249
34250    fn cancel(&self, _id: TimerId) {}
34251
34252    fn is_live(&self) -> bool {
34253      panic!("intentional timer-driver liveness panic");
34254    }
34255
34256    fn on_swept(&self) {
34257      self.swept.store(true, Ordering::SeqCst);
34258      panic!("intentional timer-driver sweep panic");
34259    }
34260  }
34261
34262  struct PanickingRegisterTimerDriver {
34263    registers: AtomicUsize,
34264    cancels: Mutex<Vec<TimerId>>,
34265    pending: Mutex<FxHashMap<TimerId, Waker>>,
34266  }
34267
34268  impl PanickingRegisterTimerDriver {
34269    fn new() -> Arc<Self> {
34270      Arc::new(Self {
34271        registers: AtomicUsize::new(0),
34272        cancels: Mutex::new(Vec::new()),
34273        pending: Mutex::new(FxHashMap::default()),
34274      })
34275    }
34276  }
34277
34278  impl TimerDriver for PanickingRegisterTimerDriver {
34279    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
34280      self.registers.fetch_add(1, Ordering::SeqCst);
34281      self.pending.lock().unwrap().insert(id, waker);
34282      panic!("intentional timer-driver register panic after arming");
34283    }
34284
34285    fn cancel(&self, id: TimerId) {
34286      self.cancels.lock().unwrap().push(id);
34287      self.pending.lock().unwrap().remove(&id);
34288    }
34289  }
34290
34291  struct PanicBeforeRetainingTimerDriver {
34292    registers: AtomicUsize,
34293  }
34294
34295  impl TimerDriver for PanicBeforeRetainingTimerDriver {
34296    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34297      self.registers.fetch_add(1, Ordering::SeqCst);
34298      panic!("intentional timer-driver register panic before retaining its waker");
34299    }
34300
34301    fn cancel(&self, _id: TimerId) {}
34302  }
34303
34304  struct PanickingCancelDropTimerDriver {
34305    cancels: Arc<AtomicUsize>,
34306    drops: Arc<AtomicUsize>,
34307  }
34308
34309  impl TimerDriver for PanickingCancelDropTimerDriver {
34310    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34311
34312    fn cancel(&self, _id: TimerId) {
34313      self.cancels.fetch_add(1, Ordering::SeqCst);
34314      panic!("intentional timer-driver cancel panic");
34315    }
34316  }
34317
34318  impl Drop for PanickingCancelDropTimerDriver {
34319    fn drop(&mut self) {
34320      self.drops.fetch_add(1, Ordering::SeqCst);
34321      panic!("intentional timer-driver destructor panic");
34322    }
34323  }
34324
34325  struct UnregisterLaterThenPanicTimerDriver {
34326    registry: Weak<TimerDriverRegistry>,
34327    later: Mutex<Option<TimerDriverId>>,
34328    registers: AtomicUsize,
34329  }
34330
34331  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
34332    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34333      self.registers.fetch_add(1, Ordering::SeqCst);
34334      let later = *self.later.lock().unwrap();
34335      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
34336        registry.unregister(later);
34337      }
34338      panic!("intentional earlier timer-driver panic after unregistering a later driver");
34339    }
34340
34341    fn cancel(&self, _id: TimerId) {}
34342  }
34343
34344  struct ReentrantLivenessDriver {
34345    registry: std::sync::Weak<TimerDriverRegistry>,
34346    registration: Mutex<Option<TimerDriverId>>,
34347  }
34348
34349  impl TimerDriver for ReentrantLivenessDriver {
34350    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34351
34352    fn cancel(&self, _id: TimerId) {}
34353
34354    fn is_live(&self) -> bool {
34355      let registration = *self.registration.lock().unwrap();
34356      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
34357        registry.unregister(id);
34358      }
34359      false
34360    }
34361  }
34362
34363  struct ReentrantMultiUnregisterTimerDriver {
34364    registry: std::sync::Weak<TimerDriverRegistry>,
34365    registrations: Mutex<Vec<TimerDriverId>>,
34366  }
34367
34368  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
34369    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34370
34371    fn cancel(&self, _id: TimerId) {}
34372
34373    fn is_live(&self) -> bool {
34374      if let Some(registry) = self.registry.upgrade() {
34375        for registration in self.registrations.lock().unwrap().iter().copied() {
34376          registry.unregister(registration);
34377        }
34378      }
34379      false
34380    }
34381  }
34382
34383  struct ReentrantSweepTimerDriver {
34384    registry: std::sync::Weak<TimerDriverRegistry>,
34385    registration: Mutex<Option<TimerDriverId>>,
34386    sweeps: AtomicUsize,
34387    removed_before_sweep: AtomicBool,
34388  }
34389
34390  impl TimerDriver for ReentrantSweepTimerDriver {
34391    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34392
34393    fn cancel(&self, _id: TimerId) {}
34394
34395    fn is_live(&self) -> bool {
34396      false
34397    }
34398
34399    fn on_swept(&self) {
34400      self.sweeps.fetch_add(1, Ordering::SeqCst);
34401      let registration = self.registration.lock().unwrap().take();
34402      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
34403        let state = registry
34404          .state
34405          .try_lock()
34406          .expect("the sweep callback must run outside the timer-driver registry lock");
34407        assert!(
34408          !state.entries.contains_key(&registration),
34409          "the dead driver must be unlinked before its sweep callback runs"
34410        );
34411        self.removed_before_sweep.store(true, Ordering::SeqCst);
34412        drop(state);
34413        // Mirrors JsTimerHostInner::evict: the sweep already removed this
34414        // registration, so explicit host cleanup must remain a cheap no-op.
34415        registry.unregister(registration);
34416      }
34417    }
34418  }
34419
34420  struct ReentrantDropDriver {
34421    registry: std::sync::Weak<TimerDriverRegistry>,
34422    fallback: TimerDriverId,
34423  }
34424
34425  impl TimerDriver for ReentrantDropDriver {
34426    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34427
34428    fn cancel(&self, _id: TimerId) {}
34429  }
34430
34431  impl Drop for ReentrantDropDriver {
34432    fn drop(&mut self) {
34433      if let Some(registry) = self.registry.upgrade() {
34434        registry.unregister(self.fallback);
34435      }
34436    }
34437  }
34438
34439  /// Waker whose `wake` just latches a flag -- observable evidence that a
34440  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
34441  #[derive(Default)]
34442  struct WakeFlag(AtomicBool);
34443
34444  impl std::task::Wake for WakeFlag {
34445    fn wake(self: Arc<Self>) {
34446      self.0.store(true, Ordering::SeqCst);
34447    }
34448  }
34449
34450  #[cfg(napi_runtime_os_threads)]
34451  fn multi_thread_executor(
34452    workers: usize,
34453    park_deadline: Option<Duration>,
34454    prefix: &str,
34455  ) -> Arc<MultiThreadExecutor> {
34456    let options = RuntimeOptions {
34457      flavor: RuntimeFlavor::MultiThread,
34458      worker_threads: workers,
34459      max_blocking_tasks: workers,
34460      thread_name_prefix: prefix.to_string(),
34461      park_deadline,
34462      drain_linger: DEFAULT_DRAIN_LINGER,
34463    };
34464    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
34465  }
34466
34467  /// Local driver registry holding exactly `driver` (the common single-host
34468  /// shape of the ported pre-registry tests).
34469  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
34470    let registry = Arc::new(TimerDriverRegistry::default());
34471    registry.register(driver);
34472    registry
34473  }
34474
34475  #[cfg(napi_runtime_os_threads)]
34476  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
34477    // The MultiThread arm never touches the driver registry; empty is fine.
34478    make_sleep(
34479      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
34480      &Arc::new(TimerDriverRegistry::default()),
34481      deadline,
34482    )
34483  }
34484
34485  #[cfg(napi_runtime_os_threads)]
34486  #[test]
34487  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
34488    use std::sync::mpsc;
34489
34490    let executor = multi_thread_executor(
34491      1,
34492      Some(Duration::from_secs(1)),
34493      "timer-registration-admission",
34494    );
34495    let (entered_tx, entered_rx) = mpsc::channel();
34496    let (release_tx, release_rx) = mpsc::channel();
34497    let registration_executor = Arc::clone(&executor);
34498    let registration = std::thread::spawn(move || {
34499      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
34500        *slot.borrow_mut() = Some(Box::new(move || {
34501          entered_tx.send(()).unwrap();
34502          release_rx.recv().unwrap();
34503        }));
34504      });
34505      registration_executor.register_timer(
34506        1,
34507        Instant::now() + Duration::from_secs(60),
34508        futures::task::noop_waker(),
34509        &Arc::new(AtomicBool::new(false)),
34510      );
34511    });
34512
34513    entered_rx
34514      .recv_timeout(Duration::from_secs(2))
34515      .expect("timer registration must pause after entering verdict admission");
34516    let verdict = executor.try_begin_deadlock_verdict();
34517    let admission_blocked_verdict = verdict.is_none();
34518    drop(verdict);
34519    release_tx.send(()).unwrap();
34520    registration.join().unwrap();
34521
34522    assert!(
34523      admission_blocked_verdict,
34524      "a timer registration must remain announced until its heap entry is visible"
34525    );
34526    assert!(
34527      executor.next_timer_deadline().is_some(),
34528      "the admitted registration must publish its timer after release"
34529    );
34530    executor.begin_shutdown();
34531    executor.wait_until_scheduler_idle();
34532  }
34533
34534  #[cfg(napi_runtime_os_threads)]
34535  #[derive(Clone, Copy, Debug)]
34536  enum TimerFireAdmissionPath {
34537    Timekeeper,
34538    Cooperative,
34539    Shutdown,
34540  }
34541
34542  #[cfg(napi_runtime_os_threads)]
34543  struct ScheduleAfterTimerWakeRelease {
34544    executor: Arc<MultiThreadExecutor>,
34545    runnable: Mutex<Option<Runnable>>,
34546    entered: std::sync::mpsc::Sender<()>,
34547    release: Mutex<std::sync::mpsc::Receiver<()>>,
34548    scheduled: std::sync::mpsc::Sender<()>,
34549  }
34550
34551  #[cfg(napi_runtime_os_threads)]
34552  impl ScheduleAfterTimerWakeRelease {
34553    fn publish(&self) {
34554      self.entered.send(()).unwrap();
34555      self
34556        .release
34557        .lock()
34558        .unwrap_or_else(std::sync::PoisonError::into_inner)
34559        .recv()
34560        .unwrap();
34561      let runnable = self
34562        .runnable
34563        .lock()
34564        .unwrap_or_else(std::sync::PoisonError::into_inner)
34565        .take();
34566      if let Some(runnable) = runnable {
34567        self.executor.schedule(runnable);
34568      }
34569      self.scheduled.send(()).unwrap();
34570    }
34571  }
34572
34573  #[cfg(napi_runtime_os_threads)]
34574  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
34575    fn wake(self: Arc<Self>) {
34576      self.publish();
34577    }
34578
34579    fn wake_by_ref(self: &Arc<Self>) {
34580      self.publish();
34581    }
34582  }
34583
34584  #[cfg(napi_runtime_os_threads)]
34585  fn insert_timer_for_fire_admission_test(
34586    executor: &MultiThreadExecutor,
34587    id: TimerId,
34588    deadline: Instant,
34589    waker: Waker,
34590    fired: Arc<AtomicBool>,
34591  ) {
34592    let mut inner = executor
34593      .timers
34594      .inner
34595      .lock()
34596      .unwrap_or_else(std::sync::PoisonError::into_inner);
34597    inner.entries.insert(id, HeapTimerEntry { waker, fired });
34598    inner.queue.push(deadline, id);
34599  }
34600
34601  #[cfg(napi_runtime_os_threads)]
34602  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
34603    use std::sync::mpsc;
34604
34605    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
34606    let (runnable, task) = async_task::spawn(async {}, |_| {});
34607    task.detach();
34608    let (entered_tx, entered_rx) = mpsc::channel();
34609    let (release_tx, release_rx) = mpsc::channel();
34610    let (scheduled_tx, scheduled_rx) = mpsc::channel();
34611    let fired = Arc::new(AtomicBool::new(false));
34612    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
34613      executor: Arc::clone(&executor),
34614      runnable: Mutex::new(Some(runnable)),
34615      entered: entered_tx,
34616      release: Mutex::new(release_rx),
34617      scheduled: scheduled_tx,
34618    }));
34619
34620    let fire_thread = match path {
34621      TimerFireAdmissionPath::Timekeeper => {
34622        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
34623        None
34624      }
34625      TimerFireAdmissionPath::Cooperative => {
34626        insert_timer_for_fire_admission_test(
34627          &executor,
34628          1,
34629          Instant::now(),
34630          waker,
34631          Arc::clone(&fired),
34632        );
34633        let fire_executor = Arc::clone(&executor);
34634        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
34635      }
34636      TimerFireAdmissionPath::Shutdown => {
34637        insert_timer_for_fire_admission_test(
34638          &executor,
34639          1,
34640          Instant::now() + Duration::from_secs(60),
34641          waker,
34642          Arc::clone(&fired),
34643        );
34644        let fire_executor = Arc::clone(&executor);
34645        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
34646      }
34647    };
34648
34649    // Hang detector on a guaranteed event that needs a freshly spawned
34650    // thread (timekeeper / fire thread) to get CPU -- generous for starved
34651    // CI runners.
34652    entered_rx
34653      .recv_timeout(Duration::from_secs(10))
34654      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
34655    assert!(
34656      executor.next_timer_deadline().is_none(),
34657      "{path:?} must exercise the remove-before-wake window"
34658    );
34659    assert!(
34660      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
34661      "{path:?} must retain admission while its waker is publishing"
34662    );
34663    let verdict = executor.try_begin_deadlock_verdict();
34664    let admission_blocked_verdict = verdict.is_none();
34665    drop(verdict);
34666
34667    release_tx.send(()).unwrap();
34668    scheduled_rx
34669      .recv_timeout(Duration::from_secs(10))
34670      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
34671    if let Some(fire_thread) = fire_thread {
34672      fire_thread.join().unwrap();
34673    }
34674    assert!(
34675      admission_blocked_verdict,
34676      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
34677    );
34678
34679    executor.begin_shutdown();
34680    executor.wait_until_scheduler_idle();
34681  }
34682
34683  #[cfg(napi_runtime_os_threads)]
34684  #[test]
34685  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
34686    for path in [
34687      TimerFireAdmissionPath::Timekeeper,
34688      TimerFireAdmissionPath::Cooperative,
34689      TimerFireAdmissionPath::Shutdown,
34690    ] {
34691      assert_timer_fire_path_holds_deadlock_admission(path);
34692    }
34693  }
34694
34695  #[cfg(napi_runtime_os_threads)]
34696  #[test]
34697  fn multi_thread_sleep_fires_from_foreign_block_on() {
34698    // The napi-caller shape: a NON-pool thread parks in
34699    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
34700    // the executor, so ONLY the dedicated timekeeper can fire the timer --
34701    // this is the timekeeper's existence test.
34702    use std::sync::mpsc;
34703
34704    let (done_tx, done_rx) = mpsc::channel();
34705    let runner = std::thread::spawn(move || {
34706      let executor = multi_thread_executor(2, None, "timer-foreign");
34707      let started = Instant::now();
34708      let mut future = std::pin::pin!(async {
34709        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
34710      });
34711      executor.block_on(future.as_mut());
34712      assert!(
34713        started.elapsed() >= Duration::from_millis(80),
34714        "the sleep must not resolve before its deadline"
34715      );
34716      let _ = done_tx.send(());
34717    });
34718
34719    match done_rx.recv_timeout(Duration::from_secs(10)) {
34720      Ok(()) => runner.join().unwrap(),
34721      Err(error) => {
34722        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
34723      }
34724    }
34725  }
34726
34727  #[cfg(napi_runtime_os_threads)]
34728  #[test]
34729  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
34730    // A long timer waits on the dedicated timer thread, leaving the sole
34731    // low-level test worker available for ordinary scheduler work.
34732    use std::sync::mpsc;
34733
34734    let (done_tx, done_rx) = mpsc::channel();
34735    let runner = std::thread::spawn(move || {
34736      let executor = multi_thread_executor(1, None, "timer-drainer");
34737
34738      // Arm a long timer through a real spawned task so the sole worker
34739      // returns to rayon afterwards (the watch-idle Debouncing shape).
34740      let sleep_exec = Arc::clone(&executor);
34741      let sleep_done = Arc::new(AtomicBool::new(false));
34742      let sleep_flag = Arc::clone(&sleep_done);
34743      let sched = Arc::clone(&executor);
34744      let (sleep_runnable, sleep_task) = async_task::spawn(
34745        async move {
34746          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34747          sleep_flag.store(true, Ordering::SeqCst);
34748        },
34749        move |r| sched.schedule(r),
34750      );
34751      executor.schedule(sleep_runnable);
34752      wait_until("the timekeeper parked on the 60s deadline", || {
34753        executor
34754          .timers
34755          .inner
34756          .lock()
34757          .unwrap()
34758          .timekeeper_parker
34759          .is_some()
34760      });
34761
34762      // Queued work must run NOW, not in 60s.
34763      let counter = Arc::new(AtomicUsize::new(0));
34764      let mut tasks = Vec::new();
34765      for _ in 0..32 {
34766        let counter = Arc::clone(&counter);
34767        let sched = Arc::clone(&executor);
34768        let (runnable, task) = async_task::spawn(
34769          async move {
34770            counter.fetch_add(1, Ordering::SeqCst);
34771          },
34772          move |r| sched.schedule(r),
34773        );
34774        executor.schedule(runnable);
34775        tasks.push(task);
34776      }
34777      for task in tasks {
34778        futures::executor::block_on(task);
34779      }
34780      assert_eq!(counter.load(Ordering::SeqCst), 32);
34781      assert!(
34782        !sleep_done.load(Ordering::SeqCst),
34783        "the 60s timer must still be pending: the work ran DURING the timer wait"
34784      );
34785      // Cancel the long sleep so the executor tears down promptly.
34786      executor.shutdown_timers();
34787      futures::executor::block_on(sleep_task);
34788      let _ = done_tx.send(());
34789    });
34790
34791    match done_rx.recv_timeout(Duration::from_secs(15)) {
34792      Ok(()) => runner.join().unwrap(),
34793      Err(error) => {
34794        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34795      }
34796    }
34797  }
34798
34799  #[cfg(napi_runtime_os_threads)]
34800  #[test]
34801  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34802    let executor = multi_thread_executor(2, None, "timer-lazy-start");
34803    assert!(
34804      executor.timers.thread.lock().unwrap().is_none(),
34805      "constructing a runtime without timers must not create an extra OS thread"
34806    );
34807
34808    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34809    assert!(
34810      executor.timers.thread.lock().unwrap().is_none(),
34811      "an unpolled sleep has not registered a deadline"
34812    );
34813    let waker = futures::task::noop_waker();
34814    let mut cx = Context::from_waker(&waker);
34815    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34816    assert!(
34817      executor.timers.thread.lock().unwrap().is_some(),
34818      "the first timer registration must start the dedicated service"
34819    );
34820  }
34821
34822  #[cfg(napi_runtime_os_threads)]
34823  #[test]
34824  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34825    use std::sync::mpsc;
34826
34827    let executor = multi_thread_executor(1, None, "timer-closed-start");
34828    executor.shutdown_timers();
34829    let handle_guard = executor.timers.thread.lock().unwrap();
34830    let ensure_executor = Arc::clone(&executor);
34831    let (completed_tx, completed_rx) = mpsc::channel();
34832    let ensure = std::thread::spawn(move || {
34833      ensure_executor.timers.ensure_started().unwrap();
34834      completed_tx.send(()).unwrap();
34835    });
34836
34837    // Hang detector on a freshly spawned thread getting CPU -- generous for
34838    // starved CI runners (a real regression blocks forever on the held lock).
34839    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34840      "ensure_started consulted or joined the timer handle before observing the closed heap",
34841    );
34842    drop(handle_guard);
34843    ensure.join().unwrap();
34844  }
34845
34846  #[cfg(napi_runtime_os_threads)]
34847  #[test]
34848  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34849    const TIMERS: u64 = 4096;
34850
34851    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34852    let base = Instant::now() + Duration::from_secs(3600);
34853    for id in 0..TIMERS {
34854      executor.register_timer(
34855        id,
34856        base + Duration::from_secs(id),
34857        futures::task::noop_waker(),
34858        &Arc::new(AtomicBool::new(false)),
34859      );
34860    }
34861    {
34862      let inner = executor.timers.inner.lock().unwrap();
34863      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34864      assert_eq!(inner.queue.len(), inner.entries.len());
34865    }
34866
34867    // Preserve the earliest node while removing every later node. A lazy
34868    // BinaryHeap would retain all 4095 tombstones until that first deadline.
34869    for id in 1..TIMERS {
34870      executor.cancel_timer(id);
34871    }
34872    {
34873      let inner = executor.timers.inner.lock().unwrap();
34874      assert_eq!(inner.entries.len(), 1);
34875      assert_eq!(inner.queue.len(), 1);
34876      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34877    }
34878
34879    executor.cancel_timer(0);
34880    {
34881      let inner = executor.timers.inner.lock().unwrap();
34882      assert!(inner.entries.is_empty());
34883      assert!(inner.queue.is_empty());
34884    }
34885    executor.shutdown_timers();
34886    executor.timers.join();
34887  }
34888
34889  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34890  #[test]
34891  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34892    let executor = multi_thread_executor(2, None, "timer-start-failure");
34893    executor
34894      .timers
34895      .fail_next_start
34896      .store(true, Ordering::SeqCst);
34897
34898    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34899    let waker = Waker::from(Arc::clone(&wake_count));
34900    let baseline_waker_refs = Arc::strong_count(&wake_count);
34901    let mut cx = Context::from_waker(&waker);
34902    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34903
34904    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34905    assert!(
34906      failure.is_err(),
34907      "the injected timer-thread start failure must panic the poll"
34908    );
34909    {
34910      let inner = executor.timers.inner.lock().unwrap();
34911      assert!(
34912        inner.entries.is_empty(),
34913        "a failed start must not retain a timer entry"
34914      );
34915      assert!(
34916        inner.queue.is_empty(),
34917        "a failed start must not retain a stale heap node"
34918      );
34919      assert!(
34920        inner.timekeeper_parker.is_none(),
34921        "a failed start must not publish a timekeeper parker"
34922      );
34923    }
34924    assert!(
34925      executor.timers.thread.lock().unwrap().is_none(),
34926      "a failed start must not retain a thread handle"
34927    );
34928    assert_eq!(
34929      Arc::strong_count(&wake_count),
34930      baseline_waker_refs,
34931      "a failed start must release the cloned timer waker"
34932    );
34933
34934    assert!(
34935      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34936      "the same Sleep must be safely retryable after a transient start failure"
34937    );
34938    {
34939      let inner = executor.timers.inner.lock().unwrap();
34940      assert_eq!(
34941        inner.entries.len(),
34942        1,
34943        "the retry must publish exactly one timer entry"
34944      );
34945      assert_eq!(
34946        inner.queue.len(),
34947        1,
34948        "the retry must publish exactly one heap node"
34949      );
34950    }
34951    assert!(
34952      executor.timers.thread.lock().unwrap().is_some(),
34953      "the retry must start the dedicated timekeeper"
34954    );
34955
34956    drop(sleep);
34957    assert!(
34958      executor.timers.inner.lock().unwrap().entries.is_empty(),
34959      "dropping the retried Sleep must cancel its registration"
34960    );
34961    executor.shutdown_timers();
34962    executor.wait_until_scheduler_idle();
34963    assert!(
34964      executor.timers.thread.lock().unwrap().is_none(),
34965      "shutdown after a recovered start failure must join the timekeeper"
34966    );
34967  }
34968
34969  #[cfg(napi_runtime_os_threads)]
34970  #[test]
34971  fn pending_timer_preserves_every_configured_rayon_worker() {
34972    use std::sync::mpsc;
34973
34974    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34975    let waker = futures::task::noop_waker();
34976    let mut cx = Context::from_waker(&waker);
34977    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34978    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34979    wait_until("the dedicated timer thread to park", || {
34980      executor
34981        .timers
34982        .inner
34983        .lock()
34984        .unwrap()
34985        .timekeeper_parker
34986        .is_some()
34987    });
34988
34989    let released = Arc::new((Mutex::new(false), Condvar::new()));
34990    let (started_tx, started_rx) = mpsc::channel();
34991    let (completed_tx, completed_rx) = mpsc::channel();
34992    for _ in 0..2 {
34993      let released = Arc::clone(&released);
34994      let started_tx = started_tx.clone();
34995      let completed_tx = completed_tx.clone();
34996      executor.pool.spawn(move || {
34997        started_tx.send(()).unwrap();
34998        let (lock, changed) = &*released;
34999        let mut released = lock
35000          .lock()
35001          .unwrap_or_else(std::sync::PoisonError::into_inner);
35002        while !*released {
35003          released = changed
35004            .wait(released)
35005            .unwrap_or_else(std::sync::PoisonError::into_inner);
35006        }
35007        completed_tx.send(()).unwrap();
35008      });
35009    }
35010    drop(started_tx);
35011    drop(completed_tx);
35012
35013    let first = started_rx.recv_timeout(Duration::from_secs(2));
35014    let second = started_rx.recv_timeout(Duration::from_secs(2));
35015    {
35016      let (lock, changed) = &*released;
35017      *lock
35018        .lock()
35019        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
35020      changed.notify_all();
35021    }
35022    drop(sleep);
35023    for _ in 0..2 {
35024      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
35025    }
35026
35027    assert!(
35028      first.is_ok(),
35029      "the first configured Rayon worker did not start"
35030    );
35031    assert!(
35032      second.is_ok(),
35033      "a pending timer consumed one of the two configured Rayon workers"
35034    );
35035  }
35036
35037  #[cfg(napi_runtime_os_threads)]
35038  #[test]
35039  fn timekeeper_never_enters_a_blocking_closure() {
35040    use std::{
35041      sync::{Barrier, mpsc},
35042      time::Duration,
35043    };
35044
35045    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
35046    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35047    let waker = Waker::from(Arc::clone(&wake_count));
35048    let mut cx = Context::from_waker(&waker);
35049    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
35050    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35051    wait_until(
35052      "the timekeeper parked before blocking work was submitted",
35053      || {
35054        executor
35055          .timers
35056          .inner
35057          .lock()
35058          .unwrap()
35059          .timekeeper_parker
35060          .is_some()
35061      },
35062    );
35063
35064    let release = Arc::new(Barrier::new(2));
35065    let blocker_release = Arc::clone(&release);
35066    let (started_tx, started_rx) = mpsc::sync_channel(1);
35067    let blocking = executor.schedule_blocking(move || {
35068      started_tx.send(()).unwrap();
35069      blocker_release.wait();
35070    });
35071    // Hang detector (the closure start needs a worker to get CPU) --
35072    // generous for starved CI runners.
35073    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
35074
35075    wait_until(
35076      "the timer waker to fire while the Rayon worker is blocked",
35077      || wake_count.0.load(Ordering::SeqCst) != 0,
35078    );
35079    release.wait();
35080    futures::executor::block_on(blocking).unwrap();
35081    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
35082  }
35083
35084  #[cfg(napi_runtime_os_threads)]
35085  #[test]
35086  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
35087    use std::sync::mpsc;
35088
35089    struct PendingEntered(Option<mpsc::Sender<()>>);
35090
35091    impl Future for PendingEntered {
35092      type Output = ();
35093
35094      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
35095        if let Some(entered) = self.0.take() {
35096          entered.send(()).unwrap();
35097        }
35098        Poll::Pending
35099      }
35100    }
35101
35102    let options = RuntimeOptions {
35103      flavor: RuntimeFlavor::MultiThread,
35104      worker_threads: 2,
35105      max_blocking_tasks: 1,
35106      thread_name_prefix: "nested-timer-wake".to_string(),
35107      park_deadline: None,
35108      drain_linger: DEFAULT_DRAIN_LINGER,
35109    };
35110    let executor =
35111      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
35112
35113    let timer_exec = Arc::clone(&executor);
35114    let (timer_tx, timer_rx) = mpsc::channel();
35115    let timer_scheduler = Arc::clone(&executor);
35116    let (timer_runnable, timer_task) = async_task::spawn(
35117      async move {
35118        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
35119        timer_tx.send(()).unwrap();
35120      },
35121      move |runnable| timer_scheduler.schedule(runnable),
35122    );
35123    executor.schedule(timer_runnable);
35124    wait_until(
35125      "the dedicated timekeeper parked before nested block_on",
35126      || {
35127        executor
35128          .timers
35129          .inner
35130          .lock()
35131          .unwrap()
35132          .timekeeper_parker
35133          .is_some()
35134      },
35135    );
35136
35137    let (entered_tx, entered_rx) = mpsc::channel();
35138    let nested_exec = Arc::clone(&executor);
35139    let nested_scheduler = Arc::clone(&executor);
35140    let (nested_runnable, nested_task) = async_task::spawn(
35141      async move {
35142        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
35143        let outcome = nested_exec.block_on(pending.as_mut());
35144        drop(pending);
35145        assert_eq!(
35146          outcome,
35147          BlockOnOutcome::Stopped,
35148          "shutdown must stop the raw nested block_on driver"
35149        );
35150      },
35151      move |runnable| nested_scheduler.schedule(runnable),
35152    );
35153    executor.schedule(nested_runnable);
35154    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
35155
35156    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
35157    let (release_tx, release_rx) = mpsc::channel();
35158    let blocking = executor.schedule_blocking(move || {
35159      blocking_started_tx.send(()).unwrap();
35160      release_rx.recv().unwrap();
35161    });
35162    blocking_started_rx
35163      .recv_timeout(Duration::from_secs(2))
35164      .expect("a general worker must service blocking work");
35165    timer_rx
35166      .recv_timeout(Duration::from_secs(2))
35167      .expect("the timer wake must reach the nested cooperative driver");
35168
35169    release_tx.send(()).unwrap();
35170    futures::executor::block_on(blocking).unwrap();
35171    executor.begin_shutdown();
35172    assert_eq!(
35173      futures::executor::block_on(nested_task.fallible()),
35174      Some(())
35175    );
35176    futures::executor::block_on(timer_task);
35177  }
35178
35179  #[cfg(napi_runtime_os_threads)]
35180  #[test]
35181  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
35182    // Task requirement: a NEW earliest deadline (heap peek changes) must
35183    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
35184    // timekeeper far in the future; the 60ms timer must still fire on time.
35185    use std::sync::mpsc;
35186
35187    let (done_tx, done_rx) = mpsc::channel();
35188    let runner = std::thread::spawn(move || {
35189      let executor = multi_thread_executor(2, None, "timer-rearm");
35190
35191      let long_exec = Arc::clone(&executor);
35192      let long_done = Arc::new(AtomicBool::new(false));
35193      let long_flag = Arc::clone(&long_done);
35194      let sched = Arc::clone(&executor);
35195      let (long_runnable, long_task) = async_task::spawn(
35196        async move {
35197          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
35198          long_flag.store(true, Ordering::SeqCst);
35199        },
35200        move |r| sched.schedule(r),
35201      );
35202      executor.schedule(long_runnable);
35203      wait_until("the timekeeper parked on the 60s sentinel", || {
35204        executor
35205          .timers
35206          .inner
35207          .lock()
35208          .unwrap()
35209          .timekeeper_parker
35210          .is_some()
35211      });
35212
35213      let started = Instant::now();
35214      let mut future = std::pin::pin!(async {
35215        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
35216      });
35217      executor.block_on(future.as_mut());
35218      let elapsed = started.elapsed();
35219      assert!(
35220        elapsed >= Duration::from_millis(60),
35221        "fired before its deadline: {elapsed:?}"
35222      );
35223      assert!(
35224        !long_done.load(Ordering::SeqCst),
35225        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
35226         timekeeper, not by the sentinel expiring"
35227      );
35228      executor.shutdown_timers();
35229      futures::executor::block_on(long_task);
35230      let _ = done_tx.send(());
35231    });
35232
35233    match done_rx.recv_timeout(Duration::from_secs(15)) {
35234      Ok(()) => runner.join().unwrap(),
35235      Err(error) => panic!(
35236        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
35237      ),
35238    }
35239  }
35240
35241  #[cfg(napi_runtime_os_threads)]
35242  #[test]
35243  fn multi_thread_sleep_cancels_registration_on_drop() {
35244    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
35245    // its heap registration (no waker fired later, heap drains empty).
35246    let executor = multi_thread_executor(1, None, "timer-cancel");
35247    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35248    let waker = Waker::from(Arc::clone(&wake_count));
35249    let mut cx = Context::from_waker(&waker);
35250
35251    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
35252    // starved runner (a past-due Sleep would resolve inline instead of
35253    // registering, failing the assert below for the wrong reason).
35254    let deadline = Instant::now() + Duration::from_millis(250);
35255    let mut sleep = heap_sleep(&executor, deadline);
35256    assert!(
35257      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
35258      "must register, not resolve"
35259    );
35260    assert!(
35261      executor.next_timer_deadline().is_some(),
35262      "the registration must be in the heap"
35263    );
35264    drop(sleep);
35265    assert!(
35266      executor.next_timer_deadline().is_none(),
35267      "cancel-on-drop must retire the heap registration"
35268    );
35269    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
35270    // cancelled timer must not wake. The negative assert is structural once
35271    // the heap is empty; waiting until the wall clock is genuinely past the
35272    // deadline keeps the ghost-fire probe honest.
35273    while Instant::now() < deadline + Duration::from_millis(25) {
35274      std::thread::sleep(Duration::from_millis(10));
35275    }
35276    executor.fire_due_timers();
35277    assert_eq!(
35278      wake_count.0.load(Ordering::SeqCst),
35279      0,
35280      "a dropped Sleep's waker must never fire"
35281    );
35282  }
35283
35284  #[cfg(napi_runtime_os_threads)]
35285  #[test]
35286  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
35287    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
35288    let waker = futures::task::noop_waker();
35289    let mut cx = Context::from_waker(&waker);
35290    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
35291
35292    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35293    wait_until(
35294      "the timekeeper parked on the timer that will be cancelled",
35295      || {
35296        executor
35297          .timers
35298          .inner
35299          .lock()
35300          .unwrap()
35301          .timekeeper_parker
35302          .is_some()
35303      },
35304    );
35305
35306    drop(sleep);
35307    wait_until(
35308      "the cancelled timer's timekeeper returned to its idle wait",
35309      || {
35310        executor.next_timer_deadline().is_none()
35311          && executor
35312            .timers
35313            .inner
35314            .lock()
35315            .unwrap()
35316            .timekeeper_parker
35317            .is_none()
35318      },
35319    );
35320  }
35321
35322  #[cfg(napi_runtime_os_threads)]
35323  #[test]
35324  fn timer_waker_replacement_drops_outside_heap_lock() {
35325    use std::sync::mpsc;
35326
35327    struct DropOtherTimer(Mutex<Option<Sleep>>);
35328
35329    #[expect(
35330      clippy::manual_noop_waker,
35331      reason = "the no-op wake type has a reentrant Drop used by this regression"
35332    )]
35333    impl std::task::Wake for DropOtherTimer {
35334      fn wake(self: Arc<Self>) {}
35335    }
35336
35337    impl Drop for DropOtherTimer {
35338      fn drop(&mut self) {
35339        drop(self.0.lock().unwrap().take());
35340      }
35341    }
35342
35343    let (done_tx, done_rx) = mpsc::channel();
35344    let runner = std::thread::spawn(move || {
35345      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
35346      let deadline = Instant::now() + Duration::from_secs(120);
35347      let mut other = heap_sleep(&executor, deadline);
35348      let noop = futures::task::noop_waker();
35349      let mut noop_cx = Context::from_waker(&noop);
35350      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
35351
35352      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
35353      let mut primary = heap_sleep(&executor, deadline);
35354      let mut old_cx = Context::from_waker(&old);
35355      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
35356      drop(old);
35357
35358      let replacement = futures::task::noop_waker();
35359      let mut replacement_cx = Context::from_waker(&replacement);
35360      assert!(
35361        Pin::new(&mut primary)
35362          .poll(&mut replacement_cx)
35363          .is_pending()
35364      );
35365      drop(primary);
35366      executor.shutdown_timers();
35367      done_tx.send(()).unwrap();
35368    });
35369    // Hang detector on a freshly spawned runner thread -- generous for
35370    // starved CI runners (a real regression deadlocks forever).
35371    done_rx
35372      .recv_timeout(Duration::from_secs(10))
35373      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
35374    runner.join().unwrap();
35375  }
35376
35377  #[cfg(napi_runtime_os_threads)]
35378  #[test]
35379  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
35380    use std::sync::mpsc;
35381
35382    struct DropFinalExecutorOnWake {
35383      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
35384      completed: mpsc::Sender<()>,
35385    }
35386
35387    impl std::task::Wake for DropFinalExecutorOnWake {
35388      fn wake(self: Arc<Self>) {
35389        let executor = self
35390          .executor
35391          .lock()
35392          .unwrap_or_else(std::sync::PoisonError::into_inner)
35393          .take()
35394          .expect("the timer waker must own the final executor reference");
35395        assert_eq!(
35396          ON_TIMER_THREAD.with(std::cell::Cell::get),
35397          Some(executor.id),
35398          "the dedicated timer thread must deliver the timer wake"
35399        );
35400        drop(executor);
35401        self
35402          .completed
35403          .send(())
35404          .expect("the test receiver must outlive the timer-thread callback");
35405      }
35406    }
35407
35408    let executor = multi_thread_executor(1, None, "timer-final-arc");
35409    let (completed_tx, completed_rx) = mpsc::channel();
35410    // The deadline must not pass before this thread has dropped its Arc: a
35411    // timer that fires first (a starved runner, or a timekeeper that starts
35412    // late) leaves `timekeeper_parker` at None for good, and the wait below
35413    // could never succeed. Arm it far out and pull it in by hand once the
35414    // timekeeper is asleep on it.
35415    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(3600));
35416    {
35417      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
35418        executor: Mutex::new(Some(Arc::clone(&executor))),
35419        completed: completed_tx,
35420      }));
35421      let mut cx = Context::from_waker(&waker);
35422      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35423    }
35424    // SLEEPING, not just published: a registration permit left over from
35425    // `poll` would otherwise let the timekeeper loop once more and fire the
35426    // pulled-in deadline while this thread still holds an Arc.
35427    wait_until(
35428      "the timer thread parked before owning the final executor Arc",
35429      || {
35430        executor
35431          .timers
35432          .inner
35433          .lock()
35434          .unwrap()
35435          .timekeeper_parker
35436          .as_ref()
35437          .is_some_and(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
35438      },
35439    );
35440    let parker = {
35441      let mut inner = executor.timers.inner.lock().unwrap();
35442      let (_, id) = inner.queue.peek().expect("the sleep's timer must be armed");
35443      inner.queue.remove(id);
35444      inner.queue.push(Instant::now(), id);
35445      Arc::clone(inner.timekeeper_parker.as_ref().unwrap())
35446    };
35447
35448    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
35449    // heap waker owns the final direct executor Arc and releases it from the
35450    // timer thread while `TimerHeap::drop` still owns that thread's handle.
35451    drop(executor);
35452    parker.unpark();
35453    // Hang detector: the fire needs only the unparked timer thread to run --
35454    // generous for starved CI runners.
35455    completed_rx
35456      .recv_timeout(Duration::from_secs(10))
35457      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
35458  }
35459
35460  #[cfg(not(target_family = "wasm"))]
35461  #[test]
35462  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
35463    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
35464    // pending wakers -- a `watcher.close()` awaiting a
35465    // debounce sleep must complete, not hang. Also pins fire-on-register for
35466    // a Sleep polled after shutdown.
35467    use std::sync::mpsc;
35468
35469    let (done_tx, done_rx) = mpsc::channel();
35470    let runner = std::thread::spawn(move || {
35471      let controller = Arc::new(RuntimeController::new());
35472      // Default options: MultiThread on native.
35473      let backend = controller.backend();
35474      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
35475        panic!("default native backend must be MultiThread");
35476      };
35477      let executor = Arc::downgrade(executor);
35478      let mut late = heap_sleep(
35479        &executor
35480          .upgrade()
35481          .expect("the initial executor must be live"),
35482        Instant::now() + Duration::from_secs(120),
35483      );
35484      drop(backend);
35485
35486      let (parked_tx, parked_rx) = mpsc::channel();
35487      let block_controller = Arc::clone(&controller);
35488      let block_exec = Weak::clone(&executor);
35489      let blocker = std::thread::spawn(move || {
35490        let mut future = std::pin::pin!(async {
35491          let executor = block_exec
35492            .upgrade()
35493            .expect("the runtime must remain live while blocking");
35494          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
35495          drop(executor);
35496          parked_tx.send(()).unwrap();
35497          sleep.await;
35498        });
35499        block_controller.block_on(future.as_mut());
35500      });
35501      parked_rx.recv().unwrap();
35502      wait_until("the 120s sleep registered in the heap", || {
35503        executor
35504          .upgrade()
35505          .is_some_and(|executor| executor.next_timer_deadline().is_some())
35506      });
35507
35508      // The acceptance surface: controller shutdown while a sleep is armed.
35509      controller
35510        .shutdown()
35511        .expect("runtime shutdown must succeed");
35512      blocker
35513        .join()
35514        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
35515
35516      // A Sleep polled AFTER shutdown resolves immediately instead of
35517      // registering with a dead heap.
35518      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
35519      let mut cx = Context::from_waker(&waker);
35520      assert!(
35521        Pin::new(&mut late).poll(&mut cx).is_ready(),
35522        "a Sleep polled after shutdown must fire early, never park a closing runtime"
35523      );
35524      let _ = done_tx.send(());
35525    });
35526
35527    match done_rx.recv_timeout(Duration::from_secs(10)) {
35528      Ok(()) => runner.join().unwrap(),
35529      Err(error) => {
35530        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
35531      }
35532    }
35533  }
35534
35535  #[cfg(napi_runtime_os_threads)]
35536  #[test]
35537  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
35538    // Timer-wait/deadline invariant: the park deadline is armed
35539    // SHORTER than the next timer deadline. The cooperative driver awaiting
35540    // the sleep sits with ZERO
35541    // executor progress for several full deadline windows -- a timer wait is
35542    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
35543    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
35544    // also allows the parked driver to race the dedicated timekeeper and fire
35545    // the timer itself, pinning the cooperative timer-bounded park.
35546    use std::sync::mpsc;
35547
35548    let (done_tx, done_rx) = mpsc::channel();
35549    let runner = std::thread::spawn(move || {
35550      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
35551
35552      let task_exec = Arc::clone(&executor);
35553      let body = async move {
35554        let started = Instant::now();
35555        let mut inner = std::pin::pin!(async {
35556          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
35557        });
35558        // Re-entrant block_on from the pool worker: the cooperative park.
35559        task_exec.block_on(inner.as_mut());
35560        assert!(started.elapsed() >= Duration::from_millis(250));
35561      };
35562      let sched = Arc::clone(&executor);
35563      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
35564      executor.schedule(runnable);
35565
35566      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
35567      // by run_runnable's catch_unwind), so the task would never complete and
35568      // the bounded harness below would report the failure.
35569      futures::executor::block_on(task);
35570      // Teardown inside the bounded window: this test was one of the silent
35571      // wedges on the Windows CI runners with every assertion already
35572      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
35573      // (a timer was polled, so the timekeeper thread is live) -- exactly
35574      // the kind of phase that must fail loudly rather than hang.
35575      drop(executor);
35576      let _ = done_tx.send(());
35577    });
35578
35579    match done_rx.recv_timeout(Duration::from_secs(10)) {
35580      Ok(()) => join_within(
35581        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
35582        runner,
35583        Duration::from_secs(30),
35584      ),
35585      Err(error) => {
35586        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
35587      }
35588    }
35589  }
35590
35591  #[cfg(napi_runtime_os_threads)]
35592  #[test]
35593  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
35594    // Timer-registration/verdict race, deterministic via the verdict hook: a
35595    // timer registered AFTER a deadline park was committed bumps no progress
35596    // counter by design, lands after the permit and queued-work re-checks, and
35597    // is invisible to the fingerprint. The heap re-check must recognize that
35598    // guaranteed future wake, veto the panic, and let the driver re-park
35599    // timer-bounded until the fire's task-wake completes the future.
35600    use std::sync::mpsc;
35601
35602    // A raw Sleep the hook registers is woken by this waker, which releases
35603    // the awaited future's gate.
35604    struct GateWake {
35605      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
35606    }
35607    impl std::task::Wake for GateWake {
35608      fn wake(self: Arc<Self>) {
35609        let gate = self.gate.lock().unwrap().take();
35610        if let Some(gate) = gate {
35611          let _ = gate.send(23);
35612        }
35613      }
35614    }
35615
35616    let (done_tx, done_rx) = mpsc::channel();
35617    let runner = std::thread::spawn(move || {
35618      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
35619      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
35620      // replacement drainer behind our back; only the verdict's heap re-check
35621      // can save this park.
35622      executor
35623        .active_drainers
35624        .store(executor.max_drainers, Ordering::SeqCst);
35625
35626      // Completion condition: the timer registered inside the hook fires and
35627      // its waker (the gate) releases the awaited future.
35628      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
35629      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
35630
35631      let hook_exec = Arc::clone(&executor);
35632      let hook_gate = Arc::clone(&gate_tx);
35633      // The Sleep must outlive the hook (drop cancels): park it in a slot the
35634      // runner thread later cleans up.
35635      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
35636      let hook_sleep_slot = Arc::clone(&parked_sleep);
35637      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
35638        *slot.borrow_mut() = Some(Box::new(move || {
35639          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
35640          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
35641          let mut cx = Context::from_waker(&waker);
35642          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35643          *hook_sleep_slot.lock().unwrap() = Some(sleep);
35644        }));
35645      });
35646
35647      // The test-only on-pool marker runs the cooperative branch and its
35648      // thread-local hook on THIS thread.
35649      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
35650      let mut output = None;
35651      {
35652        let mut future = std::pin::pin!(async {
35653          output = Some(gate_rx.await.unwrap());
35654        });
35655        executor.block_on(future.as_mut());
35656      }
35657      // Clear the hook slot BEFORE any assert can panic: this hook captures
35658      // an executor Arc and THIS executor's timekeeper is live (a timer was
35659      // polled), so a leftover hook dropped by a TLS destructor would join
35660      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
35661      // RULE at `WorkerTlsDropAction`).
35662      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
35663      assert_eq!(
35664        output,
35665        Some(23),
35666        "the edge-registered timer must have fired and released the gate"
35667      );
35668      executor.active_drainers.store(0, Ordering::SeqCst);
35669      // Teardown inside the bounded window; includes the timekeeper join
35670      // (a timer was polled). See `join_within`.
35671      drop(executor);
35672      let _ = done_tx.send(());
35673    });
35674
35675    match done_rx.recv_timeout(Duration::from_secs(10)) {
35676      Ok(()) => join_within(
35677        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
35678        runner,
35679        Duration::from_secs(30),
35680      ),
35681      Err(error) => panic!(
35682        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
35683      ),
35684    }
35685  }
35686
35687  #[cfg(panic = "unwind")]
35688  #[test]
35689  fn current_thread_sleep_without_host_driver_fails_loud() {
35690    // Missing-driver invariant: a CT runtime without a registered host driver
35691    // must fail LOUD with this exact diagnostic -- never a silent never-firing
35692    // debounce or a misleading certain-deadlock panic later.
35693    let metrics = Arc::new(RuntimeMetrics::default());
35694    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35695    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
35696
35697    let payload = catch_unwind(AssertUnwindSafe(|| {
35698      let _sleep = make_sleep(
35699        &backend,
35700        &Arc::new(TimerDriverRegistry::default()),
35701        Instant::now() + Duration::from_millis(10),
35702      );
35703    }))
35704    .expect_err("sleep_until on CurrentThread without a driver must panic");
35705    let message = payload
35706      .downcast_ref::<String>()
35707      .cloned()
35708      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
35709      .expect("panic payload must be a message");
35710    assert!(
35711      message.contains("CurrentThread runtime has no live timer driver registered"),
35712      "the diagnostic must name the missing driver, got: {message}"
35713    );
35714  }
35715
35716  #[test]
35717  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
35718    // Registry semantics: all-live snapshots with newest-live compatibility
35719    // selection, dead entries swept on contact, explicit unregister, and a
35720    // dead-only registry reading as NO driver.
35721    let registry = TimerDriverRegistry::default();
35722    let a = ManualStubDriver::new();
35723    let b = ManualStubDriver::new();
35724    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
35725    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35726    assert_ne!(a_id, b_id, "registration handles must be distinct");
35727    assert_eq!(
35728      registry.current().map(|(id, _)| id),
35729      Some(b_id),
35730      "newest live registrant serves"
35731    );
35732    assert!(registry.has_live_driver());
35733
35734    // A dead newest (stub for: its owning env torn down) is skipped AND
35735    // swept; selection falls back to the older live registrant.
35736    b.kill();
35737    assert_eq!(
35738      registry.current().map(|(id, _)| id),
35739      Some(a_id),
35740      "dead newest must be skipped"
35741    );
35742    // The sweep is permanent: resurrecting the flag must not bring the
35743    // evicted registration back.
35744    b.live.store(true, Ordering::SeqCst);
35745    assert_eq!(
35746      registry.current().map(|(id, _)| id),
35747      Some(a_id),
35748      "swept entries stay gone"
35749    );
35750
35751    // Explicit unregister (the binding's env-cleanup path) empties the
35752    // registry; empty and dead-only both read as NO live driver.
35753    registry.unregister(a_id);
35754    assert!(registry.current().is_none());
35755    assert!(
35756      !registry.has_live_driver(),
35757      "an emptied registry must not report a live driver"
35758    );
35759  }
35760
35761  #[cfg(panic = "unwind")]
35762  #[test]
35763  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35764    let registry = TimerDriverRegistry::default();
35765    let fallback = ManualStubDriver::new();
35766    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35767    let panicking = Arc::new(PanickingTimerDriver {
35768      swept: AtomicBool::new(false),
35769    });
35770    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35771
35772    assert_eq!(
35773      registry.current().map(|(id, _)| id),
35774      Some(fallback_id),
35775      "a panicking liveness callback must be treated as a dead driver"
35776    );
35777    assert!(
35778      panicking.swept.load(Ordering::SeqCst),
35779      "a panicking liveness callback must still run its contained sweep hook"
35780    );
35781    assert!(
35782      registry.has_live_driver(),
35783      "the healthy fallback must remain selectable"
35784    );
35785  }
35786
35787  #[cfg(napi_runtime_os_threads)]
35788  #[test]
35789  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35790    use std::{sync::mpsc, time::Duration};
35791
35792    let registry = Arc::new(TimerDriverRegistry::default());
35793    let fallback = ManualStubDriver::new();
35794    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35795    let reentrant = Arc::new(ReentrantLivenessDriver {
35796      registry: Arc::downgrade(&registry),
35797      registration: Mutex::new(None),
35798    });
35799    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35800    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35801
35802    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35803    let selection_registry = Arc::clone(&registry);
35804    let selection_thread = std::thread::spawn(move || {
35805      selection_tx
35806        .send(selection_registry.current().map(|(id, _)| id))
35807        .unwrap();
35808    });
35809    assert_eq!(
35810      selection_rx
35811        .recv_timeout(Duration::from_secs(10))
35812        .expect("is_live re-entry deadlocked"),
35813      Some(fallback_id)
35814    );
35815    selection_thread.join().unwrap();
35816
35817    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35818      registry: Arc::downgrade(&registry),
35819      fallback: fallback_id,
35820    });
35821    let drop_id = registry.register(Arc::clone(&drop_driver));
35822    drop(drop_driver);
35823
35824    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35825    let drop_registry = Arc::clone(&registry);
35826    let drop_thread = std::thread::spawn(move || {
35827      drop_registry.unregister(drop_id);
35828      drop_tx.send(()).unwrap();
35829    });
35830    drop_rx
35831      .recv_timeout(Duration::from_secs(10))
35832      .expect("driver destructor re-entry deadlocked");
35833    drop_thread.join().unwrap();
35834    assert!(
35835      registry.current().is_none(),
35836      "the destructor must be able to unregister the fallback"
35837    );
35838  }
35839
35840  #[test]
35841  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35842    const DRIVER_PAIRS: usize = 2_048;
35843
35844    let registry = Arc::new(TimerDriverRegistry::default());
35845    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35846    let mut live = Vec::with_capacity(DRIVER_PAIRS);
35847    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35848    for _ in 0..DRIVER_PAIRS {
35849      let driver = Arc::new(ReentrantSweepTimerDriver {
35850        registry: Arc::downgrade(&registry),
35851        registration: Mutex::new(None),
35852        sweeps: AtomicUsize::new(0),
35853        removed_before_sweep: AtomicBool::new(false),
35854      });
35855      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35856      *driver.registration.lock().unwrap() = Some(id);
35857      dead.push(driver);
35858
35859      let driver = ManualStubDriver::new();
35860      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35861      live.push(driver);
35862    }
35863    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35864      registry: Arc::downgrade(&registry),
35865      registration: Mutex::new(None),
35866      sweeps: AtomicUsize::new(0),
35867      removed_before_sweep: AtomicBool::new(false),
35868    });
35869    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35870    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35871    dead.push(dead_tail);
35872
35873    let selection = registry.live();
35874    assert_eq!(
35875      selection
35876        .as_slice()
35877        .iter()
35878        .map(|(id, _)| *id)
35879        .collect::<Vec<_>>(),
35880      live_ids,
35881      "sweeping every interleaved dead host must retain live registration order"
35882    );
35883    drop(selection);
35884    assert!(
35885      dead.iter().all(|driver| {
35886        driver.sweeps.load(Ordering::SeqCst) == 1
35887          && driver.removed_before_sweep.load(Ordering::SeqCst)
35888          && driver.registration.lock().unwrap().is_none()
35889      }),
35890      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35891    );
35892
35893    {
35894      let state = registry
35895        .state
35896        .lock()
35897        .unwrap_or_else(std::sync::PoisonError::into_inner);
35898      assert_eq!(state.entries.len(), DRIVER_PAIRS);
35899      assert_eq!(state.oldest, live_ids.first().copied());
35900      assert_eq!(state.newest, live_ids.last().copied());
35901      assert_eq!(
35902        state
35903          .ordered_snapshot()
35904          .iter()
35905          .map(|(id, _)| *id)
35906          .collect::<Vec<_>>(),
35907        live_ids,
35908        "keyed removals must preserve the linked order of every live host"
35909      );
35910    }
35911    let second_selection = registry.live();
35912    assert_eq!(
35913      second_selection
35914        .as_slice()
35915        .iter()
35916        .map(|(id, _)| *id)
35917        .collect::<Vec<_>>(),
35918      live_ids,
35919      "a second selection must retain the same live order"
35920    );
35921    drop(second_selection);
35922    assert!(
35923      dead
35924        .iter()
35925        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35926      "permanently swept hosts must not receive repeated eviction callbacks"
35927    );
35928    assert_eq!(
35929      registry.current().map(|(id, _)| id),
35930      live_ids.last().copied(),
35931      "newest-live compatibility selection must survive the mass sweep"
35932    );
35933
35934    drop(live);
35935    drop(dead);
35936  }
35937
35938  #[cfg(not(target_family = "wasm"))]
35939  #[test]
35940  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35941    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35942
35943    if std::env::var_os(CHILD_ENV).is_some() {
35944      let registry = Arc::new(TimerDriverRegistry::default());
35945      let fallback = ManualStubDriver::new();
35946      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35947      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35948        registry: Arc::downgrade(&registry),
35949        registrations: Mutex::new(Vec::new()),
35950      });
35951      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35952
35953      let first_cancels = Arc::new(AtomicUsize::new(0));
35954      let first_drops = Arc::new(AtomicUsize::new(0));
35955      let first = Arc::new(PanickingCancelDropTimerDriver {
35956        cancels: Arc::clone(&first_cancels),
35957        drops: Arc::clone(&first_drops),
35958      });
35959      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35960      drop(first);
35961
35962      let second_cancels = Arc::new(AtomicUsize::new(0));
35963      let second_drops = Arc::new(AtomicUsize::new(0));
35964      let second = Arc::new(PanickingCancelDropTimerDriver {
35965        cancels: Arc::clone(&second_cancels),
35966        drops: Arc::clone(&second_drops),
35967      });
35968      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35969      drop(second);
35970      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35971
35972      assert_eq!(
35973        registry.current().map(|(id, _)| id),
35974        Some(fallback_id),
35975        "selection must retry after reentrant removals and preserve the healthy fallback"
35976      );
35977      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35978      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35979      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35980      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35981      assert_eq!(
35982        registry
35983          .state
35984          .lock()
35985          .unwrap_or_else(std::sync::PoisonError::into_inner)
35986          .entries
35987          .len(),
35988        1
35989      );
35990      return;
35991    }
35992
35993    let output = std::process::Command::new(std::env::current_exe().unwrap())
35994      .arg("--exact")
35995      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35996      .arg("--nocapture")
35997      .env(CHILD_ENV, "1")
35998      .output()
35999      .expect("the hostile timer-driver snapshot subprocess test must start");
36000    assert!(
36001      output.status.success(),
36002      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
36003      output.status.code(),
36004      String::from_utf8_lossy(&output.stdout),
36005      String::from_utf8_lossy(&output.stderr)
36006    );
36007  }
36008
36009  #[cfg(not(target_family = "wasm"))]
36010  #[test]
36011  fn timer_driver_registry_live_result_contains_each_driver_drop() {
36012    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
36013
36014    if std::env::var_os(CHILD_ENV).is_some() {
36015      let registry = TimerDriverRegistry::default();
36016
36017      let first_cancels = Arc::new(AtomicUsize::new(0));
36018      let first_drops = Arc::new(AtomicUsize::new(0));
36019      let first = Arc::new(PanickingCancelDropTimerDriver {
36020        cancels: Arc::clone(&first_cancels),
36021        drops: Arc::clone(&first_drops),
36022      });
36023      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
36024      drop(first);
36025
36026      let second_cancels = Arc::new(AtomicUsize::new(0));
36027      let second_drops = Arc::new(AtomicUsize::new(0));
36028      let second = Arc::new(PanickingCancelDropTimerDriver {
36029        cancels: Arc::clone(&second_cancels),
36030        drops: Arc::clone(&second_drops),
36031      });
36032      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
36033      drop(second);
36034
36035      let live = registry.live();
36036      assert_eq!(live.len(), 2);
36037      registry.unregister(first_id);
36038      registry.unregister(second_id);
36039      drop(live);
36040
36041      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
36042      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
36043      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
36044      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
36045      assert!(
36046        registry
36047          .state
36048          .lock()
36049          .unwrap_or_else(std::sync::PoisonError::into_inner)
36050          .entries
36051          .is_empty()
36052      );
36053      return;
36054    }
36055
36056    let output = std::process::Command::new(std::env::current_exe().unwrap())
36057      .arg("--exact")
36058      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
36059      .arg("--nocapture")
36060      .env(CHILD_ENV, "1")
36061      .output()
36062      .expect("the hostile live timer-driver snapshot subprocess test must start");
36063    assert!(
36064      output.status.success(),
36065      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
36066      output.status.code(),
36067      String::from_utf8_lossy(&output.stdout),
36068      String::from_utf8_lossy(&output.stderr)
36069    );
36070  }
36071
36072  #[cfg(not(target_family = "wasm"))]
36073  #[test]
36074  fn timer_driver_registry_drop_contains_each_driver_destructor() {
36075    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
36076
36077    if std::env::var_os(CHILD_ENV).is_some() {
36078      let first_drops = Arc::new(AtomicUsize::new(0));
36079      let second_drops = Arc::new(AtomicUsize::new(0));
36080      let registry = TimerDriverRegistry::default();
36081      let first = Arc::new(PanickingCancelDropTimerDriver {
36082        cancels: Arc::new(AtomicUsize::new(0)),
36083        drops: Arc::clone(&first_drops),
36084      });
36085      let second = Arc::new(PanickingCancelDropTimerDriver {
36086        cancels: Arc::new(AtomicUsize::new(0)),
36087        drops: Arc::clone(&second_drops),
36088      });
36089      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
36090      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
36091      drop(first);
36092      drop(second);
36093
36094      drop(registry);
36095
36096      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
36097      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
36098      return;
36099    }
36100
36101    let output = std::process::Command::new(std::env::current_exe().unwrap())
36102      .arg("--exact")
36103      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
36104      .arg("--nocapture")
36105      .env(CHILD_ENV, "1")
36106      .output()
36107      .expect("the hostile timer-driver registry drop subprocess test must start");
36108    assert!(
36109      output.status.success(),
36110      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
36111      output.status.code(),
36112      String::from_utf8_lossy(&output.stdout),
36113      String::from_utf8_lossy(&output.stderr)
36114    );
36115  }
36116
36117  #[test]
36118  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
36119    const DRIVERS: usize = 512;
36120
36121    let metrics = Arc::new(RuntimeMetrics::default());
36122    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36123    let backend =
36124      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36125    let registry = Arc::new(TimerDriverRegistry::default());
36126    let drivers = (0..DRIVERS)
36127      .map(|_| {
36128        let driver = ManualStubDriver::new();
36129        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36130        driver
36131      })
36132      .collect::<Vec<_>>();
36133    let mut sleep = make_sleep(
36134      &backend,
36135      &registry,
36136      Instant::now() + Duration::from_secs(60),
36137    );
36138    let mut cx = Context::from_waker(Waker::noop());
36139
36140    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36141    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36142    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
36143    match &sleep.inner {
36144      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
36145      #[cfg(napi_runtime_os_threads)]
36146      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
36147    }
36148    for driver in &drivers {
36149      assert_eq!(
36150        driver.registers.lock().unwrap().len(),
36151        2,
36152        "a re-poll must refresh each live driver exactly once"
36153      );
36154    }
36155
36156    for driver in drivers.iter().step_by(2) {
36157      driver.kill();
36158    }
36159    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36160    match &sleep.inner {
36161      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
36162      #[cfg(napi_runtime_os_threads)]
36163      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
36164    }
36165    for (index, driver) in drivers.iter().enumerate() {
36166      let registers = driver.registers.lock().unwrap().len();
36167      let cancels = driver.cancels.lock().unwrap().clone();
36168      if index % 2 == 0 {
36169        assert_eq!(registers, 2);
36170        assert_eq!(cancels, vec![timer_id]);
36171      } else {
36172        assert_eq!(registers, 3);
36173        assert!(cancels.is_empty());
36174      }
36175    }
36176
36177    drop(sleep);
36178    for driver in drivers {
36179      assert_eq!(
36180        driver.cancels.lock().unwrap().as_slice(),
36181        &[timer_id],
36182        "every physical arm must be cancelled exactly once"
36183      );
36184    }
36185    assert_eq!(registry.pending_len(), 0);
36186    assert!(!executor.host_timers.has_pending());
36187  }
36188
36189  #[cfg(panic = "unwind")]
36190  #[test]
36191  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
36192    let metrics = Arc::new(RuntimeMetrics::default());
36193    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36194    let backend =
36195      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36196    let healthy = ManualStubDriver::new();
36197    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36198    let deadline = Instant::now() + Duration::from_secs(60);
36199    let mut sleep = make_sleep(&backend, &registry, deadline);
36200    let mut cx = Context::from_waker(Waker::noop());
36201
36202    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36203    let timer_id = healthy.registers.lock().unwrap()[0].0;
36204
36205    let panicking = PanickingRegisterTimerDriver::new();
36206    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
36207    let result = catch_unwind(AssertUnwindSafe(|| {
36208      let _ = Pin::new(&mut sleep).poll(&mut cx);
36209    }));
36210    assert!(
36211      result.is_err(),
36212      "the hostile register panic must retain its normal propagation"
36213    );
36214    assert_eq!(
36215      healthy.registers.lock().unwrap().len(),
36216      2,
36217      "the previously armed driver must have completed its refresh before the later panic"
36218    );
36219    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
36220    assert!(
36221      panicking.pending.lock().unwrap().contains_key(&timer_id),
36222      "the hostile driver must model an arm committed before it panics"
36223    );
36224
36225    drop(sleep);
36226    assert_eq!(
36227      healthy.cancels.lock().unwrap().as_slice(),
36228      &[timer_id],
36229      "unwind must preserve cancellation ownership for the previously armed driver"
36230    );
36231    assert_eq!(
36232      panicking.cancels.lock().unwrap().as_slice(),
36233      &[timer_id],
36234      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
36235    );
36236    assert!(panicking.pending.lock().unwrap().is_empty());
36237    assert_eq!(registry.pending_len(), 0);
36238    assert!(!executor.host_timers.has_pending());
36239  }
36240
36241  #[cfg(not(target_family = "wasm"))]
36242  #[test]
36243  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
36244    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
36245
36246    if std::env::var_os(CHILD_ENV).is_some() {
36247      let metrics = Arc::new(RuntimeMetrics::default());
36248      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36249      let backend =
36250        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36251      let registry = Arc::new(TimerDriverRegistry::default());
36252      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
36253        registry: Arc::downgrade(&registry),
36254        later: Mutex::new(None),
36255        registers: AtomicUsize::new(0),
36256      });
36257      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
36258
36259      let later_cancels = Arc::new(AtomicUsize::new(0));
36260      let later_drops = Arc::new(AtomicUsize::new(0));
36261      let later = Arc::new(PanickingCancelDropTimerDriver {
36262        cancels: Arc::clone(&later_cancels),
36263        drops: Arc::clone(&later_drops),
36264      });
36265      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
36266      *earlier.later.lock().unwrap() = Some(later_id);
36267      drop(later);
36268
36269      let mut sleep = make_sleep(
36270        &backend,
36271        &registry,
36272        Instant::now() + Duration::from_secs(60),
36273      );
36274      let mut cx = Context::from_waker(Waker::noop());
36275      let result = catch_unwind(AssertUnwindSafe(|| {
36276        let _ = Pin::new(&mut sleep).poll(&mut cx);
36277      }));
36278
36279      assert!(
36280        result.is_err(),
36281        "the earlier driver's register panic must still propagate"
36282      );
36283      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
36284      assert_eq!(
36285        later_drops.load(Ordering::SeqCst),
36286        0,
36287        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
36288      );
36289
36290      drop(sleep);
36291      assert_eq!(
36292        later_cancels.load(Ordering::SeqCst),
36293        1,
36294        "the later driver must remain owned for cancellation despite its registry removal"
36295      );
36296      assert_eq!(
36297        later_drops.load(Ordering::SeqCst),
36298        1,
36299        "the later driver's final destructor must run once behind containment"
36300      );
36301      assert_eq!(registry.pending_len(), 0);
36302      assert!(!executor.host_timers.has_pending());
36303      return;
36304    }
36305
36306    let output = std::process::Command::new(std::env::current_exe().unwrap())
36307      .arg("--exact")
36308      .arg(
36309        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
36310      )
36311      .arg("--nocapture")
36312      .env(CHILD_ENV, "1")
36313      .output()
36314      .expect("the hostile host timer snapshot subprocess test must start");
36315    assert!(
36316      output.status.success(),
36317      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
36318      output.status.code(),
36319      String::from_utf8_lossy(&output.stdout),
36320      String::from_utf8_lossy(&output.stderr)
36321    );
36322  }
36323
36324  #[cfg(not(target_family = "wasm"))]
36325  #[test]
36326  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
36327    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
36328
36329    if std::env::var_os(CHILD_ENV).is_some() {
36330      let metrics = Arc::new(RuntimeMetrics::default());
36331      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36332      let backend =
36333        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36334      let cancels = Arc::new(AtomicUsize::new(0));
36335      let drops = Arc::new(AtomicUsize::new(0));
36336      let hostile = Arc::new(PanickingCancelDropTimerDriver {
36337        cancels: Arc::clone(&cancels),
36338        drops: Arc::clone(&drops),
36339      });
36340      let healthy = ManualStubDriver::new();
36341      let registry = Arc::new(TimerDriverRegistry::default());
36342      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
36343      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36344      let mut sleep = make_sleep(
36345        &backend,
36346        &registry,
36347        Instant::now() + Duration::from_secs(60),
36348      );
36349      let mut cx = Context::from_waker(Waker::noop());
36350
36351      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36352      let timer_id = healthy.registers.lock().unwrap()[0].0;
36353
36354      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
36355      // independently contain both its cancel panic and its destructor panic.
36356      registry.unregister(hostile_id);
36357      drop(hostile);
36358      drop(sleep);
36359      assert_eq!(cancels.load(Ordering::SeqCst), 1);
36360      assert_eq!(drops.load(Ordering::SeqCst), 1);
36361      assert_eq!(
36362        healthy.cancels.lock().unwrap().as_slice(),
36363        &[timer_id],
36364        "one hostile cancellation must not skip later armed drivers"
36365      );
36366      assert_eq!(registry.pending_len(), 0);
36367      assert!(!executor.host_timers.has_pending());
36368      return;
36369    }
36370
36371    let output = std::process::Command::new(std::env::current_exe().unwrap())
36372      .arg("--exact")
36373      .arg(
36374        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
36375      )
36376      .arg("--nocapture")
36377      .env(CHILD_ENV, "1")
36378      .output()
36379      .expect("the hostile timer-driver cancellation subprocess test must start");
36380    assert!(
36381      output.status.success(),
36382      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
36383      output.status.code(),
36384      String::from_utf8_lossy(&output.stdout),
36385      String::from_utf8_lossy(&output.stderr)
36386    );
36387  }
36388
36389  #[cfg(not(target_family = "wasm"))]
36390  #[test]
36391  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
36392    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
36393
36394    if std::env::var_os(CHILD_ENV).is_some() {
36395      let metrics = Arc::new(RuntimeMetrics::default());
36396      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36397      let backend =
36398        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36399      let driver = ManualStubDriver::new();
36400      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36401      let state = Arc::new(TestHostileRawWakerState {
36402        clones: AtomicUsize::new(0),
36403        drop_panics: AtomicUsize::new(0),
36404        panic_on_clone: Some(2),
36405      });
36406
36407      let result = catch_unwind(AssertUnwindSafe(|| {
36408        let mut sleep = make_sleep(
36409          &backend,
36410          &registry,
36411          Instant::now() + Duration::from_secs(60),
36412        );
36413        let waker = test_hostile_waker(Arc::clone(&state));
36414        let mut cx = Context::from_waker(&waker);
36415        let _ = Pin::new(&mut sleep).poll(&mut cx);
36416      }));
36417      assert!(
36418        result.is_err(),
36419        "the original RawWaker clone panic must still propagate"
36420      );
36421      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
36422      assert_eq!(
36423        state.drop_panics.load(Ordering::SeqCst),
36424        1,
36425        "the first clone must be destroyed once behind a contained boundary"
36426      );
36427      assert!(driver.registers.lock().unwrap().is_empty());
36428      assert_eq!(registry.pending_len(), 0);
36429      assert!(!executor.host_timers.has_pending());
36430      return;
36431    }
36432
36433    let output = std::process::Command::new(std::env::current_exe().unwrap())
36434      .arg("--exact")
36435      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
36436      .arg("--nocapture")
36437      .env(CHILD_ENV, "1")
36438      .output()
36439      .expect("the hostile RawWaker subprocess test must start");
36440    assert!(
36441      output.status.success(),
36442      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
36443      output.status.code(),
36444      String::from_utf8_lossy(&output.stdout),
36445      String::from_utf8_lossy(&output.stderr)
36446    );
36447  }
36448
36449  #[cfg(not(target_family = "wasm"))]
36450  #[test]
36451  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
36452    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
36453
36454    if std::env::var_os(CHILD_ENV).is_some() {
36455      let metrics = Arc::new(RuntimeMetrics::default());
36456      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36457      let backend =
36458        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36459      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
36460        registers: AtomicUsize::new(0),
36461      });
36462      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36463      let state = Arc::new(TestHostileRawWakerState {
36464        clones: AtomicUsize::new(0),
36465        drop_panics: AtomicUsize::new(0),
36466        panic_on_clone: None,
36467      });
36468      let mut sleep = make_sleep(
36469        &backend,
36470        &registry,
36471        Instant::now() + Duration::from_secs(60),
36472      );
36473      let waker = test_hostile_waker(Arc::clone(&state));
36474      let mut cx = Context::from_waker(&waker);
36475
36476      let result = catch_unwind(AssertUnwindSafe(|| {
36477        let _ = Pin::new(&mut sleep).poll(&mut cx);
36478      }));
36479      assert!(
36480        result.is_err(),
36481        "the timer-driver register panic must still propagate"
36482      );
36483      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
36484      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
36485      assert_eq!(
36486        state.drop_panics.load(Ordering::SeqCst),
36487        1,
36488        "the unretained register argument must be destroyed once behind the proxy boundary"
36489      );
36490      assert_eq!(registry.pending_len(), 1);
36491      assert!(executor.host_timers.has_pending());
36492
36493      drop(sleep);
36494      assert_eq!(
36495        state.drop_panics.load(Ordering::SeqCst),
36496        3,
36497        "repoll and registry wakers must retain their existing contained cleanup"
36498      );
36499      assert_eq!(registry.pending_len(), 0);
36500      assert!(!executor.host_timers.has_pending());
36501      return;
36502    }
36503
36504    let output = std::process::Command::new(std::env::current_exe().unwrap())
36505      .arg("--exact")
36506      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
36507      .arg("--nocapture")
36508      .env(CHILD_ENV, "1")
36509      .output()
36510      .expect("the hostile register-waker subprocess test must start");
36511    assert!(
36512      output.status.success(),
36513      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
36514      output.status.code(),
36515      String::from_utf8_lossy(&output.stdout),
36516      String::from_utf8_lossy(&output.stderr)
36517    );
36518  }
36519
36520  #[test]
36521  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
36522    // A sleep armed on a driver whose host dies must keep every remaining live
36523    // registrant armed with its remaining time preserved
36524    // (absolute deadline), keep the executor's wake-token accounting at
36525    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
36526    // armed on when dropped.
36527    let metrics = Arc::new(RuntimeMetrics::default());
36528    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36529    let backend =
36530      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36531
36532    let a = ManualStubDriver::new();
36533    let b = ManualStubDriver::new();
36534    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36535    let deadline = Instant::now() + Duration::from_secs(60);
36536
36537    let (timer_id, b_id) = {
36538      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
36539      let mut cx = Context::from_waker(Waker::noop());
36540
36541      // First poll arms on A (the only registrant) and takes the wake-token.
36542      assert!(sleep.as_mut().poll(&mut cx).is_pending());
36543      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
36544      assert_eq!(armed_deadline, deadline);
36545      assert!(
36546        executor.host_timers.has_pending(),
36547        "first poll must arm the wake-token"
36548      );
36549
36550      // A's host dies; a newer live registrant appears (the binding shape:
36551      // eviction wakes every pending sleep, which then re-polls -- here the
36552      // re-poll is driven manually).
36553      a.kill();
36554      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36555      assert!(sleep.as_mut().poll(&mut cx).is_pending());
36556      assert_eq!(
36557        b.registers.lock().unwrap().as_slice(),
36558        &[(timer_id, deadline)],
36559        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
36560      );
36561      assert_eq!(
36562        a.cancels.lock().unwrap().as_slice(),
36563        &[timer_id],
36564        "the dead driver gets a best-effort cancel for the old arm"
36565      );
36566      assert_eq!(
36567        registry.current().map(|(id, _)| id),
36568        Some(b_id),
36569        "the dead driver was evicted"
36570      );
36571      assert!(
36572        executor.host_timers.has_pending(),
36573        "the wake-token survives the re-arm (still exactly this sleep's entry)"
36574      );
36575      (timer_id, b_id)
36576    };
36577
36578    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
36579    // wake-token retires.
36580    let _ = b_id;
36581    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
36582    assert!(
36583      !executor.host_timers.has_pending(),
36584      "drop must retire the wake-token"
36585    );
36586  }
36587
36588  #[cfg(napi_runtime_os_threads)]
36589  #[test]
36590  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
36591    let metrics = Arc::new(RuntimeMetrics::default());
36592    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36593    let backend =
36594      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36595    let responsive = Arc::new(StubHostTimerDriver::new());
36596    let starved = ManualStubDriver::new();
36597    let registry = Arc::new(TimerDriverRegistry::default());
36598    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
36599    // Newest-live-only selection would arm exclusively on this host and never
36600    // receive a wake. The responsive older host must be raced in parallel.
36601    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36602
36603    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
36604    let waker = Waker::from(Arc::clone(&wake_count));
36605    let mut cx = Context::from_waker(&waker);
36606    // This deadline's pre-fire budget covers only `make_sleep` plus the very
36607    // next poll (a past-due Sleep completes inline and would bypass the
36608    // racing-arm machinery under test); 500ms dwarfs those two statements on
36609    // a starved runner without dragging the fire wait out.
36610    let deadline = Instant::now() + Duration::from_millis(500);
36611    let mut sleep = make_sleep(&backend, &registry, deadline);
36612    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36613
36614    let responsive_timer = *responsive
36615      .timers
36616      .lock()
36617      .unwrap()
36618      .keys()
36619      .next()
36620      .expect("responsive timer must be armed");
36621    assert_eq!(
36622      starved.registers.lock().unwrap().as_slice(),
36623      &[(responsive_timer, deadline)],
36624      "every live host must receive the same timer identity and absolute deadline"
36625    );
36626
36627    // Event-based fire wait (this was a fixed 140ms sleep, which required
36628    // the responsive stub's freshly spawned fire thread to get CPU inside
36629    // ~80ms on a starved CI host). The wait is structurally sound: the
36630    // starved ManualStubDriver has NO fire path (it only records), so the
36631    // one-and-only wake can come from the responsive host -- the bound is a
36632    // pure hang detector.
36633    let fire_bound = Instant::now() + Duration::from_secs(30);
36634    while wake_count.0.load(Ordering::SeqCst) == 0 {
36635      assert!(
36636        Instant::now() < fire_bound,
36637        "the responsive host must wake a sleep whose newest host is live but starved"
36638      );
36639      std::thread::sleep(Duration::from_millis(10));
36640    }
36641    // The stub fires at (or after) the shared absolute deadline, so the wall
36642    // clock is past `deadline` here and the poll completes.
36643    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36644    assert_eq!(
36645      starved.cancels.lock().unwrap().as_slice(),
36646      &[responsive_timer],
36647      "completion through the winning host must cancel the starved losing arm"
36648    );
36649  }
36650
36651  #[cfg(napi_runtime_os_threads)]
36652  #[test]
36653  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
36654    use std::sync::mpsc;
36655
36656    struct ReentrantTimerRegistrationWake {
36657      registry: Weak<TimerDriverRegistry>,
36658      wakes: AtomicUsize,
36659      reentered: AtomicBool,
36660    }
36661
36662    impl std::task::Wake for ReentrantTimerRegistrationWake {
36663      fn wake(self: Arc<Self>) {
36664        self.wakes.fetch_add(1, Ordering::SeqCst);
36665        if let Some(registry) = self.registry.upgrade() {
36666          let _ = registry.has_live_driver();
36667          self.reentered.store(true, Ordering::SeqCst);
36668        }
36669      }
36670    }
36671
36672    let metrics = Arc::new(RuntimeMetrics::default());
36673    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36674    let backend =
36675      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36676    let registry = Arc::new(TimerDriverRegistry::default());
36677    let starved = ManualStubDriver::new();
36678    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36679
36680    // The whole pre-fire phase (four polls plus a registration-thread round
36681    // trip) must finish while the wall clock is still BEFORE this shared
36682    // deadline: a past-due `Sleep` completes inline at poll
36683    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
36684    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
36685    // a starved GitHub macOS runner) without dragging the fire wait out.
36686    let deadline = Instant::now() + Duration::from_secs(2);
36687    let mut first = make_sleep(&backend, &registry, deadline);
36688    let mut second = make_sleep(&backend, &registry, deadline);
36689    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
36690      registry: Arc::downgrade(&registry),
36691      wakes: AtomicUsize::new(0),
36692      reentered: AtomicBool::new(false),
36693    });
36694    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
36695      registry: Arc::downgrade(&registry),
36696      wakes: AtomicUsize::new(0),
36697      reentered: AtomicBool::new(false),
36698    });
36699    let first_waker = Waker::from(Arc::clone(&first_wake));
36700    let second_waker = Waker::from(Arc::clone(&second_wake));
36701    let mut first_cx = Context::from_waker(&first_waker);
36702    let mut second_cx = Context::from_waker(&second_waker);
36703
36704    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36705    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36706    let initial = starved.registers.lock().unwrap().clone();
36707    let first_id = initial[0].0;
36708    let second_id = initial[1].0;
36709    assert_ne!(first_id, second_id);
36710    assert_eq!(
36711      registry.pending_len(),
36712      2,
36713      "one stable repoll entry is retained per sleep"
36714    );
36715
36716    let responsive = Arc::new(StubHostTimerDriver::new());
36717    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
36718    let registration_registry = Arc::clone(&registry);
36719    let registration_driver = Arc::clone(&responsive);
36720    let registration = std::thread::spawn(move || {
36721      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
36722      registered_tx.send(id).unwrap();
36723    });
36724    // Hang detector only (the regression it pins is a PERMANENT deadlock:
36725    // `register` re-entering the repoll callback), so the bound is generous
36726    // rather than a latency assertion a starved runner could trip.
36727    registered_rx
36728      .recv_timeout(Duration::from_secs(10))
36729      .expect("late driver registration or repoll callback re-entry deadlocked");
36730    registration.join().unwrap();
36731
36732    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
36733    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
36734    assert!(first_wake.reentered.load(Ordering::SeqCst));
36735    assert!(second_wake.reentered.load(Ordering::SeqCst));
36736    assert!(
36737      responsive.timers.lock().unwrap().is_empty(),
36738      "registration requests bounded re-polls instead of invoking driver callbacks inline"
36739    );
36740
36741    // Both sleeps must still be PENDING when they re-arm on the late driver;
36742    // see the deadline-margin note above. Fail with a diagnosis instead of a
36743    // misleading Pending-assert failure if a pathological stall consumed the
36744    // whole margin anyway.
36745    assert!(
36746      Instant::now() < deadline,
36747      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
36748       this runner is starved beyond the margin this test budgets for"
36749    );
36750    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36751    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36752    let responsive_ids = responsive
36753      .timers
36754      .lock()
36755      .unwrap()
36756      .keys()
36757      .copied()
36758      .collect::<FxHashSet<_>>();
36759    assert_eq!(
36760      responsive_ids,
36761      FxHashSet::from_iter([first_id, second_id]),
36762      "every pending sleep must re-arm its existing identity on the new driver"
36763    );
36764    assert_eq!(
36765      starved
36766        .registers
36767        .lock()
36768        .unwrap()
36769        .iter()
36770        .filter(|(id, _)| *id == first_id)
36771        .count(),
36772      2,
36773      "the old host receives only the normal waker refresh on the one re-poll"
36774    );
36775    assert_eq!(
36776      starved
36777        .registers
36778        .lock()
36779        .unwrap()
36780        .iter()
36781        .filter(|(id, _)| *id == second_id)
36782        .count(),
36783      2,
36784      "the old host receives only the normal waker refresh on the one re-poll"
36785    );
36786    assert_eq!(
36787      registry.pending_len(),
36788      2,
36789      "late registration must not duplicate pending state"
36790    );
36791
36792    // Event-based fire wait instead of a fixed sleep: the starved host has
36793    // NO fire path by construction (ManualStubDriver only records), so the
36794    // second wake per sleep can only come from the late responsive host --
36795    // "while the original host remains starved" is structural, not a
36796    // wall-clock race. The bound is purely a hang detector, generous enough
36797    // for a starved runner to schedule the stub's two fire threads.
36798    let fire_bound = Instant::now() + Duration::from_secs(30);
36799    while first_wake.wakes.load(Ordering::SeqCst) < 2
36800      || second_wake.wakes.load(Ordering::SeqCst) < 2
36801    {
36802      assert!(
36803        Instant::now() < fire_bound,
36804        "the late responsive host must fire both timers while the original host remains starved"
36805      );
36806      std::thread::sleep(Duration::from_millis(10));
36807    }
36808    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36809    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36810
36811    let mut cancelled = starved.cancels.lock().unwrap().clone();
36812    cancelled.sort_unstable();
36813    let mut expected = vec![first_id, second_id];
36814    expected.sort_unstable();
36815    assert_eq!(
36816      cancelled, expected,
36817      "completion must cancel both losing starved-host arms"
36818    );
36819    assert_eq!(
36820      registry.pending_len(),
36821      0,
36822      "completion retires every repoll entry"
36823    );
36824    assert!(!executor.host_timers.has_pending());
36825  }
36826
36827  #[cfg(panic = "unwind")]
36828  #[test]
36829  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36830    // With NO live registrant left mid-flight there is nothing to re-arm on:
36831    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
36832    // sleep silently.
36833    let metrics = Arc::new(RuntimeMetrics::default());
36834    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36835    let backend =
36836      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36837
36838    let a = ManualStubDriver::new();
36839    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36840    let mut sleep = std::pin::pin!(make_sleep(
36841      &backend,
36842      &registry,
36843      Instant::now() + Duration::from_secs(60)
36844    ));
36845    let mut cx = Context::from_waker(Waker::noop());
36846    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36847
36848    a.kill();
36849    let payload = catch_unwind(AssertUnwindSafe(|| {
36850      let _ = sleep.as_mut().poll(&mut cx);
36851    }))
36852    .expect_err("a sleep with every driver dead must panic, not pend silently");
36853    let message = payload
36854      .downcast_ref::<String>()
36855      .cloned()
36856      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36857      .expect("panic payload must be a message");
36858    assert!(
36859      message.contains("lost every live timer driver mid-sleep"),
36860      "the diagnostic must name the mid-flight driver loss, got: {message}"
36861    );
36862  }
36863
36864  #[test]
36865  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36866    // Sweep-eviction invariant: the registry's selection sweep can be the
36867    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
36868    // or any call failure. A retain-only sweep would strand every sleep whose
36869    // armed waker lives in the swept driver's pending map. The sweep must call
36870    // `on_swept` outside the registry lock so the driver wakes those sleeps
36871    // into re-selection.
36872    let metrics = Arc::new(RuntimeMetrics::default());
36873    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36874    let backend =
36875      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36876
36877    let a = ManualStubDriver::new();
36878    let b = ManualStubDriver::new();
36879    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36880    let mut sleep = std::pin::pin!(make_sleep(
36881      &backend,
36882      &registry,
36883      Instant::now() + Duration::from_secs(60)
36884    ));
36885
36886    // Arm on A with an observable waker.
36887    let flag = Arc::new(WakeFlag::default());
36888    let waker = Waker::from(Arc::clone(&flag));
36889    let mut cx = Context::from_waker(&waker);
36890    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36891    assert!(!flag.0.load(Ordering::SeqCst));
36892
36893    // A's host dies; a DIFFERENT selection (any registry touch -- here a
36894    // liveness query, in production another sleep's poll) sweeps it.
36895    a.kill();
36896    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36897    let _ = registry.has_live_driver();
36898
36899    assert!(
36900      a.swept.load(Ordering::SeqCst),
36901      "the sweep must notify the swept driver"
36902    );
36903    assert!(
36904      flag.0.load(Ordering::SeqCst),
36905      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36906    );
36907
36908    // The woken sleep's re-poll lands on the live registrant.
36909    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36910    assert_eq!(
36911      b.registers.lock().unwrap().len(),
36912      1,
36913      "the re-poll must re-arm on the live driver"
36914    );
36915  }
36916
36917  #[cfg(napi_runtime_os_threads)]
36918  #[test]
36919  fn current_thread_sleep_fires_through_stub_host_driver() {
36920    // CT delegation: a registered host driver's fire must complete a sleep
36921    // awaited through the CT `block_on` drive loop.
36922    use std::sync::mpsc;
36923
36924    let (done_tx, done_rx) = mpsc::channel();
36925    let runner = std::thread::spawn(move || {
36926      let metrics = Arc::new(RuntimeMetrics::default());
36927      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36928      let backend =
36929        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36930      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36931
36932      let started = Instant::now();
36933      let mut future = std::pin::pin!(async {
36934        make_sleep(
36935          &backend,
36936          &registry_with(driver),
36937          Instant::now() + Duration::from_millis(60),
36938        )
36939        .await;
36940      });
36941      executor.block_on(future.as_mut());
36942      assert!(started.elapsed() >= Duration::from_millis(60));
36943      assert!(
36944        !executor.host_timers.has_pending(),
36945        "a completed sleep must retire its pending-registry entry"
36946      );
36947      let _ = done_tx.send(());
36948    });
36949
36950    match done_rx.recv_timeout(Duration::from_secs(10)) {
36951      Ok(()) => runner.join().unwrap(),
36952      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36953    }
36954  }
36955
36956  #[test]
36957  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36958    let metrics = Arc::new(RuntimeMetrics::default());
36959    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36960    let backend =
36961      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36962    let drivers = Arc::new(TimerDriverRegistry::default());
36963    let driver = ManualStubDriver::new();
36964    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36965    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36966    let mut cx = Context::from_waker(Waker::noop());
36967
36968    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36969    let timer_id = driver.registers.lock().unwrap()[0].0;
36970    assert_eq!(drivers.pending_len(), 1);
36971
36972    executor.host_timers.shutdown();
36973    assert_eq!(
36974      drivers.pending_len(),
36975      0,
36976      "shutdown must retire the registration-repoll index before the Sleep is dropped"
36977    );
36978    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36979    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36980  }
36981
36982  #[cfg(napi_runtime_os_threads)]
36983  #[test]
36984  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36985    use std::sync::mpsc;
36986
36987    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36988    let backend = controller.backend();
36989    let drivers = Arc::new(TimerDriverRegistry::default());
36990    let driver = ManualStubDriver::new();
36991    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36992    let sleep = make_sleep(
36993      &backend,
36994      &drivers,
36995      Instant::now() + Duration::from_secs(120),
36996    );
36997    drop(backend);
36998
36999    let (polled_tx, polled_rx) = mpsc::channel();
37000    let block_controller = Arc::clone(&controller);
37001    let blocker = std::thread::spawn(move || {
37002      let mut announced = false;
37003      let mut sleep = std::pin::pin!(sleep);
37004      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
37005        let poll = sleep.as_mut().poll(cx);
37006        if !announced {
37007          announced = true;
37008          polled_tx.send(()).unwrap();
37009        }
37010        poll
37011      }));
37012      block_controller.block_on(future.as_mut());
37013    });
37014    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
37015
37016    let (shutdown_tx, shutdown_rx) = mpsc::channel();
37017    let shutdown_controller = Arc::clone(&controller);
37018    let shutdown = std::thread::spawn(move || {
37019      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
37020    });
37021    shutdown_rx
37022      .recv_timeout(Duration::from_secs(2))
37023      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
37024      .unwrap();
37025    shutdown.join().unwrap();
37026    blocker.join().unwrap();
37027    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
37028  }
37029
37030  #[cfg(panic = "unwind")]
37031  #[test]
37032  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
37033    // Threadless pending-timer invariant: on a
37034    // THREADLESS build a pending host timer is NOT a wake-token source -- the
37035    // host event loop's `setTimeout` relay can only run on the very thread
37036    // that is about to park, so it can never fire while `block_on` holds it.
37037    // The certain check must panic with the typed diagnostic anyway (the
37038    // timer-backed kind, so the message names the shape), never fall through
37039    // to the park -- on the real wasip1 target that park is an untyped
37040    // "condvar wait not supported" abort. A native helper thread can fire the
37041    // timer in this test harness, but that concurrency cannot exist on the
37042    // threadless target and must not rescue the park.
37043    use std::sync::mpsc;
37044
37045    let (done_tx, done_rx) = mpsc::channel();
37046    let runner = std::thread::spawn(move || {
37047      let metrics = Arc::new(RuntimeMetrics::default());
37048      // Native stand-in for the threadless wasm build; the stub driver would
37049      // play the host loop from a helper thread, but the panic must land at
37050      // the first park decision, long before its 60ms fire.
37051      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
37052      let backend =
37053        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37054      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37055
37056      let payload = catch_unwind(AssertUnwindSafe(|| {
37057        let mut future = std::pin::pin!(async {
37058          make_sleep(
37059            &backend,
37060            &registry_with(driver),
37061            Instant::now() + Duration::from_millis(60),
37062          )
37063          .await;
37064        });
37065        executor.block_on(future.as_mut());
37066      }))
37067      .expect_err("a threadless park must self-detect even while a host timer is pending");
37068      let diagnostic = payload
37069        .downcast_ref::<BlockOnDeadlock>()
37070        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
37071      assert_eq!(
37072        diagnostic.kind,
37073        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
37074      );
37075      assert_eq!(
37076        diagnostic.park_deadline, None,
37077        "the certain case carries no deadline"
37078      );
37079      let message = diagnostic.to_string();
37080      assert!(
37081        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
37082        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
37083      );
37084      let _ = done_tx.send(());
37085    });
37086
37087    match done_rx.recv_timeout(Duration::from_secs(10)) {
37088      Ok(()) => runner.join().unwrap(),
37089      Err(error) => panic!(
37090        "a pending host timer suppressed the threadless certain-deadlock diagnostic -- block_on parked (or completed via a helper-thread fire that cannot exist on the real target) instead of panicking ({error})"
37091      ),
37092    }
37093  }
37094
37095  #[cfg(panic = "unwind")]
37096  #[test]
37097  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
37098    // Unpolled-sleep invariant: the registry entry only exists once the host
37099    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
37100    // creation. A created-but-never-polled Sleep has no host callback behind
37101    // it, and on a parked single thread nothing can ever poll it into
37102    // arming -- it is provably NOT a future wake source. Counting it would
37103    // mislabel the certain diagnostic as timer-backed. (The armed twin above
37104    // pins the polled side: a POLLED pending timer still certain-panics, just
37105    // with the timer-backed kind.)
37106    use std::sync::mpsc;
37107
37108    let (done_tx, done_rx) = mpsc::channel();
37109    let runner = std::thread::spawn(move || {
37110      let metrics = Arc::new(RuntimeMetrics::default());
37111      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
37112      let backend =
37113        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37114      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37115
37116      // Created and held across the park decision, but NEVER polled: no host
37117      // timer armed, no wake source.
37118      let _unpolled = make_sleep(
37119        &backend,
37120        &registry_with(driver),
37121        Instant::now() + Duration::from_secs(60),
37122      );
37123
37124      let payload = catch_unwind(AssertUnwindSafe(|| {
37125        let mut future = std::pin::pin!(NeverReady);
37126        executor.block_on(future.as_mut());
37127      }))
37128      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
37129      let diagnostic = payload
37130        .downcast_ref::<BlockOnDeadlock>()
37131        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
37132      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
37133      let _ = done_tx.send(());
37134    });
37135
37136    match done_rx.recv_timeout(Duration::from_secs(10)) {
37137      Ok(()) => runner.join().unwrap(),
37138      Err(error) => panic!(
37139        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
37140      ),
37141    }
37142  }
37143
37144  #[cfg(napi_runtime_os_threads)]
37145  #[test]
37146  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
37147    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
37148    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
37149    // legitimate (the wake is scheduled on the host loop); the deadline
37150    // verdict must not fire. Once the timer fires, the sleep completes.
37151    use std::sync::mpsc;
37152
37153    let (done_tx, done_rx) = mpsc::channel();
37154    let runner = std::thread::spawn(move || {
37155      let metrics = Arc::new(RuntimeMetrics::default());
37156      // Deadline/grace ratio: the armed park deadline doubles as the
37157      // `has_live_wait` GRACE -- once the host timer is past due by more
37158      // than one deadline window without firing, the veto stops holding BY
37159      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
37160      // the stub host's freshly spawned fire thread had to get CPU within
37161      // ~230ms of test start or the verdict fired -- the starved-CI failure.
37162      // A 2s window keeps the invariant identical (a LIVE host-timer wait
37163      // with zero progress must not be declared dead) while tolerating
37164      // multi-second scheduling stalls of the fire thread: the veto holds
37165      // until deadline+2s, and the expiry checks land on 2s boundaries, so
37166      // the fire has >4s before a verdict could stick.
37167      let executor = Arc::new(CurrentThreadExecutor::with_detection(
37168        metrics,
37169        false,
37170        Some(Duration::from_secs(2)),
37171      ));
37172      let backend =
37173        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
37174      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
37175
37176      let started = Instant::now();
37177      let mut future = std::pin::pin!(async {
37178        make_sleep(
37179          &backend,
37180          &registry_with(driver),
37181          Instant::now() + Duration::from_millis(200),
37182        )
37183        .await;
37184      });
37185      executor.block_on(future.as_mut());
37186      assert!(started.elapsed() >= Duration::from_millis(200));
37187      let _ = done_tx.send(());
37188    });
37189
37190    match done_rx.recv_timeout(Duration::from_secs(30)) {
37191      Ok(()) => runner.join().unwrap(),
37192      Err(error) => {
37193        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
37194      }
37195    }
37196  }
37197
37198  #[test]
37199  fn host_timer_unrepresentable_grace_remains_live() {
37200    let registry = HostTimerRegistry::default();
37201    let fired = Arc::new(AtomicBool::new(false));
37202    let drivers = Arc::new(TimerDriverRegistry::default());
37203    let repoll = Arc::new(HostTimerRepoll::default());
37204    assert!(
37205      Instant::now().checked_add(Duration::MAX).is_none(),
37206      "Duration::MAX must exercise the unrepresentable-grace path"
37207    );
37208    assert!(
37209      registry.register(
37210        1,
37211        Instant::now(),
37212        futures::task::noop_waker(),
37213        &fired,
37214        &drivers,
37215        &repoll,
37216      ),
37217      "the open registry must accept the timer"
37218    );
37219
37220    assert!(
37221      registry.has_live_wait(Duration::MAX),
37222      "an unrepresentable grace deadline is effectively unbounded"
37223    );
37224    registry.remove(1);
37225  }
37226
37227  // `BlockOnDeadlock::multi_thread_cooperative` only exists where the
37228  // MultiThread machinery is compiled in.
37229  #[cfg(napi_runtime_os_threads)]
37230  #[test]
37231  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
37232    // A deadline firing inside a spawned task's poll unwinds into the spawn
37233    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
37234    // message must survive that trip (JoinError::from_panic downcasts it), or
37235    // the build error would read "async runtime task panicked" with no clue.
37236    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
37237    let expected = diagnostic.to_string();
37238    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
37239    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
37240  }
37241  // --- wasm32-wasip1-threads ------------------------------------------------
37242  //
37243  // `rustc --print cfg` is identical for the two WASI targets, so these pin
37244  // what the build script's cfgs are supposed to mean. They run only where
37245  // `napi_runtime_wasi_threads` holds; the threadless complement is pinned by
37246  // `threadless_wasm_still_rejects_multi_thread` below.
37247
37248  #[cfg(napi_runtime_wasi_threads)]
37249  #[test]
37250  fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
37251    // Rayon's `THREADS_BITS` is 8 on 32-bit, so its ceiling is 255 and the
37252    // production cap of 256 never binds here. The `>= 2` half is what
37253    // `validate()` needs to accept MultiThread at all.
37254    let ceiling = crate::max_async_runtime_worker_threads();
37255    assert_eq!(
37256      ceiling, 255,
37257      "the threaded WASI target must report Rayon's realizable ceiling"
37258    );
37259    assert!(
37260      ceiling >= 2,
37261      "a ceiling below two would make MultiThread unconfigurable"
37262    );
37263  }
37264
37265  #[cfg(napi_runtime_wasi_threads)]
37266  #[test]
37267  fn wasi_threads_default_flavor_is_still_current_thread() {
37268    // Availability is not a default. Selecting MultiThread on a wasm target
37269    // stays an explicit host act, so a host that changes nothing sees no
37270    // change at all.
37271    assert_eq!(
37272      RuntimeOptions::default().flavor,
37273      RuntimeFlavor::CurrentThread,
37274      "the threaded WASI target must not silently default to MultiThread"
37275    );
37276  }
37277
37278  #[cfg(napi_runtime_wasi_threads)]
37279  #[test]
37280  fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
37281    // `available_parallelism()` returns Ok(1) under a WASI host, so a host
37282    // that flips only the flavor gets MultiThread's truthful minimum -- two
37283    // workers, one blocking lane -- rather than an error or a pool worth
37284    // having. Hosts that want parallelism must pass a worker count they got
37285    // from JavaScript.
37286    let validated = RuntimeOptions {
37287      flavor: RuntimeFlavor::MultiThread,
37288      ..RuntimeOptions::default()
37289    }
37290    .validate()
37291    .expect("MultiThread must validate on the threaded WASI target");
37292    assert_eq!(validated.worker_threads, 2);
37293    assert_eq!(validated.max_blocking_tasks, 1);
37294  }
37295
37296  #[cfg(napi_runtime_wasi_threads)]
37297  #[test]
37298  fn threadless_build_is_false_on_the_threads_target() {
37299    // `THREADLESS_BUILD` and `napi_runtime_os_threads` are orthogonal cfgs
37300    // that must agree on this one target, or the CurrentThread park logic and
37301    // the MultiThread machinery would disagree about whether a second thread
37302    // can deliver a wake.
37303    assert!(
37304      !THREADLESS_BUILD,
37305      "the threaded WASI target has real OS threads"
37306    );
37307    assert!(
37308      cfg!(napi_runtime_os_threads),
37309      "the threaded WASI target must build the OS-thread machinery"
37310    );
37311  }
37312
37313  // Memory-size refresh at scheduler handoffs (`on_thread_handoff`). This
37314  // napi-free binary links no allocator wrappers, so only `grow_zero` and the
37315  // hook ever set a thread's `local_pages`. Each test grows the memory on
37316  // another thread and publishes the new size, as napi's allocator does after
37317  // dlmalloc grows it, then reads `local_pages` inside the work under test.
37318  // Every thread that has not refreshed since is below that size, so a
37319  // reading at or above it proves the hook at that site ran; without the hook
37320  // each test fails its assertion.
37321
37322  /// Grow the memory by one page on a new thread and publish the new size,
37323  /// which this returns.
37324  #[cfg(napi_runtime_wasi_threads)]
37325  fn grow_one_page_elsewhere() -> usize {
37326    std::thread::spawn(|| {
37327      let old = core::arch::wasm32::memory_grow::<0>(1);
37328      assert_ne!(old, usize::MAX, "the WASI host must let the memory grow");
37329      napi_sys::wasi_heap_sync::grow_zero()
37330    })
37331    .join()
37332    .expect("the growing thread must finish")
37333  }
37334
37335  #[cfg(napi_runtime_wasi_threads)]
37336  #[test]
37337  fn wasi_threads_multi_thread_task_polls_refresh_the_memory_size() {
37338    // `MultiThreadExecutor::run_runnable`: a pool worker polls a task spawned
37339    // after another thread grew the memory.
37340    let controller = multi_thread_controller("wasi-handoff-task", 2, 1);
37341    let pages = grow_one_page_elsewhere();
37342    let refreshes = napi_sys::wasi_heap_sync::handoff_refreshes();
37343    let seen = futures::executor::block_on(
37344      controller.spawn(async { napi_sys::wasi_heap_sync::local_pages() }),
37345    )
37346    .expect("the task must finish");
37347    assert!(
37348      seen >= pages,
37349      "the worker polled with a stale memory size: {seen} < {pages} pages"
37350    );
37351    assert!(
37352      napi_sys::wasi_heap_sync::handoff_refreshes() > refreshes,
37353      "the handoff refresh must be counted"
37354    );
37355    controller.shutdown().expect("runtime must stop");
37356  }
37357
37358  #[cfg(napi_runtime_wasi_threads)]
37359  #[test]
37360  fn wasi_threads_current_thread_task_polls_refresh_the_memory_size() {
37361    // The free `run_runnable`: the thread driving a host turn polls a task
37362    // after another thread grew the memory.
37363    use std::sync::mpsc;
37364
37365    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37366      RuntimeMetrics::default(),
37367    )));
37368    let (seen_tx, seen_rx) = mpsc::channel();
37369    let wake_executor = Arc::clone(&executor);
37370    let (runnable, task) = async_task::spawn(
37371      async move {
37372        let _ = seen_tx.send(napi_sys::wasi_heap_sync::local_pages());
37373      },
37374      move |runnable| wake_executor.schedule(runnable),
37375    );
37376    executor.schedule(runnable);
37377    let pages = grow_one_page_elsewhere();
37378    executor.drive_host_turn();
37379    let seen = seen_rx
37380      .try_recv()
37381      .expect("one host turn must poll the task");
37382    futures::executor::block_on(task);
37383    assert!(
37384      seen >= pages,
37385      "the host turn polled with a stale memory size: {seen} < {pages} pages"
37386    );
37387  }
37388
37389  #[cfg(napi_runtime_wasi_threads)]
37390  #[test]
37391  fn wasi_threads_block_on_polls_refresh_the_memory_size() {
37392    // `CurrentThreadExecutor::block_on` and
37393    // `MultiThreadExecutor::parking_block_on`: the caller polls after another
37394    // thread grew the memory.
37395    for controller in [
37396      current_thread_controller("wasi-handoff-block-on"),
37397      multi_thread_controller("wasi-handoff-parking-block-on", 2, 1),
37398    ] {
37399      let pages = grow_one_page_elsewhere();
37400      let mut seen = 0;
37401      {
37402        let mut future = std::pin::pin!(async {
37403          seen = napi_sys::wasi_heap_sync::local_pages();
37404        });
37405        assert_eq!(
37406          controller.block_on(future.as_mut()),
37407          BlockOnOutcome::Completed
37408        );
37409      }
37410      assert!(
37411        seen >= pages,
37412        "block_on polled with a stale memory size: {seen} < {pages} pages"
37413      );
37414      controller.shutdown().expect("runtime must stop");
37415    }
37416  }
37417
37418  #[cfg(napi_runtime_wasi_threads)]
37419  #[test]
37420  fn wasi_threads_blocking_closures_refresh_the_memory_size() {
37421    // `RegisteredBlockingFunction::run`: a pool worker starts a closure
37422    // submitted after another thread grew the memory. Inside it,
37423    // `cooperative_block_on`: the same worker polls after a second grow.
37424    let controller = Arc::new(multi_thread_controller("wasi-handoff-blocking", 2, 1));
37425    let pages = grow_one_page_elsewhere();
37426    let block_on_controller = Arc::clone(&controller);
37427    let (start_seen, block_on_pages, block_on_seen) =
37428      futures::executor::block_on(controller.spawn_blocking(move || {
37429        let start_seen = napi_sys::wasi_heap_sync::local_pages();
37430        let block_on_pages = grow_one_page_elsewhere();
37431        let mut block_on_seen = 0;
37432        {
37433          let mut future = std::pin::pin!(async {
37434            block_on_seen = napi_sys::wasi_heap_sync::local_pages();
37435          });
37436          assert_eq!(
37437            block_on_controller.block_on(future.as_mut()),
37438            BlockOnOutcome::Completed
37439          );
37440        }
37441        (start_seen, block_on_pages, block_on_seen)
37442      }))
37443      .expect("the blocking closure must finish");
37444    assert!(
37445      start_seen >= pages,
37446      "the blocking closure started with a stale memory size: {start_seen} < {pages} pages"
37447    );
37448    assert!(
37449      block_on_seen >= block_on_pages,
37450      "the cooperative block_on polled with a stale memory size: \
37451       {block_on_seen} < {block_on_pages} pages"
37452    );
37453    controller.shutdown().expect("runtime must stop");
37454  }
37455
37456  #[cfg(napi_runtime_wasi_threads)]
37457  #[test]
37458  fn wasi_threads_inline_blocking_refreshes_the_memory_size() {
37459    // The CurrentThread inline blocking path: the submitting thread runs the
37460    // closure itself, after another thread grew the memory.
37461    let controller = current_thread_controller("wasi-handoff-inline-blocking");
37462    let pages = grow_one_page_elsewhere();
37463    let seen =
37464      futures::executor::block_on(controller.spawn_blocking(napi_sys::wasi_heap_sync::local_pages))
37465        .expect("the inline blocking closure must finish");
37466    assert!(
37467      seen >= pages,
37468      "the inline blocking closure ran with a stale memory size: {seen} < {pages} pages"
37469    );
37470    controller.shutdown().expect("runtime must stop");
37471  }
37472
37473  // `not(napi_runtime_os_threads)` is exactly "wasm and not the threaded WASI
37474  // target": the build script sets the cfg for every non-wasm target plus
37475  // `wasm32-wasip1-threads`.
37476  #[cfg(not(napi_runtime_os_threads))]
37477  #[test]
37478  fn threadless_wasm_still_rejects_multi_thread() {
37479    let error = RuntimeOptions {
37480      flavor: RuntimeFlavor::MultiThread,
37481      ..RuntimeOptions::default()
37482    }
37483    .validate()
37484    .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
37485    let message = error.to_string();
37486    assert!(
37487      message.contains("threadless"),
37488      "the rejection must name the property that causes it, not the family: {message}"
37489    );
37490    assert!(
37491      message.contains("wasm32-wasip1-threads"),
37492      "the rejection must name the target that does work: {message}"
37493    );
37494  }
37495
37496  #[cfg(napi_runtime_os_threads)]
37497  #[test]
37498  fn multi_thread_pool_starts_and_joins_every_worker() {
37499    // The disposal order the WASI loader depends on, as a unit test: every
37500    // worker is joined BEFORE anything tears the environment down, so no
37501    // thread can still be running over memory that is about to disappear.
37502    let options = RuntimeOptions {
37503      flavor: RuntimeFlavor::MultiThread,
37504      worker_threads: 3,
37505      max_blocking_tasks: 2,
37506      thread_name_prefix: "rd-os-threads-join".to_string(),
37507      park_deadline: None,
37508      drain_linger: DEFAULT_DRAIN_LINGER,
37509    };
37510    let executor = Arc::new(
37511      MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
37512        .expect("a three-worker pool must build"),
37513    );
37514    assert_eq!(executor.pool.current_num_threads(), 3);
37515
37516    let ran = Arc::new(AtomicBool::new(false));
37517    let task_ran = Arc::clone(&ran);
37518    let (runnable, task) = async_task::spawn(
37519      async move {
37520        task_ran.store(true, Ordering::SeqCst);
37521      },
37522      |_| {},
37523    );
37524    executor.schedule(runnable);
37525    futures::executor::block_on(task);
37526    assert!(
37527      ran.load(Ordering::SeqCst),
37528      "the pool must run scheduled work"
37529    );
37530
37531    let lifecycle = Arc::clone(&executor.worker_lifecycle);
37532    executor.begin_shutdown();
37533    executor.wait_until_scheduler_idle();
37534    // A drainer's Rayon job decrements `active_drainers` before its closure
37535    // is dropped, so the last reference can lag the idle barrier by a moment.
37536    // Spin for it under a deadline: taking the value out is what proves the
37537    // pool really goes away here, and turns a leaked reference into a
37538    // failure rather than a hang in the join below.
37539    let deadline = Instant::now() + Duration::from_secs(10);
37540    while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
37541      std::thread::yield_now();
37542    }
37543    let executor = Arc::into_inner(executor)
37544      .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
37545    drop(executor);
37546    lifecycle.wait_for_all_workers();
37547    assert_eq!(
37548      lifecycle.remaining(),
37549      0,
37550      "every physical worker must have exited before the join returns"
37551    );
37552  }
37553
37554  #[cfg(napi_runtime_os_threads)]
37555  #[test]
37556  fn current_thread_queues_concurrent_blocking_admission() {
37557    // A second OS thread submitting `spawn_blocking` while the lane owner
37558    // runs, carrying no `ambient_owner`. The gate that decides this used to
37559    // read `target_family = "wasm"` while its comment argued "threadless", so
37560    // on a threaded wasm build the closure was dropped and the handle
37561    // resolved to "the async runtime stopped before the blocking task could
37562    // start". It must be QUEUED and serviced on a later host turn.
37563    use std::sync::mpsc;
37564
37565    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37566      RuntimeMetrics::default(),
37567    )));
37568    let work = GenerationWork::new();
37569    let (outcome_tx, outcome_rx) = mpsc::channel();
37570
37571    let owner_registration = work
37572      .try_register_work()
37573      .expect("the lane owner's blocking job must be accepted");
37574    let concurrent_work = Arc::clone(&work);
37575    let concurrent_executor = Arc::clone(&executor);
37576    let task_executor = Arc::clone(&executor);
37577    let wake_executor = Arc::clone(&executor);
37578    let (runnable, task) = async_task::spawn(
37579      async move {
37580        let concurrent = task_executor
37581          .schedule_blocking(
37582            move || {
37583              std::thread::spawn(move || {
37584                let registration = concurrent_work
37585                  .try_register_work()
37586                  .expect("the concurrent blocking job must be accepted");
37587                concurrent_executor.schedule_blocking(|| 7usize, registration)
37588              })
37589              .join()
37590              .expect("the concurrent submitter thread must finish")
37591            },
37592            owner_registration,
37593          )
37594          .await
37595          .expect("the lane owner's blocking job must finish");
37596        let _ = outcome_tx.send(concurrent.await);
37597      },
37598      move |runnable| wake_executor.schedule(runnable),
37599    );
37600
37601    executor.schedule(runnable);
37602    let mut turns = 0usize;
37603    let outcome = loop {
37604      executor.drive_host_turn();
37605      if let Ok(outcome) = outcome_rx.try_recv() {
37606        break outcome;
37607      }
37608      turns += 1;
37609      assert!(
37610        turns <= 8,
37611        "queued blocking admission must make bounded host-turn progress"
37612      );
37613    };
37614    futures::executor::block_on(task);
37615    assert_eq!(
37616      outcome.expect("a concurrent blocking submission must be queued, not rejected"),
37617      7
37618    );
37619  }
37620}