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, 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#[cfg(all(test, napi_runtime_os_threads))]
916fn run_dependency_claim_test_hook() {
917  if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
918    hook();
919  }
920}
921
922#[cfg(all(test, napi_runtime_os_threads))]
923fn run_current_thread_dispatch_claim_test_hook() {
924  if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
925  {
926    hook();
927  }
928}
929
930#[cfg(all(test, napi_runtime_os_threads))]
931fn run_current_thread_dispatch_publication_blocked_test_hook() {
932  if let Some(hook) =
933    CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
934  {
935    hook();
936  }
937}
938
939#[cfg(all(test, napi_runtime_os_threads))]
940fn run_current_thread_dispatch_recovery_test_hook() {
941  if let Some(hook) =
942    CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
943  {
944    hook();
945  }
946}
947
948#[cfg(all(test, napi_runtime_os_threads))]
949fn run_current_thread_failed_publication_retired_test_hook() {
950  if let Some(hook) =
951    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
952  {
953    hook();
954  }
955}
956
957#[cfg(all(test, napi_runtime_os_threads))]
958fn run_current_thread_dispatch_call_test_hook() {
959  if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
960    hook();
961  }
962}
963
964#[cfg(all(test, napi_runtime_os_threads))]
965fn run_current_thread_dispatch_returned_test_hook() {
966  if let Some(hook) =
967    CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
968  {
969    hook();
970  }
971}
972
973#[cfg(all(test, napi_runtime_os_threads))]
974fn run_current_thread_blocking_claim_test_hook() {
975  if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
976  {
977    hook();
978  }
979}
980
981#[cfg(all(test, napi_runtime_os_threads))]
982fn run_current_thread_runnable_claim_test_hook() {
983  if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
984  {
985    hook();
986  }
987}
988
989#[cfg(all(test, napi_runtime_os_threads))]
990fn run_multi_thread_work_claimed_test_hook() {
991  if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
992    hook();
993  }
994}
995
996#[cfg(all(test, napi_runtime_os_threads))]
997fn run_multi_thread_work_started_test_hook() {
998  if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
999    hook();
1000  }
1001}
1002
1003#[cfg(all(test, napi_runtime_os_threads))]
1004fn run_dependency_predicate_test_hook() {
1005  if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
1006    hook();
1007  }
1008}
1009
1010#[cfg(all(test, napi_runtime_os_threads))]
1011fn run_before_owned_blocking_queue_claim_test_hook() {
1012  if let Some(hook) =
1013    BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
1014  {
1015    hook();
1016  }
1017}
1018
1019#[cfg(napi_runtime_os_threads)]
1020#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1021struct BlockingDependency {
1022  job: BlockingJobId,
1023  owner: Option<BlockingOwnerToken>,
1024}
1025
1026#[cfg(napi_runtime_os_threads)]
1027#[derive(Debug)]
1028struct DependencyClaim {
1029  claimed: AtomicBool,
1030  transition: Mutex<()>,
1031}
1032
1033#[cfg(napi_runtime_os_threads)]
1034impl DependencyClaim {
1035  fn new() -> Self {
1036    Self {
1037      claimed: AtomicBool::new(false),
1038      transition: Mutex::new(()),
1039    }
1040  }
1041
1042  fn is_live(&self) -> bool {
1043    !self.claimed.load(Ordering::Acquire)
1044  }
1045
1046  fn cancel_link(&self, link: &DependencyLink) {
1047    let _transition = self
1048      .transition
1049      .lock()
1050      .unwrap_or_else(std::sync::PoisonError::into_inner);
1051    link.cancel();
1052  }
1053
1054  fn try_claim(&self, link: &DependencyLink) -> bool {
1055    let _transition = self
1056      .transition
1057      .lock()
1058      .unwrap_or_else(std::sync::PoisonError::into_inner);
1059    #[cfg(test)]
1060    run_dependency_claim_test_hook();
1061    if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1062      return false;
1063    }
1064    self.claimed.store(true, Ordering::Release);
1065    true
1066  }
1067}
1068
1069#[cfg(napi_runtime_os_threads)]
1070#[derive(Debug)]
1071struct DependencyLink {
1072  live: AtomicBool,
1073  parent: Option<Weak<DependencyLink>>,
1074}
1075
1076#[cfg(napi_runtime_os_threads)]
1077impl DependencyLink {
1078  fn root() -> Arc<Self> {
1079    Arc::new(Self {
1080      live: AtomicBool::new(true),
1081      parent: None,
1082    })
1083  }
1084
1085  fn derived(parent: &Arc<Self>) -> Arc<Self> {
1086    Arc::new(Self {
1087      live: AtomicBool::new(true),
1088      parent: Some(Arc::downgrade(parent)),
1089    })
1090  }
1091
1092  fn is_live(&self) -> bool {
1093    if !self.live.load(Ordering::Acquire) {
1094      return false;
1095    }
1096    let Some(parent) = &self.parent else {
1097      return true;
1098    };
1099    let Some(mut current) = parent.upgrade() else {
1100      return false;
1101    };
1102    loop {
1103      if !current.live.load(Ordering::Acquire) {
1104        return false;
1105      }
1106      let Some(parent) = &current.parent else {
1107        return true;
1108      };
1109      let Some(next) = parent.upgrade() else {
1110        return false;
1111      };
1112      current = next;
1113    }
1114  }
1115
1116  fn cancel(&self) {
1117    self.live.store(false, Ordering::Release);
1118  }
1119}
1120
1121#[cfg(napi_runtime_os_threads)]
1122#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1123struct DependencyPublicationIdentity {
1124  job: BlockingJobId,
1125  claim: *const DependencyClaim,
1126  link: *const DependencyLink,
1127}
1128
1129#[cfg(napi_runtime_os_threads)]
1130#[derive(Clone, Debug)]
1131struct DependencyPublication {
1132  dependency: BlockingDependency,
1133  claim: Arc<DependencyClaim>,
1134  link: Arc<DependencyLink>,
1135}
1136
1137#[cfg(napi_runtime_os_threads)]
1138impl DependencyPublication {
1139  fn owned(dependency: BlockingDependency) -> Self {
1140    Self {
1141      dependency,
1142      claim: Arc::new(DependencyClaim::new()),
1143      link: DependencyLink::root(),
1144    }
1145  }
1146
1147  fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1148    Self {
1149      dependency: BlockingDependency {
1150        owner: self.dependency.owner.or(owner),
1151        ..self.dependency
1152      },
1153      claim: Arc::clone(&self.claim),
1154      link: DependencyLink::derived(&self.link),
1155    }
1156  }
1157
1158  fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1159    self.dependency.owner = self.dependency.owner.or(owner);
1160    self
1161  }
1162
1163  fn same_as(&self, other: &Self) -> bool {
1164    self.dependency == other.dependency
1165      && Arc::ptr_eq(&self.claim, &other.claim)
1166      && Arc::ptr_eq(&self.link, &other.link)
1167  }
1168
1169  fn identity(&self) -> DependencyPublicationIdentity {
1170    DependencyPublicationIdentity {
1171      job: self.dependency.job,
1172      claim: Arc::as_ptr(&self.claim),
1173      link: Arc::as_ptr(&self.link),
1174    }
1175  }
1176
1177  fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1178    Some(DependencyPublicationIdentity {
1179      job: self.dependency.job,
1180      claim: Arc::as_ptr(&self.claim),
1181      link: self.link.parent.as_ref()?.as_ptr(),
1182    })
1183  }
1184
1185  fn is_derived_from(&self, source: &Self) -> bool {
1186    self.immediate_source_identity() == Some(source.identity())
1187  }
1188
1189  fn is_live(&self) -> bool {
1190    self.claim.is_live() && self.link.is_live()
1191  }
1192
1193  fn cancel(&self) {
1194    self.claim.cancel_link(&self.link);
1195  }
1196
1197  fn try_claim(&self) -> bool {
1198    self.claim.try_claim(&self.link)
1199  }
1200}
1201
1202#[cfg(napi_runtime_os_threads)]
1203struct TaskDependencyEntry {
1204  sequence: u64,
1205  publication: DependencyPublication,
1206}
1207
1208#[cfg(napi_runtime_os_threads)]
1209struct SelectedDependencyPublication {
1210  sequence: u64,
1211  publication: DependencyPublication,
1212}
1213
1214#[cfg(napi_runtime_os_threads)]
1215#[derive(Clone, Copy)]
1216enum OwnerDependencyMode {
1217  Available,
1218  Exact,
1219}
1220
1221#[cfg(napi_runtime_os_threads)]
1222#[derive(Default)]
1223struct TaskDependencyState {
1224  current: VecDeque<TaskDependencyEntry>,
1225  next_sequence: u64,
1226  unowned_sequences: VecDeque<u64>,
1227  owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1228  // NOTE: there is deliberately no `job_sequences` index. It used to be a
1229  // `FxHashMap<BlockingJobId, FxHashSet<u64>>` costing TWO heap allocations
1230  // per blocking dispatch (the map's table plus an inner set that almost
1231  // always held exactly one sequence). Its only job was to let a dropped
1232  // blocking `JoinHandle` find the entry it had published, and the handle can
1233  // simply remember the `sequence` `append` gave it: `current` is positionally
1234  // indexed by sequence via `entry_in`, so the handle retires its own entry in
1235  // O(1) with no map, no hash, and no scan. `next_sequence` never rewinds, so
1236  // a remembered sequence can never alias a later entry.
1237  waiter: Option<Waker>,
1238}
1239
1240#[cfg(napi_runtime_os_threads)]
1241impl TaskDependencyState {
1242  /// Publish `publication` and return the sequence it was filed under, so the
1243  /// publisher can retire exactly this entry later in O(1).
1244  fn append(&mut self, publication: DependencyPublication) -> u64 {
1245    let sequence = self.next_sequence;
1246    self.next_sequence = self
1247      .next_sequence
1248      .checked_add(1)
1249      .expect("task dependency sequence space exhausted");
1250    match publication.dependency.owner {
1251      Some(owner) => self
1252        .owner_sequences
1253        .entry(owner)
1254        .or_default()
1255        .push_back(sequence),
1256      None => self.unowned_sequences.push_back(sequence),
1257    }
1258    self.current.push_back(TaskDependencyEntry {
1259      sequence,
1260      publication,
1261    });
1262    sequence
1263  }
1264
1265  fn entry_in(
1266    current: &VecDeque<TaskDependencyEntry>,
1267    sequence: u64,
1268  ) -> Option<&TaskDependencyEntry> {
1269    let front = current.front()?.sequence;
1270    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1271    let entry = current.get(index)?;
1272    (entry.sequence == sequence).then_some(entry)
1273  }
1274
1275  fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1276    Self::entry_in(&self.current, sequence)
1277  }
1278
1279  fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1280    let front = self.current.front()?.sequence;
1281    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1282    let entry = self.current.get_mut(index)?;
1283    (entry.sequence == sequence).then_some(entry)
1284  }
1285
1286  fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1287    match dependency.owner {
1288      Some(owner) => {
1289        let remove_owner = self
1290          .owner_sequences
1291          .get_mut(&owner)
1292          .is_some_and(|sequences| {
1293            if sequences.front() == Some(&sequence) {
1294              sequences.pop_front();
1295            }
1296            sequences.is_empty()
1297          });
1298        if remove_owner {
1299          self.owner_sequences.remove(&owner);
1300        }
1301      }
1302      None => {
1303        if self.unowned_sequences.front() == Some(&sequence) {
1304          self.unowned_sequences.pop_front();
1305        }
1306      }
1307    }
1308  }
1309
1310  fn clear_indexes(&mut self) {
1311    self.unowned_sequences.clear();
1312    self.owner_sequences.clear();
1313  }
1314}
1315
1316#[cfg(napi_runtime_os_threads)]
1317struct TaskDependency {
1318  state: Mutex<TaskDependencyState>,
1319  owner_hint: Option<BlockingOwnerToken>,
1320  has_current: AtomicBool,
1321  generation: u64,
1322  #[cfg(test)]
1323  entry_visits: AtomicUsize,
1324}
1325
1326#[cfg(napi_runtime_os_threads)]
1327impl Default for TaskDependency {
1328  fn default() -> Self {
1329    Self::new(u64::MAX, None)
1330  }
1331}
1332
1333#[cfg(napi_runtime_os_threads)]
1334impl TaskDependency {
1335  fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1336    Self {
1337      state: Mutex::new(TaskDependencyState::default()),
1338      owner_hint,
1339      has_current: AtomicBool::new(false),
1340      generation,
1341      #[cfg(test)]
1342      entry_visits: AtomicUsize::new(0),
1343    }
1344  }
1345
1346  fn new_task(generation: u64) -> Self {
1347    Self::new(generation, None)
1348  }
1349
1350  fn notify_waiter(&self, waiter: Option<Waker>) {
1351    if let Some(waiter) = waiter {
1352      let _generation = RuntimeGenerationGuard::enter(self.generation);
1353      wake_waker_contained(waiter);
1354    }
1355  }
1356
1357  fn clear_waiter(&self) {
1358    let waiter = self
1359      .state
1360      .lock()
1361      .unwrap_or_else(std::sync::PoisonError::into_inner)
1362      .waiter
1363      .take();
1364    if let Some(waiter) = waiter {
1365      let _generation = RuntimeGenerationGuard::enter(self.generation);
1366      let _ = catch_unwind_contained(|| drop(waiter));
1367    }
1368  }
1369
1370  fn clear(&self) {
1371    let waiter = {
1372      let mut state = self
1373        .state
1374        .lock()
1375        .unwrap_or_else(std::sync::PoisonError::into_inner);
1376      if state.current.is_empty() {
1377        None
1378      } else {
1379        for current in state.current.drain(..) {
1380          current.publication.cancel();
1381        }
1382        state.clear_indexes();
1383        self.has_current.store(false, Ordering::Release);
1384        state.waiter.take()
1385      }
1386    };
1387    self.notify_waiter(waiter);
1388  }
1389
1390  /// Retire the single entry filed under `sequence`, in O(1).
1391  ///
1392  /// This replaces a job-id lookup through a `job_sequences` index. `current`
1393  /// is positionally indexed by sequence, so the publisher that remembers the
1394  /// sequence `append` returned can cancel exactly its own entry without a
1395  /// map and without a scan. `entry_in` re-checks `entry.sequence == sequence`
1396  /// after the positional lookup and `next_sequence` never rewinds, so a
1397  /// sequence whose entry has already been drained (`begin_poll`) or pruned
1398  /// resolves to `None` rather than aliasing a later entry.
1399  fn retire(&self, sequence: u64) {
1400    let waiter = {
1401      let mut state = self
1402        .state
1403        .lock()
1404        .unwrap_or_else(std::sync::PoisonError::into_inner);
1405      let cancelled = match state.entry_mut(sequence) {
1406        Some(current) => {
1407          self.note_entry_visit();
1408          current.publication.cancel();
1409          true
1410        }
1411        None => false,
1412      };
1413      if cancelled {
1414        self.prune_stale_prefix(&mut state);
1415        state.waiter.take()
1416      } else {
1417        None
1418      }
1419    };
1420    self.notify_waiter(waiter);
1421  }
1422
1423  fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1424    let Some(first_publication) = publications.first() else {
1425      return;
1426    };
1427    let publication_identities = (publications.len() > 1).then(|| {
1428      publications
1429        .iter()
1430        .map(DependencyPublication::identity)
1431        .collect::<FxHashSet<_>>()
1432    });
1433    let waiter = {
1434      let mut state = self
1435        .state
1436        .lock()
1437        .unwrap_or_else(std::sync::PoisonError::into_inner);
1438      let removed = state
1439        .current
1440        .iter_mut()
1441        .filter_map(|current| {
1442          self.note_entry_visit();
1443          let is_derived = publication_identities.as_ref().map_or_else(
1444            || current.publication.is_derived_from(first_publication),
1445            |identities| {
1446              current
1447                .publication
1448                .immediate_source_identity()
1449                .is_some_and(|identity| identities.contains(&identity))
1450            },
1451          );
1452          if !is_derived {
1453            return None;
1454          }
1455          current.publication.cancel();
1456          Some(current.sequence)
1457        })
1458        .collect::<Vec<_>>();
1459      // No `job_sequences` index to maintain; the cancelled entries stay in
1460      // `current` until `prune_stale_prefix` retires them, exactly as before.
1461      if removed.is_empty() {
1462        None
1463      } else {
1464        self.prune_stale_prefix(&mut state);
1465        state.waiter.take()
1466      }
1467    };
1468    self.notify_waiter(waiter);
1469  }
1470
1471  /// Publish an owned dependency and return the sequence it was filed under,
1472  /// so the caller can [`retire`](Self::retire) exactly this entry in O(1).
1473  fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1474    let publication = DependencyPublication::owned(BlockingDependency {
1475      owner: dependency.owner.or(self.owner_hint),
1476      ..dependency
1477    });
1478    self.set_entry(publication)
1479  }
1480
1481  /// Whether `sequence` still resolves to a LIVE entry in this state.
1482  ///
1483  /// A publisher uses this to decide whether its earlier publication still
1484  /// stands. `false` means the entry was drained by `begin_poll`, pruned, or
1485  /// cancelled (by a lending claim, say), so a fresh publication is needed.
1486  fn holds_live(&self, sequence: u64) -> bool {
1487    let state = self
1488      .state
1489      .lock()
1490      .unwrap_or_else(std::sync::PoisonError::into_inner);
1491    TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1492      self.note_entry_visit();
1493      entry.publication.is_live()
1494    })
1495  }
1496
1497  fn set_borrowed(&self, publication: &DependencyPublication) {
1498    let _sequence = self.set_entry(publication.derived(self.owner_hint));
1499  }
1500
1501  fn set_entry(&self, publication: DependencyPublication) -> u64 {
1502    let (sequence, waiter) = {
1503      let mut state = self
1504        .state
1505        .lock()
1506        .unwrap_or_else(std::sync::PoisonError::into_inner);
1507      // Owned publications and propagated publications both create a fresh
1508      // local liveness link, so identity cannot already exist in this state.
1509      let sequence = state.append(publication);
1510      self.has_current.store(true, Ordering::Release);
1511      (sequence, state.waiter.take())
1512    };
1513    self.notify_waiter(waiter);
1514    sequence
1515  }
1516
1517  fn note_entry_visit(&self) {
1518    #[cfg(test)]
1519    self.entry_visits.fetch_add(1, Ordering::Relaxed);
1520  }
1521
1522  fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1523    while state.current.front().is_some_and(|current| {
1524      self.note_entry_visit();
1525      !current.publication.is_live()
1526    }) {
1527      let current = state
1528        .current
1529        .pop_front()
1530        .expect("the stale prefix entry must exist");
1531      state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1532    }
1533    self
1534      .has_current
1535      .store(!state.current.is_empty(), Ordering::Release);
1536  }
1537
1538  fn first_live_sequence(
1539    &self,
1540    current: &VecDeque<TaskDependencyEntry>,
1541    sequences: &mut VecDeque<u64>,
1542  ) -> Option<u64> {
1543    loop {
1544      let sequence = *sequences.front()?;
1545      let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1546        sequences.pop_front();
1547        continue;
1548      };
1549      self.note_entry_visit();
1550      if entry.publication.is_live() {
1551        return Some(sequence);
1552      }
1553      sequences.pop_front();
1554    }
1555  }
1556
1557  fn first_live_owner_sequence(
1558    &self,
1559    state: &mut TaskDependencyState,
1560    owner: BlockingOwnerToken,
1561  ) -> Option<u64> {
1562    let sequence = {
1563      let current = &state.current;
1564      state
1565        .owner_sequences
1566        .get_mut(&owner)
1567        .and_then(|sequences| self.first_live_sequence(current, sequences))
1568    };
1569    if sequence.is_none() {
1570      state.owner_sequences.remove(&owner);
1571    }
1572    sequence
1573  }
1574
1575  fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1576    self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1577  }
1578
1579  #[cfg(test)]
1580  fn get(&self) -> Option<DependencyPublication> {
1581    self
1582      .state
1583      .lock()
1584      .unwrap_or_else(std::sync::PoisonError::into_inner)
1585      .current
1586      .iter()
1587      .find(|current| current.publication.is_live())
1588      .map(|current| current.publication.clone())
1589  }
1590
1591  fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1592    self
1593      .state
1594      .lock()
1595      .unwrap_or_else(std::sync::PoisonError::into_inner)
1596      .current
1597      .iter()
1598      .filter(|current| {
1599        self.note_entry_visit();
1600        current.publication.is_live()
1601      })
1602      .map(|current| current.publication.clone())
1603      .collect()
1604  }
1605
1606  fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1607    let mut state = self
1608      .state
1609      .lock()
1610      .unwrap_or_else(std::sync::PoisonError::into_inner);
1611    self.prune_stale_prefix(&mut state);
1612    let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1613    let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1614    let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1615      (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1616      (Some(_) | None, Some(unowned)) => (unowned, true),
1617      (Some(exact), None) => (exact, false),
1618      (None, None) => return None,
1619    };
1620    if bind_owner {
1621      let removed = state.unowned_sequences.pop_front();
1622      debug_assert_eq!(removed, Some(sequence));
1623      let current = state
1624        .entry_mut(sequence)
1625        .expect("the selected dependency must remain indexed");
1626      debug_assert_eq!(current.publication.dependency.owner, None);
1627      current.publication.dependency.owner = Some(owner);
1628      // A previous exact-owner query may have observed no entry. Publishing
1629      // this sequence at the front makes the newly bound dependency visible
1630      // immediately without restarting a global scan.
1631      state
1632        .owner_sequences
1633        .entry(owner)
1634        .or_default()
1635        .push_front(sequence);
1636    }
1637    let publication = state
1638      .entry(sequence)
1639      .expect("the selected dependency must remain indexed")
1640      .publication
1641      .clone();
1642    Some(SelectedDependencyPublication {
1643      sequence,
1644      publication,
1645    })
1646  }
1647
1648  fn has_live_owner_dependency(
1649    &self,
1650    owner: BlockingOwnerToken,
1651    mode: OwnerDependencyMode,
1652  ) -> bool {
1653    #[cfg(all(test, napi_runtime_os_threads))]
1654    run_dependency_predicate_test_hook();
1655    let mut state = self
1656      .state
1657      .lock()
1658      .unwrap_or_else(std::sync::PoisonError::into_inner);
1659    self.prune_stale_prefix(&mut state);
1660    if self.first_live_owner_sequence(&mut state, owner).is_some() {
1661      return true;
1662    }
1663    matches!(mode, OwnerDependencyMode::Available)
1664      && self.first_live_unowned_sequence(&mut state).is_some()
1665  }
1666
1667  #[cfg(test)]
1668  fn get_dependency(&self) -> Option<BlockingDependency> {
1669    self.get().map(|publication| publication.dependency)
1670  }
1671
1672  #[cfg(test)]
1673  fn get_dependencies(&self) -> Vec<BlockingDependency> {
1674    self
1675      .snapshot_all_live()
1676      .into_iter()
1677      .map(|publication| publication.dependency)
1678      .collect()
1679  }
1680
1681  #[cfg(test)]
1682  fn reset_entry_visits(&self) {
1683    self.entry_visits.store(0, Ordering::Relaxed);
1684  }
1685
1686  #[cfg(test)]
1687  fn entry_visits(&self) -> usize {
1688    self.entry_visits.load(Ordering::Relaxed)
1689  }
1690
1691  /// Atomically reserve `job` for exact-dependency execution. A dependency
1692  /// transition that wins this race makes the claim fail, so lending can never
1693  /// execute a stale job copied from an earlier poll.
1694  fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1695    let mut state = self
1696      .state
1697      .lock()
1698      .unwrap_or_else(std::sync::PoisonError::into_inner);
1699    let Some(current) = state.entry(selected.sequence) else {
1700      return false;
1701    };
1702    self.note_entry_visit();
1703    if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1704      return false;
1705    }
1706    let dependency = current.publication.dependency;
1707    state
1708      .entry_mut(selected.sequence)
1709      .expect("the claimed dependency must remain indexed")
1710      .publication
1711      .cancel();
1712    state.remove_sequence_indexes(selected.sequence, dependency);
1713    self.prune_stale_prefix(&mut state);
1714    true
1715  }
1716
1717  fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1718    let _generation = RuntimeGenerationGuard::enter(self.generation);
1719    let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1720      return Vec::new();
1721    };
1722    let (previous, unused, current) = {
1723      let mut state = self
1724        .state
1725        .lock()
1726        .unwrap_or_else(std::sync::PoisonError::into_inner);
1727      let current = state
1728        .current
1729        .iter()
1730        .filter(|current| current.publication.is_live())
1731        .map(|current| current.publication.clone())
1732        .collect();
1733      if state
1734        .waiter
1735        .as_ref()
1736        .is_some_and(|previous| previous.will_wake(&waker))
1737      {
1738        (None, Some(waker), current)
1739      } else {
1740        (state.waiter.replace(waker), None, current)
1741      }
1742    };
1743    if let Some(previous) = previous {
1744      let _ = catch_unwind_contained(|| drop(previous));
1745    }
1746    if let Some(unused) = unused {
1747      let _ = catch_unwind_contained(|| drop(unused));
1748    }
1749    current
1750  }
1751
1752  fn begin_poll(&self) {
1753    if !self.has_current.load(Ordering::Acquire) {
1754      return;
1755    }
1756    let waiter = {
1757      let mut state = self
1758        .state
1759        .lock()
1760        .unwrap_or_else(std::sync::PoisonError::into_inner);
1761      if state.current.is_empty() {
1762        self.has_current.store(false, Ordering::Release);
1763        None
1764      } else {
1765        for previous in state.current.drain(..) {
1766          previous.publication.cancel();
1767        }
1768        state.clear_indexes();
1769        self.has_current.store(false, Ordering::Release);
1770        state.waiter.take()
1771      }
1772    };
1773    self.notify_waiter(waiter);
1774  }
1775
1776  #[cfg(test)]
1777  fn register_waiter(&self, waker: &Waker) {
1778    let _ = self.register_waiter_and_get(waker);
1779  }
1780}
1781
1782#[cfg(napi_runtime_os_threads)]
1783impl Drop for TaskDependency {
1784  fn drop(&mut self) {
1785    let state = self
1786      .state
1787      .get_mut()
1788      .unwrap_or_else(std::sync::PoisonError::into_inner);
1789    for current in state.current.drain(..) {
1790      current.publication.cancel();
1791    }
1792    if let Some(waiter) = state.waiter.take() {
1793      let _generation = RuntimeGenerationGuard::enter(self.generation);
1794      let _ = catch_unwind_contained(|| drop(waiter));
1795    }
1796  }
1797}
1798
1799#[cfg(napi_runtime_os_threads)]
1800thread_local! {
1801  static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1802    const { std::cell::RefCell::new(Vec::new()) };
1803}
1804
1805#[cfg(napi_runtime_os_threads)]
1806struct TaskDependencyGuard(Arc<TaskDependency>);
1807
1808#[cfg(napi_runtime_os_threads)]
1809impl TaskDependencyGuard {
1810  fn enter(dependency: &Arc<TaskDependency>) -> Self {
1811    let dependency = Arc::clone(dependency);
1812    CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1813    Self(dependency)
1814  }
1815}
1816
1817#[cfg(napi_runtime_os_threads)]
1818impl Drop for TaskDependencyGuard {
1819  fn drop(&mut self) {
1820    CURRENT_DEPENDENCY_STACK.with(|stack| {
1821      let current = stack
1822        .borrow_mut()
1823        .pop()
1824        .expect("blocking dependency context underflow");
1825      debug_assert!(Arc::ptr_eq(&current, &self.0));
1826    });
1827  }
1828}
1829
1830#[cfg(napi_runtime_os_threads)]
1831struct BlockOnDependencyGuard {
1832  dependency: Arc<TaskDependency>,
1833  _context: TaskDependencyGuard,
1834}
1835
1836#[cfg(napi_runtime_os_threads)]
1837impl BlockOnDependencyGuard {
1838  fn enter(generation: u64) -> Self {
1839    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1840    let dependency = Arc::new(TaskDependency::new(generation, owner));
1841    let context = TaskDependencyGuard::enter(&dependency);
1842    Self {
1843      dependency,
1844      _context: context,
1845    }
1846  }
1847
1848  fn clear(&self) {
1849    self.dependency.clear();
1850  }
1851}
1852
1853/// A dependency this handle published, remembered well enough to retire it in
1854/// O(1).
1855///
1856/// The sequence alone is not enough: sequences are allocated per
1857/// `TaskDependency`, so applying one to a different state would cancel an
1858/// unrelated entry. The state's identity therefore travels with it, as a
1859/// `Weak` rather than a raw pointer so a freed state cannot be impersonated by
1860/// a later allocation reusing its address -- a `Weak` keeps the allocation
1861/// itself reserved, so the pointer stays unique even after the state is gone.
1862///
1863/// It is deliberately NOT a strong `Arc`: a handle can be polled under many
1864/// frames over its life, and holding each one alive to remember it would retain
1865/// one dead `TaskDependency`, with its deque and index maps, per frame the
1866/// handle has ever seen.
1867#[cfg(napi_runtime_os_threads)]
1868struct PublishedBlockingDependency {
1869  state: Weak<TaskDependency>,
1870  sequence: u64,
1871}
1872
1873/// Every publication a blocking handle has made, one per dependency frame it
1874/// has been polled under.
1875///
1876/// A handle publishes once per Pending poll into whichever frame is on top of
1877/// `CURRENT_DEPENDENCY_STACK`. Re-polling under a frame it has already
1878/// published into supersedes that frame's entry, so the common case is exactly
1879/// one publication and `rest` never allocates.
1880///
1881/// Retiring EVERY publication is load-bearing. Dropping a blocking handle
1882/// detaches rather than dequeues, so any entry left live keeps a job that
1883/// nobody awaits selectable for owner-lane lending -- and `select_for_owner`
1884/// picks the LOWEST live sequence, so a forgotten entry outranks dependencies
1885/// published after it. Lending then runs the detached job inline in the owner's
1886/// single lane, which deadlocks if that job waits on the dependency it
1887/// displaced. The job-id index this replaces cancelled every entry for the job
1888/// in the frame it was called on, so nothing was forgotten; a handle that
1889/// remembered only its newest publication would forget both a repeat poll in
1890/// one frame and a poll under a nested frame.
1891#[cfg(napi_runtime_os_threads)]
1892#[derive(Default)]
1893struct PublishedBlockingDependencies {
1894  first: Option<PublishedBlockingDependency>,
1895  rest: Vec<PublishedBlockingDependency>,
1896}
1897
1898#[cfg(napi_runtime_os_threads)]
1899impl PublishedBlockingDependency {
1900  /// Whether this publication is still worth remembering.
1901  ///
1902  /// A token stops standing when its frame is gone -- a dropped
1903  /// `TaskDependency` already cancelled every entry it held -- or when the
1904  /// frame no longer holds the entry as live, because its `begin_poll` drained
1905  /// `current`, `prune_stale_prefix` retired it, or a lending claim took it.
1906  ///
1907  /// Absence from this thread's dependency stack deliberately does NOT count.
1908  /// It would bound this list more tightly, but it is not sound: a `JoinHandle`
1909  /// is `Send + Unpin`, so a safe adapter can serialise one behind a mutex and
1910  /// poll it from several tasks, and then a frame parked on another thread can
1911  /// still be awaiting a handle that is being polled here. Retiring on local
1912  /// absence would take that frame's publication away while it still needs it
1913  /// for owner-lane lending.
1914  fn still_stands(&self) -> bool {
1915    self
1916      .state
1917      .upgrade()
1918      .is_some_and(|state| state.holds_live(self.sequence))
1919  }
1920}
1921
1922#[cfg(napi_runtime_os_threads)]
1923impl PublishedBlockingDependencies {
1924  /// Forget publications whose frame is gone.
1925  ///
1926  /// A dropped `TaskDependency` cancels every entry it holds, so an expired
1927  /// token has nothing left to retire. Discarding it here is what keeps this
1928  /// list at the handle's currently-live frame count rather than the total
1929  /// number of frames it has ever been polled under: without it, a pending
1930  /// handle carried through a sequence of completed tasks would accumulate one
1931  /// token per task and the lookup below would grow with it.
1932  /// Forget publications that no longer stand.
1933  ///
1934  /// A token is obsolete once its frame is gone -- a dropped `TaskDependency`
1935  /// cancels every entry it holds, so there is nothing left to retire -- or
1936  /// once that frame no longer holds the entry, because its `begin_poll`
1937  /// drained `current`. Dropping both kinds keeps this list at the handle's
1938  /// currently-live frame count rather than the number of frames it has ever
1939  /// been polled under.
1940  ///
1941  /// Testing the second condition costs a lock, so it is confined to the
1942  /// multi-frame path: a handle polled under a single frame -- the
1943  /// overwhelmingly common case -- reuses its one token and never reaches it.
1944  /// Without that condition a handle carried through many tasks whose
1945  /// `JoinHandle`s are still held would keep one token per task, since those
1946  /// handles keep the states alive, and both this walk and detach would grow
1947  /// with that count.
1948  fn prune(&mut self) {
1949    if self.rest.is_empty() {
1950      if self
1951        .first
1952        .as_ref()
1953        .is_some_and(|first| first.state.strong_count() == 0)
1954      {
1955        self.first = None;
1956      }
1957      return;
1958    }
1959    self.rest.retain(PublishedBlockingDependency::still_stands);
1960    if self
1961      .first
1962      .as_ref()
1963      .is_some_and(|first| !first.still_stands())
1964    {
1965      self.first = self.rest.pop();
1966    }
1967  }
1968
1969  fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1970    self.prune();
1971    let target = Arc::as_ptr(&state);
1972    if let Some(existing) = self
1973      .first
1974      .iter_mut()
1975      .chain(self.rest.iter_mut())
1976      .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1977    {
1978      // The entry this handle already published into this frame still says the
1979      // frame depends on this job, so a repeat Pending poll must LEAVE IT
1980      // ALONE. Publishing again would append at the tail, and
1981      // `select_for_owner` picks the LOWEST live sequence, so re-polling a
1982      // handle would push its own dependency behind ones published after it.
1983      // In an H1 -> H2 -> H1 poll pass that reorders H1 behind H2, and if H2
1984      // waits on H1 the owner lane lends H2 inline while H1 stays queued
1985      // forever. Keeping the original sequence preserves the publication order
1986      // the job index produced, and skips a `DependencyClaim`/`DependencyLink`
1987      // pair per repeat poll.
1988      if state.holds_live(existing.sequence) {
1989        return;
1990      }
1991      // The entry was drained, pruned or claimed, so it no longer stands and
1992      // this frame needs a fresh one. Reusing the token keeps the list at one
1993      // entry per frame.
1994      existing.sequence = state.set_owned(dependency);
1995      return;
1996    }
1997    let sequence = state.set_owned(dependency);
1998    let published = PublishedBlockingDependency {
1999      state: Arc::downgrade(&state),
2000      sequence,
2001    };
2002    match &mut self.first {
2003      slot @ None => *slot = Some(published),
2004      Some(_) => self.rest.push(published),
2005    }
2006  }
2007
2008  /// Retire every publication, each against the state it was filed in.
2009  ///
2010  /// This is exactly inverse to publication, so it can never cancel an entry
2011  /// this handle did not create. It cannot strand a frame either: a frame is
2012  /// only stranded if it is parked awaiting something, and it cannot be parked
2013  /// awaiting a handle that is concurrently being dropped, because the handle
2014  /// is owned by the future that frame is driving. A frame that has already
2015  /// gone cancelled its entries in its own `Drop`, so an expired token is
2016  /// simply skipped.
2017  fn retire_all(self) {
2018    for published in self.first.into_iter().chain(self.rest) {
2019      if let Some(state) = published.state.upgrade() {
2020        state.retire(published.sequence);
2021      }
2022    }
2023  }
2024}
2025
2026#[cfg(napi_runtime_os_threads)]
2027fn record_blocking_dependency(
2028  published: &mut PublishedBlockingDependencies,
2029  dependency: BlockingDependency,
2030) {
2031  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2032  if let Some(current) = current {
2033    let dependency = BlockingDependency {
2034      owner: dependency
2035        .owner
2036        .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
2037      ..dependency
2038    };
2039    published.publish(current, dependency);
2040  }
2041}
2042
2043#[cfg(napi_runtime_os_threads)]
2044fn propagate_blocking_dependency(publication: DependencyPublication) {
2045  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2046  if let Some(current) = current {
2047    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2048    let publication = publication.with_owner_if_none(owner);
2049    current.set_borrowed(&publication);
2050  }
2051}
2052
2053#[cfg(napi_runtime_os_threads)]
2054fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2055  // Retire against the states that were published to, NOT against whatever
2056  // frame happens to be on top of the stack at drop time. The frame that
2057  // published is the frame whose dependency graph is now wrong.
2058  published.retire_all();
2059}
2060
2061#[cfg(napi_runtime_os_threads)]
2062fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2063  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2064  if let Some(current) = current {
2065    current.clear_if_publications(publications);
2066  }
2067}
2068
2069enum JoinHandleInner<T> {
2070  Task {
2071    task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2072    awaiter: Option<PanicContainedWakerCache>,
2073    #[cfg(napi_runtime_os_threads)]
2074    dependency: Arc<TaskDependency>,
2075  },
2076  #[cfg(napi_runtime_os_threads)]
2077  Blocking {
2078    receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2079    dependency: BlockingDependency,
2080    /// Every dependency this handle has published, so detaching retires each
2081    /// in O(1) instead of searching for them by job id.
2082    published: PublishedBlockingDependencies,
2083  },
2084  Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2085}
2086
2087pub struct JoinHandle<T>(JoinHandleInner<T>);
2088
2089impl<T> Unpin for JoinHandle<T> {}
2090
2091impl<T> JoinHandle<T> {
2092  fn detach_task(&mut self) {
2093    let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2094    match inner {
2095      JoinHandleInner::Task {
2096        task,
2097        awaiter: _,
2098        #[cfg(napi_runtime_os_threads)]
2099        dependency,
2100      } => {
2101        #[cfg(napi_runtime_os_threads)]
2102        let current_dependencies = dependency.snapshot_all_live();
2103        #[cfg(napi_runtime_os_threads)]
2104        dependency.clear_waiter();
2105        task.detach();
2106        #[cfg(napi_runtime_os_threads)]
2107        // Dependency cleanup may wake an arbitrary caller-provided waker.
2108        // Retire the task first, then contain the notification boundary so
2109        // reentrant wake code cannot observe a half-detached handle.
2110        let _ = catch_unwind_contained(|| {
2111          clear_propagated_blocking_dependencies(&current_dependencies);
2112        });
2113      }
2114      #[cfg(napi_runtime_os_threads)]
2115      JoinHandleInner::Blocking {
2116        receiver,
2117        dependency: _,
2118        published,
2119      } => {
2120        // A completed result may already be buffered in the receiver. Its
2121        // destructor is user code and must receive the same containment as an
2122        // immediate CurrentThread result.
2123        let _ = catch_unwind_contained(|| drop(receiver));
2124        // Notify only after receiver retirement, and keep that arbitrary waker
2125        // behind an independent unwind boundary.
2126        let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2127      }
2128      JoinHandleInner::Ready(result) => {
2129        let _ = catch_unwind_contained(|| drop(result));
2130      }
2131    }
2132  }
2133
2134  pub fn detach(mut self) {
2135    self.detach_task();
2136  }
2137}
2138
2139impl<T> Drop for JoinHandle<T> {
2140  fn drop(&mut self) {
2141    // Tokio's JoinHandle detaches on drop. Preserve that compatibility:
2142    // fire-and-forget callers must not silently cancel work merely because
2143    // they ignore the returned handle.
2144    self.detach_task();
2145  }
2146}
2147
2148impl<T> Future for JoinHandle<T> {
2149  type Output = Result<T, JoinError>;
2150
2151  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2152    match &mut self.get_mut().0 {
2153      JoinHandleInner::Task {
2154        task,
2155        awaiter: awaiter_slot,
2156        #[cfg(napi_runtime_os_threads)]
2157        dependency,
2158      } => {
2159        let awaiter = match awaiter_slot {
2160          Some(awaiter) => {
2161            awaiter.update(cx.waker());
2162            awaiter
2163          }
2164          None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2165        };
2166        let contained_waker = awaiter.contained();
2167        let mut contained_cx = Context::from_waker(contained_waker);
2168        let poll = Pin::new(task).poll(&mut contained_cx);
2169        #[cfg(napi_runtime_os_threads)]
2170        if poll.is_pending() {
2171          for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2172            propagate_blocking_dependency(current_dependency);
2173          }
2174        } else {
2175          dependency.clear_waiter();
2176        }
2177        if poll.is_ready() {
2178          *awaiter_slot = None;
2179        }
2180        match poll {
2181          Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2182          Poll::Ready(None) => Poll::Ready(Err(JoinError {
2183            message: "async runtime stopped before the task completed".to_string(),
2184          })),
2185          Poll::Pending => Poll::Pending,
2186        }
2187      }
2188      #[cfg(napi_runtime_os_threads)]
2189      JoinHandleInner::Blocking {
2190        receiver,
2191        dependency,
2192        published,
2193      } => {
2194        let poll = Pin::new(receiver).poll(cx);
2195        if poll.is_pending() {
2196          record_blocking_dependency(published, *dependency);
2197        }
2198        match poll {
2199          Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2200          Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2201            message: "async runtime stopped before the blocking task completed".to_string(),
2202          })),
2203          Poll::Pending => Poll::Pending,
2204        }
2205      }
2206      JoinHandleInner::Ready(result) => Poll::Ready(
2207        result
2208          .take()
2209          .expect("JoinHandle polled after completion")
2210          .map(ContainedTaskOutput::into_inner),
2211      ),
2212    }
2213  }
2214}
2215
2216#[derive(Debug, Clone, Copy)]
2217pub struct RuntimeMetricsSnapshot {
2218  pub flavor: RuntimeFlavor,
2219  pub worker_threads: usize,
2220  pub max_blocking_tasks: usize,
2221  pub tasks_spawned: u64,
2222  pub tasks_completed: u64,
2223  pub tasks_panicked: u64,
2224  pub runnable_schedules: u64,
2225  pub runnable_polls: u64,
2226  pub queued_runnables: u64,
2227  pub max_queued_runnables: u64,
2228  pub active_runnables: u64,
2229  pub max_active_runnables: u64,
2230  pub blocking_tasks_started: u64,
2231  pub blocking_tasks_completed: u64,
2232  pub active_blocking_tasks: u64,
2233  pub max_active_blocking_tasks: u64,
2234}
2235
2236#[derive(Default)]
2237struct RuntimeMetrics {
2238  tasks_spawned: AtomicU64,
2239  tasks_completed: AtomicU64,
2240  tasks_panicked: AtomicU64,
2241  runnable_schedules: AtomicU64,
2242  runnable_polls: AtomicU64,
2243  queued_runnables: AtomicU64,
2244  max_queued_runnables: AtomicU64,
2245  active_runnables: AtomicU64,
2246  max_active_runnables: AtomicU64,
2247  // Bumped at ENQUEUE time (before the queue push), the blocking-side twin of
2248  // `runnable_schedules`. Internal-only (not exported in the public snapshot):
2249  // it exists so the deadlock detector's progress fingerprint can observe a
2250  // blocking submission the moment it is made -- `blocking_tasks_started`
2251  // only advances once a worker RUNS the job, which may be arbitrarily later.
2252  blocking_tasks_scheduled: AtomicU64,
2253  blocking_tasks_started: AtomicU64,
2254  blocking_tasks_completed: AtomicU64,
2255  active_blocking_tasks: AtomicU64,
2256  max_active_blocking_tasks: AtomicU64,
2257  // Seqlock generation around resets. The deadlock detector snapshots
2258  // resettable counters, so it must distinguish identical counter values from
2259  // different reset generations instead of mistaking that ABA for no progress.
2260  reset_generation: AtomicU64,
2261  reset_lock: Mutex<()>,
2262}
2263
2264#[cfg(test)]
2265thread_local! {
2266  static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2267    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2268      const { std::cell::RefCell::new(None) };
2269}
2270
2271#[cfg(test)]
2272fn run_runtime_metrics_high_water_test_hook() {
2273  if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2274    hook();
2275  }
2276}
2277
2278impl RuntimeMetrics {
2279  fn runnable_scheduled(&self) {
2280    self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2281    let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2282    #[cfg(test)]
2283    run_runtime_metrics_high_water_test_hook();
2284    self
2285      .max_queued_runnables
2286      .fetch_max(queued, Ordering::Relaxed);
2287  }
2288
2289  fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2290    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2291    self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2292    let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2293    #[cfg(test)]
2294    run_runtime_metrics_high_water_test_hook();
2295    self
2296      .max_active_runnables
2297      .fetch_max(active, Ordering::Relaxed);
2298    ActiveRunnableGuard { metrics: self }
2299  }
2300
2301  #[cfg(any(napi_runtime_os_threads, test))]
2302  fn runnable_cancelled(&self) {
2303    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2304  }
2305
2306  /// Enqueue-time twin of [`Self::runnable_scheduled`] for blocking work:
2307  /// called BEFORE the blocking-queue push, so the deadlock detector's
2308  /// fingerprint sees a submission even when no worker has started the job.
2309  fn blocking_scheduled(&self) {
2310    self
2311      .blocking_tasks_scheduled
2312      .fetch_add(1, Ordering::Relaxed);
2313  }
2314
2315  fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2316    self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2317    if count_active_lane {
2318      let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2319      #[cfg(test)]
2320      run_runtime_metrics_high_water_test_hook();
2321      self
2322        .max_active_blocking_tasks
2323        .fetch_max(active, Ordering::Relaxed);
2324    }
2325    BlockingTaskGuard {
2326      metrics: Arc::clone(self),
2327      count_active_lane,
2328    }
2329  }
2330
2331  fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2332    let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2333    let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2334    let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2335    RuntimeMetricsSnapshot {
2336      flavor: options.flavor,
2337      worker_threads: options.worker_threads,
2338      max_blocking_tasks: options.max_blocking_tasks,
2339      tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2340      tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2341      tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2342      runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2343      runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2344      queued_runnables,
2345      max_queued_runnables: self
2346        .max_queued_runnables
2347        .load(Ordering::Relaxed)
2348        .max(queued_runnables),
2349      active_runnables,
2350      max_active_runnables: self
2351        .max_active_runnables
2352        .load(Ordering::Relaxed)
2353        .max(active_runnables),
2354      blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2355      blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2356      active_blocking_tasks,
2357      max_active_blocking_tasks: self
2358        .max_active_blocking_tasks
2359        .load(Ordering::Relaxed)
2360        .max(active_blocking_tasks),
2361    }
2362  }
2363
2364  /// Coarse liveness fingerprint for deadline-based deadlock detection (wake
2365  /// -path §8.5): any of these counters advancing between two reads means the
2366  /// runtime did SOMETHING (scheduled or polled a runnable, completed a task,
2367  /// scheduled, started or finished a blocking job), so a parked driver
2368  /// observing an advance re-arms its deadline instead of firing. Both ENQUEUE
2369  /// counters (`runnable_schedules`, `blocking_tasks_scheduled`) are included
2370  /// so a submission is progress the moment it is made -- not only once a
2371  /// worker gets to run it.
2372  fn progress_fingerprint(&self) -> ProgressFingerprint {
2373    loop {
2374      let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2375      if !reset_generation.is_multiple_of(2) {
2376        std::hint::spin_loop();
2377        continue;
2378      }
2379      let fingerprint = ProgressFingerprint {
2380        reset_generation,
2381        runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2382        runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2383        tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2384        blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2385        blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2386        blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2387      };
2388      if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2389        return fingerprint;
2390      }
2391    }
2392  }
2393
2394  fn reset(&self) {
2395    let reset_guard = self
2396      .reset_lock
2397      .lock()
2398      .unwrap_or_else(std::sync::PoisonError::into_inner);
2399    // Event counters are safe to reset independently. Live gauges are not:
2400    // queued/active guards may still hold a future decrement, and zeroing a
2401    // gauge underneath them wraps the unsigned counter on completion.
2402    //
2403    // High-water gauges remain lifetime values. Resetting them concurrently
2404    // with fetch_max would either lose a real peak or require synchronization
2405    // on every hot-path metric update.
2406    let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2407    let Some(resetting_generation) = reset_generation.checked_add(1) else {
2408      drop(reset_guard);
2409      panic!("runtime metrics reset generation space exhausted");
2410    };
2411    let Some(settled_generation) = resetting_generation.checked_add(1) else {
2412      drop(reset_guard);
2413      panic!("runtime metrics reset generation space exhausted");
2414    };
2415    self
2416      .reset_generation
2417      .store(resetting_generation, Ordering::SeqCst);
2418    self.tasks_spawned.store(0, Ordering::Relaxed);
2419    self.tasks_completed.store(0, Ordering::Relaxed);
2420    self.tasks_panicked.store(0, Ordering::Relaxed);
2421    self.runnable_schedules.store(0, Ordering::Relaxed);
2422    self.runnable_polls.store(0, Ordering::Relaxed);
2423    self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2424    self.blocking_tasks_started.store(0, Ordering::Relaxed);
2425    self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2426    self
2427      .reset_generation
2428      .store(settled_generation, Ordering::SeqCst);
2429  }
2430}
2431
2432/// See [`RuntimeMetrics::progress_fingerprint`].
2433#[derive(Clone, Copy, PartialEq, Eq)]
2434struct ProgressFingerprint {
2435  reset_generation: u64,
2436  runnable_schedules: u64,
2437  runnable_polls: u64,
2438  tasks_completed: u64,
2439  blocking_tasks_scheduled: u64,
2440  blocking_tasks_started: u64,
2441  blocking_tasks_completed: u64,
2442}
2443
2444struct ActiveRunnableGuard<'a> {
2445  metrics: &'a RuntimeMetrics,
2446}
2447
2448impl Drop for ActiveRunnableGuard<'_> {
2449  fn drop(&mut self) {
2450    self
2451      .metrics
2452      .active_runnables
2453      .fetch_sub(1, Ordering::Relaxed);
2454  }
2455}
2456
2457struct BlockingTaskGuard {
2458  metrics: Arc<RuntimeMetrics>,
2459  count_active_lane: bool,
2460}
2461
2462impl Drop for BlockingTaskGuard {
2463  fn drop(&mut self) {
2464    if self.count_active_lane {
2465      self
2466        .metrics
2467        .active_blocking_tasks
2468        .fetch_sub(1, Ordering::Relaxed);
2469    }
2470    self
2471      .metrics
2472      .blocking_tasks_completed
2473      .fetch_add(1, Ordering::Relaxed);
2474  }
2475}
2476
2477static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2478
2479fn next_runtime_generation() -> u64 {
2480  next_unique_id(
2481    &NEXT_RUNTIME_GENERATION,
2482    "async runtime generation id space exhausted",
2483  )
2484}
2485
2486thread_local! {
2487  static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2488    const { std::cell::Cell::new(None) };
2489}
2490
2491fn active_runtime_generation() -> Option<u64> {
2492  ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2493}
2494
2495struct RuntimeGenerationGuard {
2496  previous: Option<u64>,
2497}
2498
2499impl RuntimeGenerationGuard {
2500  fn enter(generation: u64) -> Self {
2501    Self::enter_optional(Some(generation))
2502  }
2503
2504  fn enter_optional(generation: Option<u64>) -> Self {
2505    Self {
2506      previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2507    }
2508  }
2509}
2510
2511impl Drop for RuntimeGenerationGuard {
2512  fn drop(&mut self) {
2513    ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2514  }
2515}
2516
2517struct ContainedFuture<F> {
2518  future: ManuallyDrop<F>,
2519  generation: u64,
2520}
2521
2522impl<F> ContainedFuture<F> {
2523  fn new(future: F, generation: u64) -> Self {
2524    Self {
2525      future: ManuallyDrop::new(future),
2526      generation,
2527    }
2528  }
2529}
2530
2531impl<F: Future> Future for ContainedFuture<F> {
2532  type Output = F::Output;
2533
2534  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2535    // SAFETY: `future` is never moved after `ContainedFuture` is pinned.
2536    let this = unsafe { self.get_unchecked_mut() };
2537    let _generation = RuntimeGenerationGuard::enter(this.generation);
2538    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2539    // structurally pinned for the lifetime of `self`.
2540    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2541  }
2542}
2543
2544impl<F> Drop for ContainedFuture<F> {
2545  fn drop(&mut self) {
2546    let _generation = RuntimeGenerationGuard::enter(self.generation);
2547    let _ = catch_unwind_contained(|| {
2548      // SAFETY: this is the sole destruction path for `future`.
2549      unsafe { ManuallyDrop::drop(&mut self.future) };
2550    });
2551  }
2552}
2553
2554#[derive(Default)]
2555struct GenerationStop {
2556  stopping: AtomicBool,
2557  publication: Mutex<()>,
2558  parkers: Mutex<Vec<Weak<DriverParker>>>,
2559}
2560
2561impl GenerationStop {
2562  fn is_stopping(&self) -> bool {
2563    self.stopping.load(Ordering::Acquire)
2564  }
2565
2566  fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2567    #[cfg(all(test, napi_runtime_os_threads))]
2568    run_before_generation_stop_publication_lock_test_hook();
2569    self.runnable_claim_guard()
2570  }
2571
2572  fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2573    GenerationStopPublicationGuard {
2574      stop: self,
2575      _publication: self
2576        .publication
2577        .lock()
2578        .unwrap_or_else(std::sync::PoisonError::into_inner),
2579    }
2580  }
2581
2582  fn publish_stopping(&self) {
2583    self.publication_guard().publish_stopping();
2584  }
2585
2586  fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2587    {
2588      let mut parkers = self
2589        .parkers
2590        .lock()
2591        .unwrap_or_else(std::sync::PoisonError::into_inner);
2592      parkers.retain(|candidate| candidate.strong_count() != 0);
2593      parkers.push(Arc::downgrade(parker));
2594    }
2595    if self.is_stopping() {
2596      parker.unpark();
2597    }
2598    GenerationStopGuard {
2599      stop: Arc::clone(self),
2600      parker: Arc::downgrade(parker),
2601    }
2602  }
2603
2604  /// Wake every active `block_on` frame without changing the generation state.
2605  /// The number of parkers is bounded by concurrent explicit drivers. Waking
2606  /// all avoids directing a queue wake only to a newer driver that is blocked
2607  /// inside its poll while an older driver is already asleep.
2608  fn wake_all(&self) -> usize {
2609    let mut count = 0;
2610    let mut registered = self
2611      .parkers
2612      .lock()
2613      .unwrap_or_else(std::sync::PoisonError::into_inner);
2614    registered.retain(|candidate| {
2615      let Some(parker) = candidate.upgrade() else {
2616        return false;
2617      };
2618      count += 1;
2619      // `DriverParker::unpark` invokes no user code and never enters the
2620      // generation registry, so waking under this lock is non-reentrant.
2621      parker.unpark();
2622      true
2623    });
2624    count
2625  }
2626
2627  fn begin_shutdown(&self) {
2628    self.publish_stopping();
2629    let parkers = {
2630      let mut registered = self
2631        .parkers
2632        .lock()
2633        .unwrap_or_else(std::sync::PoisonError::into_inner);
2634      let parkers = registered
2635        .iter()
2636        .filter_map(Weak::upgrade)
2637        .collect::<Vec<_>>();
2638      registered.clear();
2639      parkers
2640    };
2641    for parker in parkers {
2642      parker.unpark();
2643    }
2644  }
2645}
2646
2647struct GenerationStopPublicationGuard<'a> {
2648  stop: &'a GenerationStop,
2649  _publication: MutexGuard<'a, ()>,
2650}
2651
2652impl GenerationStopPublicationGuard<'_> {
2653  fn is_stopping(&self) -> bool {
2654    self.stop.is_stopping()
2655  }
2656
2657  fn publish_stopping(&self) {
2658    self.stop.stopping.store(true, Ordering::Release);
2659  }
2660}
2661
2662struct GenerationStopGuard {
2663  stop: Arc<GenerationStop>,
2664  parker: Weak<DriverParker>,
2665}
2666
2667impl Drop for GenerationStopGuard {
2668  fn drop(&mut self) {
2669    let Some(parker) = self.parker.upgrade() else {
2670      return;
2671    };
2672    let mut registered = self
2673      .stop
2674      .parkers
2675      .lock()
2676      .unwrap_or_else(std::sync::PoisonError::into_inner);
2677    if let Some(index) = registered.iter().position(|candidate| {
2678      candidate
2679        .upgrade()
2680        .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2681    }) {
2682      registered.swap_remove(index);
2683    }
2684  }
2685}
2686
2687/// Whether a work-pending read may wait for the locks its sources live under.
2688#[derive(Clone, Copy)]
2689enum PendingRead {
2690  /// Wait for each lock, for the exact answer. Only `begin_shutdown` reads
2691  /// its verdict this way; the host's poll never does.
2692  Locked,
2693  /// Never wait: a source whose lock another thread holds reads as pending.
2694  /// For the host's poll, which must never block; see
2695  /// `RuntimeController::runtime_work_pending` for why a held lock is not
2696  /// always about to come back.
2697  NonBlocking,
2698}
2699
2700impl PendingRead {
2701  /// `pending` applied to the state behind `mutex`, or `true` without looking
2702  /// when this is [`Self::NonBlocking`] and another thread holds the lock.
2703  /// Poison is ignored, as at every other lock site here.
2704  fn read<T>(self, mutex: &Mutex<T>, pending: impl FnOnce(&T) -> bool) -> bool {
2705    let state = match self {
2706      Self::Locked => mutex.lock(),
2707      Self::NonBlocking => match mutex.try_lock() {
2708        Ok(state) => Ok(state),
2709        Err(std::sync::TryLockError::Poisoned(poisoned)) => Err(poisoned),
2710        Err(std::sync::TryLockError::WouldBlock) => return true,
2711      },
2712    };
2713    pending(&state.unwrap_or_else(std::sync::PoisonError::into_inner))
2714  }
2715}
2716
2717struct GenerationWorkState {
2718  closed: bool,
2719  next_task_id: u64,
2720  active: usize,
2721  abort_handles: FxHashMap<u64, AbortHandle>,
2722}
2723
2724struct GenerationWork {
2725  id: u64,
2726  state: Mutex<GenerationWorkState>,
2727  idle: Condvar,
2728}
2729
2730impl GenerationWork {
2731  fn new() -> Arc<Self> {
2732    Arc::new(Self {
2733      id: next_runtime_generation(),
2734      state: Mutex::new(GenerationWorkState {
2735        closed: false,
2736        next_task_id: 0,
2737        active: 0,
2738        abort_handles: FxHashMap::default(),
2739      }),
2740      idle: Condvar::new(),
2741    })
2742  }
2743
2744  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2745    let (abort_handle, abort_registration) = AbortHandle::new_pair();
2746    let mut state = self
2747      .state
2748      .lock()
2749      .unwrap_or_else(std::sync::PoisonError::into_inner);
2750    if state.closed {
2751      return None;
2752    }
2753    let task_id = state.next_task_id;
2754    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2755      drop(state);
2756      panic!("async runtime generation task id space exhausted");
2757    };
2758    state.next_task_id = next_task_id;
2759    state.active += 1;
2760    state.abort_handles.insert(task_id, abort_handle);
2761    Some((
2762      abort_registration,
2763      GenerationWorkGuard {
2764        work: Arc::clone(self),
2765        task_id: Some(task_id),
2766      },
2767    ))
2768  }
2769
2770  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2771    let mut state = self
2772      .state
2773      .lock()
2774      .unwrap_or_else(std::sync::PoisonError::into_inner);
2775    if state.closed {
2776      return None;
2777    }
2778    state.active += 1;
2779    Some(GenerationWorkGuard {
2780      work: Arc::clone(self),
2781      task_id: None,
2782    })
2783  }
2784
2785  fn close_and_abort(&self) {
2786    // Take the handles out instead of cloning them into a new `Vec`: nothing
2787    // is allocated under the state lock, and no guard retiring later frees a
2788    // handle under it (see `GenerationWorkGuard::drop`). Admission is closed
2789    // in the same critical section, so no handle can be added afterwards.
2790    let abort_handles = {
2791      let mut state = self
2792        .state
2793        .lock()
2794        .unwrap_or_else(std::sync::PoisonError::into_inner);
2795      state.closed = true;
2796      std::mem::take(&mut state.abort_handles)
2797    };
2798    for abort_handle in abort_handles.values() {
2799      abort_handle.abort();
2800    }
2801  }
2802
2803  fn wait_until_idle(&self) {
2804    self.wait_until_idle_retiring(|| false, || {});
2805  }
2806
2807  /// The predicate `wait_until_idle` sleeps on, read without sleeping;
2808  /// `read` decides whether it may wait for the state lock.
2809  fn has_active_work(&self, read: PendingRead) -> bool {
2810    read.read(&self.state, |state| state.active != 0)
2811  }
2812
2813  /// Wait until every registered guard has retired, retiring deferred work
2814  /// on this stack whenever `has_deferred` reports some. Such work (a runnable
2815  /// rejected after the executor queue closed, or the runnables a terminal
2816  /// failure transition moved aside on a waker's stack) holds guards, so the
2817  /// generation can only become idle once the waiter drops them; `retire`
2818  /// runs with no lock held. `notify_idle_waiters` publishes new deferred
2819  /// work under the state lock, so a check made here cannot miss it.
2820  fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2821    let mut state = self
2822      .state
2823      .lock()
2824      .unwrap_or_else(std::sync::PoisonError::into_inner);
2825    while state.active != 0 {
2826      if has_deferred() {
2827        drop(state);
2828        retire();
2829        state = self
2830          .state
2831          .lock()
2832          .unwrap_or_else(std::sync::PoisonError::into_inner);
2833        continue;
2834      }
2835      state = self
2836        .idle
2837        .wait(state)
2838        .unwrap_or_else(std::sync::PoisonError::into_inner);
2839    }
2840  }
2841
2842  fn notify_idle_waiters(&self) {
2843    let _state = self
2844      .state
2845      .lock()
2846      .unwrap_or_else(std::sync::PoisonError::into_inner);
2847    self.idle.notify_all();
2848  }
2849}
2850
2851struct GenerationWorkGuard {
2852  work: Arc<GenerationWork>,
2853  task_id: Option<u64>,
2854}
2855
2856impl Drop for GenerationWorkGuard {
2857  fn drop(&mut self) {
2858    // The removed handle is usually the last owner of the task's abort state,
2859    // so dropping it frees memory and drops the waker parked there. Neither
2860    // runs under the state lock: the host's work-pending poll reads it, and
2861    // on `wasm32-wasip1-threads` a trap in there unwinds nothing, which
2862    // would leave the lock held for good.
2863    let abort_handle = {
2864      let mut state = self
2865        .work
2866        .state
2867        .lock()
2868        .unwrap_or_else(std::sync::PoisonError::into_inner);
2869      let abort_handle = self
2870        .task_id
2871        .and_then(|task_id| state.abort_handles.remove(&task_id));
2872      state.active = state
2873        .active
2874        .checked_sub(1)
2875        .expect("generation work count underflow");
2876      if state.active == 0 {
2877        self.work.idle.notify_all();
2878      }
2879      abort_handle
2880    };
2881    drop(abort_handle);
2882  }
2883}
2884
2885/// Keeps accepted async work registered until the complete task generator has
2886/// been destroyed. In particular, an executor queue may close after
2887/// registration but before the initial runnable is published. `async-task`
2888/// then drops the generator unpolled, whose compiler-generated capture order
2889/// must not be allowed to retire the generation guard before user captures.
2890struct RegisteredTaskFuture<F> {
2891  future: ManuallyDrop<F>,
2892  registration: Option<GenerationWorkGuard>,
2893  generation: u64,
2894}
2895
2896impl<F> RegisteredTaskFuture<F> {
2897  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2898    Self {
2899      future: ManuallyDrop::new(future),
2900      registration: Some(registration),
2901      generation,
2902    }
2903  }
2904}
2905
2906impl<F: Future> Future for RegisteredTaskFuture<F> {
2907  type Output = F::Output;
2908
2909  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2910    // SAFETY: `future` is never moved after `RegisteredTaskFuture` is pinned.
2911    let this = unsafe { self.get_unchecked_mut() };
2912    let _generation = RuntimeGenerationGuard::enter(this.generation);
2913    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2914    // structurally pinned for the lifetime of `self`.
2915    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2916  }
2917}
2918
2919impl<F> Drop for RegisteredTaskFuture<F> {
2920  fn drop(&mut self) {
2921    let _generation = RuntimeGenerationGuard::enter(self.generation);
2922    let _ = catch_unwind_contained(|| {
2923      // SAFETY: this is the sole destruction path for `future`.
2924      unsafe { ManuallyDrop::drop(&mut self.future) };
2925    });
2926    // Shutdown/restart may observe this generation idle only after every user
2927    // capture and caught panic payload above has retired.
2928    drop(self.registration.take());
2929  }
2930}
2931
2932#[cfg(napi_runtime_os_threads)]
2933struct RegisteredBlockingFunction<F> {
2934  function: Option<F>,
2935  registration: Option<GenerationWorkGuard>,
2936  generation: u64,
2937}
2938
2939#[cfg(napi_runtime_os_threads)]
2940impl<F> RegisteredBlockingFunction<F> {
2941  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2942    Self {
2943      function: Some(function),
2944      registration: Some(registration),
2945      generation,
2946    }
2947  }
2948
2949  fn run<T>(mut self) -> Result<T, JoinError>
2950  where
2951    F: FnOnce() -> T,
2952  {
2953    on_thread_handoff();
2954    let _generation = RuntimeGenerationGuard::enter(self.generation);
2955    let result = catch_unwind_join_error(
2956      self
2957        .function
2958        .take()
2959        .expect("blocking function must be available"),
2960    );
2961    drop(self.registration.take());
2962    result
2963  }
2964}
2965
2966#[cfg(napi_runtime_os_threads)]
2967impl<F> Drop for RegisteredBlockingFunction<F> {
2968  fn drop(&mut self) {
2969    let _generation = RuntimeGenerationGuard::enter(self.generation);
2970    if let Some(function) = self.function.take() {
2971      let _ = catch_unwind_contained(|| drop(function));
2972    }
2973    // A submission accepted by the controller may reach a queue that shutdown
2974    // already closed. Keep it registered until all captured user state above
2975    // has been destroyed on the submitting thread.
2976    drop(self.registration.take());
2977  }
2978}
2979
2980fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2981  on_thread_handoff();
2982  let _active = metrics.runnable_started();
2983  let _ = catch_unwind_contained(|| runnable.run());
2984}
2985
2986#[cfg(napi_runtime_os_threads)]
2987enum RunnableClaim {
2988  Empty,
2989  Run(Runnable),
2990  Cancel(Runnable),
2991}
2992
2993static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2994static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2995
2996fn next_current_thread_dispatch_id() -> u64 {
2997  next_unique_id(
2998    &NEXT_CURRENT_THREAD_DISPATCH_ID,
2999    "CurrentThread dispatch id space exhausted",
3000  )
3001  .checked_add(1)
3002  .expect("CurrentThread dispatch id space exhausted")
3003}
3004
3005struct CurrentThreadBlockingAdmission {
3006  state: Mutex<CurrentThreadBlockingAdmissionState>,
3007}
3008
3009struct CurrentThreadBlockingAdmissionState {
3010  owner: Option<BlockingOwnerToken>,
3011  closed: bool,
3012  #[cfg(napi_runtime_os_threads)]
3013  queue: BlockingQueue,
3014}
3015
3016struct CurrentThreadExecutor {
3017  id: u64,
3018  generation: u64,
3019  stop: Arc<GenerationStop>,
3020  queue: Mutex<CurrentThreadQueue>,
3021  blocking_admission: CurrentThreadBlockingAdmission,
3022  draining: AtomicBool,
3023  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
3024  scheduler_idle: Condvar,
3025  /// Exact nonzero internal capability for the currently published host turn.
3026  /// The host registry maps this to distinct registration-scoped delivery
3027  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
3028  dispatch_pending: AtomicU64,
3029  /// Exact capability of the one replacement dispatch allowed after a host
3030  /// scheduler rejects an accepted callback. A second matching cancellation
3031  /// fails the queued work instead of retrying forever.
3032  recovery_dispatch: AtomicU64,
3033  /// Terminal dispatch failure rejects scheduler publications and admission
3034  /// from the moment it is claimed (under `scheduler_idle_lock`) until
3035  /// `cancel_work_after_host_dispatch_failure` reopens the executor under the
3036  /// queue lock: after cancelling the queued work on a clean stack, or as
3037  /// soon as it has moved that work aside on a waker's stack.
3038  cancelling_failed_dispatch: AtomicBool,
3039  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3040  task_drivers: &'static CurrentThreadTaskDriverRegistry,
3041  metrics: Arc<RuntimeMetrics>,
3042  // Deadlock detection. `threadless`: no second thread can EVER deliver a
3043  // wake to a parked `block_on`, so a park decision with an
3044  // empty queue and no pending wake token is a PROVABLE deadlock (panic
3045  // immediately, no timing involved). `park_deadline`: on threaded builds,
3046  // the optional bound for a park with zero runtime progress -- off unless
3047  // configured, because a legitimately long park must never panic a
3048  // production build.
3049  threadless: bool,
3050  park_deadline: Option<Duration>,
3051  // HOST-driver timers currently armed on this runtime.
3052  // On the THREADED CT flavor a pending entry is a future wake token -- the
3053  // host event loop's timer callback -- so the deadline verdict treats a LIVE
3054  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
3055  // build it is NOT a wake token (the host relay shares the parked thread and
3056  // can never run); there the CERTAIN check only consults it to pick the
3057  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
3058  // futures minted for this executor (they insert at first poll and remove on
3059  // completion/drop).
3060  host_timers: Arc<HostTimerRegistry>,
3061  // The generation this executor serves, attached by its `RuntimeBackend`.
3062  // A runnable rejected after the queue closed still owns its
3063  // `GenerationWorkGuard`, so `schedule` must wake the generation's idle
3064  // waiter, which retires such runnables on its own stack.
3065  generation_work: ArcSwapOption<GenerationWork>,
3066}
3067
3068impl Drop for CurrentThreadExecutor {
3069  fn drop(&mut self) {
3070    // Last drain point for rejected work (each runnable still owns its
3071    // `GenerationWorkGuard`) when no idle wait ran after the queue closed.
3072    self.retire_rejected_work();
3073  }
3074}
3075
3076struct CurrentThreadQueue {
3077  closed: bool,
3078  runnables: VecDeque<Runnable>,
3079  /// Runnables the executor owes a cancellation: the work that was queued
3080  /// when a terminal dispatch failure happened (moved here by
3081  /// `cancel_work_after_host_dispatch_failure`), a schedule that arrived
3082  /// while that transition held the executor closed, or a schedule that
3083  /// arrived after shutdown closed the queue. Their `queued_runnables` debit
3084  /// has already been taken. They are dropped only on clean stacks: by the
3085  /// transition itself when it is not on a waker's stack, and otherwise by
3086  /// `retire_rejected_work` at host entry points (a host turn, delivery
3087  /// completion, driver unregistration, a drain request) outside `schedule`
3088  /// and on the shutdown side (`begin_shutdown`, the generation's idle wait,
3089  /// `wait_until_scheduler_idle`, the executor's destructor). Never on the
3090  /// waker's stack: that waker may run while its producer holds a mutex
3091  /// (`futures::Shared` wakes awaiters under its `wakers` lock) that the
3092  /// rejected future's destructor re-locks. See `CurrentThreadScheduleScope`.
3093  rejected: Vec<Runnable>,
3094  /// Blocking jobs queued when a terminal dispatch failure happened. Same
3095  /// contract as `rejected`: dropping a job destroys its closure and its
3096  /// result sender, which wakes the awaiting task.
3097  #[cfg(napi_runtime_os_threads)]
3098  rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3099}
3100
3101#[derive(Default)]
3102struct CurrentThreadSchedulerState {
3103  active_host_turns: usize,
3104  active_dispatch_calls: usize,
3105  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3106}
3107
3108struct CurrentThreadDispatchPublication {
3109  dispatch: u64,
3110  republish_requested: bool,
3111}
3112
3113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3114enum CurrentThreadDispatchRequest {
3115  Accepted,
3116  Coalesced,
3117  Unavailable,
3118}
3119
3120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3121struct CurrentThreadTaskDispatchFailure {
3122  dispatch: u64,
3123  epoch: u64,
3124}
3125
3126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3127enum CurrentThreadHostDispatchResult {
3128  Accepted,
3129  Unavailable,
3130  Failed(CurrentThreadTaskDispatchFailure),
3131}
3132
3133enum CurrentThreadDispatchPublicationStep {
3134  Republish,
3135  Complete(Option<CurrentThreadHostDispatchResult>),
3136}
3137
3138struct CurrentThreadHostTurn {
3139  executor: Arc<CurrentThreadExecutor>,
3140  draining: bool,
3141  _generation: RuntimeGenerationGuard,
3142}
3143
3144impl CurrentThreadHostTurn {
3145  fn drive(&mut self) {
3146    let executor = Arc::clone(&self.executor);
3147    executor.drive_admitted_host_turn(self);
3148  }
3149
3150  fn release_draining(&mut self) {
3151    if !self.draining {
3152      return;
3153    }
3154    let _scheduler = self
3155      .executor
3156      .scheduler_idle_lock
3157      .lock()
3158      .unwrap_or_else(std::sync::PoisonError::into_inner);
3159    self.executor.draining.store(false, Ordering::Release);
3160    self.draining = false;
3161  }
3162}
3163
3164impl Drop for CurrentThreadHostTurn {
3165  fn drop(&mut self) {
3166    let mut scheduler = self
3167      .executor
3168      .scheduler_idle_lock
3169      .lock()
3170      .unwrap_or_else(std::sync::PoisonError::into_inner);
3171    if self.draining {
3172      self.executor.draining.store(false, Ordering::Release);
3173      self.draining = false;
3174    }
3175    scheduler.active_host_turns = scheduler
3176      .active_host_turns
3177      .checked_sub(1)
3178      .expect("CurrentThread active host-turn count underflow");
3179    self.executor.scheduler_idle.notify_all();
3180  }
3181}
3182
3183/// Retains one admitted CurrentThread host callback in its runtime generation.
3184///
3185/// The host must keep this opaque lease alive until its native callback has
3186/// acknowledged or failed the delivery and destroyed every callback-owned
3187/// payload. The `Rc` marker prevents moving a TLS generation guard to another
3188/// thread before it restores the callback thread's prior runtime identity.
3189#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3190pub struct CurrentThreadTaskCallbackLease {
3191  _host_turn: CurrentThreadHostTurn,
3192  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3193}
3194
3195impl CurrentThreadTaskCallbackLease {
3196  fn new(host_turn: CurrentThreadHostTurn) -> Self {
3197    Self {
3198      _host_turn: host_turn,
3199      _not_send: std::marker::PhantomData,
3200    }
3201  }
3202}
3203
3204struct CurrentThreadDispatchCall<'a> {
3205  executor: &'a CurrentThreadExecutor,
3206  publication: Option<u64>,
3207  _generation: RuntimeGenerationGuard,
3208}
3209
3210impl CurrentThreadDispatchCall<'_> {
3211  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3212    let dispatch = self
3213      .publication
3214      .expect("the CurrentThread dispatch call must own a publication");
3215    let index = scheduler
3216      .dispatch_publications
3217      .iter()
3218      .position(|publication| publication.dispatch == dispatch)
3219      .expect("the CurrentThread dispatch publication must remain registered");
3220    scheduler.dispatch_publications.swap_remove(index);
3221    self.publication = None;
3222  }
3223}
3224
3225impl Drop for CurrentThreadDispatchCall<'_> {
3226  fn drop(&mut self) {
3227    let mut scheduler = self
3228      .executor
3229      .scheduler_idle_lock
3230      .lock()
3231      .unwrap_or_else(std::sync::PoisonError::into_inner);
3232    if let Some(dispatch) = self.publication.take()
3233      && let Some(index) = scheduler
3234        .dispatch_publications
3235        .iter()
3236        .position(|publication| publication.dispatch == dispatch)
3237    {
3238      scheduler.dispatch_publications.swap_remove(index);
3239    }
3240    scheduler.active_dispatch_calls = scheduler
3241      .active_dispatch_calls
3242      .checked_sub(1)
3243      .expect("CurrentThread active dispatch-call count underflow");
3244    self.executor.scheduler_idle.notify_all();
3245  }
3246}
3247
3248thread_local! {
3249  /// Depth of the `CurrentThreadExecutor::schedule` frames on this thread that
3250  /// a waker could have entered. `async-task` runs its scheduler callback for
3251  /// two different reasons: a wake, which any `Waker` can raise from any stack,
3252  /// and the very first schedule of a freshly submitted task, which `spawn`
3253  /// makes on its caller's own stack. Only the wake can arrive while a foreign
3254  /// mutex is held (`futures::Shared` wakes its awaiters under its `wakers`
3255  /// lock), so only `schedule` enters this scope; `schedule_submission` does
3256  /// not. A nonzero depth therefore marks a stack that may be a waker's, and
3257  /// nothing that destroys user futures inline may run on it: their destructors
3258  /// can re-lock that same mutex.
3259  static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3260}
3261
3262/// RAII marker for one waker-reachable `CurrentThreadExecutor::schedule` frame;
3263/// see `CURRENT_THREAD_SCHEDULE_DEPTH`.
3264struct CurrentThreadScheduleScope;
3265
3266impl CurrentThreadScheduleScope {
3267  fn enter() -> Self {
3268    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3269      depth.set(
3270        depth
3271          .get()
3272          .checked_add(1)
3273          .expect("CurrentThread schedule depth exhausted"),
3274      );
3275    });
3276    Self
3277  }
3278
3279  /// True while this thread is inside `schedule`: the stack may be a waker's.
3280  fn is_active() -> bool {
3281    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3282  }
3283}
3284
3285impl Drop for CurrentThreadScheduleScope {
3286  fn drop(&mut self) {
3287    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3288      depth.set(
3289        depth
3290          .get()
3291          .checked_sub(1)
3292          .expect("CurrentThread schedule depth underflow"),
3293      );
3294    });
3295  }
3296}
3297
3298thread_local! {
3299  /// Set while this thread is inside the retirement publication a waker-stack
3300  /// terminal failure issues for itself; see
3301  /// `CurrentThreadExecutor::request_rejected_work_retirement`. That
3302  /// publication calls the host driver again, so a runnable another thread
3303  /// queues meanwhile can drive a nested terminal transition on this same
3304  /// stack. Without this flag the nested transition would publish again, and a
3305  /// producer that keeps queueing work could recurse the stack without bound.
3306  ///
3307  /// It is thread-local, not per-executor: the bound it enforces is a stack
3308  /// bound. A terminal transition on ANOTHER thread finds this flag clear and
3309  /// keeps publishing its own continuation, which is what it should do -- its
3310  /// stack is its own.
3311  ///
3312  /// A nested transition records nothing when it returns. What it owes is
3313  /// executor state, not a message: it moved work into `queue.rejected` and
3314  /// consumed the dispatch that was outstanding, and the owning frame re-reads
3315  /// exactly that (`needs_rejected_work_retirement_dispatch`) before each of
3316  /// its turns. A separate "requested" flag used to carry it and could be
3317  /// discarded -- by a nested call whose request the owner never took, and
3318  /// then by the owner itself when the attempt budget ran out under it.
3319  static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3320    const { std::cell::Cell::new(false) };
3321}
3322
3323/// RAII marker for the frame that owns this thread's retirement publication;
3324/// see `CURRENT_THREAD_RETIRING_REJECTED_WORK`.
3325struct CurrentThreadRejectedWorkRetirementScope;
3326
3327impl CurrentThreadRejectedWorkRetirementScope {
3328  /// `None` when this thread is already publishing a retirement turn. The
3329  /// caller returns without publishing, and loses nothing by it: what it owes
3330  /// is the executor state the owning frame re-reads before its next turn.
3331  fn enter() -> Option<Self> {
3332    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3333      if active.replace(true) {
3334        // The owning frame keeps the flag. Do not write this as `then_some`:
3335        // its argument is eager, so the guard would be constructed here and
3336        // its destructor would clear the owner's flag on the way out.
3337        return None;
3338      }
3339      Some(Self)
3340    })
3341  }
3342}
3343
3344impl Drop for CurrentThreadRejectedWorkRetirementScope {
3345  fn drop(&mut self) {
3346    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3347  }
3348}
3349
3350struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3351
3352impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3353  fn drop(&mut self) {
3354    self
3355      .0
3356      .cancelling_failed_dispatch
3357      .store(false, Ordering::Release);
3358  }
3359}
3360
3361enum CurrentThreadDispatchFailureAction<'a> {
3362  None,
3363  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3364  Publish {
3365    dispatch: u64,
3366    call: CurrentThreadDispatchCall<'a>,
3367  },
3368}
3369
3370struct CurrentThreadDispatchFailureRecovery<'a> {
3371  action: CurrentThreadDispatchFailureAction<'a>,
3372  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3373}
3374
3375struct CurrentThreadRegistryCancellationGuard<'a> {
3376  registry: &'a CurrentThreadTaskDriverRegistry,
3377  failure: Option<CurrentThreadTaskDispatchFailure>,
3378}
3379
3380impl CurrentThreadRegistryCancellationGuard<'_> {
3381  fn new(
3382    registry: &CurrentThreadTaskDriverRegistry,
3383    failure: CurrentThreadTaskDispatchFailure,
3384  ) -> CurrentThreadRegistryCancellationGuard<'_> {
3385    CurrentThreadRegistryCancellationGuard {
3386      registry,
3387      failure: Some(failure),
3388    }
3389  }
3390}
3391
3392impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3393  fn drop(&mut self) {
3394    if let Some(failure) = self.failure.take() {
3395      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3396    }
3397  }
3398}
3399
3400struct CurrentThreadBlockingGuard<'a> {
3401  executor: &'a CurrentThreadExecutor,
3402  owner: BlockingOwnerToken,
3403  previous_owner: Option<BlockingOwnerToken>,
3404  owns_lane: bool,
3405}
3406
3407impl CurrentThreadBlockingGuard<'_> {
3408  fn counts_active_lane(&self) -> bool {
3409    self.owns_lane
3410  }
3411}
3412
3413impl Drop for CurrentThreadBlockingGuard<'_> {
3414  fn drop(&mut self) {
3415    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3416    if !self.owns_lane {
3417      return;
3418    }
3419    let mut state = self
3420      .executor
3421      .blocking_admission
3422      .state
3423      .lock()
3424      .unwrap_or_else(std::sync::PoisonError::into_inner);
3425    debug_assert_eq!(state.owner, Some(self.owner));
3426    state.owner = None;
3427    drop(state);
3428    self.executor.stop.wake_all();
3429  }
3430}
3431
3432impl CurrentThreadExecutor {
3433  /// Bound one host callback so a self-waking task cannot monopolize the
3434  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
3435  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3436  /// Force one blocking turn after this many consecutive runnable polls.
3437  #[cfg(napi_runtime_os_threads)]
3438  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3439  /// How many retirement turns one waker-stack terminal failure may take --
3440  /// the turns another thread's submissions can inflate. The one offer nothing
3441  /// can inflate follows them; see `request_rejected_work_retirement`.
3442  const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3443
3444  /// Build-default construction, kept for the pre-existing unit tests
3445  /// (production goes through [`Self::with_detection`] in
3446  /// `RuntimeBackend::new`, which also resolves the env deadline).
3447  #[cfg(test)]
3448  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3449    Self::with_detection(metrics, THREADLESS_BUILD, None)
3450  }
3451
3452  /// Full constructor for the deadlock-detection knobs: `new` fixes
3453  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
3454  /// `RuntimeBackend::new` resolves the deadline from
3455  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
3456  /// native `threadless` stand-in and short deadlines through it.
3457  #[cfg(test)]
3458  fn with_detection(
3459    metrics: Arc<RuntimeMetrics>,
3460    threadless: bool,
3461    park_deadline: Option<Duration>,
3462  ) -> Self {
3463    Self::with_generation(
3464      metrics,
3465      threadless,
3466      park_deadline,
3467      u64::MAX,
3468      Arc::new(GenerationStop::default()),
3469    )
3470  }
3471
3472  fn with_generation(
3473    metrics: Arc<RuntimeMetrics>,
3474    threadless: bool,
3475    park_deadline: Option<Duration>,
3476    generation: u64,
3477    stop: Arc<GenerationStop>,
3478  ) -> Self {
3479    Self {
3480      id: next_unique_id(
3481        &NEXT_RUNTIME_EXECUTOR_ID,
3482        "async runtime executor id space exhausted",
3483      ),
3484      generation,
3485      stop,
3486      queue: Mutex::new(CurrentThreadQueue {
3487        closed: false,
3488        runnables: VecDeque::new(),
3489        rejected: Vec::new(),
3490        #[cfg(napi_runtime_os_threads)]
3491        rejected_blocking: Vec::new(),
3492      }),
3493      blocking_admission: CurrentThreadBlockingAdmission {
3494        state: Mutex::new(CurrentThreadBlockingAdmissionState {
3495          owner: None,
3496          closed: false,
3497          #[cfg(napi_runtime_os_threads)]
3498          queue: BlockingQueue {
3499            closed: false,
3500            head: None,
3501            tail: None,
3502            jobs: FxHashMap::default(),
3503          },
3504        }),
3505      },
3506      draining: AtomicBool::new(false),
3507      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3508      scheduler_idle: Condvar::new(),
3509      dispatch_pending: AtomicU64::new(0),
3510      recovery_dispatch: AtomicU64::new(0),
3511      cancelling_failed_dispatch: AtomicBool::new(false),
3512      task_dispatch: dispatch_current_thread_tasks,
3513      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3514      metrics,
3515      threadless,
3516      park_deadline,
3517      host_timers: Arc::new(HostTimerRegistry::default()),
3518      generation_work: ArcSwapOption::empty(),
3519    }
3520  }
3521
3522  fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3523    self.generation_work.store(Some(Arc::clone(work)));
3524  }
3525
3526  /// Whether `queue.rejected` (or `queue.rejected_blocking`) holds work the
3527  /// executor still owes a cancellation.
3528  fn has_rejected_work(&self) -> bool {
3529    self.rejected_work_pending(PendingRead::Locked)
3530  }
3531
3532  /// [`Self::has_rejected_work`]; `read` decides whether it may wait for the
3533  /// queue lock.
3534  fn rejected_work_pending(&self, read: PendingRead) -> bool {
3535    read.read(&self.queue, |queue| {
3536      #[cfg(napi_runtime_os_threads)]
3537      let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3538      #[cfg(not(napi_runtime_os_threads))]
3539      let has_rejected_blocking = false;
3540      !queue.rejected.is_empty() || has_rejected_blocking
3541    })
3542  }
3543
3544  /// The predicate `wait_until_scheduler_idle` sleeps on, read without
3545  /// sleeping; `read` decides whether it may wait for the scheduler lock.
3546  fn scheduler_work_pending(&self, read: PendingRead) -> bool {
3547    read.read(&self.scheduler_idle_lock, |idle| {
3548      idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3549    })
3550  }
3551
3552  /// Cancel every rejected runnable and blocking job. Runs on the caller's
3553  /// stack with no executor lock held; the caller must not be inside a waker
3554  /// invocation (see `CurrentThreadQueue::rejected`). Dropping a runnable can
3555  /// wake a parked task whose schedule is rejected again (the queue closed,
3556  /// or a nested terminal transition moved it here), so drain until nothing
3557  /// remains. This terminates: every drop closes one task for good, so the
3558  /// closed-queue rejections are bounded by the live tasks, and a nested
3559  /// transition needs two more host-failed deliveries of its own.
3560  fn retire_rejected_work(&self) {
3561    if !self.has_rejected_work() {
3562      return;
3563    }
3564    let _generation = RuntimeGenerationGuard::enter(self.generation);
3565    loop {
3566      #[cfg(napi_runtime_os_threads)]
3567      let (rejected, rejected_blocking) = {
3568        let mut queue = self
3569          .queue
3570          .lock()
3571          .unwrap_or_else(std::sync::PoisonError::into_inner);
3572        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3573          return;
3574        }
3575        (
3576          std::mem::take(&mut queue.rejected),
3577          std::mem::take(&mut queue.rejected_blocking),
3578        )
3579      };
3580      #[cfg(not(napi_runtime_os_threads))]
3581      let rejected = {
3582        let mut queue = self
3583          .queue
3584          .lock()
3585          .unwrap_or_else(std::sync::PoisonError::into_inner);
3586        if queue.rejected.is_empty() {
3587          return;
3588        }
3589        std::mem::take(&mut queue.rejected)
3590      };
3591      for runnable in rejected {
3592        let _ = catch_unwind_contained(|| drop(runnable));
3593      }
3594      #[cfg(napi_runtime_os_threads)]
3595      for job in rejected_blocking {
3596        let _ = catch_unwind_contained(|| drop(job));
3597      }
3598    }
3599  }
3600
3601  /// Host-side entry points (a host turn, delivery completion, driver
3602  /// registration or unregistration, drain requests) retire rejected work
3603  /// here. They are clean stacks unless the host called back synchronously
3604  /// from inside a publication that a waker's `schedule` started; that case
3605  /// leaves the work for the next entry.
3606  fn retire_rejected_work_off_waker_stack(&self) {
3607    if CurrentThreadScheduleScope::is_active() {
3608      return;
3609    }
3610    self.retire_rejected_work();
3611  }
3612
3613  /// Whether the executor still owes its rejected work a continuation: work is
3614  /// parked in `queue.rejected` (or `queue.rejected_blocking`) and no host
3615  /// dispatch is outstanding to bring the clean stack that retires it.
3616  ///
3617  /// This is the whole state a retirement turn acts on. Nothing else needs to
3618  /// be remembered across a turn, on this thread or on the executor: a nested
3619  /// terminal transition that ran inside a turn's publication changed exactly
3620  /// these two things (it moved more work aside and consumed the dispatch),
3621  /// so re-reading them is the same as being told.
3622  fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3623    self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3624  }
3625
3626  /// The continuation a terminal failure reached on a waker's stack owes
3627  /// itself.
3628  ///
3629  /// That branch of `cancel_work_after_host_dispatch_failure` may not destroy
3630  /// anything where it stands, so it parks the work in `queue.rejected` and
3631  /// reopens. Reopening alone is not a continuation: the transition consumed
3632  /// the executor's one outstanding host request (`dispatch_pending` is 0) and
3633  /// left the queue empty, so `has_serviceable_work` is false and nothing --
3634  /// not `request_drain_if_work_remains`, not a wake of an already-parked task
3635  /// -- publishes again. A wake that loses both of its publications therefore
3636  /// produced no host turn at all, and the parked runnables kept their
3637  /// `GenerationWorkGuard`s and their `JoinHandle`s forever.
3638  ///
3639  /// A host turn is the clean stack that retires them: `drive_admitted_host_turn`
3640  /// calls `retire_rejected_work_off_waker_stack` before it polls anything. So
3641  /// ask for one, and keep asking while the host refuses and the work is still
3642  /// owed a stack -- the host is racing its own teardown, not necessarily dead,
3643  /// and a refusal says nothing about the driver that answers next (a second
3644  /// registered host, or the same one once its delivery backlog clears).
3645  ///
3646  /// What may be published on this stack is bounded, in two different ways,
3647  /// because there are two different things to bound.
3648  ///
3649  /// * `REJECTED_WORK_RETIREMENT_ATTEMPTS` bounds the turns a PRODUCER can
3650  ///   inflate. Each turn calls the driver again, so another thread can queue
3651  ///   a runnable while the publication is in flight; the host can then fail
3652  ///   that delivery and the replacement it reserves for the new work, and a
3653  ///   second terminal transition runs nested on this stack. It may not
3654  ///   publish where it stands -- that is what would recurse without bound --
3655  ///   so `CURRENT_THREAD_RETIRING_REJECTED_WORK` sends it back, and this
3656  ///   frame takes its next turn instead. Every such turn is two more host
3657  ///   callbacks and two more producer submissions, and the producer's own
3658  ///   drain request coalesces behind the publication already in flight, so it
3659  ///   never runs dry: measured without a budget, 32768 submissions produced
3660  ///   32769 deliveries inside one wake, and a producer left running for 400ms
3661  ///   held this single `schedule` callback -- and the `futures::Shared`
3662  ///   `wakers` mutex under it -- for 432ms. Eight turns cap that at 16 host
3663  ///   callbacks, a small constant to run under a foreign lock; two turns are
3664  ///   all the known interleaving needs.
3665  /// * `publish_final_rejected_work_retirement` bounds nothing, because
3666  ///   nothing can inflate it. It is the one delivery this stack offers with
3667  ///   the terminal-cancellation claim held, so no producer can interleave
3668  ///   with it and no nested transition can consume it. It is what the budget
3669  ///   may not be allowed to swallow: a producer that queues its last runnable
3670  ///   inside the eighth turn spends the budget and then goes quiescent, and
3671  ///   without this offer a host that would have accepted the very next
3672  ///   delivery is never asked, leaving the `JoinHandle`s pending and their
3673  ///   `GenerationWorkGuard`s held until unrelated work or shutdown arrives.
3674  ///
3675  /// * One ordinary publication follows an ACCEPTED final offer whose dispatch
3676  ///   the host consumed on this stack. A driver may claim, drive and
3677  ///   acknowledge the delivery inside `dispatch`, and that turn can do
3678  ///   nothing: it is on this waker's stack, so
3679  ///   `retire_rejected_work_off_waker_stack` returns at nonzero depth, and the
3680  ///   claim is still held, so `take_runnable` returns `None`. All it did was
3681  ///   take the continuation. The state it leaves -- work parked, no dispatch
3682  ///   outstanding -- is the state a REFUSED offer leaves, so it is owed the
3683  ///   same thing, and the same condition
3684  ///   (`needs_rejected_work_retirement_dispatch`) says so. The executor is
3685  ///   open again by then, which is what `5857092f` reopened it for, so this
3686  ///   takes the ordinary path. It is one publication and not a loop: a
3687  ///   terminal transition nested inside it finds
3688  ///   `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and publishes
3689  ///   nothing, and this frame is returning.
3690  ///
3691  /// So the turns stop at the first of: a dispatch outstanding (a delivery the
3692  /// host accepted, or one it failed and replaced, is the continuation --
3693  /// `drive_admitted_host_turn` retires on it), nothing left owed, no live
3694  /// host, and the budget -- and the budget is followed by the final offer,
3695  /// which is followed by one ordinary publication if the host took it without
3696  /// leaving the work a stack.
3697  ///
3698  /// Nothing is destroyed here: this adds no `retire_rejected_work` call. A
3699  /// nested transition inside any of these publications still sees the outer
3700  /// `CurrentThreadScheduleScope` and takes the waker branch, and the
3701  /// `complete_current_thread_task_delivery` the driver runs there reaches only
3702  /// `retire_rejected_work_off_waker_stack`, which returns at nonzero depth.
3703  ///
3704  /// When the host refuses the final offer too -- or consumes it and then the
3705  /// publication that follows it, never leaving this waker's stack -- that is
3706  /// terminal by construction and honest: the work waits for the next host that
3707  /// registers
3708  /// (`request_drain_if_queued` retires before it publishes) or for shutdown
3709  /// (`begin_shutdown`, `wait_until_idle_retiring`, `wait_until_scheduler_idle`,
3710  /// `Drop`). With no host that accepts there is no stack on which user futures
3711  /// may be destroyed. Handing them to a helper thread was rejected --
3712  /// threadless wasm has none, and host-affine futures must be destroyed on a
3713  /// host or shutdown thread.
3714  ///
3715  /// Rejected alternatives: a time budget -- it leaves the number of host
3716  /// callbacks open, pins nothing an assertion can check, and reads a clock on
3717  /// a stack meant to do as little as possible; yielding the thread -- it does
3718  /// not release the producer's mutex, which is the thing being held; parking
3719  /// the leftover need on the executor for the next depth-0 entry -- those
3720  /// entries already retire `queue.rejected` unconditionally, and in the
3721  /// interleaving that needs it there is no next entry at all, which is why
3722  /// the continuation has to be a delivery; and making EVERY turn a claimed
3723  /// one like the final offer -- it is uninflatable for the same reason it
3724  /// cancels a runnable that races it, and paying that for the ordinary turns
3725  /// would undo what `5857092f` reopened the executor for.
3726  fn request_rejected_work_retirement(self: &Arc<Self>) {
3727    if !self.has_rejected_work() {
3728      return;
3729    }
3730    let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3731      // Nested inside this thread's own retirement publication. Publishing
3732      // here is what would recurse the stack; the frame that owns the
3733      // publication takes the next turn, and reads what this one moved aside
3734      // off the executor rather than off a flag.
3735      return;
3736    };
3737    for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3738      if !self.needs_rejected_work_retirement_dispatch() {
3739        return;
3740      }
3741      if matches!(
3742        self.request_drain_with(next_current_thread_dispatch_id),
3743        CurrentThreadDispatchRequest::Unavailable
3744      ) {
3745        // Stopping, or no live driver: there is no host to offer anything to.
3746        return;
3747      }
3748    }
3749    if self.publish_final_rejected_work_retirement()
3750      && self.needs_rejected_work_retirement_dispatch()
3751    {
3752      // The host ACCEPTED the offer and consumed its dispatch without leaving
3753      // the work a stack: a turn it claims and drives inside `dispatch` runs
3754      // on this same waker's stack, where `retire_rejected_work_off_waker_stack`
3755      // returns at nonzero depth, and it runs with the claim held, where
3756      // `take_runnable` returns `None`. Such a turn can do nothing at all; all
3757      // it did was take the continuation. The state it left is the state a
3758      // refused offer leaves, and the invariant is the same, so it is owed the
3759      // same thing: work is parked and no dispatch is outstanding, so publish
3760      // one.
3761      //
3762      // This is the ordinary publication path -- the executor is open again,
3763      // which is what `5857092f` reopened it for -- and it is made once, not
3764      // looped. It cannot recurse: a terminal transition nested inside it
3765      // finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and
3766      // publishes nothing, and this frame has already returned its budget. A
3767      // host that consumes this one on the waker's stack too has never left
3768      // that stack, which is the limit already documented above: with no host
3769      // turn on a clean stack there is nowhere to destroy a user future.
3770      let _ = self.request_drain_with(next_current_thread_dispatch_id);
3771    }
3772  }
3773
3774  /// The one delivery this stack offers that a producer cannot inflate, made
3775  /// after `REJECTED_WORK_RETIREMENT_ATTEMPTS` turns were spent without
3776  /// leaving a dispatch outstanding.
3777  ///
3778  /// It holds the terminal-cancellation claim across the host callback. That
3779  /// claim is what makes it terminal rather than the ninth of an unbounded
3780  /// series: `schedule` consults it under the queue lock and parks a racing
3781  /// runnable in `queue.rejected` instead of queueing it, so no serviceable
3782  /// work can appear during the publication, and
3783  /// `begin_host_dispatch_failure_transition_locked` returns `None` while it is
3784  /// held, so a failure of this delivery reserves no replacement and reaches no
3785  /// terminal transition. One host callback, no recursion, no budget needed.
3786  ///
3787  /// A runnable that does race it is cancelled with the work already parked
3788  /// rather than queued. That is the same outcome the nested terminal
3789  /// transitions in the turns above give it, and this is reached only after
3790  /// the host has refused every one of them.
3791  ///
3792  /// Returns whether the host ACCEPTED the offer. An accepted offer is not by
3793  /// itself a continuation: a driver may claim, drive and acknowledge the
3794  /// delivery inside `dispatch`, and that turn runs on this waker's stack with
3795  /// the claim held, so it can neither retire (`retire_rejected_work_off_waker_stack`
3796  /// returns at nonzero depth) nor drain (`take_runnable` returns `None` while
3797  /// the claim is held) -- it only consumes `dispatch_pending`. The caller
3798  /// re-reads `needs_rejected_work_retirement_dispatch` and publishes once more
3799  /// when that happened.
3800  fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3801    if !self.needs_rejected_work_retirement_dispatch() {
3802      return false;
3803    }
3804    let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3805      // Another terminal transition owns the executor; it carries its own
3806      // continuation.
3807      return false;
3808    };
3809    let dispatch = next_current_thread_dispatch_id();
3810    let dispatch_call = {
3811      let mut scheduler = self.lock_scheduler_for_publication();
3812      self.dispatch_pending.store(dispatch, Ordering::Release);
3813      self
3814        .begin_host_dispatch_publication(&mut scheduler, dispatch)
3815        .expect("a fresh CurrentThread retirement dispatch must own its publication")
3816    };
3817    let result = self.publish_host_dispatch(dispatch, dispatch_call);
3818    {
3819      // Reopen while holding the queue lock, the way the transition does:
3820      // `schedule` consults the flag under it, so every runnable parked during
3821      // the publication is already in `queue.rejected` when the executor opens.
3822      let _queue = self
3823        .queue
3824        .lock()
3825        .unwrap_or_else(std::sync::PoisonError::into_inner);
3826      drop(cancelling);
3827    }
3828    // Those parked runnables carry their `GenerationWorkGuard`s and were
3829    // pushed without notifying; a waiting shutdown retires them on its stack.
3830    if let Some(work) = self.generation_work.load_full() {
3831      work.notify_idle_waiters();
3832    }
3833    result == CurrentThreadHostDispatchResult::Accepted
3834  }
3835
3836  fn fresh_owner_token(&self) -> BlockingOwnerToken {
3837    BlockingOwnerToken {
3838      executor_id: self.id,
3839      frame: next_unique_id(
3840        &NEXT_BLOCKING_FRAME,
3841        "blocking owner frame id space exhausted",
3842      ),
3843    }
3844  }
3845
3846  fn enter_blocking_owner(
3847    &self,
3848    owner: BlockingOwnerToken,
3849    owns_lane: bool,
3850  ) -> CurrentThreadBlockingGuard<'_> {
3851    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3852    CurrentThreadBlockingGuard {
3853      executor: self,
3854      owner,
3855      previous_owner,
3856      owns_lane,
3857    }
3858  }
3859
3860  fn schedule_blocking<F, T>(
3861    self: &Arc<Self>,
3862    function: F,
3863    registration: GenerationWorkGuard,
3864  ) -> JoinHandle<T>
3865  where
3866    F: FnOnce() -> T + Send + 'static,
3867    T: Send + 'static,
3868  {
3869    // See the implementation notes in this crate's README.
3870    self.metrics.blocking_scheduled();
3871    let mut function = Some(function);
3872    let mut registration = Some(registration);
3873    let ambient_owner = BLOCKING_OWNER
3874      .with(std::cell::Cell::get)
3875      .filter(|owner| owner.executor_id == self.id);
3876    let mut run_owner = None;
3877    let mut owns_lane = false;
3878    let mut rejected = false;
3879
3880    #[cfg(napi_runtime_os_threads)]
3881    let (sender, receiver) = oneshot::channel();
3882    #[cfg(napi_runtime_os_threads)]
3883    let dependency = BlockingDependency {
3884      job: BlockingJobId(next_unique_id(
3885        &NEXT_BLOCKING_JOB_ID,
3886        "blocking job id space exhausted",
3887      )),
3888      owner: ambient_owner,
3889    };
3890    #[cfg(napi_runtime_os_threads)]
3891    let mut queued = false;
3892
3893    {
3894      let mut state = self
3895        .blocking_admission
3896        .state
3897        .lock()
3898        .unwrap_or_else(std::sync::PoisonError::into_inner);
3899      #[cfg(all(test, napi_runtime_os_threads))]
3900      run_current_thread_blocking_claim_test_hook();
3901      // Shutdown publishes generation stop before taking this mutex. Recheck
3902      // it while admission is serialized so work cannot claim or queue for the
3903      // CurrentThread lane after stop publication.
3904      if self.stop.is_stopping()
3905        || state.closed
3906        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3907      {
3908        rejected = true;
3909      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3910        run_owner = ambient_owner;
3911      } else {
3912        #[cfg(napi_runtime_os_threads)]
3913        let queue_empty = state.queue.is_empty();
3914        #[cfg(not(napi_runtime_os_threads))]
3915        let queue_empty = true;
3916
3917        if state.owner.is_none() && queue_empty {
3918          let owner = self.fresh_owner_token();
3919          state.owner = Some(owner);
3920          run_owner = Some(owner);
3921          owns_lane = true;
3922        } else {
3923          #[cfg(napi_runtime_os_threads)]
3924          {
3925            let function = function
3926              .take()
3927              .expect("blocking function must be available");
3928            let registration = registration
3929              .take()
3930              .expect("blocking registration must be available");
3931            let generation = self.generation;
3932            let function = RegisteredBlockingFunction::new(function, registration, generation);
3933            state.queue.push(QueuedBlocking {
3934              id: dependency.job,
3935              run: Box::new(move || {
3936                let _generation = RuntimeGenerationGuard::enter(generation);
3937                let result = function
3938                  .run()
3939                  .map(|output| ContainedTaskOutput::new(output, generation));
3940                let _ = sender.send(result);
3941              }),
3942            });
3943            queued = true;
3944          }
3945          #[cfg(not(napi_runtime_os_threads))]
3946          {
3947            // This arm is threadless by construction: without
3948            // `napi_runtime_os_threads` no second thread exists to reach
3949            // unrelated concurrent admission. Same-stack nesting carries
3950            // `ambient_owner` and takes the borrowing branch above. On
3951            // `wasm32-wasip1-threads` a second thread DOES reach here, which
3952            // is why that target takes the queueing branch instead.
3953            rejected = true;
3954          }
3955        }
3956      }
3957    }
3958
3959    #[cfg(napi_runtime_os_threads)]
3960    if queued {
3961      self.stop.wake_all();
3962      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3963        self.request_drain();
3964      }
3965      return JoinHandle(JoinHandleInner::Blocking {
3966        receiver,
3967        dependency,
3968        published: PublishedBlockingDependencies::default(),
3969      });
3970    }
3971
3972    if rejected {
3973      let _generation = RuntimeGenerationGuard::enter(self.generation);
3974      let _ = catch_unwind_contained(|| drop(function.take()));
3975      drop(registration.take());
3976      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3977        message: "the async runtime stopped before the blocking task could start".to_string(),
3978      }))));
3979    }
3980
3981    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3982    let blocking = self.enter_blocking_owner(owner, owns_lane);
3983    let _registration = registration
3984      .take()
3985      .expect("blocking registration must be available");
3986    let _generation = RuntimeGenerationGuard::enter(self.generation);
3987    let result = {
3988      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3989      on_thread_handoff();
3990      catch_unwind_join_error(
3991        function
3992          .take()
3993          .expect("blocking function must be available"),
3994      )
3995    }
3996    .map(|output| ContainedTaskOutput::new(output, self.generation));
3997    drop(blocking);
3998    #[cfg(napi_runtime_os_threads)]
3999    if owns_lane && !self.draining.load(Ordering::Acquire) {
4000      // An admitted host turn already owns a bounded queue driver. Let its
4001      // outer loop service the released FIFO so blocking work cannot bypass
4002      // the host-turn budget and monopolize the JavaScript event loop.
4003      self.service_queued_blocking_work();
4004    }
4005    JoinHandle(JoinHandleInner::Ready(Some(result)))
4006  }
4007
4008  #[cfg(napi_runtime_os_threads)]
4009  fn take_queued_blocking(
4010    &self,
4011  ) -> Option<(
4012    Box<dyn FnOnce() + Send + 'static>,
4013    CurrentThreadBlockingGuard<'_>,
4014  )> {
4015    let (job, owner) = {
4016      let mut state = self
4017        .blocking_admission
4018        .state
4019        .lock()
4020        .unwrap_or_else(std::sync::PoisonError::into_inner);
4021      #[cfg(all(test, napi_runtime_os_threads))]
4022      run_current_thread_blocking_claim_test_hook();
4023      if self.stop.is_stopping()
4024        || state.closed
4025        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4026        || state.owner.is_some()
4027      {
4028        return None;
4029      }
4030      let job = state.queue.pop_front()?;
4031      let owner = self.fresh_owner_token();
4032      state.owner = Some(owner);
4033      (job, owner)
4034    };
4035    Some((job, self.enter_blocking_owner(owner, true)))
4036  }
4037
4038  #[cfg(napi_runtime_os_threads)]
4039  fn run_one_blocking(&self) -> bool {
4040    let Some((job, blocking)) = self.take_queued_blocking() else {
4041      return false;
4042    };
4043    let _generation = RuntimeGenerationGuard::enter(self.generation);
4044    {
4045      let _task = self.metrics.blocking_started(true);
4046      let _ = catch_unwind_contained(job);
4047    }
4048    drop(blocking);
4049    true
4050  }
4051
4052  #[cfg(napi_runtime_os_threads)]
4053  fn service_queued_blocking_work(&self) {
4054    while self.run_one_blocking() {}
4055  }
4056
4057  #[cfg(napi_runtime_os_threads)]
4058  fn has_serviceable_blocking_work(&self) -> bool {
4059    let state = self
4060      .blocking_admission
4061      .state
4062      .lock()
4063      .unwrap_or_else(std::sync::PoisonError::into_inner);
4064    !state.closed && state.owner.is_none() && !state.queue.is_empty()
4065  }
4066
4067  #[cfg(napi_runtime_os_threads)]
4068  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
4069    let Some(ambient_owner) = BLOCKING_OWNER
4070      .with(std::cell::Cell::get)
4071      .filter(|owner| owner.executor_id == self.id)
4072    else {
4073      return false;
4074    };
4075    let Some(current) = dependency.select_for_owner(ambient_owner) else {
4076      return false;
4077    };
4078    let job = {
4079      let mut state = self
4080        .blocking_admission
4081        .state
4082        .lock()
4083        .unwrap_or_else(std::sync::PoisonError::into_inner);
4084      #[cfg(all(test, napi_runtime_os_threads))]
4085      run_current_thread_blocking_claim_test_hook();
4086      if self.stop.is_stopping()
4087        || state.closed
4088        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4089        || state.owner != Some(ambient_owner)
4090      {
4091        return false;
4092      }
4093      if dependency.try_claim(&current) {
4094        state.queue.remove(current.publication.dependency.job)
4095      } else {
4096        None
4097      }
4098    };
4099    let Some(job) = job else {
4100      return false;
4101    };
4102    let _generation = RuntimeGenerationGuard::enter(self.generation);
4103    let _task = self.metrics.blocking_started(false);
4104    let _ = catch_unwind_contained(job);
4105    true
4106  }
4107
4108  fn close_blocking_admission(&self) {
4109    #[cfg(napi_runtime_os_threads)]
4110    let queued = {
4111      let mut state = self
4112        .blocking_admission
4113        .state
4114        .lock()
4115        .unwrap_or_else(std::sync::PoisonError::into_inner);
4116      state.closed = true;
4117      state.queue.closed = true;
4118      state.queue.take_all()
4119    };
4120    #[cfg(not(napi_runtime_os_threads))]
4121    {
4122      let mut state = self
4123        .blocking_admission
4124        .state
4125        .lock()
4126        .unwrap_or_else(std::sync::PoisonError::into_inner);
4127      state.closed = true;
4128    }
4129    #[cfg(napi_runtime_os_threads)]
4130    for job in queued {
4131      let _ = catch_unwind_contained(|| drop(job));
4132    }
4133  }
4134
4135  #[cfg(test)]
4136  fn with_task_dispatch(
4137    metrics: Arc<RuntimeMetrics>,
4138    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4139  ) -> Self {
4140    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4141  }
4142
4143  #[cfg(test)]
4144  fn with_task_dispatch_registry(
4145    metrics: Arc<RuntimeMetrics>,
4146    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4147    task_drivers: &'static CurrentThreadTaskDriverRegistry,
4148  ) -> Self {
4149    let mut executor = Self::new(metrics);
4150    executor.task_dispatch = task_dispatch;
4151    executor.task_drivers = task_drivers;
4152    executor
4153  }
4154
4155  /// A wake, or any other schedule whose stack the executor does not own.
4156  /// `async-task` hands every `Waker::wake` to this callback, so the frame may
4157  /// belong to a producer that is holding its own mutex; see
4158  /// `CURRENT_THREAD_SCHEDULE_DEPTH`.
4159  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4160    let _scope = CurrentThreadScheduleScope::enter();
4161    self.schedule_runnable(runnable);
4162  }
4163
4164  /// The first schedule of a freshly submitted task, made by the caller of
4165  /// `spawn` on its own stack. No waker is involved, so a terminal dispatch
4166  /// failure reached from here may cancel the rejected work inline -- and it
4167  /// must, because a submission that publishes no host delivery has no other
4168  /// continuation: nothing would ever reach `retire_rejected_work`, and the
4169  /// returned `JoinHandle` would stay pending with its generation guard held.
4170  fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4171    self.schedule_runnable(runnable);
4172  }
4173
4174  fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4175    self.metrics.runnable_scheduled();
4176    {
4177      let mut queue = self
4178        .queue
4179        .lock()
4180        .unwrap_or_else(std::sync::PoisonError::into_inner);
4181      // Never drop a rejected runnable here: that destroys the future inline
4182      // on the waker's thread, and a waker can be invoked while its producer
4183      // holds a mutex that this future's destructor needs (`futures::Shared`
4184      // wakes under its `wakers` lock and its clone re-locks it on drop).
4185      if queue.closed {
4186        // A wake can be in flight when shutdown starts: `async-task` marks
4187        // the task SCHEDULED before it invokes this closure, so the abort
4188        // that `close_and_abort` issues coalesces with the in-flight wake and
4189        // `begin_shutdown` closes the queue without waiting for it to land.
4190        // The runnable still owns its `GenerationWorkGuard`; hand it to the
4191        // shutdown side and wake the generation's idle waiter to retire it.
4192        self
4193          .metrics
4194          .queued_runnables
4195          .fetch_sub(1, Ordering::Relaxed);
4196        queue.rejected.push(runnable);
4197        drop(queue);
4198        if let Some(work) = self.generation_work.load_full() {
4199          work.notify_idle_waiters();
4200        }
4201        return;
4202      }
4203      if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4204        // A terminal failure transition is claimed but has not yet moved the
4205        // queue and reopened (it does both under this lock): the runnable
4206        // joins the work that transition cancels.
4207        self
4208          .metrics
4209          .queued_runnables
4210          .fetch_sub(1, Ordering::Relaxed);
4211        queue.rejected.push(runnable);
4212        return;
4213      }
4214      queue.runnables.push_back(runnable);
4215    }
4216    // Queue work and only then wake an explicit block_on driver. This is still
4217    // enqueue-only with respect to the wake caller: polling happens on the
4218    // driver's thread, never inline from an arbitrary waker invocation.
4219    self.stop.wake_all();
4220    if !self.draining.load(Ordering::Acquire) {
4221      self.request_drain();
4222    }
4223  }
4224
4225  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
4226  ///
4227  /// A `Waker` may be invoked while its producer holds an internal mutex
4228  /// (`futures::Shared` does this). Polling inline from the scheduler callback
4229  /// can then re-enter that future and self-deadlock on the same mutex. Once a
4230  /// host dispatcher has accepted work, this executor stays host-driven for
4231  /// the rest of its generation; a temporarily missing env leaves work queued
4232  /// for the next registration or for shutdown cancellation.
4233  fn request_drain(self: &Arc<Self>) {
4234    let _ = self.request_drain_with(next_current_thread_dispatch_id);
4235  }
4236
4237  fn lock_scheduler_for_publication(&self) -> MutexGuard<'_, CurrentThreadSchedulerState> {
4238    #[cfg(all(test, napi_runtime_os_threads))]
4239    match self.scheduler_idle_lock.try_lock() {
4240      Ok(scheduler) => scheduler,
4241      Err(std::sync::TryLockError::WouldBlock) => {
4242        run_current_thread_dispatch_publication_blocked_test_hook();
4243        self
4244          .scheduler_idle_lock
4245          .lock()
4246          .unwrap_or_else(std::sync::PoisonError::into_inner)
4247      }
4248      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4249    }
4250    #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4251    self
4252      .scheduler_idle_lock
4253      .lock()
4254      .unwrap_or_else(std::sync::PoisonError::into_inner)
4255  }
4256
4257  fn request_drain_with(
4258    self: &Arc<Self>,
4259    next_dispatch: impl FnOnce() -> u64,
4260  ) -> CurrentThreadDispatchRequest {
4261    let mut scheduler = self.lock_scheduler_for_publication();
4262    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4263      return CurrentThreadDispatchRequest::Unavailable;
4264    }
4265    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4266      return CurrentThreadDispatchRequest::Coalesced;
4267    }
4268    let dispatch = next_dispatch();
4269    if self
4270      .dispatch_pending
4271      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4272      .is_err()
4273    {
4274      return CurrentThreadDispatchRequest::Coalesced;
4275    }
4276    let dispatch_call = self
4277      .begin_host_dispatch_publication(&mut scheduler, dispatch)
4278      .expect("a fresh CurrentThread dispatch must own its publication");
4279    drop(scheduler);
4280    match self.publish_host_dispatch(dispatch, dispatch_call) {
4281      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4282        CurrentThreadDispatchRequest::Accepted
4283      }
4284      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4285    }
4286  }
4287
4288  fn claim_dispatch_call<'a>(
4289    &'a self,
4290    scheduler: &mut CurrentThreadSchedulerState,
4291  ) -> CurrentThreadDispatchCall<'a> {
4292    let generation = RuntimeGenerationGuard::enter(self.generation);
4293    let active_dispatch_calls = scheduler
4294      .active_dispatch_calls
4295      .checked_add(1)
4296      .expect("CurrentThread active dispatch-call count exhausted");
4297    scheduler.active_dispatch_calls = active_dispatch_calls;
4298    CurrentThreadDispatchCall {
4299      executor: self,
4300      publication: None,
4301      _generation: generation,
4302    }
4303  }
4304
4305  fn begin_host_dispatch_publication<'a>(
4306    &'a self,
4307    scheduler: &mut CurrentThreadSchedulerState,
4308    dispatch: u64,
4309  ) -> Option<CurrentThreadDispatchCall<'a>> {
4310    // See the implementation notes in this crate's README.
4311    if let Some(publication) = scheduler
4312      .dispatch_publications
4313      .iter_mut()
4314      .find(|publication| publication.dispatch == dispatch)
4315    {
4316      publication.republish_requested = true;
4317      return None;
4318    }
4319    let mut dispatch_call = self.claim_dispatch_call(scheduler);
4320    scheduler
4321      .dispatch_publications
4322      .push(CurrentThreadDispatchPublication {
4323        dispatch,
4324        republish_requested: false,
4325      });
4326    dispatch_call.publication = Some(dispatch);
4327    Some(dispatch_call)
4328  }
4329
4330  fn publish_host_dispatch(
4331    self: &Arc<Self>,
4332    dispatch: u64,
4333    mut dispatch_call: CurrentThreadDispatchCall<'_>,
4334  ) -> CurrentThreadHostDispatchResult {
4335    let mut panic_payload = RetainedPanicPayload::default();
4336    loop {
4337      let result = match catch_unwind(AssertUnwindSafe(|| {
4338        #[cfg(all(test, napi_runtime_os_threads))]
4339        run_current_thread_dispatch_call_test_hook();
4340        let result = (self.task_dispatch)(dispatch);
4341        #[cfg(all(test, napi_runtime_os_threads))]
4342        run_current_thread_dispatch_returned_test_hook();
4343        result
4344      })) {
4345        Ok(result) => Some(result),
4346        Err(payload) => {
4347          panic_payload.capture(payload);
4348          None
4349        }
4350      };
4351
4352      let step = catch_unwind(AssertUnwindSafe(|| {
4353        let (republish, cancelling, failure, result) = {
4354          let mut scheduler = self
4355            .scheduler_idle_lock
4356            .lock()
4357            .unwrap_or_else(std::sync::PoisonError::into_inner);
4358          let index = scheduler
4359            .dispatch_publications
4360            .iter()
4361            .position(|publication| publication.dispatch == dispatch)
4362            .expect("the CurrentThread dispatch publication must remain registered");
4363          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4364          let republish = dispatch_remains_pending
4365            && !self.stop.is_stopping()
4366            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4367            && scheduler.dispatch_publications[index].republish_requested;
4368          if republish {
4369            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4370              let _ = self.task_drivers.rearm_failed_publication(failure);
4371            }
4372            scheduler.dispatch_publications[index].republish_requested = false;
4373            (true, None, None, result)
4374          } else {
4375            dispatch_call.finish_publication(&mut scheduler);
4376            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4377              #[cfg(all(test, napi_runtime_os_threads))]
4378              run_current_thread_failed_publication_retired_test_hook();
4379              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4380            } else {
4381              None
4382            };
4383            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4384              && dispatch_remains_pending)
4385              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4386              .flatten();
4387            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4388              && !dispatch_remains_pending
4389            {
4390              // A synchronous callback may already have consumed, cancelled, or
4391              // superseded this exact capability.
4392              Some(CurrentThreadHostDispatchResult::Accepted)
4393            } else {
4394              result
4395            };
4396            (false, cancelling, failure, result)
4397          }
4398        };
4399
4400        if republish {
4401          return CurrentThreadDispatchPublicationStep::Republish;
4402        }
4403        if let Some(cancelling) = cancelling {
4404          self.cancel_work_after_host_dispatch_failure(cancelling);
4405        }
4406        if let Some(failure) = failure {
4407          self.run_host_dispatch_failure(failure.action);
4408        }
4409        CurrentThreadDispatchPublicationStep::Complete(result)
4410      }));
4411
4412      match step {
4413        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4414        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4415          if panic_payload.is_some() {
4416            panic_payload.resume();
4417          }
4418          return result
4419            .expect("a CurrentThread host dispatch without a result must resume its panic");
4420        }
4421        Err(payload) => {
4422          panic_payload.capture(payload);
4423          panic_payload.resume();
4424        }
4425      }
4426    }
4427  }
4428
4429  fn claim_host_turn(
4430    self: &Arc<Self>,
4431    scheduler: &mut CurrentThreadSchedulerState,
4432  ) -> CurrentThreadHostTurn {
4433    scheduler.active_host_turns = scheduler
4434      .active_host_turns
4435      .checked_add(1)
4436      .expect("CurrentThread active host-turn count exhausted");
4437    self.draining.store(true, Ordering::Release);
4438    CurrentThreadHostTurn {
4439      executor: Arc::clone(self),
4440      draining: true,
4441      _generation: RuntimeGenerationGuard::enter(self.generation),
4442    }
4443  }
4444
4445  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4446    let mut scheduler = self
4447      .scheduler_idle_lock
4448      .lock()
4449      .unwrap_or_else(std::sync::PoisonError::into_inner);
4450    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4451      return None;
4452    }
4453    if dispatch == 0
4454      || self
4455        .dispatch_pending
4456        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4457        .is_err()
4458    {
4459      return None;
4460    }
4461    #[cfg(all(test, napi_runtime_os_threads))]
4462    run_current_thread_dispatch_claim_test_hook();
4463    let _ =
4464      self
4465        .recovery_dispatch
4466        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4467    Some(self.claim_host_turn(&mut scheduler))
4468  }
4469
4470  #[cfg(test)]
4471  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> 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    Some(self.claim_host_turn(&mut scheduler))
4480  }
4481
4482  #[cfg(test)]
4483  fn drive_host_turn(self: &Arc<Self>) {
4484    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4485    let host_turn = if dispatch == 0 {
4486      self.try_admit_host_turn_without_dispatch()
4487    } else {
4488      self.try_admit_host_turn(dispatch)
4489    };
4490    if let Some(mut host_turn) = host_turn {
4491      host_turn.drive();
4492    }
4493  }
4494
4495  #[cfg(test)]
4496  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4497    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4498      host_turn.drive();
4499    }
4500  }
4501
4502  /// Drive a host turn whose scheduler role was claimed while the controller
4503  /// still held the lifecycle mutex. See
4504  /// the implementation notes in this crate's README.
4505  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4506    // A host turn is the continuation a terminal failure on a waker's stack
4507    // relies on: cancel the work it moved aside before polling anything new.
4508    self.retire_rejected_work_off_waker_stack();
4509    #[cfg(napi_runtime_os_threads)]
4510    let mut runnable_streak = 0usize;
4511    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4512      #[cfg(napi_runtime_os_threads)]
4513      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4514        runnable_streak = 0;
4515        if self.run_one_blocking() {
4516          continue;
4517        }
4518      }
4519      if self.drain_one() {
4520        #[cfg(napi_runtime_os_threads)]
4521        {
4522          runnable_streak += 1;
4523        }
4524        continue;
4525      }
4526      #[cfg(napi_runtime_os_threads)]
4527      if self.run_one_blocking() {
4528        runnable_streak = 0;
4529        continue;
4530      }
4531      break;
4532    }
4533    // A bounded turn releases queue-drain exclusivity before publishing its
4534    // continuation, but remains an active host turn until that publication
4535    // returns. Shutdown waits for the latter lifecycle role, not merely the
4536    // former exclusivity bit.
4537    host_turn.release_draining();
4538    self.request_drain_if_work_remains();
4539  }
4540
4541  fn request_drain_if_queued(self: &Arc<Self>) {
4542    self.retire_rejected_work_off_waker_stack();
4543    if !self.has_serviceable_work() {
4544      return;
4545    }
4546    let mut scheduler = self.lock_scheduler_for_publication();
4547    if self.stop.is_stopping()
4548      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4549      || self.draining.load(Ordering::Acquire)
4550    {
4551      return;
4552    }
4553    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4554    if dispatch == 0 {
4555      drop(scheduler);
4556      self.request_drain_if_work_remains();
4557      return;
4558    }
4559
4560    // A newly registered host joins the existing all-host race through its own
4561    // delivery token. Reusing the executor capability preserves every other
4562    // host's accepted attempt and lets old registration tokens fail closed.
4563    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4564      return;
4565    };
4566    drop(scheduler);
4567    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4568  }
4569
4570  fn retire_unavailable_host_dispatch_locked(
4571    &self,
4572    dispatch: u64,
4573  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4574    if self
4575      .dispatch_pending
4576      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4577      .is_err()
4578    {
4579      return None;
4580    }
4581    if self
4582      .recovery_dispatch
4583      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4584      .is_ok()
4585    {
4586      self.begin_failed_dispatch_cancellation()
4587    } else {
4588      None
4589    }
4590  }
4591
4592  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4593    // See the implementation notes in this crate's README.
4594    let failure = {
4595      let mut scheduler = self
4596        .scheduler_idle_lock
4597        .lock()
4598        .unwrap_or_else(std::sync::PoisonError::into_inner);
4599      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4600    };
4601    if let Some(failure) = failure {
4602      self.run_host_dispatch_failure(failure.action);
4603    }
4604    self.retire_rejected_work_off_waker_stack();
4605  }
4606
4607  fn begin_host_dispatch_failure_locked<'a>(
4608    &'a self,
4609    scheduler: &mut CurrentThreadSchedulerState,
4610    failure: CurrentThreadTaskDispatchFailure,
4611  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4612    if !self.task_drivers.is_current_failure(failure) {
4613      return None;
4614    }
4615    let registry_cancellation =
4616      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4617    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4618    Some(CurrentThreadDispatchFailureRecovery {
4619      action,
4620      _registry_cancellation: registry_cancellation,
4621    })
4622  }
4623
4624  fn begin_host_dispatch_failure_transition_locked<'a>(
4625    &'a self,
4626    scheduler: &mut CurrentThreadSchedulerState,
4627    dispatch: u64,
4628  ) -> CurrentThreadDispatchFailureAction<'a> {
4629    if dispatch == 0
4630      || self
4631        .dispatch_pending
4632        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4633        .is_err()
4634    {
4635      return CurrentThreadDispatchFailureAction::None;
4636    }
4637    #[cfg(all(test, napi_runtime_os_threads))]
4638    run_current_thread_dispatch_recovery_test_hook();
4639
4640    if self
4641      .recovery_dispatch
4642      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4643      .is_ok()
4644    {
4645      // The one fresh-turn replacement failed too. Polling inline here would
4646      // re-enter the host callback, while another retry could spin forever.
4647      // Claim terminal cancellation before allowing another publication.
4648      return self.begin_failed_dispatch_cancellation().map_or(
4649        CurrentThreadDispatchFailureAction::None,
4650        CurrentThreadDispatchFailureAction::Cancel,
4651      );
4652    }
4653
4654    if self.stop.is_stopping()
4655      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4656      || self.draining.load(Ordering::Acquire)
4657      || !self.has_serviceable_work()
4658    {
4659      return CurrentThreadDispatchFailureAction::None;
4660    }
4661
4662    // Consume the failed capability and reserve its sole replacement while
4663    // publication is excluded. A newly registered host can only join this
4664    // tagged replacement, never publish an untagged dispatch in between.
4665    let replacement = next_current_thread_dispatch_id();
4666    self.recovery_dispatch.store(replacement, Ordering::Release);
4667    self.dispatch_pending.store(replacement, Ordering::Release);
4668    let dispatch_call = self
4669      .begin_host_dispatch_publication(scheduler, replacement)
4670      .expect("a reserved CurrentThread replacement must own its publication");
4671    CurrentThreadDispatchFailureAction::Publish {
4672      dispatch: replacement,
4673      call: dispatch_call,
4674    }
4675  }
4676
4677  #[cfg(test)]
4678  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4679    let failure = {
4680      let mut scheduler = self
4681        .scheduler_idle_lock
4682        .lock()
4683        .unwrap_or_else(std::sync::PoisonError::into_inner);
4684      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4685    };
4686    self.run_host_dispatch_failure(failure);
4687  }
4688
4689  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4690    match failure {
4691      CurrentThreadDispatchFailureAction::None => {}
4692      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4693        self.cancel_work_after_host_dispatch_failure(cancelling);
4694      }
4695      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4696        let _ = self.publish_host_dispatch(dispatch, call);
4697      }
4698    }
4699  }
4700
4701  fn has_serviceable_work(&self) -> bool {
4702    let has_queued_runnable = {
4703      let queue = self
4704        .queue
4705        .lock()
4706        .unwrap_or_else(std::sync::PoisonError::into_inner);
4707      !queue.closed && !queue.runnables.is_empty()
4708    };
4709    #[cfg(napi_runtime_os_threads)]
4710    let has_queued_blocking = self.has_serviceable_blocking_work();
4711    #[cfg(not(napi_runtime_os_threads))]
4712    let has_queued_blocking = false;
4713    has_queued_runnable || has_queued_blocking
4714  }
4715
4716  fn request_drain_if_work_remains(self: &Arc<Self>) {
4717    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4718      self.request_drain();
4719    }
4720  }
4721
4722  fn begin_failed_dispatch_cancellation(
4723    &self,
4724  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4725    if self
4726      .cancelling_failed_dispatch
4727      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4728      .is_err()
4729    {
4730      return None;
4731    }
4732    self.dispatch_pending.store(0, Ordering::Release);
4733    self.recovery_dispatch.store(0, Ordering::Release);
4734    Some(CurrentThreadDispatchCancellationGuard(self))
4735  }
4736
4737  /// Terminal failure transition. The work queued at this point (runnables
4738  /// and blocking jobs) is what the failure cancels. It is MOVED into the
4739  /// rejected lists under the queue lock, with its `queued_runnables` debit,
4740  /// and what happens next depends on the stack:
4741  ///
4742  /// * On a clean stack -- a host entry point, or the submission `spawn` made
4743  ///   on its caller's own stack -- the rejected work is dropped right here,
4744  ///   with the executor still closed so that the tasks those drops wake are
4745  ///   rejected and cancelled with it, and the executor reopens under the
4746  ///   queue lock once nothing remains. This is the observable behaviour
4747  ///   terminal cancellation always had: queued work and everything it wakes
4748  ///   settle at once, and no later host work is published. A submission whose
4749  ///   two publications both failed has no other continuation -- it produced no
4750  ///   host turn -- so cancelling here is what keeps its `JoinHandle` from
4751  ///   staying pending forever with its generation guard held.
4752  /// * On a waker's stack (reached through `schedule`, whose publication the
4753  ///   host failed synchronously) nothing may be dropped: the waker's
4754  ///   producer may hold a lock the destructors re-lock. The executor reopens
4755  ///   at once instead -- staying closed would leave it without a continuation
4756  ///   while the driver stays registered -- and then publishes one fresh host
4757  ///   dispatch of its own (`request_rejected_work_retirement`). That turn is
4758  ///   the clean stack on which `retire_rejected_work` cancels the moved work;
4759  ///   without it a wake that lost both of its publications left no request
4760  ///   behind at all, and the moved runnables kept their `JoinHandle`s and
4761  ///   `GenerationWorkGuard`s until an unrelated entry happened to arrive.
4762  ///
4763  /// This cannot repeat unboundedly inside one `schedule`. Reaching here
4764  /// again needs a fresh publication and two more host-failed deliveries, and
4765  /// the queue is already empty once the move is done; the publications
4766  /// `request_rejected_work_retirement` makes are the only way such a fresh
4767  /// publication can start on this stack, and
4768  /// `CURRENT_THREAD_RETIRING_REJECTED_WORK` keeps a nested transition from
4769  /// publishing there -- it returns, and the frame that owns the publication
4770  /// reads what it moved aside off the executor and takes the next turn. Those
4771  /// turns stop at `REJECTED_WORK_RETIREMENT_ATTEMPTS`, so a sustained
4772  /// producer cannot turn this stack into a service loop, and are followed by
4773  /// the one offer a producer cannot inflate; see
4774  /// `request_rejected_work_retirement`.
4775  fn cancel_work_after_host_dispatch_failure(
4776    self: &Arc<Self>,
4777    cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4778  ) {
4779    let mut cancelling = Some(cancelling);
4780    // Blocking work first: the claimed flag blocks admission and claims, so
4781    // nothing can join or leave the FIFO between this take and the reopen.
4782    #[cfg(napi_runtime_os_threads)]
4783    let queued_blocking = {
4784      let mut state = self
4785        .blocking_admission
4786        .state
4787        .lock()
4788        .unwrap_or_else(std::sync::PoisonError::into_inner);
4789      state.queue.take_all()
4790    };
4791    {
4792      let mut queue = self
4793        .queue
4794        .lock()
4795        .unwrap_or_else(std::sync::PoisonError::into_inner);
4796      let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4797      // Rejected runnables carry their `queued_runnables` debit already.
4798      self
4799        .metrics
4800        .queued_runnables
4801        .fetch_sub(moved, Ordering::Relaxed);
4802      let runnables = std::mem::take(&mut queue.runnables);
4803      queue.rejected.extend(runnables);
4804      #[cfg(napi_runtime_os_threads)]
4805      queue.rejected_blocking.extend(queued_blocking);
4806      if CurrentThreadScheduleScope::is_active() {
4807        // Reopen while holding the queue lock: `schedule` consults the flag
4808        // under this same lock, so every runnable this transition rejects is
4809        // already in the list, and every later schedule is queued and
4810        // published.
4811        drop(cancelling.take());
4812        drop(queue);
4813        // The moved runnables still own their `GenerationWorkGuard`s; a
4814        // waiting shutdown retires them on its own stack.
4815        if let Some(work) = self.generation_work.load_full() {
4816          work.notify_idle_waiters();
4817        }
4818        // During normal operation there is no such waiter, and this branch
4819        // published no host delivery. Ask for the host turn that retires the
4820        // moved work: it is the only continuation this stack may leave behind.
4821        self.request_rejected_work_retirement();
4822        return;
4823      }
4824    }
4825
4826    // Clean stack: cancel now. Dropping a runnable can wake a parked task
4827    // whose schedule `schedule` then rejects into `queue.rejected` (the flag
4828    // is still set); drop those too, outside any lock, until none remain.
4829    // Reopen while holding the queue lock, so no runnable can be rejected
4830    // after the final pass.
4831    let _generation = RuntimeGenerationGuard::enter(self.generation);
4832    loop {
4833      #[cfg(napi_runtime_os_threads)]
4834      let (rejected, rejected_blocking) = {
4835        let mut queue = self
4836          .queue
4837          .lock()
4838          .unwrap_or_else(std::sync::PoisonError::into_inner);
4839        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4840          drop(cancelling.take());
4841          break;
4842        }
4843        (
4844          std::mem::take(&mut queue.rejected),
4845          std::mem::take(&mut queue.rejected_blocking),
4846        )
4847      };
4848      #[cfg(not(napi_runtime_os_threads))]
4849      let rejected = {
4850        let mut queue = self
4851          .queue
4852          .lock()
4853          .unwrap_or_else(std::sync::PoisonError::into_inner);
4854        if queue.rejected.is_empty() {
4855          drop(cancelling.take());
4856          break;
4857        }
4858        std::mem::take(&mut queue.rejected)
4859      };
4860      for runnable in rejected {
4861        let _ = catch_unwind_contained(|| drop(runnable));
4862      }
4863      #[cfg(napi_runtime_os_threads)]
4864      for job in rejected_blocking {
4865        let _ = catch_unwind_contained(|| drop(job));
4866      }
4867    }
4868  }
4869
4870  fn take_runnable(&self) -> Option<Runnable> {
4871    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
4872    // generation shutdown publishes while holding `stop.publication`. Claiming
4873    // under both locks gives each publication one exact ordering edge with the
4874    // runnable queue. The guards are released before polling user code.
4875    let scheduler = self
4876      .scheduler_idle_lock
4877      .lock()
4878      .unwrap_or_else(std::sync::PoisonError::into_inner);
4879    #[cfg(all(test, napi_runtime_os_threads))]
4880    run_current_thread_runnable_claim_test_hook();
4881    let stop_publication = self.stop.publication_guard();
4882    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4883      return None;
4884    }
4885    let runnable = {
4886      let mut queue = self
4887        .queue
4888        .lock()
4889        .unwrap_or_else(std::sync::PoisonError::into_inner);
4890      if queue.closed {
4891        None
4892      } else {
4893        queue.runnables.pop_front()
4894      }
4895    };
4896    drop(stop_publication);
4897    drop(scheduler);
4898    runnable
4899  }
4900
4901  fn drain_one(&self) -> bool {
4902    let runnable = self.take_runnable();
4903    if let Some(runnable) = runnable {
4904      #[cfg(napi_runtime_os_threads)]
4905      let _non_owner = BlockingOwnerGuard::enter(None);
4906      run_runnable(&self.metrics, runnable);
4907      true
4908    } else {
4909      false
4910    }
4911  }
4912
4913  /// Drive `future` to completion on this thread, draining queued runnables
4914  /// between polls. This loop OWNS its park site: the future is polled with
4915  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
4916  /// inspect the wake token directly instead of handing it to
4917  /// `futures::executor::block_on`'s opaque parker:
4918  ///
4919  ///   * `Pending` + queue empty + wake token pending: a self-waking future
4920  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
4921  ///     park, NOT a deadlock; panic only at the park decision, never at
4922  ///     Pending-return.
4923  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
4924  ///     deadlock -- no other thread exists that could ever deliver the wake
4925  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
4926  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
4927  ///     rescue here: its `setTimeout` relay shares this very thread, so it
4928  ///     can never fire while the park holds it -- it only selects the
4929  ///     timer-backed variant of the same certain diagnostic.
4930  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
4931  ///     `park_deadline` is armed, the park is bounded with progress-based
4932  ///     reset (see [`park_with_deadline`]) and panics typed if a full
4933  ///     window passes with zero runtime progress.
4934  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4935    let parker = Arc::new(DriverParker::default());
4936    let _stop_registration = self.stop.register(&parker);
4937    #[cfg(napi_runtime_os_threads)]
4938    let dependency = BlockOnDependencyGuard::enter(self.generation);
4939    let waker = std::task::Waker::from(Arc::clone(&parker));
4940    let mut cx = Context::from_waker(&waker);
4941    #[cfg(napi_runtime_os_threads)]
4942    let mut runnable_streak = 0usize;
4943    loop {
4944      if self.stop.is_stopping() {
4945        return BlockOnOutcome::Stopped;
4946      }
4947      #[cfg(napi_runtime_os_threads)]
4948      dependency.clear();
4949      on_thread_handoff();
4950      if future.as_mut().poll(&mut cx).is_ready() {
4951        return BlockOnOutcome::Completed;
4952      }
4953      // Shutdown must outrank queue work and self-wake permits. Otherwise an
4954      // always-self-waking future can continue before reaching the park-site
4955      // check forever while its generation work guard blocks quiescence.
4956      if self.stop.is_stopping() {
4957        return BlockOnOutcome::Stopped;
4958      }
4959      #[cfg(napi_runtime_os_threads)]
4960      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4961        runnable_streak = 0;
4962        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4963          continue;
4964        }
4965      }
4966      if self.drain_one() {
4967        #[cfg(napi_runtime_os_threads)]
4968        {
4969          runnable_streak += 1;
4970        }
4971        continue;
4972      }
4973      #[cfg(napi_runtime_os_threads)]
4974      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4975        runnable_streak = 0;
4976        continue;
4977      }
4978      #[cfg(napi_runtime_os_threads)]
4979      {
4980        runnable_streak = 0;
4981      }
4982      // PARK DECISION: the future is Pending and the queue is empty. A token
4983      // stored during the poll (or by a racing thread) means a wake is
4984      // already pending: consume it and re-poll instead of parking.
4985      if parker.consume_permit() {
4986        continue;
4987      }
4988      // Threadless timer invariant: on a THREADLESS build even a pending
4989      // HOST timer is NOT a wake-token source -- the host event loop's
4990      // `setTimeout` relay can only run on the very thread that is about to
4991      // park, so it can never fire while `block_on` holds it. EVERY threadless
4992      // park at this decision is therefore provably dead; a pending timer only
4993      // selects the more precise diagnostic. Falling through instead would hit
4994      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
4995      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
4996      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
4997      // below stays: it serves the THREADED CT flavor, where the host loop
4998      // genuinely runs concurrently.)
4999      if self.threadless {
5000        if self.host_timers.has_pending() {
5001          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
5002        }
5003        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
5004      }
5005      match self.park_deadline {
5006        // A wake landing between `consume_permit` above and the park is
5007        // stored as the parker's permit, so `park` returns immediately --
5008        // never lost.
5009        None => parker.park(),
5010        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
5011          DeadlineParkOutcome::Woken => {}
5012          DeadlineParkOutcome::Expired(armed) => {
5013            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
5014            // a wake can land in the instructions after the window
5015            // closed, and the fingerprint read inside `park_with_deadline`
5016            // is stale by now. Re-check both before declaring death; a hit
5017            // means the park was healthy -- continue the loop (re-poll,
5018            // drain, fresh full window on the next park). A LIVE host-timer
5019            // wait also vetoes, re-checked LAST -- arming a host timer bumps
5020            // no progress
5021            // counter, so it is invisible to the fingerprint read: waiting
5022            // out a host timer with zero progress is legitimate, its wake is
5023            // scheduled on the host loop. `deadline` doubles as the grace
5024            // for host firing lag; a timer past due by more than one full
5025            // window without firing is the frozen-JS-loop signature this
5026            // detection exists for, so it stops vetoing.
5027            if parker.consume_permit()
5028              || self.metrics.progress_fingerprint() != armed
5029              || self.host_timers.has_live_wait(deadline)
5030            {
5031              continue;
5032            }
5033            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
5034          }
5035        },
5036      }
5037    }
5038  }
5039
5040  fn begin_shutdown(&self) {
5041    let _generation = RuntimeGenerationGuard::enter(self.generation);
5042    self.stop.begin_shutdown();
5043    self.close_blocking_admission();
5044    let (queued, rejected) = {
5045      let mut queue = self
5046        .queue
5047        .lock()
5048        .unwrap_or_else(std::sync::PoisonError::into_inner);
5049      queue.closed = true;
5050      #[cfg(napi_runtime_os_threads)]
5051      let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
5052      let rejected = std::mem::take(&mut queue.rejected);
5053      #[cfg(napi_runtime_os_threads)]
5054      let rejected = (rejected, rejected_blocking);
5055      (std::mem::take(&mut queue.runnables), rejected)
5056    };
5057    self.dispatch_pending.store(0, Ordering::Release);
5058    self.recovery_dispatch.store(0, Ordering::Release);
5059    self.task_drivers.retire_for_shutdown();
5060    for runnable in queued {
5061      self
5062        .metrics
5063        .queued_runnables
5064        .fetch_sub(1, Ordering::Relaxed);
5065      // Dropping the last runnable cancels its detached async-task and retires
5066      // the generation guard. Isolate user future destructors from shutdown.
5067      let _ = catch_unwind_contained(|| drop(runnable));
5068    }
5069    // Rejected work (already unaccounted above) is cancelled by whichever
5070    // side takes it first: this one, or a host entry that got there earlier.
5071    #[cfg(napi_runtime_os_threads)]
5072    let (rejected, rejected_blocking) = rejected;
5073    for runnable in rejected {
5074      let _ = catch_unwind_contained(|| drop(runnable));
5075    }
5076    #[cfg(napi_runtime_os_threads)]
5077    for job in rejected_blocking {
5078      let _ = catch_unwind_contained(|| drop(job));
5079    }
5080    self.host_timers.shutdown();
5081  }
5082
5083  fn wait_until_scheduler_idle(&self) {
5084    // Registered work has retired by now on the backend path; this catches
5085    // rejections from unregistered tasks (tests) before the executor goes.
5086    self.retire_rejected_work();
5087    let mut idle = self
5088      .scheduler_idle_lock
5089      .lock()
5090      .unwrap_or_else(std::sync::PoisonError::into_inner);
5091    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5092      idle = self
5093        .scheduler_idle
5094        .wait(idle)
5095        .unwrap_or_else(std::sync::PoisonError::into_inner);
5096    }
5097    debug_assert!(
5098      idle.dispatch_publications.is_empty(),
5099      "an idle CurrentThread scheduler must not retain dispatch publications"
5100    );
5101  }
5102}
5103
5104// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
5105// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
5106// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
5107// cooperatively instead of parking the worker. Carrying the executor id (not a
5108// bare bool) keeps this marker scoped exactly like
5109// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
5110// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
5111// none of B's accounting, so it parks like any foreign-thread caller rather
5112// than driving B's queues as an unaccounted within-cap driver. Under the single
5113// global executor used in production these ids always match, so this is a
5114// defensive scoping gate (consistent with the blocking-owner machinery), not a
5115// reachable production change.
5116#[cfg(napi_runtime_os_threads)]
5117thread_local! {
5118  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5119  /// Identifies the dedicated timer thread for lifecycle self-wait
5120  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
5121  /// timer thread must never become a cooperative Rayon scheduler driver.
5122  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5123}
5124
5125// Identifies scheduler-driver frames on a pool worker. Unlike
5126// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
5127// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
5128// must not place work in the per-thread LIFO slot because no drain frame may
5129// remain to consume it after the Rayon job returns.
5130#[cfg(napi_runtime_os_threads)]
5131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5132struct SchedulerDriverRole {
5133  executor_id: u64,
5134  can_run_blocking: bool,
5135}
5136
5137#[cfg(napi_runtime_os_threads)]
5138thread_local! {
5139  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5140    const { std::cell::Cell::new(None) };
5141}
5142
5143// Per-worker LIFO slot (tokio-style): holds at most ONE
5144// runnable that was scheduled FROM this pool worker (typically a task the
5145// currently running runnable just woke). It is popped by this same thread's
5146// next drain/run_one iteration BEFORE the shared FIFO, so the hot
5147// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
5148// wake and no drainer spawn. Tagged with the executor id -- the same scoping
5149// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
5150// drain/run_one/flush of the SAME executor may pop it.
5151//
5152// STRANDING INVARIANT: slot work is invisible to every other
5153// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
5154// owning thread MUST either run its slot entry or flush it to the shared FIFO
5155// before it stops draining -- see the flush calls at `drain`'s returns, the
5156// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
5157// unwinds. A runnable left here while the thread returns to rayon would be a
5158// silently lost task and a `queued_runnables` leak.
5159#[cfg(napi_runtime_os_threads)]
5160thread_local! {
5161  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5162    const { std::cell::Cell::new(None) };
5163}
5164
5165#[cfg(napi_runtime_os_threads)]
5166struct OnPoolWorkerGuard {
5167  previous_worker: Option<u64>,
5168  previous_driver: Option<SchedulerDriverRole>,
5169}
5170
5171#[cfg(napi_runtime_os_threads)]
5172impl OnPoolWorkerGuard {
5173  // Save the previous marker and install `id`, so nested/re-entrant drains
5174  // (possibly from different executors on the same thread) restore the exact
5175  // prior executor id on drop instead of unconditionally clearing it.
5176  fn enter(id: u64) -> Self {
5177    Self::enter_with_role(id, true)
5178  }
5179
5180  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5181    Self {
5182      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5183      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5184        flag.replace(Some(SchedulerDriverRole {
5185          executor_id: id,
5186          can_run_blocking,
5187        }))
5188      }),
5189    }
5190  }
5191}
5192
5193#[cfg(napi_runtime_os_threads)]
5194impl Drop for OnPoolWorkerGuard {
5195  fn drop(&mut self) {
5196    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5197    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5198  }
5199}
5200
5201// Identifies one specific counted-blocking owner frame on one specific executor.
5202//
5203// `executor_id` is assigned once per executor (so a stale token left on a
5204// thread by a shut-down executor can never authorize work on a replacement);
5205// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
5206// half of dependency lending: the exact job half comes from the pending
5207// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
5208// runnable driver owns no frame, so it must respect normal blocking admission.
5209//
5210// CurrentThread uses the same identity to distinguish same-stack lane borrowing
5211// from cross-driver contention. MultiThread additionally reserves active owner
5212// frames while an otherwise-idle cooperative driver reuses their admitted lane.
5213#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5214struct BlockingOwnerToken {
5215  executor_id: u64,
5216  frame: u64,
5217}
5218
5219#[cfg(napi_runtime_os_threads)]
5220enum OwnedBlockingOutcome {
5221  Ineligible,
5222  OwnerBusy(BlockingOwnerToken),
5223  ReservationReleased {
5224    owner: BlockingOwnerToken,
5225    ran: bool,
5226  },
5227}
5228
5229// Process-global owner-frame source for per-frame dependency-lending
5230// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
5231static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5232#[cfg(napi_runtime_os_threads)]
5233static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5234
5235// The owner frame (if any) currently running a counted blocking closure on THIS
5236// thread. The exact frame identity scopes any over-cap dependency lending.
5237thread_local! {
5238  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5239    const { std::cell::Cell::new(None) };
5240}
5241
5242// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
5243// running a counted blocking closure, or with `None` while a cooperative driver
5244// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
5245// runnable an owner happens to drive via `run_one` is a logical non-owner and
5246// must not inherit the owner's over-cap privilege). Restored on drop, so the
5247// pattern is panic-safe even though `run_runnable` already catches panics.
5248#[cfg(napi_runtime_os_threads)]
5249struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5250
5251#[cfg(napi_runtime_os_threads)]
5252impl BlockingOwnerGuard {
5253  fn enter(token: Option<BlockingOwnerToken>) -> Self {
5254    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5255  }
5256}
5257
5258#[cfg(napi_runtime_os_threads)]
5259impl Drop for BlockingOwnerGuard {
5260  fn drop(&mut self) {
5261    BLOCKING_OWNER.with(|cell| cell.set(self.0));
5262  }
5263}
5264
5265#[cfg(napi_runtime_os_threads)]
5266#[derive(Default)]
5267struct ActiveBlockingOwners {
5268  // `None` is available; `Some(id)` is reserved by exactly that lending
5269  // transfer. The identity prevents a delayed stale drop from releasing a
5270  // newer reservation of the same owner frame.
5271  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5272}
5273
5274#[cfg(napi_runtime_os_threads)]
5275impl ActiveBlockingOwners {
5276  fn register(&self, owner: BlockingOwnerToken) {
5277    let previous = self
5278      .owners
5279      .lock()
5280      .unwrap_or_else(std::sync::PoisonError::into_inner)
5281      .insert(owner, None);
5282    debug_assert!(previous.is_none());
5283  }
5284
5285  fn unregister(&self, owner: BlockingOwnerToken) {
5286    let removed = self
5287      .owners
5288      .lock()
5289      .unwrap_or_else(std::sync::PoisonError::into_inner)
5290      .remove(&owner);
5291    debug_assert!(removed.is_some());
5292  }
5293
5294  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5295    let mut owners = self
5296      .owners
5297      .lock()
5298      .unwrap_or_else(std::sync::PoisonError::into_inner);
5299    let reservation = owners.get_mut(&owner)?;
5300    if reservation.is_some() {
5301      return None;
5302    }
5303    let id = next_unique_id(
5304      &NEXT_OWNER_RESERVATION,
5305      "blocking owner reservation id space exhausted",
5306    );
5307    *reservation = Some(id);
5308    Some(id)
5309  }
5310
5311  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5312    let mut owners = self
5313      .owners
5314      .lock()
5315      .unwrap_or_else(std::sync::PoisonError::into_inner);
5316    if let Some(current) = owners.get_mut(&owner)
5317      && *current == Some(reservation)
5318    {
5319      *current = None;
5320    }
5321  }
5322
5323  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5324    self
5325      .owners
5326      .lock()
5327      .unwrap_or_else(std::sync::PoisonError::into_inner)
5328      .get(&owner)
5329      .is_some_and(Option::is_none)
5330  }
5331}
5332
5333#[cfg(napi_runtime_os_threads)]
5334struct ActiveBlockingOwnerGuard<'a> {
5335  owners: &'a ActiveBlockingOwners,
5336  owner: BlockingOwnerToken,
5337}
5338
5339#[cfg(napi_runtime_os_threads)]
5340impl<'a> ActiveBlockingOwnerGuard<'a> {
5341  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5342    owners.register(owner);
5343    Self { owners, owner }
5344  }
5345}
5346
5347#[cfg(napi_runtime_os_threads)]
5348impl Drop for ActiveBlockingOwnerGuard<'_> {
5349  fn drop(&mut self) {
5350    self.owners.unregister(self.owner);
5351  }
5352}
5353
5354#[cfg(napi_runtime_os_threads)]
5355struct OwnerLaneReservation<'a> {
5356  owners: &'a ActiveBlockingOwners,
5357  owner: BlockingOwnerToken,
5358  reservation: u64,
5359}
5360
5361#[cfg(napi_runtime_os_threads)]
5362impl<'a> OwnerLaneReservation<'a> {
5363  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5364    let reservation = owners.reserve(owner)?;
5365    Some(Self {
5366      owners,
5367      owner,
5368      reservation,
5369    })
5370  }
5371}
5372
5373#[cfg(napi_runtime_os_threads)]
5374impl Drop for OwnerLaneReservation<'_> {
5375  fn drop(&mut self) {
5376    self.owners.release(self.owner, self.reservation);
5377  }
5378}
5379
5380// A blocking job queued for the pool. Its stable id is copied into the returned
5381// JoinHandle and propagated through async JoinHandle dependencies, allowing a
5382// saturated owner to run exactly the job its nested block_on awaits.
5383#[cfg(napi_runtime_os_threads)]
5384type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5385
5386#[cfg(napi_runtime_os_threads)]
5387struct QueuedBlocking {
5388  id: BlockingJobId,
5389  run: BlockingClosure,
5390}
5391
5392#[cfg(napi_runtime_os_threads)]
5393struct BlockingQueueNode {
5394  previous: Option<BlockingJobId>,
5395  next: Option<BlockingJobId>,
5396  run: Box<dyn FnOnce() + Send + 'static>,
5397}
5398
5399#[cfg(napi_runtime_os_threads)]
5400struct BlockingQueue {
5401  closed: bool,
5402  head: Option<BlockingJobId>,
5403  tail: Option<BlockingJobId>,
5404  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5405}
5406
5407#[cfg(napi_runtime_os_threads)]
5408impl BlockingQueue {
5409  fn push(&mut self, job: QueuedBlocking) {
5410    let previous = self.tail;
5411    let replaced = self.jobs.insert(
5412      job.id,
5413      BlockingQueueNode {
5414        previous,
5415        next: None,
5416        run: job.run,
5417      },
5418    );
5419    debug_assert!(replaced.is_none());
5420    if let Some(previous) = previous {
5421      self
5422        .jobs
5423        .get_mut(&previous)
5424        .expect("blocking queue tail must be indexed")
5425        .next = Some(job.id);
5426    } else {
5427      self.head = Some(job.id);
5428    }
5429    self.tail = Some(job.id);
5430  }
5431
5432  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5433    self.head.and_then(|id| self.remove(id))
5434  }
5435
5436  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5437    let node = self.jobs.remove(&id)?;
5438    if let Some(previous) = node.previous {
5439      self
5440        .jobs
5441        .get_mut(&previous)
5442        .expect("blocking queue predecessor must be indexed")
5443        .next = node.next;
5444    } else {
5445      self.head = node.next;
5446    }
5447    if let Some(next) = node.next {
5448      self
5449        .jobs
5450        .get_mut(&next)
5451        .expect("blocking queue successor must be indexed")
5452        .previous = node.previous;
5453    } else {
5454      self.tail = node.previous;
5455    }
5456    Some(node.run)
5457  }
5458
5459  fn is_empty(&self) -> bool {
5460    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5461    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5462    self.jobs.is_empty()
5463  }
5464
5465  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5466    self.head = None;
5467    self.tail = None;
5468    std::mem::take(&mut self.jobs)
5469      .into_values()
5470      .map(|node| node.run)
5471      .collect()
5472  }
5473}
5474
5475#[cfg(napi_runtime_os_threads)]
5476struct WorkerLifecycle {
5477  remaining: Mutex<usize>,
5478  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5479}
5480
5481#[cfg(napi_runtime_os_threads)]
5482impl WorkerLifecycle {
5483  fn new(worker_threads: usize) -> Arc<Self> {
5484    Arc::new(Self {
5485      remaining: Mutex::new(worker_threads),
5486      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5487    })
5488  }
5489
5490  fn retain(&self, handle: std::thread::JoinHandle<()>) {
5491    self
5492      .handles
5493      .lock()
5494      .unwrap_or_else(std::sync::PoisonError::into_inner)
5495      .as_mut()
5496      .expect("runtime worker handles already joined")
5497      .push(handle);
5498  }
5499
5500  fn worker_exited(&self) {
5501    let mut remaining = self
5502      .remaining
5503      .lock()
5504      .unwrap_or_else(std::sync::PoisonError::into_inner);
5505    *remaining = remaining
5506      .checked_sub(1)
5507      .expect("runtime worker exit count underflow");
5508  }
5509
5510  fn wait_for_all_workers(&self) {
5511    let handles = self
5512      .handles
5513      .lock()
5514      .unwrap_or_else(std::sync::PoisonError::into_inner)
5515      .take()
5516      .expect("runtime worker handles already joined");
5517    for handle in handles {
5518      if let Err(payload) = crate::sync::join(handle) {
5519        discard_panic_payload(payload);
5520      }
5521    }
5522  }
5523
5524  #[cfg(test)]
5525  fn remaining(&self) -> usize {
5526    *self
5527      .remaining
5528      .lock()
5529      .unwrap_or_else(std::sync::PoisonError::into_inner)
5530  }
5531}
5532
5533#[cfg(napi_runtime_os_threads)]
5534struct MultiThreadExecutor {
5535  generation: u64,
5536  stop: Arc<GenerationStop>,
5537  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
5538  // executor can never authorize an over-cap escape on a replacement.
5539  id: u64,
5540  pool: ThreadPool,
5541  // Lock-free MPMC injector replacing the former `Mutex<VecDeque<Runnable>>`.
5542  // Invariant: every push goes through `push_to_queue_and_wake` and every
5543  // emptiness check through `has_queued_runnable` -- their paired SeqCst fences
5544  // restore the removed mutex's synchronizes-with edge (full lost-wakeup proof
5545  // at `push_to_queue_and_wake`). A raw `injector.push`/`is_empty` at a new site
5546  // silently breaks the pairing.
5547  injector: Injector<Runnable>,
5548  blocking_queue: Mutex<BlockingQueue>,
5549  active_drainers: AtomicUsize,
5550  active_blocking: AtomicUsize,
5551  // Guards the idle-waiter count. `finish_draining` runs once per drain batch
5552  // -- i.e. per task whenever the drainer catches the producer -- and used to
5553  // call `notify_all` unconditionally. On Linux `Condvar::notify_all` issues a
5554  // `futex_wake_all` SYSCALL even with an empty waiter set, and the only waiter
5555  // site in this crate (`wait_until_scheduler_idle`) is reachable only from
5556  // shutdown/restart. Counting waiters under this same mutex lets the steady
5557  // state skip the syscall entirely.
5558  scheduler_idle_lock: Mutex<usize>,
5559  scheduler_idle: Condvar,
5560  worker_lifecycle: Arc<WorkerLifecycle>,
5561  // Registry of pool workers currently parked inside a re-entrant cooperative
5562  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
5563  // has a fixed number of OS threads, so a parked worker cannot rely on
5564  // `ensure_drainer` spawning a replacement (there is no free thread to run
5565  // it) -- it must wake and run the work itself. Future-wakes do NOT go
5566  // through this registry: each driver's own `DriverParker` is its awaited
5567  // future's waker (see the wake-domain note at `DriverParker`).
5568  parked_drivers: ParkedDrivers,
5569  active_blocking_owners: ActiveBlockingOwners,
5570  #[cfg(test)]
5571  owner_lending_attempts: AtomicUsize,
5572  // Linger-lifecycle observability for tests (see the idle-linger block in
5573  // `drain`): counts every linger-parker REGISTRATION into `parked_drivers`
5574  // and every idle-budget EXPIRY exit (the deadline-elapsed `break` paths,
5575  // counted AFTER the parker is deregistered, so `expiries` rising implies
5576  // the linger parker has permanently left the registry for that episode).
5577  // Tests that must observe a lingering drainer's full register->expire
5578  // lifecycle wait on these instead of sampling the non-monotonic registry
5579  // count, which is transiently at its settled value BEFORE the linger
5580  // parker registers.
5581  #[cfg(test)]
5582  drain_linger_registrations: AtomicUsize,
5583  #[cfg(test)]
5584  drain_linger_expiries: AtomicUsize,
5585  max_drainers: usize,
5586  max_blocking: usize,
5587  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
5588  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
5589  // design because it parks for entire builds and a deadline there would
5590  // panic healthy production runs. Off unless
5591  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
5592  // `PARK_DEADLINE_ENV` into it).
5593  park_deadline: Option<Duration>,
5594  // Idle-linger budget for pool drainers (`None` = disabled); resolved from
5595  // `RuntimeOptions::drain_linger` (`ZERO` = disabled, embedder-resolved from
5596  // `DRAIN_LINGER_ENV` at addon load) at construction and ceiling-clamped
5597  // there so even direct unvalidated test constructions stay bounded.
5598  drain_linger: Option<Duration>,
5599  // Shared with the timer heap because firing removes the timer before invoking
5600  // its waker. The admission remains active until that wake has published any
5601  // resulting runnable, so a final verdict cannot observe both sources empty.
5602  deadlock_state: Arc<MultiThreadDeadlockState>,
5603  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
5604  // Cooperative parks also fire due timers after timer-bounded waits.
5605  timers: TimerHeap,
5606  metrics: Arc<RuntimeMetrics>,
5607}
5608
5609#[cfg(napi_runtime_os_threads)]
5610impl MultiThreadExecutor {
5611  /// Max work units before a drainer yields. Cooperative drivers use the same
5612  /// value as the LIFO streak cap.
5613  const RUNNABLE_BUDGET: usize = 64;
5614  /// Force one blocking admission after this many consecutive runnable polls
5615  /// when blocking capacity and queued work are both available.
5616  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5617
5618  #[cfg(test)]
5619  fn new(
5620    options: &RuntimeOptions,
5621    metrics: Arc<RuntimeMetrics>,
5622  ) -> Result<Self, RuntimeConfigError> {
5623    Self::new_for_generation(
5624      options,
5625      metrics,
5626      u64::MAX,
5627      Arc::new(GenerationStop::default()),
5628    )
5629  }
5630
5631  fn new_for_generation(
5632    options: &RuntimeOptions,
5633    metrics: Arc<RuntimeMetrics>,
5634    generation: u64,
5635    stop: Arc<GenerationStop>,
5636  ) -> Result<Self, RuntimeConfigError> {
5637    let id = next_unique_id(
5638      &NEXT_RUNTIME_EXECUTOR_ID,
5639      "async runtime executor id space exhausted",
5640    );
5641    let thread_name_prefix = options.thread_name_prefix.clone();
5642    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5643      options.park_deadline.is_some(),
5644    ));
5645    let timers = TimerHeap::new(
5646      generation,
5647      id,
5648      format!("{thread_name_prefix}-timer"),
5649      Arc::clone(&deadlock_state),
5650    );
5651    // Production options are validated before construction, so this exact
5652    // physical count is also the configured/reported count. Direct executor
5653    // unit tests may still construct a one-thread topology intentionally.
5654    let pool_threads = options.worker_threads;
5655    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5656    let exit_lifecycle = Arc::clone(&worker_lifecycle);
5657    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5658    let pool = ThreadPoolBuilder::new()
5659      .num_threads(pool_threads)
5660      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5661      .start_handler(move |_| {
5662        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5663      })
5664      .exit_handler(move |_| {
5665        IN_SCHEDULER_DRIVER.with(|driver| {
5666          if driver.get().is_some_and(|role| role.executor_id == id) {
5667            driver.set(None);
5668          }
5669        });
5670        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
5671        // run after this Rayon hook and may re-enter lifecycle APIs while the
5672        // external shutdown caller is joining this worker; retaining the
5673        // executor identity makes that re-entry fail fast instead of waiting
5674        // in Stopping and deadlocking the join.
5675        exit_lifecycle.worker_exited();
5676      })
5677      .spawn_handler(move |thread| {
5678        let mut builder = std::thread::Builder::new();
5679        if let Some(name) = thread.name() {
5680          builder = builder.name(name.to_string());
5681        }
5682        if let Some(stack_size) = thread.stack_size() {
5683          builder = builder.stack_size(stack_size);
5684        }
5685        let handle = builder.spawn(move || thread.run())?;
5686        spawn_lifecycle.retain(handle);
5687        Ok(())
5688      })
5689      .build()
5690      .map_err(|error| {
5691        worker_lifecycle.wait_for_all_workers();
5692        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5693      })?;
5694    Ok(Self {
5695      generation,
5696      stop,
5697      id,
5698      pool,
5699      injector: Injector::new(),
5700      blocking_queue: Mutex::new(BlockingQueue {
5701        closed: false,
5702        head: None,
5703        tail: None,
5704        jobs: FxHashMap::default(),
5705      }),
5706      active_drainers: AtomicUsize::new(0),
5707      active_blocking: AtomicUsize::new(0),
5708      scheduler_idle_lock: Mutex::new(0),
5709      scheduler_idle: Condvar::new(),
5710      worker_lifecycle,
5711      parked_drivers: ParkedDrivers::default(),
5712      active_blocking_owners: ActiveBlockingOwners::default(),
5713      #[cfg(test)]
5714      owner_lending_attempts: AtomicUsize::new(0),
5715      #[cfg(test)]
5716      drain_linger_registrations: AtomicUsize::new(0),
5717      #[cfg(test)]
5718      drain_linger_expiries: AtomicUsize::new(0),
5719      max_drainers: pool_threads,
5720      max_blocking: options.max_blocking_tasks,
5721      park_deadline: options.park_deadline,
5722      drain_linger: {
5723        // Defensive re-clamp: production options were already clamped by
5724        // `RuntimeOptions::validate`, but direct executor unit tests construct
5725        // from unvalidated options.
5726        let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5727        (!budget.is_zero()).then_some(budget)
5728      },
5729      deadlock_state,
5730      timers,
5731      metrics,
5732    })
5733  }
5734
5735  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5736    self.deadlock_state.begin_admission()
5737  }
5738
5739  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5740    self.deadlock_state.try_begin_verdict()
5741  }
5742
5743  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5744    // See the implementation notes in this crate's README.
5745    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5746    self.parked_drivers.wake_one_owner_dependency(owner)
5747  }
5748
5749  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5750    let owner = reservation.owner;
5751    // Keep release and its targeted handoff in one detector-visible critical
5752    // section. Unwind drops the mutex after the lane is already available, so
5753    // a later verdict rechecks that availability even if selection panicked.
5754    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5755    drop(reservation);
5756    self.parked_drivers.wake_one_owner_dependency(owner)
5757  }
5758
5759  fn schedule(self: &Arc<Self>, runnable: Runnable) {
5760    let mut admission = self.begin_deadlock_admission();
5761    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
5762    // still "queued" until popped (`runnable_started` balances it either way).
5763    self.metrics.runnable_scheduled();
5764    // LIFO fast path: a runnable scheduled from a pool
5765    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
5766    // shared FIFO -- this thread pops it before its next FIFO look, so no
5767    // queue mutex, no wake, no drainer spawn.
5768    //
5769    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
5770    // on the stack (drain/run_one loop iterations, cooperative polls). It is
5771    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
5772    // frame including `blocking()` calls, but the slot is only popped after
5773    // the closure returns -- so a closure that spawns and then synchronously
5774    // waits on the child would deadlock with the child slotted (no wake, no
5775    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
5776    // while straight-line blocking-closure code runs (drain/run_one blocking
5777    // branches, the over-cap escape) and `None` while runnables run (the
5778    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
5779    // slot and take the FIFO+wake path whenever an owner frame is ambient.
5780    // (A cooperative poll entered FROM a blocking closure also carries the
5781    // closure's token and loses the fast path -- a rare, conservative,
5782    // correctness-first trade.)
5783    if IN_SCHEDULER_DRIVER
5784      .with(std::cell::Cell::get)
5785      .is_some_and(|role| role.executor_id == self.id)
5786      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5787    {
5788      match LIFO_SLOT.with(std::cell::Cell::take) {
5789        None => {
5790          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5791          if let Some(admission) = admission.as_mut() {
5792            admission.mark_published();
5793          }
5794          return;
5795        }
5796        // Same-executor occupant: the NEWEST runnable takes the slot (it is
5797        // the hottest); the displaced occupant falls back to the shared FIFO
5798        // with a normal wake so other workers can pick it up.
5799        Some((id, displaced)) if id == self.id => {
5800          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5801          self.push_to_queue_and_wake(displaced);
5802          if let Some(admission) = admission.as_mut() {
5803            admission.mark_published();
5804          }
5805          return;
5806        }
5807        // Foreign-executor occupant (defensive: requires a thread acting as a
5808        // worker of two executors, e.g. under a test-installed marker; the
5809        // production process has one global executor). We must not flush
5810        // another executor's runnable onto OUR queue, so leave it in place --
5811        // its own executor's drain on this thread pops or flushes it -- and
5812        // route the new runnable to the FIFO.
5813        Some(foreign) => {
5814          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5815        }
5816      }
5817    }
5818    self.push_to_queue_and_wake(runnable);
5819    if let Some(admission) = admission.as_mut() {
5820      admission.mark_published();
5821    }
5822  }
5823
5824  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
5825  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
5826  /// already happened at their original schedule).
5827  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5828    self.injector.push(runnable);
5829    // The former queue mutex provided the synchronizes-with edge the
5830    // lost-wakeup proof depends on (see `wake_one`). With a lock-free injector
5831    // that edge is gone, so restore it explicitly: a SeqCst fence between the
5832    // push and BOTH wake mechanisms (`wake_one`'s SeqCst `count` load and
5833    // `ensure_drainer`'s `active_drainers` load), mirrored by the SeqCst fence
5834    // in `has_queued_runnable` before the waiter/drainer re-check. This is the
5835    // Dekker ordering rayon-core's injector+sleep protocol uses; without both
5836    // fences a worker can park next to stealable work.
5837    std::sync::atomic::fence(Ordering::SeqCst);
5838    self.wake_for_new_work();
5839  }
5840
5841  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
5842  /// parked cooperative driver; when none is parked, fall back to spawning a
5843  /// drainer. A single wake suffices because the woken driver re-checks
5844  /// every work source in its loop before re-parking, and a wake it absorbs
5845  /// without consuming is handed on at `cooperative_block_on` exit (miss
5846  /// compensation). MUST be called AFTER the work was pushed
5847  /// under its queue mutex -- see the lost-wakeup argument at
5848  /// [`ParkedDrivers::wake_one`].
5849  fn wake_for_new_work(self: &Arc<Self>) {
5850    if !self.parked_drivers.wake_one() {
5851      self.ensure_drainer();
5852    }
5853  }
5854
5855  fn wake_for_blocking_work(self: &Arc<Self>) {
5856    // Prefer a cooperative driver that can consume the blocking FIFO;
5857    // otherwise arm a normal Rayon drainer.
5858    if !self.parked_drivers.wake_one_blocking() {
5859      self.ensure_drainer();
5860    }
5861  }
5862
5863  #[cfg(test)]
5864  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5865  where
5866    F: FnOnce() -> T + Send + 'static,
5867    T: Send + 'static,
5868  {
5869    self.schedule_blocking_result(move || catch_unwind_join_error(function))
5870  }
5871
5872  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5873  where
5874    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5875    T: Send + 'static,
5876  {
5877    let mut admission = self.begin_deadlock_admission();
5878    let (sender, receiver) = oneshot::channel();
5879    let generation = self.generation;
5880    let dependency = BlockingDependency {
5881      job: BlockingJobId(next_unique_id(
5882        &NEXT_BLOCKING_JOB_ID,
5883        "blocking job id space exhausted",
5884      )),
5885      owner: BLOCKING_OWNER
5886        .with(std::cell::Cell::get)
5887        .filter(|owner| owner.executor_id == self.id),
5888    };
5889    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
5890    // detector's fingerprint must be able to observe
5891    // this submission even while the job merely sits queued -- symmetric with
5892    // `runnable_scheduled` on the runnable path.
5893    self.metrics.blocking_scheduled();
5894    let queued = QueuedBlocking {
5895      id: dependency.job,
5896      run: Box::new(move || {
5897        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5898        let _ = sender.send(result);
5899      }),
5900    };
5901    let rejected = {
5902      let mut queue = self
5903        .blocking_queue
5904        .lock()
5905        .unwrap_or_else(std::sync::PoisonError::into_inner);
5906      if queue.closed {
5907        Some(queued)
5908      } else {
5909        queue.push(queued);
5910        None
5911      }
5912    };
5913    if let Some(rejected) = rejected {
5914      // Shutdown may close the queue after the controller registered this
5915      // generation work but before it reaches the executor. Drop captured
5916      // user values outside the queue lock and contain hostile destructors,
5917      // matching the queued-job shutdown path.
5918      let _generation = RuntimeGenerationGuard::enter(self.generation);
5919      let _ = catch_unwind_contained(|| drop(rejected));
5920    } else {
5921      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
5922      self.wake_for_blocking_work();
5923      if let Some(admission) = admission.as_mut() {
5924        admission.mark_published();
5925      }
5926    }
5927    JoinHandle(JoinHandleInner::Blocking {
5928      receiver,
5929      dependency,
5930      published: PublishedBlockingDependencies::default(),
5931    })
5932  }
5933
5934  fn ensure_drainer(self: &Arc<Self>) {
5935    loop {
5936      let active = self.active_drainers.load(Ordering::Acquire);
5937      if active >= self.max_drainers {
5938        return;
5939      }
5940      if self
5941        .active_drainers
5942        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5943        .is_ok()
5944      {
5945        let executor = Arc::clone(self);
5946        self.pool.spawn_fifo(move || executor.drain());
5947        return;
5948      }
5949    }
5950  }
5951
5952  fn drain(self: Arc<Self>) {
5953    // LINGER GATE: captured BEFORE the guard installs the marker below. In
5954    // production `drain` only ever runs via `pool.spawn_fifo`, i.e. on a pool
5955    // worker whose start handler already set `ON_POOL_WORKER`, so lingering is
5956    // active there; a direct test call from a foreign thread keeps the legacy
5957    // return-when-empty shape.
5958    let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5959    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
5960    // drives the queue cooperatively instead of parking the worker.
5961    let _on_pool = OnPoolWorkerGuard::enter(self.id);
5962    // Unwind backstop: never leave a runnable stranded in
5963    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
5964    // below (so its re-check can see the flushed runnable); this guard only
5965    // covers unwinds, which should be impossible (runnables and blocking
5966    // wrappers are catch_unwind'd) but must not lose a task if they happen.
5967    let _slot_backstop = LifoSlotFlushGuard(&self);
5968
5969    let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5970    // Linger state (parker + generation-stop registration), created lazily on
5971    // this frame's first idle episode and reused for the rest of the frame.
5972    let mut linger: Option<DrainLinger> = None;
5973    // Absolute exit deadline of the CURRENT idle episode, armed on the first
5974    // empty observation after work ran. Spurious wakes (timer churn, a stolen
5975    // queue wake) re-park with the REMAINING time rather than a fresh budget,
5976    // so a lingering drainer occupies its pool slot for at most
5977    // `linger_budget` past the last work unit it executed.
5978    let mut idle_deadline: Option<Instant> = None;
5979    // Absolute residence deadline of the WHOLE frame, armed on the frame's
5980    // FIRST idle observation and -- unlike `idle_deadline` -- never reset by
5981    // executed work. Without it, periodic sub-budget work re-arms the episode
5982    // deadline forever and this frame never returns its rayon worker; see
5983    // [`DRAIN_LINGER_FRAME_FACTOR`].
5984    let mut frame_deadline: Option<Instant> = None;
5985
5986    'frame: loop {
5987      let mut runnable_streak = 0usize;
5988      for _ in 0..Self::RUNNABLE_BUDGET {
5989        if self.run_one_fair(&mut runnable_streak) {
5990          idle_deadline = None;
5991          continue;
5992        }
5993        // Queue observed empty. MANDATORY flush before the shared exit AND
5994        // before any park: slot work is invisible to other threads and to
5995        // `finish_draining`'s re-check, so no path may leave this loop (or
5996        // sleep) with an occupied slot. On this path the slot was observed
5997        // empty at the top of the iteration and no user code ran since, so
5998        // the flush is defensive -- kept unconditional so every exit upholds
5999        // the stranding invariant by construction.
6000        self.flush_lifo_slot();
6001        // IDLE LINGER: instead of exiting and paying a full
6002        // spawn_fifo/rayon-idle-protocol round trip on the NEXT wave, park on
6003        // our own parker, registered in `parked_drivers` so
6004        // `wake_for_new_work`/`wake_for_blocking_work` deliver ONE targeted
6005        // wake. `active_drainers` stays counted for the whole linger: this
6006        // frame is still a live drainer, so `ensure_drainer`'s cap and the
6007        // idle-wait predicate keep their meaning.
6008        //
6009        // LOST-WAKEUP ARGUMENT (identical protocol to a cooperative driver's
6010        // park): register FIRST (the registry `count` store), THEN re-check
6011        // the shared queues (`has_queued_work_for_role`'s SeqCst fence pairs
6012        // with `push_to_queue_and_wake`'s post-push fence), THEN park -- see
6013        // [`ParkedDrivers::wake_one`]. On the timeout path, `wake_one` may
6014        // have popped this parker (counting the wake as DELIVERED, so the
6015        // producer spawned no drainer) while its `unpark` is still in flight:
6016        // the pop precedes our `deregister` on the registry mutex, so the
6017        // producer's push (made BEFORE its wake) is visible to the
6018        // post-deregister permit/queue re-checks below, which then RESUME
6019        // draining instead of exiting. Only a truly empty re-check falls
6020        // through to `finish_draining`, whose own post-decrement re-check
6021        // remains the existing final net for pushes racing the exit.
6022        let Some(budget) = linger_budget else {
6023          break 'frame;
6024        };
6025        if self.stop.is_stopping() {
6026          break 'frame;
6027        }
6028        let now = Instant::now();
6029        // FRAME RESIDENCE BOUND: exiting here (or via the min-capped park
6030        // below) funnels through the UNCHANGED finish_draining path, whose
6031        // re-check re-arms a FIFO drain via ensure_drainer/spawn_fifo when
6032        // work remains -- that re-entry through rayon's queue is exactly the
6033        // interleaving point that lets pending raw rayon work run.
6034        let frame = *frame_deadline.get_or_insert_with(|| {
6035          now
6036            + budget
6037              .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
6038              .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
6039        });
6040        let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
6041        if now >= deadline {
6042          // Deadline EXPIRY observed at the top: either a wake consumed the
6043          // rest of the episode's idle budget, or the frame residence bound
6044          // elapsed while work kept re-arming the episode deadline. Either
6045          // way this frame's lingering is over, and the linger parker of the
6046          // previous episode -- if one registered -- was already deregistered
6047          // after its park.
6048          #[cfg(test)]
6049          self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6050          break 'frame;
6051        }
6052        let state = match &linger {
6053          Some(state) => state,
6054          None => linger.insert(DrainLinger::new(&self)),
6055        };
6056        self
6057          .parked_drivers
6058          .register_with_role(&state.parker, true, None);
6059        #[cfg(test)]
6060        self
6061          .drain_linger_registrations
6062          .fetch_add(1, Ordering::SeqCst);
6063        if self.has_queued_work_for_role(true) {
6064          self.parked_drivers.deregister(&state.parker);
6065          continue 'frame;
6066        }
6067        // Stop re-check AFTER registering: `begin_shutdown` unparks every
6068        // generation-stop-registered parker (ours included, via
6069        // `DrainLinger`'s registration), so a stop publishing after this
6070        // check still cuts the park short instead of waiting out the budget.
6071        if self.stop.is_stopping() {
6072          self.parked_drivers.deregister(&state.parker);
6073          break 'frame;
6074        }
6075        let woken = state.parker.park_timeout(deadline - now);
6076        self.parked_drivers.deregister(&state.parker);
6077        if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
6078          continue 'frame;
6079        }
6080        // Idle-budget EXPIRY: the park timed out with no wake, no permit and
6081        // no queued work. Counted AFTER the deregister above, so a test
6082        // observing `drain_linger_expiries` rise knows the linger parker has
6083        // permanently left the registry for this idle episode.
6084        #[cfg(test)]
6085        self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
6086        break 'frame;
6087      }
6088      // Budget exhausted: yield this worker back to rayon exactly as before
6089      // the linger existed -- `finish_draining` re-arms a FIFO drain job
6090      // BEHIND any pending rayon work (par_iter splits, nested jobs), which
6091      // is the pool-fairness property the budget exists for.
6092      break 'frame;
6093    }
6094
6095    // MANDATORY flush before `finish_draining` so its
6096    // re-check sees a slot runnable on the shared FIFO and re-arms a drainer.
6097    self.flush_lifo_slot();
6098    self.finish_draining();
6099  }
6100
6101  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
6102  /// A foreign executor's entry is left untouched (see the scoping note at
6103  /// `LIFO_SLOT`).
6104  fn pop_lifo_slot(&self) -> Option<Runnable> {
6105    LIFO_SLOT.with(|slot| match slot.take() {
6106      Some((id, runnable)) if id == self.id => Some(runnable),
6107      other => {
6108        slot.set(other);
6109        None
6110      }
6111    })
6112  }
6113
6114  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
6115  /// with a wake so any worker can pick it up. MANDATORY on every path where
6116  /// this thread stops draining this executor: `drain`'s
6117  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
6118  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
6119  /// slot while its thread leaves executor code is a silently lost task.
6120  fn flush_lifo_slot(self: &Arc<Self>) {
6121    let mut admission = self.begin_deadlock_admission();
6122    if let Some(runnable) = self.pop_lifo_slot() {
6123      self.push_to_queue_and_wake(runnable);
6124      if let Some(admission) = admission.as_mut() {
6125        admission.mark_published();
6126      }
6127    }
6128  }
6129
6130  /// Mint a fresh owner token for a counted blocking closure entering on this
6131  /// executor. Each entry gets a unique `frame` so nested owners on the same
6132  /// thread can never run each other's queued jobs over the cap.
6133  fn fresh_owner_token(&self) -> BlockingOwnerToken {
6134    BlockingOwnerToken {
6135      executor_id: self.id,
6136      frame: next_unique_id(
6137        &NEXT_BLOCKING_FRAME,
6138        "blocking owner frame id space exhausted",
6139      ),
6140    }
6141  }
6142
6143  fn finish_draining(self: &Arc<Self>) {
6144    // LIFO-slot invariant: this re-check reads only the
6145    // SHARED queues -- per-worker slots are invisible to it, and that is
6146    // sound because slot work never needs a foreign observer. A slot entry is
6147    // always either popped and run by its owning thread (drain / run_one) or
6148    // flushed to the shared FIFO by that same thread BEFORE it stops draining
6149    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
6150    // covers unwinds; cooperative parks flush defensively). No thread leaves
6151    // executor code with its slot occupied, so the exit-then-respawn window
6152    // below cannot hide slot work from `ensure_drainer`.
6153    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6154    let has_runnable = self.has_queued_runnable();
6155    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6156      && !self
6157        .blocking_queue
6158        .lock()
6159        .unwrap_or_else(std::sync::PoisonError::into_inner)
6160        .is_empty();
6161    if has_runnable || has_blocking {
6162      self.ensure_drainer();
6163    }
6164    // The lock is still taken unconditionally: it is the release edge that
6165    // publishes the `active_drainers` decrement above to a waiter that is about
6166    // to evaluate the predicate. Only the wake is now conditional, and the
6167    // waiter count is read under the very mutex the waiter must hold to
6168    // register, so a waiter is never missed. See `wait_until_scheduler_idle`.
6169    let idle = self
6170      .scheduler_idle_lock
6171      .lock()
6172      .unwrap_or_else(std::sync::PoisonError::into_inner);
6173    if *idle > 0 {
6174      self.scheduler_idle.notify_all();
6175    }
6176  }
6177
6178  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6179    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6180      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
6181      // freeze a worker that the awaited future may itself need to make
6182      // progress. Drive the queue cooperatively instead (mirrors the
6183      // CurrentThread drive loop). The id check mirrors the
6184      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
6185      // owns none of this executor's accounting, so it must take the parking
6186      // path below rather than drive these queues as an unaccounted driver.
6187      self.cooperative_block_on(future)
6188    } else {
6189      // Non-pool (e.g. napi caller) thread, or a pool worker of another
6190      // executor: park without driving queues. The runtime owns this park so
6191      // generation shutdown can wake and cancel it.
6192      self.parking_block_on(future)
6193    }
6194  }
6195
6196  fn parking_block_on(
6197    self: &Arc<Self>,
6198    mut future: Pin<&mut dyn Future<Output = ()>>,
6199  ) -> BlockOnOutcome {
6200    let parker = Arc::new(DriverParker::default());
6201    let _stop_registration = self.stop.register(&parker);
6202    let waker = Waker::from(Arc::clone(&parker));
6203    let mut cx = Context::from_waker(&waker);
6204    loop {
6205      if self.stop.is_stopping() {
6206        return BlockOnOutcome::Stopped;
6207      }
6208      on_thread_handoff();
6209      if future.as_mut().poll(&mut cx).is_ready() {
6210        return BlockOnOutcome::Completed;
6211      }
6212      if self.stop.is_stopping() {
6213        return BlockOnOutcome::Stopped;
6214      }
6215      parker.park();
6216    }
6217  }
6218
6219  fn run_runnable(
6220    self: &Arc<Self>,
6221    runnable: Runnable,
6222    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6223  ) {
6224    on_thread_handoff();
6225    // Ownership is lexical to a blocking closure. A runnable driven from that
6226    // closure must not borrow its over-cap privilege.
6227    let _non_owner = BlockingOwnerGuard::enter(None);
6228    let _active = self.metrics.runnable_started();
6229    #[cfg(test)]
6230    run_multi_thread_work_started_test_hook();
6231    // The queue/slot removal is invisible to the deadline verdict until start
6232    // accounting advances. Keep the detector gate open across exactly that gap,
6233    // but never across a user poll.
6234    drop(admission);
6235    let _ = catch_unwind_contained(|| runnable.run());
6236  }
6237
6238  fn cancel_runnable(
6239    &self,
6240    runnable: Runnable,
6241    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6242  ) {
6243    self.metrics.runnable_cancelled();
6244    // Cancellation accounting closes the dequeue gap just as start accounting
6245    // does for a runnable that will be polled.
6246    drop(admission);
6247    let _generation = RuntimeGenerationGuard::enter(self.generation);
6248    let _ = catch_unwind_contained(|| drop(runnable));
6249  }
6250
6251  fn finish_runnable_claim(
6252    self: &Arc<Self>,
6253    claim: RunnableClaim,
6254    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6255  ) -> bool {
6256    match claim {
6257      RunnableClaim::Empty => {
6258        drop(admission);
6259        false
6260      }
6261      RunnableClaim::Run(runnable) => {
6262        #[cfg(test)]
6263        run_multi_thread_work_claimed_test_hook();
6264        self.run_runnable(runnable, admission);
6265        true
6266      }
6267      RunnableClaim::Cancel(runnable) => {
6268        #[cfg(test)]
6269        run_multi_thread_work_claimed_test_hook();
6270        self.cancel_runnable(runnable, admission);
6271        true
6272      }
6273    }
6274  }
6275
6276  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6277    match runnable {
6278      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6279      Some(runnable) => RunnableClaim::Run(runnable),
6280      None => RunnableClaim::Empty,
6281    }
6282  }
6283
6284  /// Lock-free single-item steal from the shared injector, retrying the
6285  /// transient `Steal::Retry` contention state. Replaces the former
6286  /// `queue.lock().pop_front()`; `Injector` preserves FIFO order.
6287  fn steal_one(&self) -> Option<Runnable> {
6288    loop {
6289      match self.injector.steal() {
6290        Steal::Success(runnable) => return Some(runnable),
6291        Steal::Empty => return None,
6292        Steal::Retry => continue,
6293      }
6294    }
6295  }
6296
6297  /// Test-only raw FIFO enqueue: the former `queue.lock().push_back()` staging
6298  /// that tests use to seed queued work WITHOUT the accompanying wake (so no
6299  /// drainer races the manual `run_one`/`drain` calls). Keeps the post-push
6300  /// SeqCst fence from `push_to_queue_and_wake` so any wake/park interaction a
6301  /// test performs afterwards still sees the Dekker pairing documented on
6302  /// `injector`.
6303  #[cfg(test)]
6304  fn push_fifo_raw(&self, runnable: Runnable) {
6305    self.injector.push(runnable);
6306    std::sync::atomic::fence(Ordering::SeqCst);
6307  }
6308
6309  fn claim_fifo_runnable(&self) -> RunnableClaim {
6310    let stop_publication = self.stop.runnable_claim_guard();
6311    let runnable = self.steal_one();
6312    let stopping = stop_publication.is_stopping();
6313    drop(stop_publication);
6314    Self::runnable_claim(stopping, runnable)
6315  }
6316
6317  fn claim_runnable(&self) -> RunnableClaim {
6318    let stop_publication = self.stop.runnable_claim_guard();
6319    let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6320    let stopping = stop_publication.is_stopping();
6321    drop(stop_publication);
6322    Self::runnable_claim(stopping, runnable)
6323  }
6324
6325  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6326    let admission = self.begin_deadlock_admission();
6327    let claim = self.claim_fifo_runnable();
6328    self.finish_runnable_claim(claim, admission)
6329  }
6330
6331  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
6332  fn run_one_runnable(self: &Arc<Self>) -> bool {
6333    let admission = self.begin_deadlock_admission();
6334    // Same owner-clear as the FIFO-only path: a runnable from the slot must
6335    // not smuggle an owner frame's over-cap privilege.
6336    let claim = self.claim_runnable();
6337    self.finish_runnable_claim(claim, admission)
6338  }
6339
6340  fn run_one_blocking(self: &Arc<Self>) -> bool {
6341    if let Some((blocking, admission)) = self.take_blocking() {
6342      let _generation = RuntimeGenerationGuard::enter(self.generation);
6343      {
6344        let _task = self.metrics.blocking_started(true);
6345        #[cfg(test)]
6346        run_multi_thread_work_started_test_hook();
6347        // Publish start before the dequeue admission retires. The detector can
6348        // then observe either the queued job, the active admission, or progress.
6349        drop(admission);
6350        let owner = self.fresh_owner_token();
6351        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6352        let _owner = BlockingOwnerGuard::enter(Some(owner));
6353        // The queued wrapper contains panics from the user function, but
6354        // delivering its result can still drop user-owned state when the
6355        // receiver is gone. Contain that destructor boundary as well so an
6356        // unwind cannot bypass blocking-slot and drainer retirement.
6357        let _ = catch_unwind_contained(blocking);
6358      }
6359      self.active_blocking.fetch_sub(1, Ordering::Release);
6360      return true;
6361    }
6362    false
6363  }
6364
6365  /// Run one fair scheduler unit. Runnable locality remains the normal
6366  /// priority, but a continuously hot runnable stream must yield one quantum
6367  /// to the blocking FIFO.
6368  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6369    self.run_one_fair_with_exact(runnable_streak, None)
6370  }
6371
6372  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
6373  /// been satisfied, a live exact dependency must claim its owner's lane before
6374  /// unrelated FIFO blocking work can consume the last ordinary slot.
6375  fn run_one_fair_with_exact(
6376    self: &Arc<Self>,
6377    runnable_streak: &mut usize,
6378    exact_dependency: Option<&TaskDependency>,
6379  ) -> bool {
6380    let mut exact_attempted = false;
6381    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6382      *runnable_streak = 0;
6383      if let Some(dependency) = exact_dependency {
6384        exact_attempted = true;
6385        if self.try_owned_blocking_over_cap(dependency) {
6386          return true;
6387        }
6388      }
6389      if self.run_one_blocking() {
6390        return true;
6391      }
6392    }
6393    if self.run_one_runnable() {
6394      *runnable_streak += 1;
6395      return true;
6396    }
6397    if !exact_attempted
6398      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6399    {
6400      *runnable_streak = 0;
6401      return true;
6402    }
6403    if self.run_one_blocking() {
6404      *runnable_streak = 0;
6405      return true;
6406    }
6407    false
6408  }
6409
6410  #[cfg(test)]
6411  fn run_one(self: &Arc<Self>) -> bool {
6412    let mut runnable_streak = 0;
6413    self.run_one_fair(&mut runnable_streak)
6414  }
6415
6416  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
6417  /// park while the blocking cap is saturated. The dependency carries its
6418  /// exact owner frame through JoinHandle lineage. Reserving that frame
6419  /// transfers its counted lane to this otherwise-idle cooperative driver.
6420  ///
6421  /// Owner lending is lexical: the cooperative driver must carry the exact
6422  /// blocking frame whose counted lane it reuses. Rayon does not propagate
6423  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
6424  /// prove that an untagged worker belongs to the sole active owner.
6425  fn run_owned_blocking_over_cap(
6426    self: &Arc<Self>,
6427    dependency: &TaskDependency,
6428  ) -> OwnedBlockingOutcome {
6429    #[cfg(test)]
6430    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6431
6432    let Some(ambient_owner) = BLOCKING_OWNER
6433      .with(std::cell::Cell::get)
6434      .filter(|owner| owner.executor_id == self.id)
6435    else {
6436      return OwnedBlockingOutcome::Ineligible;
6437    };
6438    let Some(current) = dependency.select_for_owner(ambient_owner) else {
6439      return OwnedBlockingOutcome::Ineligible;
6440    };
6441    let admission = self.begin_deadlock_admission();
6442    let owner = current
6443      .publication
6444      .dependency
6445      .owner
6446      .expect("selected dependency must carry its owner");
6447    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6448    else {
6449      return OwnedBlockingOutcome::OwnerBusy(owner);
6450    };
6451    #[cfg(test)]
6452    run_before_owned_blocking_queue_claim_test_hook();
6453    let job = {
6454      let mut queue = self
6455        .blocking_queue
6456        .lock()
6457        .unwrap_or_else(std::sync::PoisonError::into_inner);
6458      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
6459      // admission states before consuming the dependency so a shutdown that
6460      // reached this claim first leaves the job queued for cancellation.
6461      if queue.closed || self.stop.is_stopping() {
6462        None
6463      // Claim while holding the queue lock. A concurrent clear/switch that
6464      // wins first makes this fail; once the claim succeeds, no stale snapshot
6465      // can authorize removal of the old job.
6466      } else if dependency.try_claim(&current) {
6467        queue.remove(current.publication.dependency.job)
6468      } else {
6469        None
6470      }
6471    };
6472    let ran = if let Some(job) = job {
6473      #[cfg(test)]
6474      run_multi_thread_work_claimed_test_hook();
6475      let _generation = RuntimeGenerationGuard::enter(self.generation);
6476      {
6477        let _task = self.metrics.blocking_started(false);
6478        #[cfg(test)]
6479        run_multi_thread_work_started_test_hook();
6480        // The exact job is no longer visible in the indexed FIFO. Publish its
6481        // start before allowing a deadline verdict to close the admission gate.
6482        drop(admission);
6483        let nested_owner = self.fresh_owner_token();
6484        let _active_owner =
6485          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6486        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6487        let _ = catch_unwind_contained(job);
6488      }
6489      true
6490    } else {
6491      drop(admission);
6492      false
6493    };
6494    // Every reservation release can make another exact dependency serviceable,
6495    // including failed-claim and already-removed-job exits.
6496    self.release_owner_lane_and_wake(reservation);
6497    OwnedBlockingOutcome::ReservationReleased { owner, ran }
6498  }
6499
6500  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6501    matches!(
6502      self.run_owned_blocking_over_cap(dependency),
6503      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6504    )
6505  }
6506
6507  fn service_owner_handoff(
6508    self: &Arc<Self>,
6509    parker: &DriverParker,
6510    dependency: &TaskDependency,
6511    can_run_blocking: bool,
6512  ) -> Option<bool> {
6513    let handoff_owner = parker.take_owner_handoff()?;
6514    if !can_run_blocking {
6515      self.wake_one_owner_dependency(handoff_owner);
6516      return Some(false);
6517    }
6518    match self.run_owned_blocking_over_cap(dependency) {
6519      OwnedBlockingOutcome::Ineligible => {
6520        self.wake_one_owner_dependency(handoff_owner);
6521        Some(false)
6522      }
6523      OwnedBlockingOutcome::OwnerBusy(owner) => {
6524        if owner != handoff_owner {
6525          self.wake_one_owner_dependency(handoff_owner);
6526        }
6527        Some(false)
6528      }
6529      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6530        if owner != handoff_owner {
6531          self.wake_one_owner_dependency(handoff_owner);
6532        }
6533        Some(ran)
6534      }
6535    }
6536  }
6537
6538  fn forward_owner_handoff(&self, parker: &DriverParker) {
6539    if let Some(owner) = parker.take_owner_handoff() {
6540      self.wake_one_owner_dependency(owner);
6541    }
6542  }
6543
6544  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6545    let Some(ambient_owner) = BLOCKING_OWNER
6546      .with(std::cell::Cell::get)
6547      .filter(|owner| owner.executor_id == self.id)
6548    else {
6549      return false;
6550    };
6551    self.active_blocking_owners.is_available(ambient_owner)
6552      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6553  }
6554
6555  fn has_exact_owner_work_for_role(
6556    &self,
6557    parker: &DriverParker,
6558    dependency: &TaskDependency,
6559    can_run_blocking: bool,
6560  ) -> bool {
6561    parker.has_owner_handoff()
6562      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6563  }
6564
6565  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
6566  /// of parking the worker (which would freeze one of the pool's fixed OS
6567  /// threads), it drives the awaited future while servicing queued work. When
6568  /// nothing is immediately runnable it parks on its own [`DriverParker`],
6569  /// which is woken directly by the awaited future's waker and through the
6570  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
6571  /// wake reaches this worker even when every worker is parked and
6572  /// `ensure_drainer` has no free thread to spawn a replacement.
6573  fn cooperative_block_on(
6574    self: &Arc<Self>,
6575    mut future: Pin<&mut dyn Future<Output = ()>>,
6576  ) -> BlockOnOutcome {
6577    // One parker per `block_on` frame. Nested re-entrant frames each own one:
6578    // only the innermost frame runs or parks at any moment, and an outer
6579    // frame's future-wake is stored as its parker's permit, consumed when
6580    // control returns to that frame's loop.
6581    let parker = Arc::new(DriverParker::default());
6582    let _stop_registration = self.stop.register(&parker);
6583    let dependency = BlockOnDependencyGuard::enter(self.generation);
6584    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6585    let can_run_blocking =
6586      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6587    let _exit = CooperativeDriverExitGuard {
6588      executor: self,
6589      parker: &parker,
6590      can_run_blocking,
6591    };
6592    // WAKE-DOMAIN SPLIT: the awaited future is polled with
6593    // THIS driver's own parker as its waker, so a future-wake targets exactly
6594    // the driver that awaits it -- delivered as a stored permit (skip next
6595    // park) when this driver is currently running, so it is never lost.
6596    let waker = std::task::Waker::from(Arc::clone(&parker));
6597    let mut cx = Context::from_waker(&waker);
6598    // Consecutive `run_one` successes since the last yield point: the
6599    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
6600    // it, a hot slot chain (each runnable schedules a successor into this
6601    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
6602    // loop inside `run_one` forever and the shared FIFO would never be
6603    // reached from this thread -- a deadlock on a 1-worker pool when the
6604    // awaited future depends on a FIFO task.
6605    let mut runnable_streak = 0usize;
6606    let mut work_streak = 0usize;
6607    let mut force_fifo = false;
6608    loop {
6609      if self.stop.is_stopping() {
6610        return BlockOnOutcome::Stopped;
6611      }
6612      dependency.clear();
6613      on_thread_handoff();
6614      if future.as_mut().poll(&mut cx).is_ready() {
6615        return BlockOnOutcome::Completed;
6616      }
6617      if self.stop.is_stopping() {
6618        return BlockOnOutcome::Stopped;
6619      }
6620      if self
6621        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6622        .is_some_and(|ran| ran)
6623      {
6624        continue;
6625      }
6626      if force_fifo {
6627        force_fifo = false;
6628        if self.run_one_fifo_runnable() {
6629          runnable_streak += 1;
6630          work_streak += 1;
6631          continue;
6632        }
6633      }
6634      let ran_work = if can_run_blocking {
6635        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6636      } else if self.run_one_runnable() {
6637        runnable_streak += 1;
6638        true
6639      } else {
6640        false
6641      };
6642      if ran_work {
6643        work_streak += 1;
6644        if work_streak >= Self::RUNNABLE_BUDGET {
6645          // Budget yield: move a hot slot occupant to the shared FIFO (with
6646          // a wake), so the next `run_one` pops the FIFO front (the oldest
6647          // work) and other workers can steal the chain. The awaited future
6648          // is polled every iteration regardless, so it is never starved.
6649          self.flush_lifo_slot();
6650          work_streak = 0;
6651          force_fifo = true;
6652        }
6653        continue;
6654      }
6655      runnable_streak = 0;
6656      work_streak = 0;
6657      // MANDATORY pre-park flush. Defensive on every
6658      // currently reachable path: `run_one` above popped the slot and no user
6659      // code ran since -- but parking with a stranded slot runnable would
6660      // silently lose that task, so the invariant is upheld unconditionally.
6661      self.flush_lifo_slot();
6662      // Park protocol: register FIRST, then re-check, then park -- the
6663      // waiter-side mirror of the push-then-wake schedule side. See the
6664      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
6665      self.parked_drivers.register_with_role(
6666        &parker,
6667        can_run_blocking,
6668        Some(Arc::clone(&dependency.dependency)),
6669      );
6670      if self.has_queued_work_for_role(can_run_blocking)
6671        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6672      {
6673        self.parked_drivers.deregister(&parker);
6674        continue;
6675      }
6676      if self.stop.is_stopping() {
6677        return BlockOnOutcome::Stopped;
6678      }
6679      // TIMER-BOUNDED PARK: while any heap
6680      // timer is pending, this park is a TIMER WAIT, not a deadlock
6681      // candidate -- the wall clock is a guaranteed wake source -- so it uses
6682      // its own bounded primitive instead of `park`/`park_with_deadline` and
6683      // fires whatever came due on the way out. An armed park deadline is
6684      // deliberately NOT evaluated during a timer wait: the fire schedules
6685      // the woken task (fingerprint progress), and the next timerless park
6686      // arms a fresh full window, so genuine-deadlock detection is delayed
6687      // past the timer, never lost. This is also load-bearing for liveness:
6688      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
6689      // job has no free thread to run on, so this driver must fire due
6690      // timers itself. (A timer registered between the deadline read and the
6691      // park re-arms this parker through `note_earlier_timer`'s
6692      // `wake_one` -- we are registered -- or wakes the timekeeper.)
6693      if let Some(next) = self.next_timer_deadline() {
6694        let now = Instant::now();
6695        if next > now {
6696          parker.park_timeout(next - now);
6697        }
6698        self.parked_drivers.deregister(&parker);
6699        self.fire_due_timers();
6700        continue;
6701      }
6702      match self.park_deadline {
6703        None => parker.park(),
6704        Some(deadline) => {
6705          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
6706          // candidate deadlock only when a
6707          // FULL deadline window passes with zero executor progress. The
6708          // foreign-thread whole-build park in `block_on`'s else-branch is
6709          // exempt by design.
6710          let deadlock_publications = self.deadlock_state.publications();
6711          match park_with_deadline(&parker, deadline, &self.metrics) {
6712            DeadlineParkOutcome::Woken => {}
6713            DeadlineParkOutcome::Expired(armed) => {
6714              #[cfg(test)]
6715              run_deadline_expiry_test_hook();
6716              // EXPIRY DECISION. While this parker was
6717              // registered, a racing `wake_one` may have popped it, stored
6718              // its permit and reported the wake as DELIVERED (so the
6719              // scheduler spawned no drainer) -- panicking now would swallow
6720              // a healthy wake and kill a live build. Deregister FIRST so no
6721              // FURTHER wake can be counted against this parker, then
6722              // re-check every way a wake could have landed around the
6723              // expiry:
6724              //   * permit: a `wake_one` (or direct future-wake) that landed
6725              //     while still registered MUST be acted on;
6726              //   * queued work: the work behind such a wake was pushed
6727              //     under the queue mutex BEFORE `wake_one` popped us, so
6728              //     even if its `unpark` is still in flight the queue
6729              //     re-check observes it;
6730              //   * pending timers: arming a timer bumps NO progress counter
6731              //     by design, so a registration racing this already-
6732              //     committed park (it read an empty heap before parking) is
6733              //     invisible to every other re-check -- yet its wake (the
6734              //     fire) is wall-clock-guaranteed, so this park is healthy.
6735              //     The loop then re-parks TIMER-BOUNDED (see above) and
6736              //     fires the timer itself if needed;
6737              //   * fingerprint: the verdict requires ZERO progress, and the
6738              //     read inside `park_with_deadline` is stale by the time we
6739              //     get here -- re-read as the LAST step. Both ENQUEUE
6740              //     counters are in the fingerprint (`runnable_schedules`
6741              //     and `blocking_tasks_scheduled`, both bumped before the
6742              //     push), so a submission of EITHER kind
6743              //     landing after the queue re-check above is still caught
6744              //     here.
6745              // Any hit => not a deadlock: continue the cooperative loop
6746              // (fresh future poll, `run_one` picks up the work, and the
6747              // next park arms a fresh FULL deadline window). The residue is
6748              // exactly a BARE direct future-wake -- no queue push, no
6749              // counter movement -- landing in the few instructions after
6750              // these re-checks: the inherent, unclosable residue of any
6751              // timing-based detector, one reason the deadline is opt-in.
6752              self.parked_drivers.deregister(&parker);
6753              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6754                continue;
6755              }
6756              // Test-only seam between the permit/queued-work re-checks and
6757              // the fingerprint verdict.
6758              #[cfg(test)]
6759              run_deadline_verdict_test_hook();
6760              if self.metrics.progress_fingerprint() != armed {
6761                continue;
6762              }
6763              // Pending-timer re-check LAST, after even the fingerprint:
6764              // arming a timer bumps no counter, so it is the one healthy
6765              // wake source that can land after every earlier re-check and
6766              // still be invisible to the fingerprint read above. (A
6767              // registration racing the instructions after this line is the
6768              // same inherent residue class as the bare future-wake -- and
6769              // its timer is still served: `note_earlier_timer` claims the
6770              // timekeeper independently of this driver.)
6771              if self.next_timer_deadline().is_some() {
6772                continue;
6773              }
6774              // Test-only seam for a cross-thread scheduler admission after
6775              // every legacy final check and before the synchronized verdict.
6776              #[cfg(test)]
6777              run_deadline_final_verdict_test_hook();
6778              let Some(verdict) = self.try_begin_deadlock_verdict() else {
6779                continue;
6780              };
6781              #[cfg(test)]
6782              run_deadline_gated_verdict_test_hook();
6783              // Lock order is verdict gate -> stop publication -> exact-owner
6784              // publication. The controller takes lifecycle -> stop
6785              // publication and never acquires the verdict gate or exact-owner
6786              // publication while retaining either lock.
6787              let stop_publication = self.stop.publication_guard();
6788              #[cfg(test)]
6789              run_deadline_synchronized_stop_test_hook();
6790              if stop_publication.is_stopping() {
6791                return BlockOnOutcome::Stopped;
6792              }
6793              let owner_handoff_publication = self
6794                .deadlock_state
6795                .owner_handoff_publication_guard()
6796                .expect("a deadline verdict requires exact-owner publication");
6797              #[cfg(test)]
6798              run_deadline_exact_owner_verdict_test_hook(&parker);
6799              // The closed gate excludes later publications. Any admission
6800              // that began earlier either remains counted (so gate acquisition
6801              // failed above) or completed before this recheck and advanced
6802              // the publication epoch. The stop-publication guard also keeps
6803              // the lifecycle in Running if this verdict linearized first. The
6804              // exact-owner publication guard similarly orders reservation
6805              // release and targeted handoff with the final predicates below.
6806              if self.has_exact_owner_work_for_role(
6807                &parker,
6808                &dependency.dependency,
6809                can_run_blocking,
6810              ) || parker.consume_permit()
6811                || self.has_queued_work_for_role(can_run_blocking)
6812                || self.deadlock_state.publications() != deadlock_publications
6813                || self.metrics.progress_fingerprint() != armed
6814                || self.next_timer_deadline().is_some()
6815              {
6816                continue;
6817              }
6818              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6819              // Panic hooks run before unwind. Release every synchronization
6820              // guard first so a hook that submits work, releases an owner
6821              // lane, or starts shutdown cannot wait for the panic that invokes
6822              // it to unwind.
6823              drop(owner_handoff_publication);
6824              drop(stop_publication);
6825              drop(verdict);
6826              std::panic::panic_any(diagnostic);
6827            }
6828          }
6829        }
6830      }
6831      // `wake_one` removes the parkers it pops; a direct future-wake does
6832      // not. Deregister-if-present covers both.
6833      self.parked_drivers.deregister(&parker);
6834    }
6835  }
6836
6837  /// Is there work a cooperative driver could pick up right now? Used by the
6838  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
6839  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
6840  /// blocking job within the cap). The exact over-cap dependency is handled
6841  /// separately before registration.
6842  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6843    if self.has_queued_runnable() {
6844      return true;
6845    }
6846    can_run_blocking
6847      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6848      && !self
6849        .blocking_queue
6850        .lock()
6851        .unwrap_or_else(std::sync::PoisonError::into_inner)
6852        .is_empty()
6853  }
6854
6855  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6856    if self.has_queued_runnable() {
6857      self.wake_for_new_work();
6858    }
6859    // Shutdown-aborted runnables still need a driver so their generators can
6860    // retire, but queued blocking closures are cancelled by begin_shutdown and
6861    // must never be admitted by exit compensation after stop is observable.
6862    if self.stop.is_stopping() {
6863      return;
6864    }
6865    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6866      && !self
6867        .blocking_queue
6868        .lock()
6869        .unwrap_or_else(std::sync::PoisonError::into_inner)
6870        .is_empty()
6871    {
6872      // This driver may have absorbed the queue wake that made its awaited
6873      // future ready. Runnable and blocking residue are independent. If no
6874      // other blocking-capable driver is available, merely spawning a Rayon
6875      // job is insufficient: this worker may return into arbitrary
6876      // synchronous user code while every other Rayon lane is busy.
6877      // Consume one admitted job before exit, then hand any residue to another
6878      // blocking-capable driver.
6879      let consumed = can_run_blocking && self.run_one_blocking();
6880      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6881        && !self
6882          .blocking_queue
6883          .lock()
6884          .unwrap_or_else(std::sync::PoisonError::into_inner)
6885          .is_empty();
6886      if !self.stop.is_stopping() && (!consumed || has_residue) {
6887        self.wake_for_blocking_work();
6888      }
6889    }
6890  }
6891
6892  fn has_queued_runnable(&self) -> bool {
6893    // Waiter-side mirror of the `push_to_queue_and_wake` fence: a parking
6894    // driver (pre-park re-check) and the drainer-exit re-check in
6895    // `finish_draining` must not observe the injector empty when a racing push
6896    // is already visible to a wake. This SeqCst fence pairs with the post-push
6897    // fence so the Dekker ordering holds without the former queue mutex. Only
6898    // on park/quiesce decision paths, never the per-task hot path.
6899    std::sync::atomic::fence(Ordering::SeqCst);
6900    !self.injector.is_empty()
6901  }
6902
6903  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
6904  /// driver may run any job within `max_blocking`; only the over-cap escape is
6905  /// restricted to an exact dependency id.
6906  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6907    let admission = self.begin_deadlock_admission();
6908    loop {
6909      let active = self.active_blocking.load(Ordering::Acquire);
6910      if active >= self.max_blocking {
6911        return None;
6912      }
6913      let mut queue = self
6914        .blocking_queue
6915        .lock()
6916        .unwrap_or_else(std::sync::PoisonError::into_inner);
6917      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6918        return None;
6919      }
6920      if self
6921        .active_blocking
6922        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6923        .is_ok()
6924      {
6925        let blocking = queue
6926          .pop_front()
6927          .expect("a non-empty blocking queue must have a FIFO head");
6928        drop(queue);
6929        #[cfg(test)]
6930        run_multi_thread_work_claimed_test_hook();
6931        return Some((blocking, admission));
6932      }
6933    }
6934  }
6935}
6936
6937#[cfg(napi_runtime_os_threads)]
6938struct MultiThreadDeadlockState {
6939  enabled: bool,
6940  verdict_closed: AtomicBool,
6941  admissions: AtomicUsize,
6942  publications: AtomicU64,
6943  owner_handoff_publication: Mutex<()>,
6944}
6945
6946#[cfg(napi_runtime_os_threads)]
6947impl MultiThreadDeadlockState {
6948  fn new(enabled: bool) -> Self {
6949    Self {
6950      enabled,
6951      verdict_closed: AtomicBool::new(false),
6952      admissions: AtomicUsize::new(0),
6953      publications: AtomicU64::new(0),
6954      owner_handoff_publication: Mutex::new(()),
6955    }
6956  }
6957
6958  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6959    if !self.enabled {
6960      return None;
6961    }
6962    loop {
6963      while self.verdict_closed.load(Ordering::SeqCst) {
6964        #[cfg(test)]
6965        run_deadlock_closed_gate_admission_test_hook();
6966        std::hint::spin_loop();
6967      }
6968      self.admissions.fetch_add(1, Ordering::SeqCst);
6969      if !self.verdict_closed.load(Ordering::SeqCst) {
6970        return Some(MultiThreadDeadlockAdmissionGuard {
6971          state: Arc::clone(self),
6972          published: false,
6973        });
6974      }
6975      self.admissions.fetch_sub(1, Ordering::SeqCst);
6976    }
6977  }
6978
6979  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6980    debug_assert!(
6981      self.enabled,
6982      "a deadline verdict requires an enabled admission state"
6983    );
6984    if self
6985      .verdict_closed
6986      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6987      .is_err()
6988    {
6989      return None;
6990    }
6991    if self.admissions.load(Ordering::SeqCst) != 0 {
6992      self.verdict_closed.store(false, Ordering::SeqCst);
6993      return None;
6994    }
6995    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6996  }
6997
6998  fn publications(&self) -> u64 {
6999    self.publications.load(Ordering::SeqCst)
7000  }
7001
7002  fn owner_handoff_publication_guard(&self) -> Option<MutexGuard<'_, ()>> {
7003    self.enabled.then(|| {
7004      self
7005        .owner_handoff_publication
7006        .lock()
7007        .unwrap_or_else(std::sync::PoisonError::into_inner)
7008    })
7009  }
7010}
7011
7012#[cfg(napi_runtime_os_threads)]
7013struct MultiThreadDeadlockAdmissionGuard {
7014  state: Arc<MultiThreadDeadlockState>,
7015  published: bool,
7016}
7017
7018#[cfg(napi_runtime_os_threads)]
7019impl MultiThreadDeadlockAdmissionGuard {
7020  fn mark_published(&mut self) {
7021    self.published = true;
7022  }
7023}
7024
7025#[cfg(napi_runtime_os_threads)]
7026impl Drop for MultiThreadDeadlockAdmissionGuard {
7027  fn drop(&mut self) {
7028    if self.published {
7029      self.state.publications.fetch_add(1, Ordering::SeqCst);
7030    }
7031    // Publication and every scheduler-state write above must precede the final
7032    // admission decrement observed by a closed verdict gate.
7033    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
7034  }
7035}
7036
7037#[cfg(napi_runtime_os_threads)]
7038struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
7039
7040#[cfg(napi_runtime_os_threads)]
7041impl Drop for MultiThreadDeadlockVerdictGuard {
7042  fn drop(&mut self) {
7043    self.0.verdict_closed.store(false, Ordering::SeqCst);
7044  }
7045}
7046
7047/// Cooperative-driver exit backstop. A user-future panic can unwind from any
7048/// poll, including after this driver absorbed a queue wake, received an exact
7049/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
7050/// independently so one cleanup failure cannot suppress the others or
7051/// double-panic the process.
7052#[cfg(napi_runtime_os_threads)]
7053struct CooperativeDriverExitGuard<'a> {
7054  executor: &'a Arc<MultiThreadExecutor>,
7055  parker: &'a Arc<DriverParker>,
7056  can_run_blocking: bool,
7057}
7058
7059#[cfg(napi_runtime_os_threads)]
7060impl Drop for CooperativeDriverExitGuard<'_> {
7061  fn drop(&mut self) {
7062    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
7063    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
7064    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
7065    let _ = catch_unwind_contained(|| {
7066      self
7067        .executor
7068        .compensate_queued_work_wake(self.can_run_blocking);
7069    });
7070  }
7071}
7072
7073/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
7074/// unwinding drain can never strand a runnable in the
7075/// thread-local slot of a thread that is about to return to rayon. Normal
7076/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
7077/// no-op (the slot is already empty).
7078#[cfg(napi_runtime_os_threads)]
7079struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
7080
7081#[cfg(napi_runtime_os_threads)]
7082impl Drop for LifoSlotFlushGuard<'_> {
7083  fn drop(&mut self) {
7084    self.0.flush_lifo_slot();
7085  }
7086}
7087
7088/// Per-drain-frame idle-linger state, created lazily on the frame's first
7089/// idle episode (see the linger block in `MultiThreadExecutor::drain`): the
7090/// parker queue wakes are delivered to while this drainer lingers, plus its
7091/// generation-stop registration so `begin_shutdown` cuts a linger short
7092/// instead of `wait_until_scheduler_idle` waiting out the idle budget. The
7093/// registration guard deregisters on drop when the drain frame exits.
7094#[cfg(napi_runtime_os_threads)]
7095struct DrainLinger {
7096  parker: Arc<DriverParker>,
7097  _stop_registration: GenerationStopGuard,
7098}
7099
7100#[cfg(napi_runtime_os_threads)]
7101impl DrainLinger {
7102  fn new(executor: &MultiThreadExecutor) -> Self {
7103    let parker = Arc::new(DriverParker::default());
7104    let stop_registration = executor.stop.register(&parker);
7105    Self {
7106      parker,
7107      _stop_registration: stop_registration,
7108    }
7109  }
7110}
7111
7112// ---------------------------------------------------------------------------
7113// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
7114//
7115// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
7116//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
7117//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
7118//     driver that awaits it. The permit bit makes a wake delivered while that
7119//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
7120//     next park instead of losing the wake.
7121//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
7122//     serve queue work, so these go through the `ParkedDrivers` registry and
7123//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
7124//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
7125//     exit (miss compensation).
7126// Foreign-thread (napi caller) parks in `block_on` use
7127// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
7128// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
7129// frame, no registry) so it too OWNS its park site for deadlock detection
7130// while `ParkedDrivers` remains MultiThread-only.
7131
7132/// No permit; the driver is running (or consumed its last permit).
7133const PARKER_EMPTY: usize = 0;
7134/// A wake permit is stored; the next `park` consumes it without sleeping.
7135const PARKER_NOTIFIED: usize = 1;
7136/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
7137const PARKER_SLEEPING: usize = 2;
7138
7139/// One driver's private parker: a saturating one-permit token plus a condvar
7140/// to sleep on when no permit is stored. Also the `Waker` for the future that
7141/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
7142#[derive(Default)]
7143struct DriverParker {
7144  state: AtomicUsize,
7145  lock: Mutex<()>,
7146  condvar: UnboundedCondvar,
7147  #[cfg(napi_runtime_os_threads)]
7148  owner_handoff_pending: AtomicBool,
7149  #[cfg(napi_runtime_os_threads)]
7150  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7151}
7152
7153impl DriverParker {
7154  #[cfg(napi_runtime_os_threads)]
7155  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7156    let mut pending = self
7157      .owner_handoff
7158      .lock()
7159      .unwrap_or_else(std::sync::PoisonError::into_inner);
7160    debug_assert!(pending.is_none());
7161    *pending = Some(owner);
7162    self.owner_handoff_pending.store(true, Ordering::Release);
7163  }
7164
7165  #[cfg(napi_runtime_os_threads)]
7166  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7167    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7168      return None;
7169    }
7170    self
7171      .owner_handoff
7172      .lock()
7173      .unwrap_or_else(std::sync::PoisonError::into_inner)
7174      .take()
7175  }
7176
7177  #[cfg(napi_runtime_os_threads)]
7178  fn has_owner_handoff(&self) -> bool {
7179    self.owner_handoff_pending.load(Ordering::Acquire)
7180  }
7181
7182  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
7183  /// permit is stored and the driver's next [`Self::park`] returns
7184  /// immediately -- a wake delivered while the driver is running is never
7185  /// lost. Multiple grants coalesce into one permit, which is safe because a
7186  /// woken driver re-checks every work source in its loop before it can park
7187  /// again.
7188  fn unpark(&self) {
7189    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7190      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
7191      // releases the lock only inside `condvar.wait`; the empty critical
7192      // section here fences the notify so it cannot fire in the window
7193      // between the sleeper's state store and its wait.
7194      drop(
7195        self
7196          .lock
7197          .lock()
7198          .unwrap_or_else(std::sync::PoisonError::into_inner),
7199      );
7200      self.condvar.notify_one();
7201    }
7202  }
7203
7204  /// Consume a stored wake permit if one is present, without ever sleeping.
7205  /// This is the park DECISION's token check: a `true` return means a wake is
7206  /// already pending, so the caller must re-poll instead of parking (or
7207  /// panicking).
7208  fn consume_permit(&self) -> bool {
7209    self
7210      .state
7211      .compare_exchange(
7212        PARKER_NOTIFIED,
7213        PARKER_EMPTY,
7214        Ordering::SeqCst,
7215        Ordering::SeqCst,
7216      )
7217      .is_ok()
7218  }
7219
7220  /// Block until the permit is granted, consuming it. Returns immediately if
7221  /// a permit is already stored. Only the owning driver calls `park`, so at
7222  /// most one thread ever sleeps here.
7223  fn park(&self) {
7224    // Fast path: consume a stored permit without touching the lock.
7225    if self.consume_permit() {
7226      return;
7227    }
7228    let mut guard = self
7229      .lock
7230      .lock()
7231      .unwrap_or_else(std::sync::PoisonError::into_inner);
7232    match self.state.compare_exchange(
7233      PARKER_EMPTY,
7234      PARKER_SLEEPING,
7235      Ordering::SeqCst,
7236      Ordering::SeqCst,
7237    ) {
7238      Ok(_) => {}
7239      // The permit arrived between the fast path and here: consume it. (Only
7240      // this thread stores PARKER_SLEEPING, so the observed value can only be
7241      // PARKER_NOTIFIED.)
7242      Err(_) => {
7243        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7244        return;
7245      }
7246    }
7247    loop {
7248      guard = self
7249        .condvar
7250        .wait(guard)
7251        .unwrap_or_else(std::sync::PoisonError::into_inner);
7252      if self
7253        .state
7254        .compare_exchange(
7255          PARKER_NOTIFIED,
7256          PARKER_EMPTY,
7257          Ordering::SeqCst,
7258          Ordering::SeqCst,
7259        )
7260        .is_ok()
7261      {
7262        return;
7263      }
7264      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
7265    }
7266  }
7267
7268  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
7269  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
7270  /// A `false` return has retracted the SLEEPING claim and left the parker
7271  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
7272  /// detection.
7273  fn park_timeout(&self, timeout: Duration) -> bool {
7274    if self.consume_permit() {
7275      return true;
7276    }
7277    let mut guard = self
7278      .lock
7279      .lock()
7280      .unwrap_or_else(std::sync::PoisonError::into_inner);
7281    match self.state.compare_exchange(
7282      PARKER_EMPTY,
7283      PARKER_SLEEPING,
7284      Ordering::SeqCst,
7285      Ordering::SeqCst,
7286    ) {
7287      Ok(_) => {}
7288      // The permit arrived between the fast path and here: consume it. (Only
7289      // this thread stores PARKER_SLEEPING, so the observed value can only be
7290      // PARKER_NOTIFIED.)
7291      Err(_) => {
7292        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7293        return true;
7294      }
7295    }
7296    let Some(deadline) = Instant::now().checked_add(timeout) else {
7297      // An unrepresentable deadline is farther away than this platform can
7298      // observe. Treat it as an unbounded wait instead of panicking on a
7299      // user-provided deadlock-detection duration.
7300      loop {
7301        guard = self
7302          .condvar
7303          .wait(guard)
7304          .unwrap_or_else(std::sync::PoisonError::into_inner);
7305        if self
7306          .state
7307          .compare_exchange(
7308            PARKER_NOTIFIED,
7309            PARKER_EMPTY,
7310            Ordering::SeqCst,
7311            Ordering::SeqCst,
7312          )
7313          .is_ok()
7314        {
7315          return true;
7316        }
7317      }
7318    };
7319    loop {
7320      if self
7321        .state
7322        .compare_exchange(
7323          PARKER_NOTIFIED,
7324          PARKER_EMPTY,
7325          Ordering::SeqCst,
7326          Ordering::SeqCst,
7327        )
7328        .is_ok()
7329      {
7330        return true;
7331      }
7332      let now = Instant::now();
7333      if now >= deadline {
7334        // Timed out: retract the SLEEPING claim -- unless a permit landed in
7335        // the meantime (an `unpark` that already saw SLEEPING may be waiting
7336        // on `lock` to notify; consuming its permit here IS the wake it
7337        // intended to deliver, so report "woken", not "timed out").
7338        return match self.state.compare_exchange(
7339          PARKER_SLEEPING,
7340          PARKER_EMPTY,
7341          Ordering::SeqCst,
7342          Ordering::SeqCst,
7343        ) {
7344          Ok(_) => false,
7345          Err(_) => {
7346            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7347            true
7348          }
7349        };
7350      }
7351      let (returned_guard, _timed_out) = self
7352        .condvar
7353        .wait_timeout(guard, deadline - now)
7354        .unwrap_or_else(std::sync::PoisonError::into_inner);
7355      guard = returned_guard;
7356      // Loop re-checks the permit first, then the deadline (spurious wakeups
7357      // simply wait out the remaining time).
7358    }
7359  }
7360}
7361
7362impl std::task::Wake for DriverParker {
7363  fn wake(self: Arc<Self>) {
7364    self.unpark();
7365  }
7366
7367  fn wake_by_ref(self: &Arc<Self>) {
7368    self.unpark();
7369  }
7370}
7371
7372/// Outcome of [`park_with_deadline`].
7373enum DeadlineParkOutcome {
7374  /// Woken by a permit (consumed); the park was healthy.
7375  Woken,
7376  /// A full deadline window elapsed with zero observed progress. Carries the
7377  /// fingerprint captured when that window was ARMED so the caller can make
7378  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
7379  /// verdict: a wake can land in the instructions after the window closes,
7380  /// and for MultiThread a `wake_one` can still pop the
7381  /// still-registered parker and count its wake as DELIVERED. Callers must
7382  /// deregister (MT), then re-check permit / queued work / fingerprint before
7383  /// panicking.
7384  Expired(ProgressFingerprint),
7385}
7386
7387/// Deadline-bounded park with PROGRESS-BASED reset.
7388/// Every time `deadline` elapses without a wake, the runtime's progress
7389/// fingerprint is compared against the value captured when the deadline was
7390/// ARMED: any advance re-arms the deadline instead of firing, because a
7391/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
7392/// proceeds -- must never be declared dead. `Expired` is a *candidate*
7393/// deadlock only; the panic decision lives with the caller (see
7394/// [`DeadlineParkOutcome`]).
7395fn park_with_deadline(
7396  parker: &DriverParker,
7397  deadline: Duration,
7398  metrics: &RuntimeMetrics,
7399) -> DeadlineParkOutcome {
7400  let mut armed = metrics.progress_fingerprint();
7401  loop {
7402    if parker.park_timeout(deadline) {
7403      return DeadlineParkOutcome::Woken;
7404    }
7405    let current = metrics.progress_fingerprint();
7406    if current != armed {
7407      armed = current;
7408      continue;
7409    }
7410    return DeadlineParkOutcome::Expired(armed);
7411  }
7412}
7413
7414// Test-only injection seam for the deadline-expiry race: fired on the driver
7415// thread immediately after
7416// `park_with_deadline` reports `Expired`, while the parker is STILL
7417// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
7418// exact window in which a racing `wake_one` can pop the parker, store its
7419// permit and report the wake as delivered. Lets a test interleave that race
7420// deterministically instead of wall-clock lottery.
7421#[cfg(all(test, napi_runtime_os_threads))]
7422thread_local! {
7423  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7424    const { std::cell::RefCell::new(None) };
7425}
7426
7427#[cfg(all(test, napi_runtime_os_threads))]
7428fn run_deadline_expiry_test_hook() {
7429  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7430    hook();
7431  }
7432}
7433
7434// Second test-only injection seam for the enqueue/verdict race: fired AFTER
7435// the expiry decision's permit and queued-work re-checks both came up
7436// empty (parker already deregistered) and BEFORE the final fingerprint
7437// verdict -- the window in which an enqueue is invisible to every earlier
7438// re-check and only its ENQUEUE counter in the fingerprint can prevent a
7439// false BlockOnDeadlock.
7440#[cfg(all(test, napi_runtime_os_threads))]
7441thread_local! {
7442  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7443    const { std::cell::RefCell::new(None) };
7444}
7445
7446#[cfg(all(test, napi_runtime_os_threads))]
7447fn run_deadline_verdict_test_hook() {
7448  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7449    hook();
7450  }
7451}
7452
7453// Final test-only injection seam: fired after every permit/queue/fingerprint/
7454// timer check from the legacy verdict and immediately before the synchronized
7455// admission gate closes. A separate OS thread can complete a publication here
7456// to prove the final verdict recheck has a real cross-thread ordering edge.
7457#[cfg(all(test, napi_runtime_os_threads))]
7458thread_local! {
7459  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7460    const { std::cell::RefCell::new(None) };
7461}
7462
7463#[cfg(all(test, napi_runtime_os_threads))]
7464fn run_deadline_final_verdict_test_hook() {
7465  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7466    hook();
7467  }
7468}
7469
7470// Fired after the synchronized verdict gate has closed and before the verdict
7471// takes the stop-publication mutex for its final rechecks. A test can pause
7472// here while shutdown publishes generation stop and its abort wake reaches the
7473// closed admission gate.
7474#[cfg(all(test, napi_runtime_os_threads))]
7475thread_local! {
7476  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7477    const { std::cell::RefCell::new(None) };
7478}
7479
7480#[cfg(all(test, napi_runtime_os_threads))]
7481fn run_deadline_gated_verdict_test_hook() {
7482  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7483    hook();
7484  }
7485}
7486
7487// Fired after the final verdict has acquired the stop-publication mutex. A
7488// shutdown test can retain that guard while another thread reaches the
7489// lifecycle -> stop-publication lock edge.
7490#[cfg(all(test, napi_runtime_os_threads))]
7491thread_local! {
7492  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7493    const { std::cell::RefCell::new(None) };
7494}
7495
7496#[cfg(all(test, napi_runtime_os_threads))]
7497fn run_deadline_synchronized_stop_test_hook() {
7498  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7499    hook();
7500  }
7501}
7502
7503// Fired after the final verdict owns exact-owner publication and before it
7504// checks handoff/lane predicates. Tests can remove unrelated saving predicates
7505// while retaining the synchronized owner state under examination.
7506#[cfg(all(test, napi_runtime_os_threads))]
7507type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7508
7509#[cfg(all(test, napi_runtime_os_threads))]
7510thread_local! {
7511  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7512    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7513      const { std::cell::RefCell::new(None) };
7514}
7515
7516#[cfg(all(test, napi_runtime_os_threads))]
7517fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7518  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7519    hook(parker);
7520  }
7521}
7522
7523// Fired immediately before a test thread takes the stop-publication mutex.
7524// Installed only on the shutdown thread, this proves the controller still
7525// holds the lifecycle in Running while waiting for a verdict-owned guard.
7526#[cfg(all(test, napi_runtime_os_threads))]
7527thread_local! {
7528  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7529    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7530      const { std::cell::RefCell::new(None) };
7531}
7532
7533#[cfg(all(test, napi_runtime_os_threads))]
7534fn run_before_generation_stop_publication_lock_test_hook() {
7535  if let Some(hook) =
7536    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7537  {
7538    hook();
7539  }
7540}
7541
7542// Fired on an admission thread after it observes a closed verdict gate. The
7543// hook is thread-local so a shutdown test can prove that an abort wake reached
7544// this exact wait without affecting unrelated scheduler threads.
7545#[cfg(all(test, napi_runtime_os_threads))]
7546thread_local! {
7547  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7548    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7549      const { std::cell::RefCell::new(None) };
7550}
7551
7552#[cfg(all(test, napi_runtime_os_threads))]
7553fn run_deadlock_closed_gate_admission_test_hook() {
7554  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7555  {
7556    hook();
7557  }
7558}
7559
7560// Timer registration enters the shared verdict admission before this seam.
7561// Tests can pause there to prove the timer remains an announced publication
7562// until its heap entry becomes visible.
7563#[cfg(all(test, napi_runtime_os_threads))]
7564thread_local! {
7565  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7566    const { std::cell::RefCell::new(None) };
7567}
7568
7569#[cfg(all(test, napi_runtime_os_threads))]
7570fn run_timer_registration_admission_test_hook() {
7571  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7572    hook();
7573  }
7574}
7575
7576#[cfg(all(test, napi_runtime_os_threads))]
7577thread_local! {
7578  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7579    const { std::cell::RefCell::new(None) };
7580}
7581
7582#[cfg(all(test, napi_runtime_os_threads))]
7583fn run_owner_handoff_selected_test_hook() {
7584  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7585    hook();
7586  }
7587}
7588
7589/// Registry of parked (or committed-to-parking) cooperative drivers, used by
7590/// queue-wakes to wake exactly one of them.
7591#[cfg(napi_runtime_os_threads)]
7592#[derive(Default)]
7593struct ParkedDrivers {
7594  parked: Mutex<Vec<ParkedDriver>>,
7595  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
7596  // by `wake_one`'s no-waiter fast path.
7597  count: AtomicUsize,
7598}
7599
7600#[cfg(napi_runtime_os_threads)]
7601struct ParkedDriver {
7602  parker: Arc<DriverParker>,
7603  can_run_blocking: bool,
7604  dependency: Option<Arc<TaskDependency>>,
7605}
7606
7607#[cfg(napi_runtime_os_threads)]
7608impl ParkedDrivers {
7609  /// Register `parker` as parked-or-parking. MUST precede the caller's final
7610  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
7611  #[cfg(test)]
7612  fn register(&self, parker: &Arc<DriverParker>) {
7613    self.register_with_role(parker, true, None);
7614  }
7615
7616  fn register_with_role(
7617    &self,
7618    parker: &Arc<DriverParker>,
7619    can_run_blocking: bool,
7620    dependency: Option<Arc<TaskDependency>>,
7621  ) {
7622    let mut parked = self
7623      .parked
7624      .lock()
7625      .unwrap_or_else(std::sync::PoisonError::into_inner);
7626    parked.push(ParkedDriver {
7627      parker: Arc::clone(parker),
7628      can_run_blocking,
7629      dependency,
7630    });
7631    self.count.store(parked.len(), Ordering::SeqCst);
7632  }
7633
7634  /// Remove `parker` if still registered. `wake_one` removes the parkers it
7635  /// pops, while a direct future-wake does not, so drivers call this after
7636  /// every park (and after a re-check aborts one).
7637  fn deregister(&self, parker: &Arc<DriverParker>) {
7638    let removed = {
7639      let mut parked = self
7640        .parked
7641        .lock()
7642        .unwrap_or_else(std::sync::PoisonError::into_inner);
7643      let removed = parked
7644        .iter()
7645        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7646        .map(|index| parked.swap_remove(index));
7647      self.count.store(parked.len(), Ordering::SeqCst);
7648      removed
7649    };
7650    drop(removed);
7651  }
7652
7653  /// Wake one parked driver -- most recently parked first, its stack and
7654  /// caches are the warmest -- returning whether anyone was woken.
7655  ///
7656  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
7657  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
7658  /// registers itself (the `count` store above, under the registry mutex),
7659  /// THEN re-checks the work queues under Q, and only then parks. Order the
7660  /// two Q critical sections:
7661  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
7662  ///     it, the re-check observes the work and the driver does not park --
7663  ///     skipping the wake (fast path below) is safe.
7664  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
7665  ///     push locked it. The driver's `count` store is sequenced before its
7666  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
7667  ///     lock of Q, and the push is sequenced before the fast-path load
7668  ///     below; the load therefore happens-after the `count` store and must
7669  ///     observe `count >= 1`: the slow path pops a registered parker and
7670  ///     wakes it.
7671  ///
7672  /// A wake delivered to a driver that raced out of its park (or aborted it)
7673  /// is stored as that driver's permit; the driver re-checks every work
7674  /// source before it can sleep on the next park, so the wake still lands.
7675  fn wake_one(&self) -> bool {
7676    if self.count.load(Ordering::SeqCst) == 0 {
7677      // No-waiter fast path: nobody is parked or committed to parking (any
7678      // in-flight parker that missed this check re-checks the queues after
7679      // registering, per the argument above).
7680      return false;
7681    }
7682    let driver = {
7683      let mut parked = self
7684        .parked
7685        .lock()
7686        .unwrap_or_else(std::sync::PoisonError::into_inner);
7687      let driver = parked.pop();
7688      self.count.store(parked.len(), Ordering::SeqCst);
7689      driver
7690    };
7691    match driver {
7692      Some(driver) => {
7693        driver.parker.unpark();
7694        true
7695      }
7696      None => false,
7697    }
7698  }
7699
7700  /// Wake the most recently parked cooperative driver that is eligible to
7701  /// consume blocking work.
7702  fn wake_one_blocking(&self) -> bool {
7703    if self.count.load(Ordering::SeqCst) == 0 {
7704      return false;
7705    }
7706    let driver = {
7707      let mut parked = self
7708        .parked
7709        .lock()
7710        .unwrap_or_else(std::sync::PoisonError::into_inner);
7711      let driver = parked
7712        .iter()
7713        .rposition(|driver| driver.can_run_blocking)
7714        .map(|index| parked.swap_remove(index));
7715      self.count.store(parked.len(), Ordering::SeqCst);
7716      driver
7717    };
7718    match driver {
7719      Some(driver) => {
7720        driver.parker.unpark();
7721        true
7722      }
7723      None => false,
7724    }
7725  }
7726
7727  /// Wake one parked driver whose live dependency belongs to `owner`.
7728  ///
7729  /// The dependency is published with the parker, so an unrelated newer
7730  /// driver cannot consume the bounded owner-lane handoff.
7731  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7732    loop {
7733      if self.count.load(Ordering::SeqCst) == 0 {
7734        return false;
7735      }
7736      let candidates = {
7737        let parked = self
7738          .parked
7739          .lock()
7740          .unwrap_or_else(std::sync::PoisonError::into_inner);
7741        parked
7742          .iter()
7743          .rev()
7744          .filter_map(|driver| {
7745            if !driver.can_run_blocking {
7746              return None;
7747            }
7748            driver
7749              .dependency
7750              .as_ref()
7751              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7752          })
7753          .collect::<Vec<_>>()
7754      };
7755      let selected = candidates.into_iter().find(|(_, dependency)| {
7756        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7757      });
7758      let Some((selected_parker, selected_dependency)) = selected else {
7759        return false;
7760      };
7761      let driver = {
7762        let mut parked = self
7763          .parked
7764          .lock()
7765          .unwrap_or_else(std::sync::PoisonError::into_inner);
7766        let driver = parked
7767          .iter()
7768          .rposition(|driver| {
7769            Arc::ptr_eq(&driver.parker, &selected_parker)
7770              && driver
7771                .dependency
7772                .as_ref()
7773                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7774          })
7775          .map(|index| parked.swap_remove(index));
7776        // Publish the exact-owner token before releasing the registry lock. A
7777        // direct future wake can race this selection, but the driver must
7778        // deregister under this same lock before it can poll Ready and forward
7779        // an unconsumed handoff on exit.
7780        if let Some(driver) = &driver {
7781          driver.parker.grant_owner_handoff(owner);
7782        }
7783        self.count.store(parked.len(), Ordering::SeqCst);
7784        driver
7785      };
7786      if let Some(driver) = driver {
7787        #[cfg(test)]
7788        run_owner_handoff_selected_test_hook();
7789        driver.parker.unpark();
7790        return true;
7791      }
7792    }
7793  }
7794
7795  /// Wake every timer-bounded driver so cancellation of the earliest timer
7796  /// cannot leave pool workers parked until a stale deadline.
7797  fn wake_all(&self) {
7798    if self.count.load(Ordering::SeqCst) == 0 {
7799      return;
7800    }
7801    let drivers = {
7802      let mut parked = self
7803        .parked
7804        .lock()
7805        .unwrap_or_else(std::sync::PoisonError::into_inner);
7806      let drivers = parked.drain(..).collect::<Vec<_>>();
7807      self.count.store(0, Ordering::SeqCst);
7808      drivers
7809    };
7810    for driver in drivers {
7811      driver.parker.unpark();
7812    }
7813  }
7814}
7815
7816// ---------------------------------------------------------------------------
7817// Runtime timer facility: `sleep_until` without a tokio
7818// reactor. Two drivers, selected by runtime flavor:
7819//
7820//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
7821//     timekeeper thread. Timer waits never consume the configured CPU workers.
7822//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
7823//     deadline, so the parked driver can fire due timers without depending on
7824//     any particular inter-thread wake ordering.
7825//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
7826//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
7827//     driver at import; wasi-single uses the same path). A CT runtime without
7828//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
7829//
7830// DEADLOCK-DETECTION INTERACTIONS:
7831//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
7832//     clock is a guaranteed wake source. Timer waits therefore use their own
7833//     primitive (a plain `park_timeout` bounded by the earliest deadline),
7834//     NEVER `park_with_deadline`, so an armed park deadline
7835//     shorter than the next timer cannot fire on a legitimate timer wait; the
7836//     fire itself schedules the woken task, which IS fingerprint progress for
7837//     every other parked driver. The expiry VERDICT additionally re-checks
7838//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
7839//     counter by design, so a registration racing an already-committed
7840//     deadline park must veto the panic there.
7841//   * CT certain check (threadless): a pending HOST timer is NOT a wake
7842//     token -- the JS setTimeout relay can only run on the very thread that
7843//     is parking, so it can never fire while `block_on` holds it. The check
7844//     panics unconditionally on a threadless build; a pending timer merely
7845//     selects the timer-backed variant of the typed diagnostic. The CT
7846//     deadline verdict (threaded flavor, where the host loop genuinely runs
7847//     concurrently) counts only LIVE waits (deadline still in the future): a
7848//     pending timer whose deadline passed without firing is the
7849//     frozen-JS-event-loop signature that the deadline-based detection
7850//     exists to catch, so it must NOT suppress the panic.
7851
7852/// Host-turn dispatcher for the CurrentThread runnable queue.
7853///
7854/// The NAPI binding registers one weak native threadsafe-function driver per
7855/// importing environment. Its custom Node-API callback owns the fresh host turn
7856/// and keeps delivery capabilities out of JavaScript. Scheduling through a
7857/// fresh host turn is required for soundness: arbitrary futures may invoke
7858/// their waker while holding internal locks, so polling the runnable inline
7859/// from that wake can self-deadlock.
7860pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7861  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
7862  /// [`CurrentThreadTaskDelivery::capability`].
7863  ///
7864  /// The delivery capability is scoped to this exact host registration and
7865  /// attempt. The registry maps it back to the executor's one internal
7866  /// dispatch capability only while this attempt remains current.
7867  /// `false` means this driver can no longer dispatch and must be swept. After
7868  /// returning `true`, the driver must eventually call either
7869  /// [`acknowledge_current_thread_task_delivery`] or
7870  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
7871  /// acknowledgement is accepted only after
7872  /// [`drive_current_thread_tasks`] claimed this exact delivery.
7873  ///
7874  /// `dispatch` must return promptly and must not wait on another thread. In
7875  /// particular it must never block on the thread that will acknowledge or
7876  /// fail this delivery (for instance by spawning a helper that calls
7877  /// [`fail_current_thread_task_delivery`] and joining it). The executor
7878  /// calls `dispatch` from `schedule`, which may be a waker's stack: a waker
7879  /// can be invoked while its producer holds a mutex (`futures::Shared` wakes
7880  /// its awaiters under its `wakers` lock). When a delivery failure turns
7881  /// terminal, the rejected work is cancelled on the stack of the thread that
7882  /// failed it, and a rejected future's destructor may re-lock the very mutex
7883  /// the blocked waker still holds. A `dispatch` that waits on that thread
7884  /// therefore deadlocks with it.
7885  ///
7886  /// Failing the delivery synchronously from inside `dispatch`, on the
7887  /// calling thread, is handled: the executor recognises that stack as a
7888  /// possible waker's, moves the rejected work aside and cancels it on the
7889  /// next clean stack (see `CurrentThreadQueue::rejected`). Failing or
7890  /// acknowledging it later, from any thread, is handled too. The Node-API
7891  /// driver satisfies this contract: it makes one nonblocking
7892  /// threadsafe-function call and returns `false` at once when the
7893  /// environment is closing.
7894  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7895
7896  fn is_live(&self) -> bool {
7897    true
7898  }
7899
7900  /// Called after this driver has been removed from the registry, with no
7901  /// registry lock held. Must be idempotent with explicit host cleanup.
7902  fn on_swept(&self) {}
7903}
7904
7905#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7906pub struct CurrentThreadTaskDriverId(u64);
7907
7908type CurrentThreadTaskDriverSnapshot =
7909  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7910
7911#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7912pub struct CurrentThreadTaskDelivery {
7913  driver: CurrentThreadTaskDriverId,
7914  capability: u64,
7915  dispatch: u64,
7916}
7917
7918impl CurrentThreadTaskDelivery {
7919  /// Opaque driver capability for this exact registration attempt.
7920  pub fn capability(self) -> u64 {
7921    self.capability
7922  }
7923}
7924
7925struct CurrentThreadTaskDeliveryState {
7926  delivery: CurrentThreadTaskDelivery,
7927  claimed: bool,
7928  accepted: bool,
7929}
7930
7931struct CurrentThreadTaskDriverEntry {
7932  driver: Arc<dyn CurrentThreadTaskDriver>,
7933  delivery: Option<CurrentThreadTaskDeliveryState>,
7934  pending: Option<u64>,
7935  previous: Option<CurrentThreadTaskDriverId>,
7936  next: Option<CurrentThreadTaskDriverId>,
7937}
7938
7939#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7940enum CurrentThreadDispatchPhase {
7941  #[default]
7942  Pending,
7943  Serviced,
7944  Cancelling,
7945}
7946
7947#[derive(Default)]
7948struct CurrentThreadDispatchState {
7949  phase: CurrentThreadDispatchPhase,
7950  epoch: u64,
7951  publications: usize,
7952  references: usize,
7953  accepted: bool,
7954  accepted_failure: bool,
7955}
7956
7957impl CurrentThreadDispatchState {
7958  fn is_serviced(&self) -> bool {
7959    self.phase == CurrentThreadDispatchPhase::Serviced
7960  }
7961
7962  fn is_cancelling(&self) -> bool {
7963    self.phase == CurrentThreadDispatchPhase::Cancelling
7964  }
7965
7966  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7967    CurrentThreadTaskDispatchFailure {
7968      dispatch,
7969      epoch: self.epoch,
7970    }
7971  }
7972}
7973
7974#[derive(Default)]
7975struct CurrentThreadTaskDriverRegistryState {
7976  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7977  oldest: Option<CurrentThreadTaskDriverId>,
7978  newest: Option<CurrentThreadTaskDriverId>,
7979  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7980  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7981}
7982
7983impl CurrentThreadTaskDriverRegistryState {
7984  fn insert_driver(
7985    &mut self,
7986    id: CurrentThreadTaskDriverId,
7987    driver: Arc<dyn CurrentThreadTaskDriver>,
7988  ) {
7989    let previous = self.newest;
7990    let replaced = self.entries.insert(
7991      id,
7992      CurrentThreadTaskDriverEntry {
7993        driver,
7994        delivery: None,
7995        pending: None,
7996        previous,
7997        next: None,
7998      },
7999    );
8000    assert!(
8001      replaced.is_none(),
8002      "CurrentThread task driver id must be unique"
8003    );
8004    if let Some(previous) = previous {
8005      self
8006        .entries
8007        .get_mut(&previous)
8008        .expect("the newest CurrentThread task driver must remain registered")
8009        .next = Some(id);
8010    } else {
8011      self.oldest = Some(id);
8012    }
8013    self.newest = Some(id);
8014  }
8015
8016  fn remove_driver(
8017    &mut self,
8018    id: CurrentThreadTaskDriverId,
8019  ) -> Option<CurrentThreadTaskDriverEntry> {
8020    let entry = self.entries.remove(&id)?;
8021    if let Some(previous) = entry.previous {
8022      self
8023        .entries
8024        .get_mut(&previous)
8025        .expect("the previous CurrentThread task driver must remain registered")
8026        .next = entry.next;
8027    } else {
8028      self.oldest = entry.next;
8029    }
8030    if let Some(next) = entry.next {
8031      self
8032        .entries
8033        .get_mut(&next)
8034        .expect("the next CurrentThread task driver must remain registered")
8035        .previous = entry.previous;
8036    } else {
8037      self.newest = entry.previous;
8038    }
8039    if let Some(delivery) = &entry.delivery {
8040      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
8041      debug_assert_eq!(
8042        indexed,
8043        Some(id),
8044        "delivery capability index must match its driver"
8045      );
8046    }
8047    Some(entry)
8048  }
8049
8050  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8051    let mut snapshot = Vec::with_capacity(self.entries.len());
8052    let mut current = self.oldest;
8053    while let Some(id) = current {
8054      let entry = self
8055        .entries
8056        .get(&id)
8057        .expect("CurrentThread task driver order must reference a live entry");
8058      snapshot.push((id, Arc::clone(&entry.driver)));
8059      current = entry.next;
8060    }
8061    debug_assert_eq!(
8062      snapshot.len(),
8063      self.entries.len(),
8064      "CurrentThread task driver order must contain every entry exactly once"
8065    );
8066    snapshot
8067  }
8068
8069  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
8070    if self.entries.len() != expected.len() {
8071      return false;
8072    }
8073    let mut current = self.oldest;
8074    for expected in expected {
8075      if current != Some(*expected) {
8076        return false;
8077      }
8078      current = self
8079        .entries
8080        .get(expected)
8081        .expect("CurrentThread task driver order must reference a live entry")
8082        .next;
8083    }
8084    current.is_none()
8085  }
8086}
8087
8088struct CurrentThreadTaskDriverSelection {
8089  live: CurrentThreadTaskDriverSnapshot,
8090  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8091}
8092
8093#[derive(Default)]
8094struct CurrentThreadTaskDeliveryCompletion {
8095  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8096  redispatch: bool,
8097}
8098
8099struct CurrentThreadTaskDispatchCompletion {
8100  result: CurrentThreadHostDispatchResult,
8101  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8102}
8103
8104struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8105  registry: &'a CurrentThreadTaskDriverRegistry,
8106  dispatch: Option<u64>,
8107}
8108
8109impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8110  fn finish(mut self) -> CurrentThreadHostDispatchResult {
8111    let dispatch = self
8112      .dispatch
8113      .expect("the CurrentThread task dispatch publication must remain armed");
8114    let result = self.registry.finish_publication(dispatch);
8115    self.dispatch = None;
8116    result
8117  }
8118}
8119
8120impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8121  fn drop(&mut self) {
8122    if let Some(dispatch) = self.dispatch.take() {
8123      self.registry.abort_publication(dispatch);
8124    }
8125  }
8126}
8127
8128#[derive(Default)]
8129struct CurrentThreadTaskDriverRegistry {
8130  state: Mutex<CurrentThreadTaskDriverRegistryState>,
8131  next_id: AtomicU64,
8132  next_delivery: AtomicU64,
8133}
8134
8135impl CurrentThreadTaskDriverRegistry {
8136  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8137    let id = next_unique_id(
8138      &self.next_id,
8139      "CurrentThread task driver id space exhausted",
8140    );
8141    let id = CurrentThreadTaskDriverId(id);
8142    self
8143      .state
8144      .lock()
8145      .unwrap_or_else(std::sync::PoisonError::into_inner)
8146      .insert_driver(id, driver);
8147    id
8148  }
8149
8150  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8151    let (removed, failed_dispatches) = {
8152      let mut state = self
8153        .state
8154        .lock()
8155        .unwrap_or_else(std::sync::PoisonError::into_inner);
8156      let removed = state.remove_driver(id);
8157      let mut failed_dispatches = Vec::new();
8158      if let Some(entry) = &removed {
8159        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8160      }
8161      (removed, failed_dispatches)
8162    };
8163    let _ = catch_unwind_contained(|| drop(removed));
8164    CurrentThreadTaskDeliveryCompletion {
8165      failed_dispatches,
8166      redispatch: false,
8167    }
8168  }
8169
8170  fn live(&self) -> CurrentThreadTaskDriverSelection {
8171    let mut failed_dispatches = Vec::new();
8172    loop {
8173      let snapshot = CurrentThreadTaskDriverSnapshot::new({
8174        let state = self
8175          .state
8176          .lock()
8177          .unwrap_or_else(std::sync::PoisonError::into_inner);
8178        state.ordered_snapshot()
8179      });
8180      let mut dead_ids = FxHashSet::default();
8181      let mut live_ids = Vec::new();
8182      for (id, driver) in snapshot.as_slice() {
8183        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8184          live_ids.push(*id);
8185        } else {
8186          dead_ids.insert(*id);
8187        }
8188      }
8189
8190      let (live, swept, retry) = {
8191        let mut state = self
8192          .state
8193          .lock()
8194          .unwrap_or_else(std::sync::PoisonError::into_inner);
8195        let mut swept = Vec::new();
8196        let mut current = state.oldest;
8197        while let Some(id) = current {
8198          let next = state
8199            .entries
8200            .get(&id)
8201            .expect("CurrentThread task driver order must reference a live entry")
8202            .next;
8203          if dead_ids.contains(&id)
8204            && let Some(entry) = state.remove_driver(id)
8205          {
8206            swept.push(entry);
8207          }
8208          current = next;
8209        }
8210        for entry in &swept {
8211          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8212        }
8213        let retry = !state.ordered_ids_match(&live_ids);
8214        let live = if retry {
8215          Vec::new()
8216        } else {
8217          state.ordered_snapshot()
8218        };
8219        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8220      };
8221
8222      // A reentrant probe can unregister multiple snapshot-only drivers.
8223      // See the implementation notes in this crate's README.
8224      drop(snapshot);
8225      for entry in swept {
8226        let _ = catch_unwind_contained(|| entry.driver.on_swept());
8227        let _ = catch_unwind_contained(|| drop(entry));
8228      }
8229      if !retry {
8230        return CurrentThreadTaskDriverSelection {
8231          live,
8232          failed_dispatches,
8233        };
8234      }
8235    }
8236  }
8237
8238  #[cfg(test)]
8239  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8240    let mut live = self.live().live;
8241    live.pop().map(|(id, driver)| (id, driver.into_inner()))
8242  }
8243
8244  fn next_delivery(
8245    &self,
8246    driver: CurrentThreadTaskDriverId,
8247    dispatch: u64,
8248  ) -> CurrentThreadTaskDelivery {
8249    let capability = next_unique_id(
8250      &self.next_delivery,
8251      "CurrentThread task delivery id space exhausted",
8252    )
8253    .checked_add(1)
8254    .expect("CurrentThread task delivery id space exhausted");
8255    CurrentThreadTaskDelivery {
8256      driver,
8257      capability,
8258      dispatch,
8259    }
8260  }
8261
8262  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8263    let dispatch_state = state
8264      .dispatches
8265      .get_mut(&dispatch)
8266      .expect("a referenced CurrentThread dispatch must remain registered");
8267    dispatch_state.references = dispatch_state
8268      .references
8269      .checked_add(1)
8270      .expect("CurrentThread dispatch reference count exhausted");
8271  }
8272
8273  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8274    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8275      return;
8276    };
8277    dispatch_state.references = dispatch_state
8278      .references
8279      .checked_sub(1)
8280      .expect("CurrentThread dispatch reference count underflow");
8281  }
8282
8283  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8284    let publication = {
8285      let mut state = self
8286        .state
8287        .lock()
8288        .unwrap_or_else(std::sync::PoisonError::into_inner);
8289      let dispatch_state = state.dispatches.entry(dispatch).or_default();
8290      if dispatch_state.is_serviced() {
8291        return CurrentThreadTaskDispatchCompletion {
8292          result: CurrentThreadHostDispatchResult::Accepted,
8293          failed_dispatches: Vec::new(),
8294        };
8295      }
8296      if dispatch_state.is_cancelling() {
8297        return CurrentThreadTaskDispatchCompletion {
8298          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8299          failed_dispatches: Vec::new(),
8300        };
8301      }
8302      dispatch_state.publications = dispatch_state
8303        .publications
8304        .checked_add(1)
8305        .expect("CurrentThread dispatch publication count exhausted");
8306      CurrentThreadTaskDispatchPublicationGuard {
8307        registry: self,
8308        dispatch: Some(dispatch),
8309      }
8310    };
8311
8312    let CurrentThreadTaskDriverSelection {
8313      mut live,
8314      mut failed_dispatches,
8315    } = self.live();
8316    let planned_deliveries = {
8317      let mut state = self
8318        .state
8319        .lock()
8320        .unwrap_or_else(std::sync::PoisonError::into_inner);
8321      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8322        return CurrentThreadTaskDispatchCompletion {
8323          result: CurrentThreadHostDispatchResult::Unavailable,
8324          failed_dispatches,
8325        };
8326      };
8327      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8328        Vec::new()
8329      } else {
8330        // Reserve every capability and all output storage before mutating an
8331        // entry. If ID allocation unwinds, the publication guard can retire
8332        // the barrier without leaving an undispatched physical slot occupied.
8333        let mut reserved_deliveries = Vec::with_capacity(live.len());
8334        for (id, _) in live.as_slice().iter().rev() {
8335          let Some(entry) = state.entries.get(id) else {
8336            continue;
8337          };
8338          if entry.delivery.is_none() {
8339            reserved_deliveries.push(self.next_delivery(*id, dispatch));
8340          }
8341        }
8342        state.delivery_drivers.reserve(reserved_deliveries.len());
8343        let mut reserved_deliveries = reserved_deliveries.into_iter();
8344        let mut planned_deliveries = Vec::with_capacity(live.len());
8345        let mut retired = Vec::with_capacity(live.len());
8346        let mut accepted_reservation = false;
8347        for (id, _) in live.as_slice().iter().rev() {
8348          let Some(entry) = state.entries.get(id) else {
8349            continue;
8350          };
8351          if let Some(delivery) = &entry.delivery
8352            && delivery.delivery.dispatch == dispatch
8353          {
8354            accepted_reservation |= delivery.accepted;
8355            continue;
8356          }
8357          if entry.delivery.is_some() {
8358            let previous = state
8359              .entries
8360              .get_mut(id)
8361              .expect("the CurrentThread task driver must remain registered")
8362              .pending
8363              .replace(dispatch);
8364            if previous != Some(dispatch) {
8365              if let Some(previous) = previous {
8366                Self::remove_dispatch_reference(&mut state, previous);
8367              }
8368              Self::add_dispatch_reference(&mut state, dispatch);
8369            }
8370            retired.push(previous);
8371            accepted_reservation = true;
8372            continue;
8373          }
8374          let delivery = reserved_deliveries
8375            .next()
8376            .expect("every available CurrentThread host must have a reserved delivery");
8377          let pending = state
8378            .entries
8379            .get_mut(id)
8380            .expect("the CurrentThread task driver must remain registered")
8381            .pending
8382            .take();
8383          let accepted = pending == Some(dispatch);
8384          if !accepted && pending.is_some() {
8385            retired.push(pending);
8386          }
8387          match pending {
8388            Some(pending) if pending == dispatch => {}
8389            Some(pending) => {
8390              Self::remove_dispatch_reference(&mut state, pending);
8391              Self::add_dispatch_reference(&mut state, dispatch);
8392            }
8393            None => Self::add_dispatch_reference(&mut state, dispatch),
8394          }
8395          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8396          assert!(
8397            indexed.is_none(),
8398            "CurrentThread delivery capability must be unique"
8399          );
8400          state
8401            .entries
8402            .get_mut(id)
8403            .expect("the CurrentThread task driver must remain registered")
8404            .delivery = Some(CurrentThreadTaskDeliveryState {
8405            delivery,
8406            claimed: false,
8407            accepted,
8408          });
8409          accepted_reservation |= accepted;
8410          planned_deliveries.push((*id, delivery));
8411        }
8412        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8413        debug_assert!(
8414          all_reserved_deliveries_installed,
8415          "every reserved CurrentThread delivery must be installed"
8416        );
8417        if accepted_reservation {
8418          state
8419            .dispatches
8420            .get_mut(&dispatch)
8421            .expect("the publishing dispatch state must remain registered")
8422            .accepted = true;
8423        }
8424        for dispatch in retired.into_iter().flatten() {
8425          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8426        }
8427        planned_deliveries
8428      }
8429    };
8430
8431    // Pair newest-first delivery plans with contained driver owners after
8432    // releasing the registry lock. Normal, rejection, and unwind cleanup can
8433    // then destroy every selected driver independently without re-entering the
8434    // registry under its mutex.
8435    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8436    let mut planned_deliveries = planned_deliveries.into_iter();
8437    let mut next_delivery = planned_deliveries.next();
8438    while let Some((id, driver)) = live.pop() {
8439      let Some((planned_id, delivery)) = next_delivery else {
8440        break;
8441      };
8442      if planned_id == id {
8443        deliveries.push((id, driver, delivery));
8444        next_delivery = planned_deliveries.next();
8445      } else {
8446        next_delivery = Some((planned_id, delivery));
8447      }
8448    }
8449    debug_assert!(
8450      next_delivery.is_none() && planned_deliveries.len() == 0,
8451      "every planned CurrentThread delivery must retain its selected driver"
8452    );
8453
8454    for (id, driver, delivery) in deliveries {
8455      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8456        self.accept_delivery(id, delivery);
8457      } else {
8458        let (entry, failures) = self.reject_delivery(id, delivery);
8459        failed_dispatches.extend(failures);
8460        if let Some(entry) = entry {
8461          let _ = catch_unwind_contained(|| entry.driver.on_swept());
8462          let _ = catch_unwind_contained(|| drop(entry));
8463        }
8464      }
8465    }
8466
8467    let result = publication.finish();
8468    debug_assert!(
8469      !failed_dispatches
8470        .iter()
8471        .any(|failure| failure.dispatch == dispatch),
8472      "the active publication must report its own terminal outcome directly"
8473    );
8474    CurrentThreadTaskDispatchCompletion {
8475      result,
8476      failed_dispatches,
8477    }
8478  }
8479
8480  #[cfg(test)]
8481  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8482    self.dispatch_with_completion(dispatch).result
8483  }
8484
8485  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8486    let mut state = self
8487      .state
8488      .lock()
8489      .unwrap_or_else(std::sync::PoisonError::into_inner);
8490    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8491      entry
8492        .delivery
8493        .as_ref()
8494        .is_some_and(|current| current.delivery == delivery)
8495    }) {
8496      state
8497        .entries
8498        .get_mut(&id)
8499        .expect("the matching CurrentThread task driver must remain registered")
8500        .delivery
8501        .as_mut()
8502        .expect("the matching delivery must remain installed")
8503        .accepted = true;
8504      true
8505    } else {
8506      // A callback may complete or unregister synchronously before
8507      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
8508      // proves acceptance; if no host serviced it, losing the reference is an
8509      // accepted delivery failure retained behind this publication barrier.
8510      false
8511    };
8512    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8513      dispatch_state.accepted = true;
8514      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8515    }
8516  }
8517
8518  fn reject_delivery(
8519    &self,
8520    id: CurrentThreadTaskDriverId,
8521    delivery: CurrentThreadTaskDelivery,
8522  ) -> (
8523    Option<CurrentThreadTaskDriverEntry>,
8524    Vec<CurrentThreadTaskDispatchFailure>,
8525  ) {
8526    let mut state = self
8527      .state
8528      .lock()
8529      .unwrap_or_else(std::sync::PoisonError::into_inner);
8530    if !state.entries.get(&id).is_some_and(|entry| {
8531      entry
8532        .delivery
8533        .as_ref()
8534        .is_some_and(|current| current.delivery == delivery)
8535    }) {
8536      return (None, Vec::new());
8537    }
8538    let entry = state
8539      .remove_driver(id)
8540      .expect("the matching CurrentThread task driver must remain registered");
8541    let mut failures = Vec::new();
8542    Self::finish_entry_references(&mut state, &entry, &mut failures);
8543    (Some(entry), failures)
8544  }
8545
8546  fn finish_delivery(
8547    &self,
8548    delivery: CurrentThreadTaskDelivery,
8549    failed: bool,
8550  ) -> CurrentThreadTaskDeliveryCompletion {
8551    let mut state = self
8552      .state
8553      .lock()
8554      .unwrap_or_else(std::sync::PoisonError::into_inner);
8555    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8556      entry
8557        .delivery
8558        .as_ref()
8559        .is_some_and(|current| current.delivery == delivery)
8560    }) {
8561      return CurrentThreadTaskDeliveryCompletion::default();
8562    }
8563    let current = state
8564      .entries
8565      .get_mut(&delivery.driver)
8566      .expect("the matching CurrentThread task driver must remain registered")
8567      .delivery
8568      .take()
8569      .expect("the matching delivery must remain installed");
8570    let indexed = state.delivery_drivers.remove(&delivery.capability);
8571    debug_assert_eq!(
8572      indexed,
8573      Some(delivery.driver),
8574      "delivery capability index must match its driver"
8575    );
8576    let redispatch = state
8577      .entries
8578      .get(&delivery.driver)
8579      .expect("the matching CurrentThread task driver must remain registered")
8580      .pending
8581      .is_some_and(|dispatch| {
8582        state
8583          .dispatches
8584          .get(&dispatch)
8585          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8586      });
8587    let mut failed_dispatches = Vec::new();
8588    // Callback completion proves only that JavaScript ran. A success return is
8589    // authoritative only after `drive_current_thread_tasks` claimed this exact
8590    // host attempt; otherwise a no-op or malicious callback could retire the
8591    // registry reference while leaving the executor dispatch pending forever.
8592    Self::finish_dispatch_reference(
8593      &mut state,
8594      delivery.dispatch,
8595      true,
8596      failed || !current.claimed,
8597      &mut failed_dispatches,
8598    );
8599    CurrentThreadTaskDeliveryCompletion {
8600      failed_dispatches,
8601      redispatch,
8602    }
8603  }
8604
8605  fn claim_delivery(&self, capability: u64) -> Option<u64> {
8606    let mut state = self
8607      .state
8608      .lock()
8609      .unwrap_or_else(std::sync::PoisonError::into_inner);
8610    let driver = *state.delivery_drivers.get(&capability)?;
8611    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8612    if delivery.capability != capability {
8613      return None;
8614    }
8615    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8616    if dispatch_state.is_serviced()
8617      || dispatch_state.is_cancelling()
8618      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8619    {
8620      return None;
8621    }
8622    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8623    current.claimed = true;
8624    current.accepted = true;
8625    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8626    Some(delivery.dispatch)
8627  }
8628
8629  fn mark_serviced(&self, dispatch: u64) {
8630    let mut state = self
8631      .state
8632      .lock()
8633      .unwrap_or_else(std::sync::PoisonError::into_inner);
8634    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8635      return;
8636    };
8637    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8638    let mut removed_references = 0usize;
8639    for entry in state.entries.values_mut() {
8640      if entry.pending == Some(dispatch) {
8641        entry.pending = None;
8642        removed_references = removed_references
8643          .checked_add(1)
8644          .expect("CurrentThread dispatch reference count exhausted");
8645      }
8646    }
8647    let dispatch_state = state
8648      .dispatches
8649      .get_mut(&dispatch)
8650      .expect("the serviced CurrentThread dispatch must remain registered");
8651    dispatch_state.references = dispatch_state
8652      .references
8653      .checked_sub(removed_references)
8654      .expect("CurrentThread dispatch reference count underflow");
8655    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8656  }
8657
8658  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8659    let delivery = {
8660      let state = self
8661        .state
8662        .lock()
8663        .unwrap_or_else(std::sync::PoisonError::into_inner);
8664      state
8665        .delivery_drivers
8666        .get(&capability)
8667        .and_then(|driver| state.entries.get(driver))
8668        .and_then(|entry| entry.delivery.as_ref())
8669        .map(|delivery| delivery.delivery)
8670        .filter(|delivery| delivery.capability == capability)
8671    };
8672    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8673      self.finish_delivery(delivery, true)
8674    })
8675  }
8676
8677  fn finish_dispatch_reference(
8678    state: &mut CurrentThreadTaskDriverRegistryState,
8679    dispatch: u64,
8680    accepted: bool,
8681    failed: bool,
8682    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8683  ) {
8684    let (publications, references) = {
8685      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8686        return;
8687      };
8688      dispatch_state.references = dispatch_state
8689        .references
8690        .checked_sub(1)
8691        .expect("CurrentThread dispatch reference count underflow");
8692      dispatch_state.accepted |= accepted;
8693      dispatch_state.accepted_failure |= accepted && failed;
8694      (dispatch_state.publications, dispatch_state.references)
8695    };
8696    if references != 0 || publications != 0 {
8697      return;
8698    }
8699    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8700      return;
8701    };
8702    if dispatch_state.accepted_failure
8703      && !dispatch_state.is_serviced()
8704      && !dispatch_state.is_cancelling()
8705    {
8706      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8707      failures.push(dispatch_state.failure(dispatch));
8708    } else if !dispatch_state.is_cancelling() {
8709      state.dispatches.remove(&dispatch);
8710    }
8711  }
8712
8713  fn finish_entry_references(
8714    state: &mut CurrentThreadTaskDriverRegistryState,
8715    entry: &CurrentThreadTaskDriverEntry,
8716    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8717  ) {
8718    if let Some(delivery) = &entry.delivery {
8719      Self::finish_dispatch_reference(
8720        state,
8721        delivery.delivery.dispatch,
8722        delivery.accepted,
8723        delivery.accepted,
8724        failures,
8725      );
8726    }
8727    if let Some(pending) = entry.pending {
8728      Self::finish_dispatch_reference(state, pending, true, true, failures);
8729    }
8730  }
8731
8732  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8733    let mut state = self
8734      .state
8735      .lock()
8736      .unwrap_or_else(std::sync::PoisonError::into_inner);
8737    let publications = {
8738      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8739        return CurrentThreadHostDispatchResult::Unavailable;
8740      };
8741      dispatch_state.publications = dispatch_state
8742        .publications
8743        .checked_sub(1)
8744        .expect("CurrentThread dispatch publication count underflow");
8745      dispatch_state.publications
8746    };
8747    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8748      return CurrentThreadHostDispatchResult::Accepted;
8749    }
8750    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8751      return CurrentThreadHostDispatchResult::Unavailable;
8752    };
8753    if dispatch_state.is_cancelling() {
8754      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8755    }
8756    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8757      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8758      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8759    }
8760    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8761    state.dispatches.remove(&dispatch);
8762    if accepted {
8763      CurrentThreadHostDispatchResult::Accepted
8764    } else {
8765      CurrentThreadHostDispatchResult::Unavailable
8766    }
8767  }
8768
8769  fn abort_publication(&self, dispatch: u64) {
8770    let mut state = self
8771      .state
8772      .lock()
8773      .unwrap_or_else(std::sync::PoisonError::into_inner);
8774    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8775      return;
8776    };
8777    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8778      return;
8779    };
8780    dispatch_state.publications = publications;
8781    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8782  }
8783
8784  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8785    let mut state = self
8786      .state
8787      .lock()
8788      .unwrap_or_else(std::sync::PoisonError::into_inner);
8789    let can_rearm = state
8790      .dispatches
8791      .get(&failure.dispatch)
8792      .is_some_and(|dispatch_state| {
8793        dispatch_state.is_cancelling()
8794          && dispatch_state.epoch == failure.epoch
8795          && dispatch_state.publications == 0
8796      })
8797      && !Self::has_dispatch_reference(&state, failure.dispatch);
8798    if !can_rearm {
8799      return false;
8800    }
8801    let dispatch_state = state
8802      .dispatches
8803      .get_mut(&failure.dispatch)
8804      .expect("the exact failed CurrentThread dispatch must remain registered");
8805    let epoch = dispatch_state
8806      .epoch
8807      .checked_add(1)
8808      .expect("CurrentThread dispatch failure epoch space exhausted");
8809    *dispatch_state = CurrentThreadDispatchState {
8810      epoch,
8811      ..CurrentThreadDispatchState::default()
8812    };
8813    true
8814  }
8815
8816  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8817    self
8818      .state
8819      .lock()
8820      .unwrap_or_else(std::sync::PoisonError::into_inner)
8821      .dispatches
8822      .get(&failure.dispatch)
8823      .is_some_and(|dispatch_state| {
8824        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8825      })
8826  }
8827
8828  fn retire_unreferenced_dispatch(
8829    state: &mut CurrentThreadTaskDriverRegistryState,
8830    dispatch: Option<u64>,
8831  ) {
8832    let Some(dispatch) = dispatch else {
8833      return;
8834    };
8835    if Self::has_dispatch_reference(state, dispatch) {
8836      return;
8837    }
8838    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8839      return;
8840    };
8841    if dispatch_state.publications == 0
8842      && !dispatch_state.is_cancelling()
8843      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8844    {
8845      state.dispatches.remove(&dispatch);
8846    }
8847  }
8848
8849  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8850    state
8851      .dispatches
8852      .get(&dispatch)
8853      .is_some_and(|dispatch_state| dispatch_state.references != 0)
8854  }
8855
8856  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8857    let mut state = self
8858      .state
8859      .lock()
8860      .unwrap_or_else(std::sync::PoisonError::into_inner);
8861    if state
8862      .dispatches
8863      .get(&failure.dispatch)
8864      .is_some_and(|dispatch_state| {
8865        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8866      })
8867    {
8868      state.dispatches.remove(&failure.dispatch);
8869    }
8870  }
8871
8872  fn retire_for_shutdown(&self) {
8873    let mut state = self
8874      .state
8875      .lock()
8876      .unwrap_or_else(std::sync::PoisonError::into_inner);
8877    state.dispatches.clear();
8878    for entry in state.entries.values_mut() {
8879      entry.pending = None;
8880    }
8881  }
8882}
8883
8884static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8885  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8886
8887pub fn register_current_thread_task_driver(
8888  driver: Arc<dyn CurrentThreadTaskDriver>,
8889) -> CurrentThreadTaskDriverId {
8890  CURRENT_THREAD_TASK_DRIVERS.register(driver)
8891}
8892
8893/// Request service for work that may have accumulated while no live host
8894/// dispatcher was registered. A new host receives the existing internal
8895/// dispatch capability through a fresh registration-scoped delivery token;
8896/// already accepting hosts keep their attempts.
8897pub fn request_current_thread_task_drain() {
8898  RUNTIME.request_current_thread_drain();
8899}
8900
8901pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8902  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8903}
8904
8905fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8906  let CurrentThreadTaskDispatchCompletion {
8907    result,
8908    failed_dispatches,
8909  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8910  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8911    failed_dispatches,
8912    redispatch: false,
8913  });
8914  result
8915}
8916
8917pub type TimerId = u64;
8918
8919/// Process-global id source for [`Sleep`] futures (both drivers).
8920static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8921
8922/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
8923/// helper thread on a threadless build, so it delegates
8924/// `sleep_until` to the host event loop through this trait. The Node binding
8925/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
8926/// at import -- one per importing napi env (main thread AND workers).
8927pub trait TimerDriver: Send + Sync + 'static {
8928  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
8929  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
8930  /// waker refresh, not a second timer.
8931  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8932  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
8933  /// raced ahead is acceptable (the woken task observes the sleep completed).
8934  fn cancel(&self, id: TimerId);
8935  /// Whether this driver can still deliver wakes. `false` once the driver's
8936  /// host is gone (the Node binding's driver dies with its owning napi env:
8937  /// worker exit aborts the weak threadsafe function). Selection skips and
8938  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
8939  fn is_live(&self) -> bool {
8940    true
8941  }
8942  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
8943  /// entries (its `is_live` turned false), with NO registry lock held.
8944  /// Implementations with pending-waker bookkeeping must wake everything
8945  /// armed on them here, so those sleeps re-poll onto the next live
8946  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
8947  /// host (before its env-cleanup hook or any call failure runs), and a
8948  /// silent `retain` would strand sleeps whose only wake source was the swept
8949  /// driver. Idempotent with the owner's other eviction paths. Default no-op
8950  /// for drivers without waker state.
8951  fn on_swept(&self) {}
8952}
8953
8954/// Handle to one registration in a [`TimerDriverRegistry`], returned by
8955/// [`TimerDriverRegistry::register`] and consumed by
8956/// [`TimerDriverRegistry::unregister`].
8957#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8958pub struct TimerDriverId(u64);
8959
8960type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8961
8962struct TimerDriverEntry {
8963  driver: Arc<dyn TimerDriver>,
8964  previous: Option<TimerDriverId>,
8965  next: Option<TimerDriverId>,
8966}
8967
8968#[derive(Default)]
8969struct TimerDriverRegistryState {
8970  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8971  oldest: Option<TimerDriverId>,
8972  newest: Option<TimerDriverId>,
8973}
8974
8975impl TimerDriverRegistryState {
8976  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8977    let previous = self.newest;
8978    let replaced = self.entries.insert(
8979      id,
8980      TimerDriverEntry {
8981        driver,
8982        previous,
8983        next: None,
8984      },
8985    );
8986    assert!(replaced.is_none(), "timer driver id must be unique");
8987    if let Some(previous) = previous {
8988      self
8989        .entries
8990        .get_mut(&previous)
8991        .expect("the newest timer driver must remain registered")
8992        .next = Some(id);
8993    } else {
8994      self.oldest = Some(id);
8995    }
8996    self.newest = Some(id);
8997  }
8998
8999  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
9000    let entry = self.entries.remove(&id)?;
9001    if let Some(previous) = entry.previous {
9002      self
9003        .entries
9004        .get_mut(&previous)
9005        .expect("the previous timer driver must remain registered")
9006        .next = entry.next;
9007    } else {
9008      self.oldest = entry.next;
9009    }
9010    if let Some(next) = entry.next {
9011      self
9012        .entries
9013        .get_mut(&next)
9014        .expect("the next timer driver must remain registered")
9015        .previous = entry.previous;
9016    } else {
9017      self.newest = entry.previous;
9018    }
9019    Some(entry)
9020  }
9021
9022  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
9023    let mut snapshot = Vec::with_capacity(self.entries.len());
9024    let mut current = self.oldest;
9025    while let Some(id) = current {
9026      let entry = self
9027        .entries
9028        .get(&id)
9029        .expect("timer driver order must reference a live entry");
9030      snapshot.push((id, Arc::clone(&entry.driver)));
9031      current = entry.next;
9032    }
9033    debug_assert_eq!(
9034      snapshot.len(),
9035      self.entries.len(),
9036      "timer driver order must contain every entry exactly once"
9037    );
9038    snapshot
9039  }
9040
9041  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
9042    if self.entries.len() != expected.len() {
9043      return false;
9044    }
9045    let mut current = self.oldest;
9046    for expected in expected {
9047      if current != Some(*expected) {
9048        return false;
9049      }
9050      current = self
9051        .entries
9052        .get(expected)
9053        .expect("timer driver order must reference a live entry")
9054        .next;
9055    }
9056    current.is_none()
9057  }
9058}
9059
9060/// Host timer drivers keyed by registration order. A driver is owned by a host
9061/// context that can die independently
9062/// of the process -- the Node binding registers one driver per importing napi
9063/// env, and a worker's env teardown kills its driver's threadsafe function. A
9064/// single first-wins slot is therefore unsound: a worker that imports the
9065/// binding first and then exits would permanently shadow the slot with a dead
9066/// driver, and every CT timer armed afterwards would busy-fail against it.
9067/// Selecting only the newest live driver is also
9068/// insufficient: a live worker can be CPU-blocked indefinitely while another
9069/// environment remains responsive. The registry therefore keeps all
9070/// registrants and returns a stable snapshot of every live driver at each
9071/// [`Sleep`] poll, sweeping dead entries out as it goes.
9072///
9073/// Instance-based (not a hidden global) so tests exercise
9074/// registration/eviction/selection against local registries; production uses
9075/// the process-global one behind
9076/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
9077#[derive(Default)]
9078pub struct TimerDriverRegistry {
9079  /// Keyed lookup with a linked registration order. Exact unregister and a
9080  /// swept binding host's idempotent unregister are expected O(1), while
9081  /// snapshots keep deterministic oldest-to-newest fallback order.
9082  state: Mutex<TimerDriverRegistryState>,
9083  /// Stable repoll signals for every currently polled host sleep. A newly
9084  /// registered driver snapshots these after publishing itself, then wakes
9085  /// them outside both registry locks so each sleep re-arms the same timer id
9086  /// on the expanded all-live driver set.
9087  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
9088  next_id: AtomicU64,
9089}
9090
9091impl Drop for TimerDriverRegistry {
9092  fn drop(&mut self) {
9093    let state = self
9094      .state
9095      .get_mut()
9096      .unwrap_or_else(std::sync::PoisonError::into_inner);
9097    state.oldest = None;
9098    state.newest = None;
9099    let entries = std::mem::take(&mut state.entries);
9100    for (_, entry) in entries {
9101      let _ = catch_unwind_contained(|| drop(entry));
9102    }
9103  }
9104}
9105
9106impl TimerDriverRegistry {
9107  /// Add `driver` as the newest registrant and return its handle.
9108  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9109    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9110    let id = TimerDriverId(id);
9111    self
9112      .state
9113      .lock()
9114      .unwrap_or_else(std::sync::PoisonError::into_inner)
9115      .insert_driver(id, driver);
9116    // Publish the driver before snapshotting pending sleeps. A sleep publishes
9117    // its repoll signal before selecting live drivers. Therefore either this
9118    // snapshot sees the sleep, or that sleep's subsequent selection sees this
9119    // driver; no registration/insertion interleaving can miss both sides.
9120    let pending = {
9121      let mut entries = self
9122        .pending
9123        .lock()
9124        .unwrap_or_else(std::sync::PoisonError::into_inner);
9125      let mut pending = Vec::with_capacity(entries.len());
9126      entries.retain(|_, repoll| {
9127        if let Some(repoll) = repoll.upgrade() {
9128          pending.push(repoll);
9129          true
9130        } else {
9131          false
9132        }
9133      });
9134      pending
9135    };
9136    for repoll in pending {
9137      repoll.wake();
9138    }
9139    id
9140  }
9141
9142  /// Drop the registration behind `id` (no-op when already swept/removed).
9143  pub fn unregister(&self, id: TimerDriverId) {
9144    let removed = self
9145      .state
9146      .lock()
9147      .unwrap_or_else(std::sync::PoisonError::into_inner)
9148      .remove_driver(id);
9149    // A driver's destructor may re-enter this registry. Keep it outside the
9150    // entries lock just like is_live/on_swept callbacks.
9151    let _ = catch_unwind_contained(|| drop(removed));
9152  }
9153
9154  /// Every LIVE driver in registration order, sweeping dead entries out as a
9155  /// side effect.
9156  ///
9157  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
9158  /// lock held. `is_live` is externally implemented and may re-enter this
9159  /// registry; `on_swept` does so in the binding by calling `unregister` and
9160  /// waking pending sleeps whose re-polls call `current()` again. Selection
9161  /// therefore probes a snapshot, removes dead entries under the lock, then
9162  /// runs hooks after release. A concurrent registration/unregistration can
9163  /// stale the snapshot, in which case selection retries.
9164  fn live(&self) -> HostTimerDriverSnapshot {
9165    loop {
9166      let snapshot = HostTimerDriverSnapshot::new({
9167        let state = self
9168          .state
9169          .lock()
9170          .unwrap_or_else(std::sync::PoisonError::into_inner);
9171        state.ordered_snapshot()
9172      });
9173      let mut dead_ids = FxHashSet::default();
9174      let mut live_ids = Vec::new();
9175      for (id, driver) in snapshot.as_slice() {
9176        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9177          live_ids.push(*id);
9178        } else {
9179          dead_ids.insert(*id);
9180        }
9181      }
9182
9183      let (live, swept, retry) = {
9184        let mut state = self
9185          .state
9186          .lock()
9187          .unwrap_or_else(std::sync::PoisonError::into_inner);
9188        let mut swept = Vec::new();
9189        let mut current = state.oldest;
9190        while let Some(id) = current {
9191          let next = state
9192            .entries
9193            .get(&id)
9194            .expect("timer driver order must reference a live entry")
9195            .next;
9196          if dead_ids.contains(&id)
9197            && let Some(entry) = state.remove_driver(id)
9198          {
9199            swept.push(entry);
9200          }
9201          current = next;
9202        }
9203        let retry = !state.ordered_ids_match(&live_ids);
9204        let live = if retry {
9205          Vec::new()
9206        } else {
9207          state.ordered_snapshot()
9208        };
9209        (HostTimerDriverSnapshot::new(live), swept, retry)
9210      };
9211
9212      // A reentrant probe can unregister multiple snapshot-only drivers.
9213      // See the implementation notes in this crate's README.
9214      drop(snapshot);
9215      for entry in swept {
9216        let _ = catch_unwind_contained(|| entry.driver.on_swept());
9217        let _ = catch_unwind_contained(|| drop(entry));
9218      }
9219      if !retry {
9220        return live;
9221      }
9222    }
9223  }
9224
9225  #[cfg(test)]
9226  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9227    let mut live = self.live();
9228    live.pop().map(|(id, driver)| (id, driver.into_inner()))
9229  }
9230
9231  /// Whether a LIVE driver is currently registered (dead-only counts as no).
9232  pub fn has_live_driver(&self) -> bool {
9233    !self.live().is_empty()
9234  }
9235
9236  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9237    let replaced = self
9238      .pending
9239      .lock()
9240      .unwrap_or_else(std::sync::PoisonError::into_inner)
9241      .insert(id, Arc::downgrade(repoll));
9242    assert!(
9243      replaced.is_none(),
9244      "host timer {id} registered its repoll signal twice"
9245    );
9246    // Shutdown can close and try to remove the signal immediately before this
9247    // insertion. The post-insert check is the other side of that race.
9248    if repoll.is_closed() {
9249      self.remove_pending(id);
9250    }
9251  }
9252
9253  fn remove_pending(&self, id: TimerId) {
9254    let removed = self
9255      .pending
9256      .lock()
9257      .unwrap_or_else(std::sync::PoisonError::into_inner)
9258      .remove(&id);
9259    let _ = catch_unwind_contained(|| drop(removed));
9260  }
9261
9262  #[cfg(test)]
9263  fn pending_len(&self) -> usize {
9264    self
9265      .pending
9266      .lock()
9267      .unwrap_or_else(std::sync::PoisonError::into_inner)
9268      .len()
9269  }
9270}
9271
9272/// The process-global registry behind [`sleep_until`] (see
9273/// [`TimerDriverRegistry`] for the lifetime story).
9274static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9275  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9276
9277/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
9278/// Every host context that can serve timers registers its own driver (the
9279/// Node binding: one per importing napi env); every LIVE registrant receives
9280/// each timer so a starved environment cannot own its only wake source.
9281/// Returns the handle for [`unregister_timer_driver`].
9282pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9283  TIMER_DRIVERS.register(driver)
9284}
9285
9286/// Remove a driver registered via [`register_timer_driver`] -- called when
9287/// its host context dies (the Node binding evicts on env teardown and on
9288/// callback failure). Idempotent.
9289pub fn unregister_timer_driver(id: TimerDriverId) {
9290  TIMER_DRIVERS.unregister(id);
9291}
9292
9293/// Whether a LIVE host timer driver is currently registered. Lets the
9294/// embedder report CurrentThread timer availability honestly (e.g. through a
9295/// capabilities export) instead of guessing: with no
9296/// live driver a CurrentThread `sleep_until` would panic, so the capability
9297/// must read `false` -- including when the only registrants are DEAD (their
9298/// owning envs torn down), not merely when none ever registered.
9299pub fn has_live_timer_driver() -> bool {
9300  TIMER_DRIVERS.has_live_driver()
9301}
9302
9303/// Coalescing wake signal used only to make an already-pending [`Sleep`]
9304/// re-select host drivers after a new registration. The task's arbitrary
9305/// waker is never cloned, invoked, or dropped while this mutex is held.
9306#[derive(Default)]
9307struct HostTimerRepoll {
9308  state: Mutex<HostTimerRepollState>,
9309}
9310
9311#[derive(Default)]
9312struct HostTimerRepollState {
9313  closed: bool,
9314  notified: bool,
9315  waker: Option<Waker>,
9316}
9317
9318impl HostTimerRepoll {
9319  fn register(&self, waker: Waker) {
9320    let (replaced, wake) = {
9321      let mut state = self
9322        .state
9323        .lock()
9324        .unwrap_or_else(std::sync::PoisonError::into_inner);
9325      if state.closed {
9326        (Some(waker), None)
9327      } else if state.notified {
9328        state.notified = false;
9329        (state.waker.take(), Some(waker))
9330      } else {
9331        (state.waker.replace(waker), None)
9332      }
9333    };
9334    let _ = catch_unwind_contained(|| drop(replaced));
9335    if let Some(waker) = wake {
9336      wake_waker_contained(waker);
9337    }
9338  }
9339
9340  fn wake(&self) {
9341    let waker = {
9342      let mut state = self
9343        .state
9344        .lock()
9345        .unwrap_or_else(std::sync::PoisonError::into_inner);
9346      if state.closed {
9347        return;
9348      }
9349      if let Some(waker) = state.waker.take() {
9350        Some(waker)
9351      } else {
9352        state.notified = true;
9353        None
9354      }
9355    };
9356    if let Some(waker) = waker {
9357      wake_waker_contained(waker);
9358    }
9359  }
9360
9361  fn close(&self) {
9362    let waker = {
9363      let mut state = self
9364        .state
9365        .lock()
9366        .unwrap_or_else(std::sync::PoisonError::into_inner);
9367      state.closed = true;
9368      state.notified = false;
9369      state.waker.take()
9370    };
9371    let _ = catch_unwind_contained(|| drop(waker));
9372  }
9373
9374  fn is_closed(&self) -> bool {
9375    self
9376      .state
9377      .lock()
9378      .unwrap_or_else(std::sync::PoisonError::into_inner)
9379      .closed
9380  }
9381}
9382
9383/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
9384/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
9385/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
9386/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
9387/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
9388/// detection -- see the interaction notes at the top of this section.
9389#[derive(Default)]
9390struct HostTimerRegistry {
9391  state: Mutex<HostTimerRegistryState>,
9392}
9393
9394#[derive(Default)]
9395struct HostTimerRegistryState {
9396  closed: bool,
9397  timers: FxHashMap<TimerId, HostTimerEntry>,
9398}
9399
9400struct HostTimerEntry {
9401  deadline: Instant,
9402  waker: Waker,
9403  fired: Arc<AtomicBool>,
9404  drivers: Weak<TimerDriverRegistry>,
9405  repoll: Arc<HostTimerRepoll>,
9406}
9407
9408impl HostTimerRegistry {
9409  fn register(
9410    &self,
9411    id: TimerId,
9412    deadline: Instant,
9413    waker: Waker,
9414    fired: &Arc<AtomicBool>,
9415    drivers: &Arc<TimerDriverRegistry>,
9416    repoll: &Arc<HostTimerRepoll>,
9417  ) -> bool {
9418    let replaced = {
9419      let mut state = self
9420        .state
9421        .lock()
9422        .unwrap_or_else(std::sync::PoisonError::into_inner);
9423      if state.closed || fired.load(Ordering::SeqCst) {
9424        fired.store(true, Ordering::SeqCst);
9425        Err(waker)
9426      } else {
9427        Ok(state.timers.insert(
9428          id,
9429          HostTimerEntry {
9430            deadline,
9431            waker,
9432            fired: Arc::clone(fired),
9433            drivers: Arc::downgrade(drivers),
9434            repoll: Arc::clone(repoll),
9435          },
9436        ))
9437      }
9438    };
9439    match replaced {
9440      Ok(Some(entry)) => {
9441        let _ = catch_unwind_contained(|| drop(entry));
9442        true
9443      }
9444      Ok(None) => true,
9445      Err(waker) => {
9446        let _ = catch_unwind_contained(|| drop(waker));
9447        false
9448      }
9449    }
9450  }
9451
9452  fn remove(&self, id: TimerId) {
9453    let removed = self
9454      .state
9455      .lock()
9456      .unwrap_or_else(std::sync::PoisonError::into_inner)
9457      .timers
9458      .remove(&id);
9459    if let Some(entry) = removed {
9460      let _ = catch_unwind_contained(|| drop(entry));
9461    }
9462  }
9463
9464  fn shutdown(&self) {
9465    let timers = {
9466      let mut state = self
9467        .state
9468        .lock()
9469        .unwrap_or_else(std::sync::PoisonError::into_inner);
9470      state.closed = true;
9471      state
9472        .timers
9473        .drain()
9474        .map(|(id, entry)| {
9475          entry.fired.store(true, Ordering::SeqCst);
9476          (id, entry)
9477        })
9478        .collect::<Vec<_>>()
9479    };
9480    for (id, entry) in timers {
9481      entry.repoll.close();
9482      if let Some(drivers) = entry.drivers.upgrade() {
9483        drivers.remove_pending(id);
9484      }
9485      wake_waker_contained(entry.waker);
9486    }
9487  }
9488
9489  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
9490  /// only to pick the diagnostic variant: on a threadless build the park is
9491  /// provably dead either way (the host relay shares the parked thread), but
9492  /// a pending timer means the timer-backed message applies.
9493  fn has_pending(&self) -> bool {
9494    !self
9495      .state
9496      .lock()
9497      .unwrap_or_else(std::sync::PoisonError::into_inner)
9498      .timers
9499      .is_empty()
9500  }
9501
9502  /// Any host timer whose deadline (plus `grace` for host firing lag) is
9503  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
9504  /// timer with zero progress is legitimate. A timer past due by more than
9505  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
9506  /// signature the deadline detection exists to catch, so it must not keep
9507  /// suppressing the panic. Callers pass the armed park deadline as `grace`
9508  /// (the detector's own granularity: firing lag within one window is
9509  /// indistinguishable from an expiry racing the fire).
9510  fn has_live_wait(&self, grace: Duration) -> bool {
9511    let now = Instant::now();
9512    self
9513      .state
9514      .lock()
9515      .unwrap_or_else(std::sync::PoisonError::into_inner)
9516      .timers
9517      .values()
9518      .any(|entry| {
9519        entry
9520          .deadline
9521          .checked_add(grace)
9522          .is_none_or(|deadline| deadline > now)
9523      })
9524  }
9525}
9526
9527/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
9528/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
9529/// arm) both retire the wake-token exactly once.
9530struct HostTimerPendingGuard {
9531  registry: Arc<HostTimerRegistry>,
9532  drivers: Arc<TimerDriverRegistry>,
9533  repoll: Arc<HostTimerRepoll>,
9534  id: TimerId,
9535}
9536
9537impl Drop for HostTimerPendingGuard {
9538  fn drop(&mut self) {
9539    self.repoll.close();
9540    self.registry.remove(self.id);
9541    self.drivers.remove_pending(self.id);
9542  }
9543}
9544
9545/// Runtime-owned timer heap for the MultiThread executor.
9546///
9547/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
9548/// Rayon and therefore cannot consume one of the configured CPU workers while
9549/// a long timer is armed.
9550#[cfg(napi_runtime_os_threads)]
9551struct TimerHeap {
9552  inner: Arc<Mutex<TimerHeapInner>>,
9553  parker: Arc<DriverParker>,
9554  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9555  deadlock_state: Arc<MultiThreadDeadlockState>,
9556  #[cfg(test)]
9557  fail_next_start: AtomicBool,
9558  generation: u64,
9559  executor_id: u64,
9560  thread_name: String,
9561}
9562
9563#[cfg(napi_runtime_os_threads)]
9564#[derive(Default)]
9565struct TimerHeapInner {
9566  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
9567  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
9568  /// one later unbounded cleanup pass.
9569  queue: TimerDeadlineQueue,
9570  entries: FxHashMap<TimerId, HeapTimerEntry>,
9571  /// The parker of the dedicated timekeeper while it is waiting on a timer.
9572  /// `register_timer` unparks it when the earliest deadline moves up, so the
9573  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
9574  timekeeper_parker: Option<Arc<DriverParker>>,
9575  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
9576  /// later registrations must fire immediately (a `Sleep` polled after
9577  /// shutdown resolves early instead of parking a closing runtime forever).
9578  closed: bool,
9579}
9580
9581#[cfg(napi_runtime_os_threads)]
9582struct HeapTimerEntry {
9583  waker: Waker,
9584  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
9585  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
9586  /// even when the wall clock has not reached `deadline` (shutdown
9587  /// drain-fire).
9588  fired: Arc<AtomicBool>,
9589}
9590
9591#[cfg(napi_runtime_os_threads)]
9592#[derive(Default)]
9593struct TimerDeadlineQueue {
9594  nodes: Vec<(Instant, TimerId)>,
9595  positions: FxHashMap<TimerId, usize>,
9596}
9597
9598#[cfg(napi_runtime_os_threads)]
9599impl TimerDeadlineQueue {
9600  #[cfg(test)]
9601  fn len(&self) -> usize {
9602    self.nodes.len()
9603  }
9604
9605  #[cfg(test)]
9606  fn is_empty(&self) -> bool {
9607    self.nodes.is_empty()
9608  }
9609
9610  fn clear(&mut self) {
9611    self.nodes.clear();
9612    self.positions.clear();
9613  }
9614
9615  fn peek(&self) -> Option<(Instant, TimerId)> {
9616    self.nodes.first().copied()
9617  }
9618
9619  fn push(&mut self, deadline: Instant, id: TimerId) {
9620    let index = self.nodes.len();
9621    assert!(
9622      self.positions.insert(id, index).is_none(),
9623      "timer deadline queue received duplicate id {id}"
9624    );
9625    self.nodes.push((deadline, id));
9626    self.sift_up(index);
9627  }
9628
9629  fn pop(&mut self) -> Option<(Instant, TimerId)> {
9630    let first = self.nodes.first().copied()?;
9631    self.positions.remove(&first.1);
9632    let last = self
9633      .nodes
9634      .pop()
9635      .expect("the non-empty timer heap must have a final node");
9636    if !self.nodes.is_empty() {
9637      self.nodes[0] = last;
9638      self.positions.insert(last.1, 0);
9639      self.sift_down(0);
9640    }
9641    Some(first)
9642  }
9643
9644  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9645    let index = self.positions.remove(&id)?;
9646    let removed = self.nodes[index];
9647    let last = self.nodes.pop().expect("an indexed timer node must exist");
9648    if index < self.nodes.len() {
9649      self.nodes[index] = last;
9650      self.positions.insert(last.1, index);
9651      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9652        self.sift_up(index);
9653      } else {
9654        self.sift_down(index);
9655      }
9656    }
9657    Some(removed)
9658  }
9659
9660  fn sift_up(&mut self, mut index: usize) {
9661    while index > 0 {
9662      let parent = (index - 1) / 2;
9663      if self.nodes[parent] <= self.nodes[index] {
9664        break;
9665      }
9666      self.swap(parent, index);
9667      index = parent;
9668    }
9669  }
9670
9671  fn sift_down(&mut self, mut index: usize) {
9672    loop {
9673      let left = index * 2 + 1;
9674      if left >= self.nodes.len() {
9675        return;
9676      }
9677      let right = left + 1;
9678      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9679        right
9680      } else {
9681        left
9682      };
9683      if self.nodes[index] <= self.nodes[child] {
9684        return;
9685      }
9686      self.swap(index, child);
9687      index = child;
9688    }
9689  }
9690
9691  fn swap(&mut self, left: usize, right: usize) {
9692    self.nodes.swap(left, right);
9693    self.positions.insert(self.nodes[left].1, left);
9694    self.positions.insert(self.nodes[right].1, right);
9695  }
9696}
9697
9698#[cfg(napi_runtime_os_threads)]
9699impl TimerHeapInner {
9700  fn next_deadline(&self) -> Option<Instant> {
9701    self.queue.peek().map(|(deadline, _)| deadline)
9702  }
9703}
9704
9705#[cfg(napi_runtime_os_threads)]
9706impl TimerHeap {
9707  fn new(
9708    generation: u64,
9709    executor_id: u64,
9710    thread_name: String,
9711    deadlock_state: Arc<MultiThreadDeadlockState>,
9712  ) -> Self {
9713    Self {
9714      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9715      parker: Arc::new(DriverParker::default()),
9716      thread: Mutex::new(None),
9717      deadlock_state,
9718      #[cfg(test)]
9719      fail_next_start: AtomicBool::new(false),
9720      generation,
9721      executor_id,
9722      thread_name,
9723    }
9724  }
9725
9726  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9727    if self
9728      .inner
9729      .lock()
9730      .unwrap_or_else(std::sync::PoisonError::into_inner)
9731      .closed
9732    {
9733      return Ok(());
9734    }
9735    let current_thread = std::thread::current().id();
9736    loop {
9737      let finished = {
9738        let mut thread = self
9739          .thread
9740          .lock()
9741          .unwrap_or_else(std::sync::PoisonError::into_inner);
9742        match thread.as_ref() {
9743          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9744          Some(handle) if !handle.is_finished() => return Ok(()),
9745          Some(_) => thread.take(),
9746          None => {
9747            // Shutdown can close the heap after the optimistic check above.
9748            // Recheck before publishing another service thread.
9749            if self
9750              .inner
9751              .lock()
9752              .unwrap_or_else(std::sync::PoisonError::into_inner)
9753              .closed
9754            {
9755              return Ok(());
9756            }
9757            #[cfg(test)]
9758            if self.fail_next_start.swap(false, Ordering::SeqCst) {
9759              return Err(RuntimeConfigError(
9760                "injected runtime timer thread creation failure".to_string(),
9761              ));
9762            }
9763
9764            let inner = Arc::clone(&self.inner);
9765            let parker = Arc::clone(&self.parker);
9766            let deadlock_state = Arc::clone(&self.deadlock_state);
9767            let generation = self.generation;
9768            let executor_id = self.executor_id;
9769            let handle = std::thread::Builder::new()
9770              .name(self.thread_name.clone())
9771              .spawn(move || {
9772                // Keep the identity through OS-thread TLS teardown. Unlike
9773                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
9774                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9775                let _generation = RuntimeGenerationGuard::enter(generation);
9776                timer_timekeeper_main(&inner, &parker, &deadlock_state);
9777              })
9778              .map_err(|error| {
9779                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9780              })?;
9781            *thread = Some(handle);
9782            return Ok(());
9783          }
9784        }
9785      };
9786
9787      // A finished timer thread may still be running TLS destructors that
9788      // re-enter timer code. Never hold the handle mutex while waiting for
9789      // those destructors, and never attempt to join this thread from itself.
9790      if let Some(finished) = finished
9791        && finished.thread().id() != current_thread
9792        && let Err(payload) = finished.join()
9793      {
9794        discard_panic_payload(payload);
9795      }
9796      if self
9797        .inner
9798        .lock()
9799        .unwrap_or_else(std::sync::PoisonError::into_inner)
9800        .closed
9801      {
9802        return Ok(());
9803      }
9804    }
9805  }
9806
9807  fn close(&self) -> Vec<Waker> {
9808    let mut inner = self
9809      .inner
9810      .lock()
9811      .unwrap_or_else(std::sync::PoisonError::into_inner);
9812    inner.closed = true;
9813    inner.queue.clear();
9814    inner.timekeeper_parker = None;
9815    inner
9816      .entries
9817      .drain()
9818      .map(|(_, entry)| {
9819        entry.fired.store(true, Ordering::SeqCst);
9820        entry.waker
9821      })
9822      .collect()
9823  }
9824
9825  fn join(&self) {
9826    let handle = self
9827      .thread
9828      .lock()
9829      .unwrap_or_else(std::sync::PoisonError::into_inner)
9830      .take();
9831    if let Some(handle) = handle {
9832      if handle.thread().id() == std::thread::current().id() {
9833        // A custom RawWaker may own the final direct executor Arc and release
9834        // it from this thread. Closing the heap makes the loop exit after the
9835        // callback returns; joining self would deadlock that exit.
9836        drop(handle);
9837        return;
9838      }
9839      if let Err(payload) = crate::sync::join(handle) {
9840        discard_panic_payload(payload);
9841      }
9842    }
9843  }
9844}
9845
9846#[cfg(napi_runtime_os_threads)]
9847impl Drop for TimerHeap {
9848  fn drop(&mut self) {
9849    let mut admission = self.deadlock_state.begin_admission();
9850    let wakers = self.close();
9851    if !wakers.is_empty()
9852      && let Some(admission) = admission.as_mut()
9853    {
9854      admission.mark_published();
9855    }
9856    self.parker.unpark();
9857    for waker in wakers {
9858      wake_waker_contained(waker);
9859    }
9860    drop(admission);
9861    self.join();
9862  }
9863}
9864
9865#[cfg(napi_runtime_os_threads)]
9866fn timer_timekeeper_main(
9867  inner: &Mutex<TimerHeapInner>,
9868  parker: &Arc<DriverParker>,
9869  deadlock_state: &Arc<MultiThreadDeadlockState>,
9870) {
9871  loop {
9872    // Acquire before touching the heap. If a verdict has already closed the
9873    // gate, a due timer remains visible until the verdict's final timer recheck
9874    // causes that guard to reopen.
9875    let mut admission = deadlock_state.begin_admission();
9876    let (due, deadline) = {
9877      let mut inner = inner
9878        .lock()
9879        .unwrap_or_else(std::sync::PoisonError::into_inner);
9880      if inner.closed {
9881        inner.timekeeper_parker = None;
9882        return;
9883      }
9884      let now = Instant::now();
9885      let mut due = Vec::new();
9886      while let Some((deadline, id)) = inner.queue.peek() {
9887        if deadline > now {
9888          break;
9889        }
9890        inner.queue.pop();
9891        if let Some(entry) = inner.entries.remove(&id) {
9892          entry.fired.store(true, Ordering::SeqCst);
9893          due.push(entry.waker);
9894        }
9895      }
9896      let deadline = inner.next_deadline();
9897      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9898      (due, deadline)
9899    };
9900
9901    if !due.is_empty() {
9902      if let Some(admission) = admission.as_mut() {
9903        admission.mark_published();
9904      }
9905      for waker in due {
9906        wake_waker_contained(waker);
9907      }
9908      continue;
9909    }
9910    // Never retain an admission while sleeping: it protects only the
9911    // remove-then-wake publication window.
9912    drop(admission);
9913
9914    match deadline {
9915      Some(deadline) => {
9916        let now = Instant::now();
9917        if deadline > now {
9918          parker.park_timeout(deadline - now);
9919        }
9920      }
9921      None => parker.park(),
9922    }
9923  }
9924}
9925
9926#[cfg(napi_runtime_os_threads)]
9927impl MultiThreadExecutor {
9928  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
9929  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
9930  /// shutdown, and set here immediately when the heap is already closed (the
9931  /// caller re-checks it after registering, so a post-shutdown poll resolves
9932  /// instead of parking forever).
9933  fn register_timer(
9934    self: &Arc<Self>,
9935    id: TimerId,
9936    deadline: Instant,
9937    waker: Waker,
9938    fired: &Arc<AtomicBool>,
9939  ) {
9940    let mut admission = self.begin_deadlock_admission();
9941    #[cfg(test)]
9942    run_timer_registration_admission_test_hook();
9943    // Establish the service thread before publishing the entry. If thread
9944    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
9945    // heap state exists for Drop to miss.
9946    if let Err(error) = self.timers.ensure_started() {
9947      let _ = catch_unwind_contained(|| drop(waker));
9948      panic!("{error}");
9949    }
9950    let (became_earliest, published, discarded_waker) = {
9951      let mut inner = self
9952        .timers
9953        .inner
9954        .lock()
9955        .unwrap_or_else(std::sync::PoisonError::into_inner);
9956      if inner.closed || fired.load(Ordering::SeqCst) {
9957        fired.store(true, Ordering::SeqCst);
9958        (false, false, Some(waker))
9959      } else if let Some(entry) = inner.entries.get_mut(&id) {
9960        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
9961        (
9962          false,
9963          false,
9964          Some(std::mem::replace(&mut entry.waker, waker)),
9965        )
9966      } else {
9967        let previous = inner.next_deadline();
9968        inner.entries.insert(
9969          id,
9970          HeapTimerEntry {
9971            waker,
9972            fired: Arc::clone(fired),
9973          },
9974        );
9975        inner.queue.push(deadline, id);
9976        (
9977          previous.is_none_or(|previous| deadline < previous),
9978          true,
9979          None,
9980        )
9981      }
9982    };
9983    if published && let Some(admission) = admission.as_mut() {
9984      admission.mark_published();
9985    }
9986    if let Some(waker) = discarded_waker {
9987      // RawWaker destruction is user code and may re-enter timer cancellation.
9988      // Keep it outside the heap mutex and contain hostile destructors.
9989      let _ = catch_unwind_contained(|| drop(waker));
9990    }
9991    if became_earliest {
9992      // Re-arm the lazy service thread and any cooperative timer wait.
9993      self.note_earlier_timer();
9994    }
9995  }
9996
9997  /// Best-effort cancel: remove the entry and its indexed heap node.
9998  /// If cancellation changes the earliest deadline, wake every timer-bounded
9999  /// driver so no pool worker remains parked on the stale deadline.
10000  fn cancel_timer(&self, id: TimerId) {
10001    let (removed, earliest_changed) = {
10002      let mut inner = self
10003        .timers
10004        .inner
10005        .lock()
10006        .unwrap_or_else(std::sync::PoisonError::into_inner);
10007      let previous = inner.next_deadline();
10008      let removed = inner.entries.remove(&id);
10009      if removed.is_some() {
10010        let queued = inner.queue.remove(id);
10011        debug_assert!(
10012          queued.is_some(),
10013          "a live timer entry must have one indexed deadline node"
10014        );
10015      }
10016      let current = inner.next_deadline();
10017      (removed, previous != current)
10018    };
10019    if let Some(entry) = removed {
10020      // A RawWaker destructor may cancel another timer. Never run it while
10021      // holding the heap lock.
10022      let _ = catch_unwind_contained(|| drop(entry));
10023    }
10024    if earliest_changed {
10025      self.timers.parker.unpark();
10026      self.parked_drivers.wake_all();
10027    }
10028  }
10029
10030  /// Earliest pending deadline (None when the heap is empty or closed).
10031  fn next_timer_deadline(&self) -> Option<Instant> {
10032    self
10033      .timers
10034      .inner
10035      .lock()
10036      .unwrap_or_else(std::sync::PoisonError::into_inner)
10037      .next_deadline()
10038  }
10039
10040  /// Pop and wake everything due at/before now. Wakers are collected under
10041  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
10042  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
10043  /// the lock, before the wake, so the woken poll observes it.
10044  fn fire_due_timers(&self) {
10045    let mut admission = self.begin_deadlock_admission();
10046    let due: Vec<Waker> = {
10047      let mut inner = self
10048        .timers
10049        .inner
10050        .lock()
10051        .unwrap_or_else(std::sync::PoisonError::into_inner);
10052      let now = Instant::now();
10053      let mut due = Vec::new();
10054      while let Some((deadline, id)) = inner.queue.peek() {
10055        if deadline > now {
10056          break;
10057        }
10058        inner.queue.pop();
10059        if let Some(entry) = inner.entries.remove(&id) {
10060          entry.fired.store(true, Ordering::SeqCst);
10061          due.push(entry.waker);
10062        }
10063      }
10064      due
10065    };
10066    if !due.is_empty()
10067      && let Some(admission) = admission.as_mut()
10068    {
10069      admission.mark_published();
10070    }
10071    for waker in due {
10072      // Wakers may originate outside this executor. Isolate user RawWaker
10073      // implementations so a panic cannot unwind the dedicated timekeeper.
10074      wake_waker_contained(waker);
10075    }
10076  }
10077
10078  /// The earliest pending deadline moved up (or the heap went non-empty).
10079  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
10080  /// whose current park may still reflect the previous deadline.
10081  fn note_earlier_timer(self: &Arc<Self>) {
10082    self.timers.parker.unpark();
10083    self.parked_drivers.wake_one();
10084  }
10085
10086  /// Drain-fire every pending heap timer and poison later registrations.
10087  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
10088  /// sleeps resolve on their next poll; the woken tasks are
10089  /// scheduled through the normal path, so a coordinator awaiting a debounce
10090  /// completes its close sequence instead of parking forever.
10091  fn shutdown_timers(&self) {
10092    let mut admission = self.begin_deadlock_admission();
10093    let wakers = self.timers.close();
10094    if !wakers.is_empty()
10095      && let Some(admission) = admission.as_mut()
10096    {
10097      admission.mark_published();
10098    }
10099    for waker in wakers {
10100      // A user-provided RawWaker may panic. Shutdown is a lifecycle
10101      // transition, so one hostile wake must not leave the controller stuck
10102      // in `Stopping` and prevent every later restart.
10103      wake_waker_contained(waker);
10104    }
10105    self.timers.parker.unpark();
10106  }
10107
10108  fn begin_shutdown(&self) {
10109    let _generation = RuntimeGenerationGuard::enter(self.generation);
10110    // Publish stop before taking the blocking queue. The final deadlock verdict
10111    // holds stop publication while rechecking that queue, so queue -> stop
10112    // would invert its lock order. A concurrent taker either claims while stop
10113    // is still false or observes stop after this publication.
10114    self.stop.begin_shutdown();
10115    let queued = {
10116      let mut queue = self
10117        .blocking_queue
10118        .lock()
10119        .unwrap_or_else(std::sync::PoisonError::into_inner);
10120      // The blocking FIFO mutex closes queue admission. `take_blocking` either
10121      // claimed a job before stop publication or observes stop/closure.
10122      queue.closed = true;
10123      queue.take_all()
10124    };
10125    // Captured values belong to user code and may panic in Drop. Retire each
10126    // rejected closure independently so one destructor cannot strand the
10127    // runtime in `Stopping` or prevent the remaining queued jobs from being
10128    // cancelled.
10129    for queued in queued {
10130      let _ = catch_unwind_contained(|| drop(queued));
10131    }
10132    self.shutdown_timers();
10133  }
10134
10135  /// Non-blocking read of the predicate `wait_until_scheduler_idle` sleeps on.
10136  /// The timer join is deliberately excluded: it is a thread lifetime, not
10137  /// work a host can let finish by yielding.
10138  fn scheduler_work_pending(&self) -> bool {
10139    self.active_drainers.load(Ordering::Acquire) != 0
10140  }
10141
10142  fn wait_until_scheduler_idle(&self) {
10143    self.timers.join();
10144    let mut idle = self
10145      .scheduler_idle_lock
10146      .lock()
10147      .unwrap_or_else(std::sync::PoisonError::into_inner);
10148    // Register BEFORE the first predicate evaluation and stay registered for
10149    // the whole wait. `finish_draining` reads this count under the same mutex,
10150    // so any drainer that retires after this increment is published either
10151    // sees `> 0` and wakes us, or had already decremented `active_drainers`
10152    // before we read the predicate -- in which case we never sleep.
10153    *idle += 1;
10154    while self.active_drainers.load(Ordering::Acquire) != 0 {
10155      idle = self
10156        .scheduler_idle
10157        .wait(idle)
10158        .unwrap_or_else(std::sync::PoisonError::into_inner);
10159    }
10160    *idle -= 1;
10161  }
10162}
10163
10164/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
10165/// (or early on runtime shutdown drain-fire). Dropping it cancels the
10166/// underlying registration -- the `tokio::select!` losing-arm semantics the
10167/// watch coordinator's debounce loop relies on.
10168pub struct Sleep {
10169  deadline: Instant,
10170  inner: SleepInner,
10171}
10172
10173enum SleepInner {
10174  #[cfg(napi_runtime_os_threads)]
10175  Heap(HeapSleep),
10176  Host(HostSleep),
10177}
10178
10179#[cfg(napi_runtime_os_threads)]
10180struct HeapSleep {
10181  id: TimerId,
10182  executor: Weak<MultiThreadExecutor>,
10183  fired: Arc<AtomicBool>,
10184  registered: bool,
10185}
10186
10187struct HostSleep {
10188  id: TimerId,
10189  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
10190  /// registrant. Arming redundant host timers prevents a live but starved
10191  /// environment from owning the sleep's only wake source.
10192  drivers: Arc<TimerDriverRegistry>,
10193  /// Drivers this sleep last armed on. The first responsive fire wakes the
10194  /// sleep; completion cancels every losing arm, including pre-delivery arms
10195  /// whose binding relay retains a cancellation tombstone.
10196  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10197  /// The executor's registry, held so the FIRST poll can arm the wake-token
10198  /// at the same moment `driver.register` arms the host timer.
10199  registry: Arc<HostTimerRegistry>,
10200  fired: Arc<AtomicBool>,
10201  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
10202  /// request a re-poll without retaining or invoking a task waker under the
10203  /// driver-registry locks.
10204  repoll: Arc<HostTimerRepoll>,
10205  /// Retires this sleep's wake-token in the executor's registry exactly once
10206  /// (created at first poll alongside the host registration, taken on
10207  /// completion, dropped with the Sleep on cancellation). `None` before the
10208  /// first poll: an unpolled Sleep holds no wake-token.
10209  pending: Option<HostTimerPendingGuard>,
10210}
10211
10212fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10213  let _ = catch_unwind_contained(|| driver.cancel(id));
10214  let _ = catch_unwind_contained(|| drop(driver));
10215}
10216
10217fn cancel_host_timer_drivers(
10218  id: TimerId,
10219  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10220) {
10221  for (_, driver) in armed.drain() {
10222    cancel_host_timer_driver(id, driver);
10223  }
10224}
10225
10226impl Future for Sleep {
10227  type Output = ();
10228
10229  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10230    let deadline = self.deadline;
10231    let this = self.get_mut();
10232    match &mut this.inner {
10233      #[cfg(napi_runtime_os_threads)]
10234      SleepInner::Heap(heap) => {
10235        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10236          if heap.registered {
10237            // Wall-clock completion may beat the (busy) timekeeper's fire:
10238            // retire the registration so it cannot wake spuriously later.
10239            if let Some(executor) = heap.executor.upgrade() {
10240              executor.cancel_timer(heap.id);
10241            }
10242            heap.registered = false;
10243          }
10244          return Poll::Ready(());
10245        }
10246        let Some(executor) = heap.executor.upgrade() else {
10247          heap.fired.store(true, Ordering::SeqCst);
10248          heap.registered = false;
10249          return Poll::Ready(());
10250        };
10251        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10252        heap.registered = true;
10253        // Registration on a closed (shut-down) heap stores nothing and sets
10254        // `fired` instead: re-check so this poll resolves rather than parking
10255        // a closing runtime forever.
10256        if heap.fired.load(Ordering::SeqCst) {
10257          heap.registered = false;
10258          return Poll::Ready(());
10259        }
10260        Poll::Pending
10261      }
10262      SleepInner::Host(host) => {
10263        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10264          cancel_host_timer_drivers(host.id, &mut host.armed);
10265          host.pending.take();
10266          return Poll::Ready(());
10267        }
10268        // Clone both wakers before publishing either one. If the second clone
10269        // unwinds, destroy the first clone behind its own panic boundary before
10270        // resuming the original panic, so a hostile RawWaker destructor cannot
10271        // turn clone failure into a process-aborting double panic.
10272        let repoll_waker = cx.waker().clone();
10273        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10274          Ok(waker) => waker,
10275          Err(payload) => {
10276            let _ = catch_unwind_contained(|| drop(repoll_waker));
10277            resume_unwind(payload);
10278          }
10279        };
10280        host.repoll.register(repoll_waker);
10281        if !host.registry.register(
10282          host.id,
10283          deadline,
10284          registry_waker,
10285          &host.fired,
10286          &host.drivers,
10287          &host.repoll,
10288        ) {
10289          cancel_host_timer_drivers(host.id, &mut host.armed);
10290          if host.pending.take().is_none() {
10291            host.repoll.close();
10292          }
10293          return Poll::Ready(());
10294        }
10295        if host.pending.is_none() {
10296          // FIRST poll: the registry entry appears at the same moment the
10297          // host timer is armed below -- and only then. An unpolled Sleep has
10298          // no host callback behind it, so counting it at creation would
10299          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
10300          // mislabel the threadless certain diagnostic as timer-backed. The
10301          // certain panic itself no longer consults pending timers for its
10302          // verdict.
10303          host.drivers.register_pending(host.id, &host.repoll);
10304          host.pending = Some(HostTimerPendingGuard {
10305            registry: Arc::clone(&host.registry),
10306            drivers: Arc::clone(&host.drivers),
10307            repoll: Arc::clone(&host.repoll),
10308            id: host.id,
10309          });
10310        }
10311        // Re-select every LIVE driver on every poll. A live but starved
10312        // environment may accept a TSFN call and then stop servicing its
10313        // event loop, so no single host may own the only timer arm.
10314        let current = host.drivers.live();
10315        assert!(
10316          !current.as_slice().is_empty(),
10317          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10318           that registered timer drivers (via \
10319           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10320           one per importing env through `registerTimerHost`) have all been torn down, so this \
10321           `sleep_until` can never be woken."
10322        );
10323        // Publish cancellation ownership for the complete live snapshot before
10324        // invoking any arbitrary callback. An earlier driver may unregister a
10325        // later one and then unwind; both the Sleep and the contained snapshot
10326        // must still own that later driver while the original panic propagates.
10327        // See the implementation notes in this crate's README.
10328        let missing = current
10329          .as_slice()
10330          .iter()
10331          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10332          .count();
10333        host.armed.reserve(missing);
10334        for (current_id, current_driver) in current.as_slice() {
10335          host
10336            .armed
10337            .entry(*current_id)
10338            .or_insert_with(|| Arc::clone(current_driver));
10339        }
10340        for (current_id, _) in current.as_slice() {
10341          let armed_driver = host
10342            .armed
10343            .get(current_id)
10344            .expect("every live timer driver must have cancellation ownership before callbacks");
10345          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10346        }
10347        let current_ids = current
10348          .as_slice()
10349          .iter()
10350          .map(|(current_id, _)| *current_id)
10351          .collect::<FxHashSet<_>>();
10352        let retired = host
10353          .armed
10354          .keys()
10355          .filter(|armed_id| !current_ids.contains(armed_id))
10356          .copied()
10357          .collect::<Vec<_>>();
10358        for retired in retired {
10359          let old_driver = host
10360            .armed
10361            .remove(&retired)
10362            .expect("the retired timer driver must remain armed");
10363          cancel_host_timer_driver(host.id, old_driver);
10364        }
10365        Poll::Pending
10366      }
10367    }
10368  }
10369}
10370
10371impl Drop for Sleep {
10372  fn drop(&mut self) {
10373    match &mut self.inner {
10374      #[cfg(napi_runtime_os_threads)]
10375      SleepInner::Heap(heap) => {
10376        if heap.registered
10377          && let Some(executor) = heap.executor.upgrade()
10378        {
10379          executor.cancel_timer(heap.id);
10380        }
10381      }
10382      SleepInner::Host(host) => {
10383        // Close directly as an unwind backstop even when first-poll setup
10384        // failed before `pending` could install its normal cleanup guard.
10385        host.repoll.close();
10386        cancel_host_timer_drivers(host.id, &mut host.armed);
10387        // `pending` (if still held) drops here and retires the wake-token.
10388      }
10389    }
10390  }
10391}
10392
10393/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
10394/// the executor-owned heap; CurrentThread requires a LIVE host driver
10395/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
10396/// missing driver must never become a silent never-firing debounce).
10397pub fn sleep_until(deadline: Instant) -> Sleep {
10398  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10399}
10400
10401/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
10402/// build executors and driver registries locally and must not touch the
10403/// process-global ones).
10404fn make_sleep(
10405  backend: &RuntimeBackend,
10406  host_drivers: &Arc<TimerDriverRegistry>,
10407  deadline: Instant,
10408) -> Sleep {
10409  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10410  match &backend.executor {
10411    #[cfg(napi_runtime_os_threads)]
10412    RuntimeExecutor::MultiThread(executor) => Sleep {
10413      deadline,
10414      inner: SleepInner::Heap(HeapSleep {
10415        id,
10416        executor: Arc::downgrade(executor),
10417        fired: Arc::new(AtomicBool::new(false)),
10418        registered: false,
10419      }),
10420    },
10421    RuntimeExecutor::CurrentThread(executor) => {
10422      // Fail loud at CREATION when no live driver exists (the
10423      // never-registered case gets its diagnostic at the sleep_until call
10424      // site, not on some later poll). The driver is NOT pinned here: each
10425      // poll re-selects every live registrant, so a driver dying while this
10426      // sleep is in flight is cancelled while the remaining hosts stay armed
10427      // -- and if every driver dies mid-flight, the poll path has its own loud
10428      // panic.
10429      assert!(
10430        host_drivers.has_live_driver(),
10431        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10432         single-thread flavor delegates timers to the host event loop. Install one via \
10433         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10434         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10435      );
10436      // The registry entry is created at FIRST POLL (when `driver.register`
10437      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
10438      // -- nothing was handed to the host loop -- so counting it at creation
10439      // would wrongly veto the threaded deadline verdict and mislabel the
10440      // threadless certain diagnostic as timer-backed. First-poll accounting
10441      // has no false-panic side.
10442      Sleep {
10443        deadline,
10444        inner: SleepInner::Host(HostSleep {
10445          id,
10446          drivers: Arc::clone(host_drivers),
10447          armed: FxHashMap::default(),
10448          registry: Arc::clone(&executor.host_timers),
10449          fired: Arc::new(AtomicBool::new(false)),
10450          repoll: Arc::new(HostTimerRepoll::default()),
10451          pending: None,
10452        }),
10453      }
10454    }
10455  }
10456}
10457
10458#[derive(Clone)]
10459enum RuntimeExecutor {
10460  CurrentThread(Arc<CurrentThreadExecutor>),
10461  #[cfg(napi_runtime_os_threads)]
10462  MultiThread(Arc<MultiThreadExecutor>),
10463}
10464
10465enum WeakRuntimeExecutor {
10466  CurrentThread(Weak<CurrentThreadExecutor>),
10467  #[cfg(napi_runtime_os_threads)]
10468  MultiThread(Weak<MultiThreadExecutor>),
10469}
10470
10471impl WeakRuntimeExecutor {
10472  fn schedule(&self, runnable: Runnable) {
10473    match self {
10474      Self::CurrentThread(executor) => {
10475        if let Some(executor) = executor.upgrade() {
10476          executor.schedule(runnable);
10477        }
10478      }
10479      #[cfg(napi_runtime_os_threads)]
10480      Self::MultiThread(executor) => {
10481        if let Some(executor) = executor.upgrade() {
10482          executor.schedule(runnable);
10483        }
10484      }
10485    }
10486  }
10487}
10488
10489#[derive(Clone)]
10490struct RuntimeBackend {
10491  work: Arc<GenerationWork>,
10492  executor: RuntimeExecutor,
10493}
10494
10495impl RuntimeBackend {
10496  fn new(
10497    options: &RuntimeOptions,
10498    metrics: Arc<RuntimeMetrics>,
10499  ) -> Result<Self, RuntimeConfigError> {
10500    #[cfg(test)]
10501    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10502      return Err(RuntimeConfigError(
10503        "injected async runtime backend creation failure".to_string(),
10504      ));
10505    }
10506
10507    let work = GenerationWork::new();
10508    let stop = Arc::new(GenerationStop::default());
10509    let executor = match options.flavor {
10510      RuntimeFlavor::CurrentThread => {
10511        let executor = CurrentThreadExecutor::with_generation(
10512          metrics,
10513          THREADLESS_BUILD,
10514          options.park_deadline,
10515          work.id,
10516          Arc::clone(&stop),
10517        );
10518        executor.attach_generation_work(&work);
10519        RuntimeExecutor::CurrentThread(Arc::new(executor))
10520      }
10521      RuntimeFlavor::MultiThread => {
10522        #[cfg(napi_runtime_os_threads)]
10523        {
10524          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10525            options, metrics, work.id, stop,
10526          )?))
10527        }
10528        #[cfg(not(napi_runtime_os_threads))]
10529        {
10530          let _ = metrics;
10531          return Err(RuntimeConfigError(
10532            "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10533             (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10534              .to_string(),
10535          ));
10536        }
10537      }
10538    };
10539    Ok(Self { work, executor })
10540  }
10541
10542  #[cfg(test)]
10543  fn from_executor(executor: RuntimeExecutor) -> Self {
10544    let work = GenerationWork::new();
10545    if let RuntimeExecutor::CurrentThread(executor) = &executor {
10546      executor.attach_generation_work(&work);
10547    }
10548    Self { work, executor }
10549  }
10550
10551  fn generation(&self) -> u64 {
10552    self.work.id
10553  }
10554
10555  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10556    match &self.executor {
10557      RuntimeExecutor::CurrentThread(executor) => {
10558        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10559      }
10560      #[cfg(napi_runtime_os_threads)]
10561      RuntimeExecutor::MultiThread(executor) => {
10562        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10563      }
10564    }
10565  }
10566
10567  /// First schedule of a freshly submitted task. This runs on the stack of the
10568  /// caller of `spawn`, never on a waker's; see
10569  /// `CurrentThreadExecutor::schedule_submission`.
10570  fn schedule_submission(&self, runnable: Runnable) {
10571    match &self.executor {
10572      RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10573      #[cfg(napi_runtime_os_threads)]
10574      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10575    }
10576  }
10577
10578  fn spawn_registered_blocking<F, T>(
10579    &self,
10580    function: F,
10581    registration: GenerationWorkGuard,
10582    metrics: &Arc<RuntimeMetrics>,
10583  ) -> JoinHandle<T>
10584  where
10585    F: FnOnce() -> T + Send + 'static,
10586    T: Send + 'static,
10587  {
10588    match &self.executor {
10589      RuntimeExecutor::CurrentThread(executor) => {
10590        let _ = metrics;
10591        executor.schedule_blocking(function, registration)
10592      }
10593      #[cfg(napi_runtime_os_threads)]
10594      RuntimeExecutor::MultiThread(executor) => {
10595        let generation = self.generation();
10596        let _ = metrics;
10597        let function = RegisteredBlockingFunction::new(function, registration, generation);
10598        executor.schedule_blocking_result(move || function.run())
10599      }
10600    }
10601  }
10602
10603  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10604    match &self.executor {
10605      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10606      #[cfg(napi_runtime_os_threads)]
10607      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10608    }
10609  }
10610
10611  fn generation_stop(&self) -> Arc<GenerationStop> {
10612    match &self.executor {
10613      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10614      #[cfg(napi_runtime_os_threads)]
10615      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10616    }
10617  }
10618
10619  fn begin_shutdown(&self) {
10620    let _generation = RuntimeGenerationGuard::enter(self.generation());
10621    self.work.close_and_abort();
10622    match &self.executor {
10623      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10624      #[cfg(napi_runtime_os_threads)]
10625      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10626    }
10627  }
10628
10629  fn wait_until_idle(&self) {
10630    match &self.executor {
10631      RuntimeExecutor::CurrentThread(executor) => {
10632        // A wake in flight across `begin_shutdown` is rejected by the closed
10633        // queue and parked in `queue.rejected` with its guard still held, and
10634        // a failure transition racing `begin_shutdown` from a waker's stack
10635        // moves the queued guards there too; retire them here, on this stack,
10636        // until the generation is idle.
10637        self.work.wait_until_idle_retiring(
10638          || executor.has_rejected_work(),
10639          || executor.retire_rejected_work(),
10640        );
10641        executor.wait_until_scheduler_idle();
10642      }
10643      #[cfg(napi_runtime_os_threads)]
10644      RuntimeExecutor::MultiThread(executor) => {
10645        self.work.wait_until_idle();
10646        executor.wait_until_scheduler_idle();
10647      }
10648    }
10649  }
10650
10651  /// Non-blocking counterpart of `wait_until_idle`: whether that call would
10652  /// have to wait for anything. Same sources, read without sleeping --
10653  /// registered async/blocking work, work the executor still owes a
10654  /// cancellation, and an in-flight drain or host turn.
10655  ///
10656  /// Joining the worker threads (phase 2's `wait_for_all_workers`) is NOT a
10657  /// source here: a Rayon worker exits when the last backend handle is
10658  /// dropped, which only phase 2 does, so `WorkerLifecycle::remaining` is
10659  /// non-zero for the whole window between the phases by construction and
10660  /// would make this poll never answer `false`. That join waits on thread
10661  /// teardown, never on user work, so yielding to the host cannot help it.
10662  ///
10663  /// `read` decides whether the sources that live under a lock may wait for
10664  /// it; the MultiThread scheduler source is an atomic either way.
10665  fn work_pending(&self, read: PendingRead) -> bool {
10666    if self.work.has_active_work(read) {
10667      return true;
10668    }
10669    match &self.executor {
10670      RuntimeExecutor::CurrentThread(executor) => {
10671        executor.rejected_work_pending(read) || executor.scheduler_work_pending(read)
10672      }
10673      #[cfg(napi_runtime_os_threads)]
10674      RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10675    }
10676  }
10677
10678  #[cfg(napi_runtime_os_threads)]
10679  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10680    match &self.executor {
10681      RuntimeExecutor::CurrentThread(_) => None,
10682      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10683    }
10684  }
10685
10686  fn stop_identity(&self) -> RuntimeStopIdentity {
10687    RuntimeStopIdentity {
10688      generation: self.generation(),
10689      #[cfg(napi_runtime_os_threads)]
10690      executor_id: match &self.executor {
10691        RuntimeExecutor::CurrentThread(_) => None,
10692        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10693      },
10694    }
10695  }
10696
10697  fn is_current(&self) -> bool {
10698    self.stop_identity().is_current()
10699  }
10700}
10701
10702#[derive(Clone, Copy)]
10703struct RuntimeStopIdentity {
10704  generation: u64,
10705  #[cfg(napi_runtime_os_threads)]
10706  executor_id: Option<u64>,
10707}
10708
10709impl RuntimeStopIdentity {
10710  fn is_current(self) -> bool {
10711    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10712      return true;
10713    }
10714    #[cfg(napi_runtime_os_threads)]
10715    if let Some(executor_id) = self.executor_id
10716      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10717        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10718    {
10719      return true;
10720    }
10721    false
10722  }
10723}
10724
10725enum RuntimeLifecycle {
10726  Initial,
10727  StoppedBeforeFirstUse,
10728  Running(RuntimeBackend),
10729  Stopping(RuntimeStopIdentity),
10730  StoppingWithoutBackend(ZeroBackendShutdownTarget),
10731  Stopped,
10732}
10733
10734#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10735enum ZeroBackendShutdownTarget {
10736  StoppedBeforeFirstUse,
10737  Stopped,
10738}
10739
10740impl ZeroBackendShutdownTarget {
10741  fn into_lifecycle(self) -> RuntimeLifecycle {
10742    match self {
10743      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10744      Self::Stopped => RuntimeLifecycle::Stopped,
10745    }
10746  }
10747}
10748
10749struct RuntimeState {
10750  options: RuntimeOptions,
10751  lifecycle: RuntimeLifecycle,
10752  rejected_drops: usize,
10753  /// Handoff between the two shutdown phases, `None` unless a
10754  /// `begin_shutdown` is outstanding. `RuntimeLifecycle::Stopping` carries no
10755  /// backend, so the single-call `shutdown` keeps it on its own stack; the
10756  /// split needs somewhere to park it across the host's yield window.
10757  ///
10758  /// Scoped to the generation the phase 1 was opened against: a successful
10759  /// `start` publishes a fresh one and drops it
10760  /// (`forget_shutdown_handoff`).
10761  ///
10762  /// A `ShutdownDrain::Backend` handoff is taken: whoever finds it takes it,
10763  /// drains it and publishes the stop; everybody else answers off the
10764  /// lifecycle. A `ShutdownDrain::ZeroBackend` handoff is not -- there is no
10765  /// backend to own. Nobody takes it. Every `finish_shutdown` that finds it
10766  /// waits on `rejected_drops`, and the one that observes zero clears the
10767  /// slot together with the publication, under one lock hold. Either way the
10768  /// handoff carries no owner and no credit -- see `phase_ever_opened`.
10769  ///
10770  /// The slot never goes empty while a stop is still owed. For `Backend` the
10771  /// taker swaps the handoff for `ShutdownDrain::Joining`, which stays until
10772  /// `Stopped` is published under this same lock. For `ZeroBackend` the
10773  /// handoff itself stays, untouched, until the target lifecycle is published
10774  /// under that lock. That keeps the non-blocking surfaces
10775  /// (`begin_shutdown`'s re-entry arm, `runtime_work_pending`) truthful for
10776  /// the whole wait instead of reading like a runtime with nothing pending.
10777  draining: Option<ShutdownDrain>,
10778  /// Sticky: `true` once any `begin_shutdown` has OPENED a phase (any arm that
10779  /// parks a handoff, `Backend` or `Settled`). Never cleared -- not by a
10780  /// finish, not by a `start`, not by a restart.
10781  ///
10782  /// It answers the one question `finish_shutdown` cannot answer from
10783  /// `draining` and the lifecycle alone. An empty handoff slot on a runtime
10784  /// that has never announced a stop is a host bug (phase 2 called on its
10785  /// own); an empty slot on a runtime that has announced one only means
10786  /// somebody else already completed, dropped or superseded it, which phase 2
10787  /// reports as `Ok(())`.
10788  ///
10789  /// A counter was tried here and is wrong by construction: the credit is
10790  /// spent by whichever thread performs the join, NOT by the thread that
10791  /// opened the phase. A concurrent in-protocol `finish_shutdown` therefore
10792  /// pairs off a single-call `shutdown`'s own phase, and that `shutdown` is
10793  /// then told its stop was never started -- 8.2% of the time on
10794  /// CurrentThread, after it had already published the stop. `(begin, start)`
10795  /// pairs leak credits on top of that, turning later orphan finishes into
10796  /// `Ok`. A bool can neither race nor leak.
10797  phase_ever_opened: bool,
10798}
10799
10800impl RuntimeState {
10801  /// Park an accepted phase 1's handoff and record that a stop has been
10802  /// announced. Both halves are set under the one lock every opening arm
10803  /// already holds, so no phase can be open without the flag.
10804  fn open_phase_one(&mut self, drain: ShutdownDrain) {
10805    self.phase_ever_opened = true;
10806    self.draining = Some(drain);
10807  }
10808
10809  /// Drop any parked handoff. Called by every path that publishes a
10810  /// generation (`start`, and `backend_locked`'s lazy first backend): a fresh
10811  /// generation owes nothing to a handoff opened against an earlier one.
10812  ///
10813  /// Dropping `draining` here is what keeps a stale handoff from answering
10814  /// for the new generation -- a surviving `Settled` would make the next
10815  /// `begin_shutdown` return from its outstanding-handoff arm without
10816  /// stopping anything, let `finish_shutdown` consume it and report `Ok(())`,
10817  /// and pin `runtime_work_pending` to `false`, so a `shutdown` would claim
10818  /// success while this generation kept running, kept accepting work and kept
10819  /// its MultiThread workers unjoined.
10820  ///
10821  /// It does NOT clear `phase_ever_opened`. Dropping the handoff answers the
10822  /// stale-handoff question completely; whether phase 2 may be called at all
10823  /// is a different question, and a `start` does not turn an outstanding phase
10824  /// 2 into a host bug -- it turns it into a no-op.
10825  fn forget_shutdown_handoff(&mut self) {
10826    // Only a settled handoff can ever reach here: `ShutdownDrain::Backend` is
10827    // parked together with the published `Stopping` and `Joining` replaces it
10828    // until `Stopped` is published, and every `start` arm waits out
10829    // `Stopping` instead of publishing over it. `ZeroBackend` is the same
10830    // argument one lifecycle over: it is parked together with the published
10831    // `StoppingWithoutBackend`, every `start` arm waits that out, and
10832    // `backend_locked` refuses to build a backend under it.
10833    debug_assert!(
10834      !matches!(
10835        self.draining,
10836        Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10837      ),
10838      "a start must never publish over an outstanding backend drain"
10839    );
10840    self.draining = None;
10841  }
10842}
10843
10844/// What `RuntimeController::finish_shutdown` still owes an outstanding
10845/// `begin_shutdown`.
10846enum ShutdownDrain {
10847  /// `Stopping(identity)` is published and this backend is waiting to be
10848  /// drained and joined. Nobody owns it yet, so it is still up for grabs.
10849  Backend(RuntimeBackend),
10850  /// A `finish_shutdown` has TAKEN the handoff above and is inside the join.
10851  /// The marker stays until that thread publishes `Stopped`: the lifecycle
10852  /// reads `Stopping` for the whole window, so an empty slot would make a
10853  /// re-entering phase 1 park on `lifecycle_changed` and
10854  /// `runtime_work_pending` answer `false` about work the finisher is
10855  /// demonstrably still waiting for.
10856  ///
10857  /// The backend rides along only for the FIRST half of that window
10858  /// (`wait_until_idle`), which is the half that can still report work. The
10859  /// drainer clears it to `None` before releasing its own last reference,
10860  /// because that release is what drops the executor and lets the workers
10861  /// exit -- a marker that kept a clone alive would wedge the worker join
10862  /// forever (observed: `wait_for_all_workers` never returning). By then
10863  /// `wait_until_idle` has returned, so `None` is the truthful verdict for
10864  /// the remaining half.
10865  ///
10866  /// It is NOT up for grabs -- a second `finish_shutdown` that finds it waits
10867  /// for the publication and reports it, exactly as it does for an empty slot
10868  /// on a runtime that has announced a stop.
10869  Joining(Option<RuntimeBackend>),
10870  /// Phase 1 completed a zero-backend transition (`Initial`, or an already
10871  /// stopped runtime): phase 2 has nothing left to drain.
10872  Settled,
10873  /// Phase 1 announced a zero-backend stop (`Initial`, or an already stopped
10874  /// runtime) that still owes the retirement of one or more
10875  /// rejected-submission destructors before `target` may be published.
10876  ///
10877  /// Those destructors are arbitrary user `Drop` code running on the
10878  /// REJECTING thread, so waiting for them is exactly what phase 1 must not
10879  /// do: on the threaded WASI artifact that thread can be waiting for a
10880  /// JavaScript turn the phase-1 caller can only give by returning. The
10881  /// lifecycle reads `StoppingWithoutBackend(target)` for the whole window,
10882  /// exactly as `Stopping` + `Backend` do for a generation with a backend, and
10883  /// phase 2 does the wait and publishes the target.
10884  ///
10885  /// It is NOT taken out of the slot the way `Backend` is -- there is nothing
10886  /// to own -- so every `finish_shutdown` that finds it re-reads the slot
10887  /// after each wake and only the one that observes `rejected_drops == 0`
10888  /// under the published target publishes it.
10889  ZeroBackend(ZeroBackendShutdownTarget),
10890}
10891
10892#[cfg(test)]
10893thread_local! {
10894  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10895    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10896  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10897    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10898  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10899    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10900  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10901    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10902  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10903    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10904  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10905    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10906  static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10907    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10908  static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10909    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10910  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10911    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10912}
10913
10914#[cfg(test)]
10915fn run_before_runtime_submission_lock_test_hook() {
10916  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10917  {
10918    hook();
10919  }
10920}
10921
10922#[cfg(test)]
10923fn run_after_async_work_registration_test_hook() {
10924  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10925  {
10926    hook();
10927  }
10928}
10929
10930#[cfg(test)]
10931fn run_after_blocking_work_registration_test_hook() {
10932  if let Some(hook) =
10933    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10934  {
10935    hook();
10936  }
10937}
10938
10939#[cfg(test)]
10940fn run_after_current_thread_host_admission_test_hook() {
10941  if let Some(hook) =
10942    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10943  {
10944    hook();
10945  }
10946}
10947
10948#[cfg(test)]
10949fn run_before_initial_start_wait_test_hook() {
10950  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10951    hook();
10952  }
10953}
10954
10955#[cfg(test)]
10956fn run_after_generation_stop_publication_test_hook() {
10957  if let Some(hook) =
10958    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10959  {
10960    hook();
10961  }
10962}
10963
10964/// Runs on the thread that TOOK the outstanding phase 1, after the handoff is
10965/// out of the state and before it waits: anything a test starts from here
10966/// provably arrives at the handshake second.
10967#[cfg(test)]
10968fn run_after_shutdown_drain_taken_test_hook() {
10969  if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10970    hook();
10971  }
10972}
10973
10974/// Runs inside the single-call `shutdown`, in the window between its two
10975/// halves where it holds no lock: this is exactly where a racing `start` can
10976/// land and drop the handoff phase 1 parked.
10977#[cfg(test)]
10978fn run_between_shutdown_phases_test_hook() {
10979  if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10980    hook();
10981  }
10982}
10983
10984thread_local! {
10985  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10986    const { std::cell::Cell::new(0) };
10987}
10988
10989struct RejectedSubmissionDropContext;
10990
10991impl RejectedSubmissionDropContext {
10992  fn enter() -> Self {
10993    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10994    Self
10995  }
10996
10997  fn is_current() -> bool {
10998    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10999  }
11000}
11001
11002impl Drop for RejectedSubmissionDropContext {
11003  fn drop(&mut self) {
11004    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
11005      depth.set(
11006        depth
11007          .get()
11008          .checked_sub(1)
11009          .expect("rejected submission drop depth underflow"),
11010      );
11011    });
11012  }
11013}
11014
11015struct RuntimeController {
11016  state: Mutex<RuntimeState>,
11017  lifecycle_changed: Condvar,
11018  metrics: Arc<RuntimeMetrics>,
11019  // Lock-free mirror of `state.lifecycle`'s backend, read by `fast_register`
11020  // without the `state` lock. Invariant: store `Some(b)` on every transition
11021  // INTO `Running(b)` and `None` on every transition OUT -- exactly 3 sites
11022  // today (lazy-create, start, shutdown). Any new Running-boundary write MUST
11023  // mirror here, or `fast_register` routes work onto a superseded backend.
11024  current_backend: ArcSwapOption<RuntimeBackend>,
11025}
11026
11027struct RejectedSubmissionGuard<'a> {
11028  controller: &'a RuntimeController,
11029}
11030
11031impl Drop for RejectedSubmissionGuard<'_> {
11032  fn drop(&mut self) {
11033    let mut state = self
11034      .controller
11035      .state
11036      .lock()
11037      .unwrap_or_else(std::sync::PoisonError::into_inner);
11038    state.rejected_drops = state
11039      .rejected_drops
11040      .checked_sub(1)
11041      .expect("rejected submission drop count underflow");
11042    self.controller.lifecycle_changed.notify_all();
11043  }
11044}
11045
11046impl RuntimeController {
11047  fn new() -> Self {
11048    let options = RuntimeOptions::default()
11049      .validate()
11050      .expect("default async runtime options must be valid");
11051    Self {
11052      state: Mutex::new(RuntimeState {
11053        options,
11054        lifecycle: RuntimeLifecycle::Initial,
11055        rejected_drops: 0,
11056        draining: None,
11057        phase_ever_opened: false,
11058      }),
11059      lifecycle_changed: Condvar::new(),
11060      metrics: Arc::new(RuntimeMetrics::default()),
11061      current_backend: ArcSwapOption::empty(),
11062    }
11063  }
11064
11065  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11066    let options = options.validate()?;
11067    let mut state = self
11068      .state
11069      .lock()
11070      .unwrap_or_else(std::sync::PoisonError::into_inner);
11071    Self::ensure_configuration_mutable(&state)?;
11072    state.options = options;
11073    Ok(())
11074  }
11075
11076  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11077    self.configure_partial_inner(patch, |_| {})
11078  }
11079
11080  fn configure_partial_inner(
11081    &self,
11082    patch: RuntimeOptionsPatch,
11083    after_merge: impl FnOnce(&RuntimeOptions),
11084  ) -> Result<(), RuntimeConfigError> {
11085    let mut state = self
11086      .state
11087      .lock()
11088      .unwrap_or_else(std::sync::PoisonError::into_inner);
11089    Self::ensure_configuration_mutable(&state)?;
11090
11091    let mut options = state.options.clone();
11092    patch.apply_to(&mut options);
11093    after_merge(&options);
11094    let options = options.validate()?;
11095    state.options = options;
11096    Ok(())
11097  }
11098
11099  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11100    if matches!(
11101      &state.lifecycle,
11102      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11103    ) {
11104      return Ok(());
11105    }
11106    Err(RuntimeConfigError(
11107      "the async runtime configuration is frozen; configure it before the first async call"
11108        .to_string(),
11109    ))
11110  }
11111
11112  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11113    let Some(active_generation) = active_runtime_generation() else {
11114      return Ok(());
11115    };
11116    let lifecycle_generation = match &state.lifecycle {
11117      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11118      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11119      RuntimeLifecycle::Initial
11120      | RuntimeLifecycle::StoppedBeforeFirstUse
11121      | RuntimeLifecycle::StoppingWithoutBackend(_)
11122      | RuntimeLifecycle::Stopped => None,
11123    };
11124    if lifecycle_generation == Some(active_generation) {
11125      return Ok(());
11126    }
11127    Err(RuntimeConfigError(
11128      "cannot access or control the async runtime from work owned by a retired generation"
11129        .to_string(),
11130    ))
11131  }
11132
11133  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11134    Self::ensure_active_generation_current(state)?;
11135    match &state.lifecycle {
11136      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11137      RuntimeLifecycle::StoppedBeforeFirstUse
11138      | RuntimeLifecycle::Stopping(_)
11139      | RuntimeLifecycle::StoppingWithoutBackend(_)
11140      | RuntimeLifecycle::Stopped => {
11141        return Err(RuntimeConfigError(
11142          "the async runtime is stopped; call start before submitting work".to_string(),
11143        ));
11144      }
11145      RuntimeLifecycle::Initial => {}
11146    }
11147    if state.rejected_drops != 0 {
11148      return Err(RuntimeConfigError(
11149        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11150      ));
11151    }
11152    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11153    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11154    state.forget_shutdown_handoff();
11155    self.current_backend.store(Some(Arc::new(backend.clone())));
11156    Ok(backend)
11157  }
11158
11159  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11160    let mut state = self
11161      .state
11162      .lock()
11163      .unwrap_or_else(std::sync::PoisonError::into_inner);
11164    self.backend_locked(&mut state)
11165  }
11166
11167  fn backend(&self) -> RuntimeBackend {
11168    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11169  }
11170
11171  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11172    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11173      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11174      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11175      RuntimeLifecycle::Initial
11176      | RuntimeLifecycle::StoppedBeforeFirstUse
11177      | RuntimeLifecycle::StoppingWithoutBackend(_)
11178      | RuntimeLifecycle::Stopped => None,
11179    });
11180    state.rejected_drops += 1;
11181    generation
11182  }
11183
11184  /// Lock-free spawn fast path. Returns Some((backend, registration)) when the
11185  /// runtime is Running, the caller's generation is current, and registration
11186  /// succeeds -- all without taking the outer `state` lock. Returns None (fall
11187  /// to the locked slow path) for any miss: no current backend, retired
11188  /// generation, or a closed generation. Staleness is safe: see the safety
11189  /// invariant -- try_register_* re-checks `closed`, and wait_until_idle already
11190  /// gates on any registration that slips in during shutdown.
11191  #[inline]
11192  fn fast_register<R>(
11193    &self,
11194    register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11195  ) -> Option<(Arc<RuntimeBackend>, R)> {
11196    let guard = self.current_backend.load();
11197    let backend = guard.as_ref()?; // None => Initial/Stopping/Stopped => slow path
11198    if let Some(active) = active_runtime_generation()
11199      && active != backend.generation()
11200    {
11201      return None; // retired generation => slow path yields exact error
11202    }
11203    let registration = register(&backend.work)?; // closed => None => slow path rejects
11204    // Hand back the already-loaded Arc (one refcount bump) rather than deep-
11205    // cloning `RuntimeBackend`'s two inner Arcs; both callers only borrow it.
11206    Some((Arc::clone(backend), registration))
11207  }
11208
11209  fn try_spawn_tracked<F, T>(
11210    &self,
11211    future: F,
11212  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11213  where
11214    F: Future<Output = T> + Send + 'static,
11215    T: Send + 'static,
11216  {
11217    #[cfg(test)]
11218    run_before_runtime_submission_lock_test_hook();
11219
11220    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11221      #[cfg(test)]
11222      run_after_async_work_registration_test_hook();
11223      return Ok(spawn_registered(
11224        &backend,
11225        Arc::clone(&self.metrics),
11226        future,
11227        registration,
11228      ));
11229    }
11230
11231    let (backend, registration) = {
11232      let mut state = self
11233        .state
11234        .lock()
11235        .unwrap_or_else(std::sync::PoisonError::into_inner);
11236      let backend = match self.backend_locked(&mut state) {
11237        Ok(backend) => backend,
11238        Err(error) => {
11239          let generation = Self::begin_rejected_drop(&mut state);
11240          return Err((error, future, generation));
11241        }
11242      };
11243      let Some(registration) = backend.work.try_register_async() else {
11244        let generation = Self::begin_rejected_drop(&mut state);
11245        return Err((
11246          RuntimeConfigError(
11247            "the async runtime is stopped; call start before submitting work".to_string(),
11248          ),
11249          future,
11250          generation,
11251        ));
11252      };
11253      (backend, registration)
11254    };
11255    #[cfg(test)]
11256    run_after_async_work_registration_test_hook();
11257    Ok(spawn_registered(
11258      &backend,
11259      Arc::clone(&self.metrics),
11260      future,
11261      registration,
11262    ))
11263  }
11264
11265  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11266  where
11267    F: Future<Output = T> + Send + 'static,
11268    T: Send + 'static,
11269  {
11270    match self.try_spawn_tracked(future) {
11271      Ok(handle) => Ok(handle),
11272      Err((error, future, _generation)) => {
11273        drop(RejectedSubmissionGuard { controller: self });
11274        Err((error, future))
11275      }
11276    }
11277  }
11278
11279  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11280  where
11281    F: Future<Output = T> + Send + 'static,
11282    T: Send + 'static,
11283  {
11284    match self.try_spawn_tracked(future) {
11285      Ok(handle) => handle,
11286      Err((error, future, generation)) => {
11287        let _rejection = RejectedSubmissionGuard { controller: self };
11288        let _drop_context = RejectedSubmissionDropContext::enter();
11289        let _generation = generation.map(RuntimeGenerationGuard::enter);
11290        let _ = catch_unwind_contained(|| drop(future));
11291        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11292          message: error.to_string(),
11293        }))))
11294      }
11295    }
11296  }
11297
11298  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11299  where
11300    F: Future<Output = ()> + Send + 'static,
11301  {
11302    match self.try_spawn(future) {
11303      Ok(handle) => {
11304        handle.detach();
11305        Ok(())
11306      }
11307      Err((_, future)) => Err(future),
11308    }
11309  }
11310
11311  fn try_spawn_blocking_tracked<F, T>(
11312    &self,
11313    function: F,
11314  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11315  where
11316    F: FnOnce() -> T + Send + 'static,
11317    T: Send + 'static,
11318  {
11319    #[cfg(test)]
11320    run_before_runtime_submission_lock_test_hook();
11321
11322    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11323      #[cfg(test)]
11324      run_after_blocking_work_registration_test_hook();
11325      return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11326    }
11327
11328    let (backend, registration) = {
11329      let mut state = self
11330        .state
11331        .lock()
11332        .unwrap_or_else(std::sync::PoisonError::into_inner);
11333      let backend = match self.backend_locked(&mut state) {
11334        Ok(backend) => backend,
11335        Err(error) => {
11336          let generation = Self::begin_rejected_drop(&mut state);
11337          return Err((error, function, generation));
11338        }
11339      };
11340      let Some(registration) = backend.work.try_register_work() else {
11341        let generation = Self::begin_rejected_drop(&mut state);
11342        return Err((
11343          RuntimeConfigError(
11344            "the async runtime is stopped; call start before submitting work".to_string(),
11345          ),
11346          function,
11347          generation,
11348        ));
11349      };
11350      (backend, registration)
11351    };
11352    #[cfg(test)]
11353    run_after_blocking_work_registration_test_hook();
11354    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11355  }
11356
11357  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11358  where
11359    F: FnOnce() -> T + Send + 'static,
11360    T: Send + 'static,
11361  {
11362    match self.try_spawn_blocking_tracked(function) {
11363      Ok(handle) => Ok(handle),
11364      Err((error, function, _generation)) => {
11365        drop(RejectedSubmissionGuard { controller: self });
11366        Err((error, function))
11367      }
11368    }
11369  }
11370
11371  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11372  where
11373    F: FnOnce() -> T + Send + 'static,
11374    T: Send + 'static,
11375  {
11376    match self.try_spawn_blocking_tracked(function) {
11377      Ok(handle) => handle,
11378      Err((error, function, generation)) => {
11379        let _rejection = RejectedSubmissionGuard { controller: self };
11380        let _drop_context = RejectedSubmissionDropContext::enter();
11381        let _generation = generation.map(RuntimeGenerationGuard::enter);
11382        let _ = catch_unwind_contained(|| drop(function));
11383        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11384          message: error.to_string(),
11385        }))))
11386      }
11387    }
11388  }
11389
11390  #[cfg(test)]
11391  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11392    self
11393      .try_block_on(future)
11394      .unwrap_or_else(|error| panic!("{error}"))
11395  }
11396
11397  fn try_block_on_scope(
11398    &self,
11399  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11400    let mut state = self
11401      .state
11402      .lock()
11403      .unwrap_or_else(std::sync::PoisonError::into_inner);
11404    let backend = match self.backend_locked(&mut state) {
11405      Ok(backend) => backend,
11406      Err(error) => {
11407        let generation = Self::begin_rejected_drop(&mut state);
11408        return Err((error, generation));
11409      }
11410    };
11411    let Some(registration) = backend.work.try_register_work() else {
11412      let generation = Self::begin_rejected_drop(&mut state);
11413      return Err((
11414        RuntimeConfigError(
11415          "the async runtime is stopped; call start before submitting work".to_string(),
11416        ),
11417        generation,
11418      ));
11419    };
11420    Ok((backend, registration))
11421  }
11422
11423  fn try_block_on(
11424    &self,
11425    future: Pin<&mut dyn Future<Output = ()>>,
11426  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11427    let (backend, registration) = match self.try_block_on_scope() {
11428      Ok(scope) => scope,
11429      Err((error, _generation)) => {
11430        drop(RejectedSubmissionGuard { controller: self });
11431        return Err(error);
11432      }
11433    };
11434    let _registration = registration;
11435    let _generation = RuntimeGenerationGuard::enter(backend.generation());
11436    Ok(backend.block_on(future))
11437  }
11438
11439  fn start(&self) -> Result<(), RuntimeConfigError> {
11440    let mut state = self
11441      .state
11442      .lock()
11443      .unwrap_or_else(std::sync::PoisonError::into_inner);
11444    loop {
11445      Self::ensure_active_generation_current(&state)?;
11446      match &state.lifecycle {
11447        // napi starts the runtime while the addon is still being registered.
11448        // Keep the first backend lazy so configureAsyncRuntime remains usable
11449        // until the first async binding call.
11450        RuntimeLifecycle::Initial => {
11451          if state.rejected_drops == 0 {
11452            // A started runtime owes nothing to a handoff parked against an
11453            // earlier generation. The stop that handoff announced still
11454            // happened, so `phase_ever_opened` stays set and a phase 2 that
11455            // arrives afterwards is a no-op instead of an error.
11456            state.forget_shutdown_handoff();
11457            return Ok(());
11458          }
11459          if RejectedSubmissionDropContext::is_current() {
11460            return Err(RuntimeConfigError(
11461              "cannot start the async runtime while a rejected submission is being destroyed"
11462                .to_string(),
11463            ));
11464          }
11465          #[cfg(test)]
11466          run_before_initial_start_wait_test_hook();
11467          state = self
11468            .lifecycle_changed
11469            .wait(state)
11470            .unwrap_or_else(std::sync::PoisonError::into_inner);
11471        }
11472        RuntimeLifecycle::Running(_) => {
11473          state.forget_shutdown_handoff();
11474          return Ok(());
11475        }
11476        RuntimeLifecycle::Stopping(identity) => {
11477          if identity.is_current() {
11478            return Err(RuntimeConfigError(
11479              "cannot restart the async runtime from work in the generation being stopped"
11480                .to_string(),
11481            ));
11482          }
11483          state = self
11484            .lifecycle_changed
11485            .wait(state)
11486            .unwrap_or_else(std::sync::PoisonError::into_inner);
11487        }
11488        RuntimeLifecycle::StoppingWithoutBackend(_) => {
11489          if RejectedSubmissionDropContext::is_current() {
11490            return Err(RuntimeConfigError(
11491              "cannot restart the async runtime while a rejected submission is being destroyed"
11492                .to_string(),
11493            ));
11494          }
11495          state = self
11496            .lifecycle_changed
11497            .wait(state)
11498            .unwrap_or_else(std::sync::PoisonError::into_inner);
11499        }
11500        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11501          if state.rejected_drops == 0 {
11502            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11503              state.lifecycle = RuntimeLifecycle::Initial;
11504              state.forget_shutdown_handoff();
11505              return Ok(());
11506            }
11507            break;
11508          }
11509          if RejectedSubmissionDropContext::is_current() {
11510            return Err(RuntimeConfigError(
11511              "cannot restart the async runtime while a rejected submission is being destroyed"
11512                .to_string(),
11513            ));
11514          }
11515          state = self
11516            .lifecycle_changed
11517            .wait(state)
11518            .unwrap_or_else(std::sync::PoisonError::into_inner);
11519        }
11520      }
11521    }
11522    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11523    self.current_backend.store(Some(Arc::new(backend.clone())));
11524    state.lifecycle = RuntimeLifecycle::Running(backend);
11525    state.forget_shutdown_handoff();
11526    Ok(())
11527  }
11528
11529  fn options(&self) -> RuntimeOptions {
11530    self
11531      .state
11532      .lock()
11533      .unwrap_or_else(std::sync::PoisonError::into_inner)
11534      .options
11535      .clone()
11536  }
11537
11538  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11539    let state = self
11540      .state
11541      .lock()
11542      .unwrap_or_else(std::sync::PoisonError::into_inner);
11543    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11544      return None;
11545    };
11546    match &backend.executor {
11547      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11548      #[cfg(napi_runtime_os_threads)]
11549      RuntimeExecutor::MultiThread(_) => None,
11550    }
11551  }
11552
11553  fn request_current_thread_drain(&self) {
11554    if let Some(executor) = self.running_current_thread_executor() {
11555      executor.request_drain_if_queued();
11556    }
11557  }
11558
11559  fn retire_rejected_current_thread_work(&self) {
11560    if let Some(executor) = self.running_current_thread_executor() {
11561      executor.retire_rejected_work_off_waker_stack();
11562    }
11563  }
11564
11565  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11566    let executor = {
11567      let state = self
11568        .state
11569        .lock()
11570        .unwrap_or_else(std::sync::PoisonError::into_inner);
11571      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11572        return;
11573      };
11574      match &backend.executor {
11575        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11576        #[cfg(napi_runtime_os_threads)]
11577        RuntimeExecutor::MultiThread(_) => return,
11578      }
11579    };
11580    executor.cancel_host_dispatch(failure);
11581  }
11582
11583  #[cfg(test)]
11584  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11585    let executor = {
11586      let state = self
11587        .state
11588        .lock()
11589        .unwrap_or_else(std::sync::PoisonError::into_inner);
11590      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11591        return;
11592      };
11593      match &backend.executor {
11594        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11595        #[cfg(napi_runtime_os_threads)]
11596        RuntimeExecutor::MultiThread(_) => return,
11597      }
11598    };
11599    executor.cancel_host_dispatch_for_test(dispatch);
11600  }
11601
11602  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11603    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11604    self.drive_current_thread_dispatch_with_lease(dispatch, true)
11605  }
11606
11607  fn drive_current_thread_dispatch_with_lease(
11608    &self,
11609    dispatch: u64,
11610    mark_serviced: bool,
11611  ) -> Option<CurrentThreadTaskCallbackLease> {
11612    let mut host_turn = {
11613      let state = self
11614        .state
11615        .lock()
11616        .unwrap_or_else(std::sync::PoisonError::into_inner);
11617      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11618        return None;
11619      };
11620      let executor = match &backend.executor {
11621        RuntimeExecutor::CurrentThread(executor) => executor,
11622        #[cfg(napi_runtime_os_threads)]
11623        RuntimeExecutor::MultiThread(_) => return None,
11624      };
11625      let host_turn = executor.try_admit_host_turn(dispatch)?;
11626      if mark_serviced {
11627        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11628      }
11629      host_turn
11630    };
11631    #[cfg(test)]
11632    run_after_current_thread_host_admission_test_hook();
11633    host_turn.drive();
11634    Some(CurrentThreadTaskCallbackLease::new(host_turn))
11635  }
11636
11637  #[cfg(test)]
11638  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11639    self
11640      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11641      .is_some()
11642  }
11643
11644  /// Phase 1 of the two-phase shutdown: publish `Stopping`, close admission,
11645  /// abort registered tasks, drop queued work, drain-fire timers and wake
11646  /// parkers -- then return WITHOUT waiting for work that is already running.
11647  ///
11648  /// `Ok(true)` = the announced stop still owes a wait, so the caller should
11649  /// let its host loop turn and poll [`Self::runtime_work_pending`] before
11650  /// phase 2. That is registered work of a generation with a backend, and --
11651  /// on a zero-backend stop -- a rejected submission still being destroyed on
11652  /// another thread, which phase 1 parks as `ShutdownDrain::ZeroBackend`
11653  /// rather than wait out. `Ok(false)` = there is nothing left and
11654  /// `finish_shutdown` will not block.
11655  ///
11656  /// This exists for a host that cannot yield inside a single-call
11657  /// `shutdown`: the threaded WASI artifact enters shutdown from the JS
11658  /// thread, so a blocking closure that still needs a JS turn could never get
11659  /// one while that thread sat in `wait_until_idle`.
11660  ///
11661  /// A `begin_shutdown` while one is outstanding opens no second phase and
11662  /// never waits: it answers with the current work-pending verdict, so the
11663  /// thread that owns the shutdown may re-enter it from a host turn. That
11664  /// holds for the whole of an outstanding stop, including the window in
11665  /// which another thread is already inside the join -- the handoff slot
11666  /// carries `ShutdownDrain::Joining` there rather than going empty, so this
11667  /// call answers from it instead of parking on the published `Stopping`.
11668  /// Phase 1s and phase 2s do not have to balance -- `finish_shutdown`
11669  /// completes the most recently announced stop and is idempotent.
11670  ///
11671  /// `start()` between the phases is NOT allowed -- it waits in `Stopping`
11672  /// until phase 2 publishes `Stopped`, and is rejected outright when it comes
11673  /// from work in the generation being stopped. Admission needs no guard of
11674  /// its own: `backend_locked` already rejects every submission under
11675  /// `Stopping`. The same holds for a zero-backend stop that still owes a
11676  /// rejected destruction: it publishes `StoppingWithoutBackend` for the whole
11677  /// window, and `start` waits there until phase 2 publishes the target. Only
11678  /// a zero-backend phase 1 that settled outright (`ShutdownDrain::Settled`,
11679  /// nothing published to wait in) lets a `start` through -- it drops the
11680  /// handoff, and the `finish_shutdown` that follows finds the fresh
11681  /// generation running and is a no-op `Ok(())`.
11682  fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11683    // See `finish_shutdown`: the same waits, the same reason.
11684    let _bounded_waits = BoundedWaits::enter();
11685    let backend = {
11686      let mut state = self
11687        .state
11688        .lock()
11689        .unwrap_or_else(std::sync::PoisonError::into_inner);
11690      // Before the outstanding-handoff arm, not only inside the loop below:
11691      // that arm returns early, and work of a RETIRED generation must be told
11692      // so rather than be handed the current stop's verdict. The window it
11693      // covers is the whole of an outstanding stop, join included, so the
11694      // guard cannot sit behind it.
11695      Self::ensure_active_generation_current(&state)?;
11696      if let Some(drain) = &state.draining {
11697        let outstanding = match drain {
11698          // `Joining` answers exactly like `Backend`: the stop it carries has
11699          // been announced and is being completed right now, so this call has
11700          // nothing to open and nothing to wait for -- only a verdict to give.
11701          ShutdownDrain::Backend(backend) => Some(backend.clone()),
11702          ShutdownDrain::Joining(backend) => backend.clone(),
11703          // A zero-backend stop that still owes a rejected destruction. The
11704          // verdict lives in `state`, not in a backend, so it is answered
11705          // here -- under this lock, before the `drop(state)` below -- and,
11706          // like every other arm of this re-entry, it never waits. The
11707          // drop-context error keeps the surface identical to the
11708          // `StoppingWithoutBackend` and `Stopped` arms below: a destructor
11709          // can never drive the shutdown of the state it is blocking.
11710          ShutdownDrain::ZeroBackend(_) => {
11711            if RejectedSubmissionDropContext::is_current() {
11712              return Err(RuntimeConfigError(
11713                "cannot shut down the async runtime while a rejected submission is being destroyed"
11714                  .to_string(),
11715              ));
11716            }
11717            return Ok(state.rejected_drops != 0);
11718          }
11719          ShutdownDrain::Settled => None,
11720        };
11721        // A second phase 1 opens nothing: the stop this handoff carries has
11722        // already been announced, and one `finish_shutdown` completes it for
11723        // every caller. Answering from the handoff can never skip stopping a
11724        // live generation: the handoff belongs to the generation it was opened
11725        // against, and `forget_shutdown_handoff` drops it at every `start`.
11726        //
11727        // Answer off the lifecycle lock: probing a backend takes the
11728        // generation and executor locks, and the same re-entry must not
11729        // nest them under this one.
11730        drop(state);
11731        return Ok(outstanding.is_some_and(|backend| backend.work_pending(PendingRead::Locked)));
11732      }
11733      loop {
11734        Self::ensure_active_generation_current(&state)?;
11735        match &state.lifecycle {
11736          RuntimeLifecycle::Initial => {
11737            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11738              return Err(RuntimeConfigError(
11739                "cannot shut down the async runtime while a rejected submission is being destroyed"
11740                  .to_string(),
11741              ));
11742            }
11743            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11744            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11745            if state.rejected_drops != 0 {
11746              // Phase 1 NEVER waits. The outstanding retirement is another
11747              // thread's user `Drop`, and that thread may be waiting for a
11748              // host turn this one can only give by returning. Park the
11749              // announced stop for phase 2 and report it as pending work, so
11750              // the host keeps turning its loop -- the turn that destructor
11751              // may be waiting for.
11752              state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11753              self.lifecycle_changed.notify_all();
11754              return Ok(true);
11755            }
11756            state.lifecycle = target.into_lifecycle();
11757            state.open_phase_one(ShutdownDrain::Settled);
11758            self.lifecycle_changed.notify_all();
11759            return Ok(false);
11760          }
11761          RuntimeLifecycle::Running(backend) => {
11762            if backend.is_current() {
11763              return Err(RuntimeConfigError(
11764                "cannot shut down the async runtime from work running on that runtime".to_string(),
11765              ));
11766            }
11767            let identity = backend.stop_identity();
11768            let stop = backend.generation_stop();
11769            // Lock order is lifecycle -> stop publication. Publish before
11770            // mutating the lifecycle so a gated verdict that acquired the
11771            // publication mutex first wins while this state remains Running;
11772            // otherwise the verdict must observe the published stop.
11773            let stop_publication = stop.publication_guard();
11774            stop_publication.publish_stopping();
11775            let RuntimeLifecycle::Running(backend) =
11776              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11777            else {
11778              unreachable!();
11779            };
11780            self.current_backend.store(None);
11781            // Park the backend for phase 2. `Stopping` carries only an
11782            // identity, so without this slot the backend would live on this
11783            // stack and phase 1 could not return.
11784            state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11785            // Release before aborting tasks or running any user destruction.
11786            // Neither path may retain the publication mutex across scheduler
11787            // admission, lifecycle reentry, or a panic boundary.
11788            drop(stop_publication);
11789            break backend;
11790          }
11791          RuntimeLifecycle::Stopping(identity) => {
11792            if identity.is_current() {
11793              return Err(RuntimeConfigError(
11794                "cannot wait for async runtime shutdown from work in the generation being stopped"
11795                  .to_string(),
11796              ));
11797            }
11798            state = self
11799              .lifecycle_changed
11800              .wait(state)
11801              .unwrap_or_else(std::sync::PoisonError::into_inner);
11802          }
11803          RuntimeLifecycle::StoppingWithoutBackend(_) => {
11804            if RejectedSubmissionDropContext::is_current() {
11805              return Err(RuntimeConfigError(
11806                "cannot shut down the async runtime while a rejected submission is being destroyed"
11807                  .to_string(),
11808              ));
11809            }
11810            state = self
11811              .lifecycle_changed
11812              .wait(state)
11813              .unwrap_or_else(std::sync::PoisonError::into_inner);
11814          }
11815          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11816            if state.rejected_drops == 0 {
11817              state.open_phase_one(ShutdownDrain::Settled);
11818              return Ok(false);
11819            }
11820            if RejectedSubmissionDropContext::is_current() {
11821              return Err(RuntimeConfigError(
11822                "cannot shut down the async runtime while a rejected submission is being destroyed"
11823                  .to_string(),
11824              ));
11825            }
11826            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11827              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11828            } else {
11829              ZeroBackendShutdownTarget::Stopped
11830            };
11831            // Same rule as the `Initial` arm: announce the stop, hand the
11832            // wait to phase 2, and return without waiting on another thread's
11833            // destructor.
11834            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11835            state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11836            self.lifecycle_changed.notify_all();
11837            return Ok(true);
11838          }
11839        }
11840      }
11841    };
11842
11843    #[cfg(test)]
11844    run_after_generation_stop_publication_test_hook();
11845    backend.begin_shutdown();
11846    Ok(backend.work_pending(PendingRead::Locked))
11847  }
11848
11849  /// Non-blocking poll for the window between the phases: `false` means
11850  /// `finish_shutdown` will not have to wait for user work. Reads the running
11851  /// generation when no phase 1 is outstanding, so a host may also use it to
11852  /// decide whether a shutdown would block at all.
11853  ///
11854  /// It stays truthful while a `finish_shutdown` is inside the join: that
11855  /// thread leaves `ShutdownDrain::Joining` in the slot, so this poll reports
11856  /// the very work the join is waiting for instead of `false`.
11857  ///
11858  /// Never blocks, not even on a lock: every lock it reads under is only
11859  /// tried ([`PendingRead::NonBlocking`]), and one held by another thread
11860  /// answers `true`, so the host takes one more turn and asks again. A held
11861  /// lock is not always about to come back: on `wasm32-wasip1-threads` a pool
11862  /// thread that traps while holding one unwinds nothing, and waiting for it
11863  /// here would park the JavaScript thread in `memory.atomic.wait32` for good
11864  /// -- before the loader can see that thread's crash.
11865  fn runtime_work_pending(&self) -> bool {
11866    let backend = {
11867      let state = match self.state.try_lock() {
11868        Ok(state) => state,
11869        Err(std::sync::TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
11870        Err(std::sync::TryLockError::WouldBlock) => return true,
11871      };
11872      match (&state.draining, &state.lifecycle) {
11873        // `Joining` is the in-flight drain: while it still carries the
11874        // backend, report the work the finisher is waiting for, not `false`.
11875        (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11876          backend.clone()
11877        }
11878        // A zero-backend stop that still owes a rejected destruction: that
11879        // destruction IS the pending work, and reporting it is what makes the
11880        // host keep turning its event loop -- the turn the destructor may be
11881        // waiting for. `false` here would send the loader straight into the
11882        // phase-2 join in the same synchronous turn.
11883        (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11884        (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11885        // `Settled` is a handoff for a generation that had no backend, and
11886        // `forget_shutdown_handoff` drops it at every `start`, so it never
11887        // shadows a running generation. A backend-less `Joining` is a drain
11888        // past `wait_until_idle`: no user work can be left to report.
11889        (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11890          return false;
11891        }
11892      }
11893    };
11894    backend.work_pending(PendingRead::NonBlocking)
11895  }
11896
11897  /// Phase 2 of the two-phase shutdown: wait for the generation to go idle,
11898  /// join the workers and publish `Stopped`. Everything after
11899  /// `begin_shutdown`, in the same order the single call runs it.
11900  ///
11901  /// It completes the most recently announced stop, and it is IDEMPOTENT. It
11902  /// owns no phase and counts none: whoever finds a `ShutdownDrain::Backend`
11903  /// handoff takes it, swaps it for `ShutdownDrain::Joining`, drains it and
11904  /// publishes the stop while retiring that marker, and every other caller
11905  /// answers off the lifecycle -- waiting out a join that is still in flight,
11906  /// then reporting `Ok(())` for the stop that was delivered. A
11907  /// `ShutdownDrain::ZeroBackend` handoff is never taken and never swapped:
11908  /// every caller that finds it waits on the outstanding rejected
11909  /// destruction, the one that observes it retired publishes the target and
11910  /// clears the slot, and the rest wake to that published stop and report it
11911  /// the same way. Phase 1s and phase 2s therefore do not have to balance,
11912  /// and two threads completing the same shutdown both get `Ok(())` instead
11913  /// of one of them being told its own stop never happened.
11914  ///
11915  /// It never stops a generation it did not begin. A `start` between the
11916  /// phases -- or any later restart -- drops a handoff that published nothing
11917  /// to wait in (`ShutdownDrain::Settled`) and publishes a fresh generation; a
11918  /// `finish_shutdown` that arrives afterwards with nothing outstanding is a
11919  /// no-op `Ok(())` and does NOT adopt that generation. A `ZeroBackend`
11920  /// handoff is never dropped that way: it publishes `StoppingWithoutBackend`
11921  /// for its whole window, so a `start` waits there until phase 2 publishes
11922  /// the target.
11923  ///
11924  /// The only missing-phase error left is phase 2 on a runtime that has never
11925  /// begun a shutdown at all (`phase_ever_opened == false`): the host called
11926  /// the second half without ever calling the first. Like `shutdown`, it also
11927  /// rejects a caller that is work of the generation being stopped, which could
11928  /// only wait for itself.
11929  ///
11930  /// On `wasm32-wasip1-threads` every lock, condvar wait and join this thread
11931  /// makes in here (and in `begin_shutdown`) stops once the host reports a
11932  /// crashed thread, with a trap: a pool thread that trapped holds its locks
11933  /// and its work for good, and waiting for either would park the host's
11934  /// JavaScript thread before the loader could see the crash. See
11935  /// `crate::sync`.
11936  fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
11937    let _bounded_waits = BoundedWaits::enter();
11938    const STOPPING_WAIT_ERROR: &str =
11939      "cannot wait for async runtime shutdown from work in the generation being stopped";
11940    const REJECTED_DROP_ERROR: &str =
11941      "cannot shut down the async runtime while a rejected submission is being destroyed";
11942    const NO_PHASE_ERROR: &str =
11943      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
11944
11945    let backend = {
11946      let mut state = self
11947        .state
11948        .lock()
11949        .unwrap_or_else(std::sync::PoisonError::into_inner);
11950      // Every arm but `Backend` leaves this function; only that one falls
11951      // through to the take below.
11952      match &state.draining {
11953        Some(ShutdownDrain::Settled) => {
11954          // Phase 1 completed a zero-backend transition; nothing to drain.
11955          state.draining = None;
11956          return Ok(());
11957        }
11958        Some(ShutdownDrain::Backend(backend)) => {
11959          if backend.is_current() {
11960            return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11961          }
11962        }
11963        // Phase 1 announced a zero-backend stop that still owed the
11964        // retirement of a rejected submission's destructor. This is the wait
11965        // phase 1 refused to do. The marker is NOT taken -- there is nothing
11966        // to own -- so the slot is re-read after every wake: only the caller
11967        // that observes `rejected_drops == 0` while the slot still carries
11968        // this target publishes it, and a second finisher that wakes to an
11969        // already-published stop reports it like any other completed phase 2.
11970        Some(ShutdownDrain::ZeroBackend(_)) => loop {
11971          let target = match (&state.draining, &state.lifecycle) {
11972            (
11973              Some(ShutdownDrain::ZeroBackend(parked)),
11974              RuntimeLifecycle::StoppingWithoutBackend(published),
11975            ) if published == parked => *parked,
11976            // Another finisher published it, or a `start` superseded it.
11977            // Either way the stop this call was asked to complete has been
11978            // delivered.
11979            _ => return Ok(()),
11980          };
11981          if state.rejected_drops == 0 {
11982            // Retire the marker and publish the stop under the ONE lock
11983            // acquisition, exactly as the backend path does below: no
11984            // observer may ever see `StoppingWithoutBackend` without a drain,
11985            // or the terminal lifecycle with one.
11986            state.draining = None;
11987            state.lifecycle = target.into_lifecycle();
11988            self.lifecycle_changed.notify_all();
11989            return Ok(());
11990          }
11991          if RejectedSubmissionDropContext::is_current() {
11992            return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11993          }
11994          // `rejected_drops` can flicker 1 -> 0 -> 1 across the host's turns:
11995          // a fresh submission under `StoppingWithoutBackend` is rejected and
11996          // increments it again. Re-observe, exactly as the backend path's
11997          // tail wait does.
11998          state = self
11999            .lifecycle_changed
12000            .wait(state)
12001            .unwrap_or_else(std::sync::PoisonError::into_inner);
12002        },
12003        // `Joining`: another caller took the handoff and is inside the join.
12004        // Same answer as an empty slot on a runtime that announced a stop --
12005        // wait out the publication and report it. `phase_ever_opened` is
12006        // necessarily set here, so the orphan check below never fires.
12007        Some(ShutdownDrain::Joining(_)) | None => {
12008          if !state.phase_ever_opened {
12009            // No shutdown has ever been announced on this runtime, so there is
12010            // nothing for this call to complete: the host called the second
12011            // half without the first.
12012            return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
12013          }
12014          // A stop WAS announced. The slot holds an in-flight drain, or is
12015          // empty because a `start` dropped the handoff or the joining thread
12016          // has already published and retired its marker. Wait out a
12017          // join still in flight and report its publication -- a concurrent
12018          // `shutdown` waits in exactly this place today. Waiting inside this
12019          // arm, instead of re-entering the match, also keeps this caller from
12020          // adopting a phase 1 that was opened after it woke.
12021          loop {
12022            match &state.lifecycle {
12023              RuntimeLifecycle::Stopping(identity) => {
12024                if identity.is_current() {
12025                  return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
12026                }
12027              }
12028              RuntimeLifecycle::StoppingWithoutBackend(_) => {
12029                if RejectedSubmissionDropContext::is_current() {
12030                  return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
12031                }
12032              }
12033              // Stopped/StoppedBeforeFirstUse: the publication an announced
12034              // stop was owed has happened, whichever thread performed the
12035              // join. Initial/Running: a `start` superseded that generation --
12036              // this call must report the stop, not stop the new generation.
12037              RuntimeLifecycle::StoppedBeforeFirstUse
12038              | RuntimeLifecycle::Stopped
12039              | RuntimeLifecycle::Initial
12040              | RuntimeLifecycle::Running(_) => return Ok(()),
12041            }
12042            state = self
12043              .lifecycle_changed
12044              .wait(state)
12045              .unwrap_or_else(std::sync::PoisonError::into_inner);
12046          }
12047        }
12048      }
12049      // Take the backend so this thread owns the drain and the slot can
12050      // never be taken twice -- but leave an explicit in-flight marker behind
12051      // instead of emptying the slot, so the non-blocking surfaces keep
12052      // telling the truth until `Stopped` is published below. Nothing can
12053      // overwrite it in between: `open_phase_one` is only reached with an
12054      // empty slot, and every `start` arm waits out the `Stopping` this
12055      // marker is published alongside.
12056      let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
12057        unreachable!();
12058      };
12059      state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
12060      backend
12061    };
12062
12063    #[cfg(test)]
12064    run_after_shutdown_drain_taken_test_hook();
12065    backend.wait_until_idle();
12066
12067    #[cfg(napi_runtime_os_threads)]
12068    let worker_lifecycle = backend.worker_lifecycle();
12069    // Hand the backend back out of the marker BEFORE dropping this thread's
12070    // own reference: `drop(backend)` below has to be the LAST one, because
12071    // releasing the executor is what lets the workers exit and what
12072    // `wait_for_all_workers` then joins. The marker itself stays -- phase 1
12073    // and the poll must keep answering without waiting for the rest of the
12074    // join -- it just stops carrying a backend, which `wait_until_idle`
12075    // having returned already makes the truthful answer.
12076    {
12077      let mut state = self
12078        .state
12079        .lock()
12080        .unwrap_or_else(std::sync::PoisonError::into_inner);
12081      debug_assert!(
12082        matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
12083        "nothing may disturb an in-flight drain marker before its publication"
12084      );
12085      state.draining = Some(ShutdownDrain::Joining(None));
12086    }
12087    drop(backend);
12088
12089    #[cfg(napi_runtime_os_threads)]
12090    if let Some(worker_lifecycle) = worker_lifecycle {
12091      worker_lifecycle.wait_for_all_workers();
12092    }
12093
12094    let mut state = self
12095      .state
12096      .lock()
12097      .unwrap_or_else(std::sync::PoisonError::into_inner);
12098    while state.rejected_drops != 0 {
12099      state = self
12100        .lifecycle_changed
12101        .wait(state)
12102        .unwrap_or_else(std::sync::PoisonError::into_inner);
12103    }
12104    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
12105    debug_assert!(
12106      matches!(state.draining, Some(ShutdownDrain::Joining(None))),
12107      "nothing may retire an in-flight drain marker but its own publication"
12108    );
12109    // Retire the marker and publish the stop under the ONE lock acquisition:
12110    // no observer may ever see `Stopping` without a drain, or `Stopped` with
12111    // one.
12112    state.draining = None;
12113    state.lifecycle = RuntimeLifecycle::Stopped;
12114    self.lifecycle_changed.notify_all();
12115    Ok(())
12116  }
12117
12118  /// Single-call shutdown: the two phases back to back. Ordering and
12119  /// semantics are exactly what this function has always done -- phase 1 ends
12120  /// at `RuntimeBackend::begin_shutdown` and phase 2 resumes at
12121  /// `wait_until_idle`, with nothing in between.
12122  ///
12123  /// It takes the lifecycle lock twice, so a `start` (or another shutdown) can
12124  /// land between the halves. Phase 2 being idempotent is what makes that
12125  /// window free: whether the stop this call announced is published by this
12126  /// thread, by a concurrent `finish_shutdown` that took the handoff first, or
12127  /// superseded by a racing `start`, this call reports `Ok(())` for the stop it
12128  /// performed -- it never has to be told that its own shutdown was never
12129  /// started.
12130  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12131    self.begin_shutdown()?;
12132    #[cfg(test)]
12133    run_between_shutdown_phases_test_hook();
12134    self.finish_shutdown()
12135  }
12136}
12137
12138static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12139
12140pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12141  RUNTIME.configure(options)
12142}
12143
12144pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12145  RUNTIME.configure_partial(patch)
12146}
12147
12148pub fn configured_options() -> RuntimeOptions {
12149  RUNTIME.options()
12150}
12151
12152pub fn is_multi_threaded() -> bool {
12153  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12154}
12155
12156fn spawn_registered<F, T>(
12157  backend: &RuntimeBackend,
12158  metrics: Arc<RuntimeMetrics>,
12159  future: F,
12160  registration: (AbortRegistration, GenerationWorkGuard),
12161) -> JoinHandle<T>
12162where
12163  F: Future<Output = T> + Send + 'static,
12164  T: Send + 'static,
12165{
12166  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12167  let generation = backend.generation();
12168  let (abort_registration, work_registration) = registration;
12169  #[cfg(napi_runtime_os_threads)]
12170  let dependency = Arc::new(TaskDependency::new_task(generation));
12171  #[cfg(napi_runtime_os_threads)]
12172  let task_dependency = Arc::clone(&dependency);
12173  let wrapped = async move {
12174    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12175    let mut abortable = std::pin::pin!(abortable);
12176    let polled = futures::future::poll_fn(|cx| {
12177      #[cfg(napi_runtime_os_threads)]
12178      task_dependency.begin_poll();
12179      #[cfg(napi_runtime_os_threads)]
12180      let _dependency = TaskDependencyGuard::enter(&task_dependency);
12181      abortable.as_mut().poll(cx)
12182    });
12183    match AssertUnwindSafe(polled).catch_unwind().await {
12184      Ok(Ok(output)) => {
12185        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12186        Ok(ContainedTaskOutput::new(output, generation))
12187      }
12188      Ok(Err(_)) => Err(JoinError {
12189        message: "async runtime stopped before the task completed".to_string(),
12190      }),
12191      Err(panic) => {
12192        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12193        Err(panic_payload_to_join_error(panic))
12194      }
12195    }
12196  };
12197  let scheduler = backend.downgrade_executor();
12198  // `async-task` aborts the process if its future's destructor unwinds. Keep
12199  // the entire task generator behind our manually-dropped containment wrapper,
12200  // including the never-polled case where `future` is still a generator field.
12201  let (runnable, task) = async_task::spawn(
12202    RegisteredTaskFuture::new(wrapped, work_registration, generation),
12203    move |runnable| {
12204      scheduler.schedule(runnable);
12205    },
12206  );
12207  backend.schedule_submission(runnable);
12208  JoinHandle(JoinHandleInner::Task {
12209    task: task.fallible(),
12210    awaiter: None,
12211    #[cfg(napi_runtime_os_threads)]
12212    dependency,
12213  })
12214}
12215
12216pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12217where
12218  F: Future<Output = T> + Send + 'static,
12219  T: Send + 'static,
12220{
12221  RUNTIME.spawn(future)
12222}
12223
12224/// Submit a future without consuming it when the runtime is not accepting work.
12225///
12226/// Lifecycle owners can retain the returned future and retry it after
12227/// [`start`] completes a runtime restart.
12228///
12229/// **On a wasm target, prefer this over [`spawn`].** Building the MultiThread
12230/// pool is lazy and fail-loud: the first async call is what calls
12231/// `wasi.thread-spawn`, and whether that works is a JavaScript fact the crate
12232/// cannot observe up front. Here the failure is an `Err` you can act on; the
12233/// infallible helpers turn it into a panic, which `panic = "abort"` then turns
12234/// into a dead instance.
12235pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12236where
12237  F: Future<Output = T> + Send + 'static,
12238  T: Send + 'static,
12239{
12240  RUNTIME.try_spawn(future)
12241}
12242
12243pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12244where
12245  F: Future<Output = ()> + Send + 'static,
12246{
12247  RUNTIME.try_spawn_detached(future)
12248}
12249
12250pub fn spawn_detached<F>(future: F)
12251where
12252  F: Future<Output = ()> + Send + 'static,
12253{
12254  spawn(future).detach();
12255}
12256
12257pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12258where
12259  F: FnOnce() -> T + Send + 'static,
12260  T: Send + 'static,
12261{
12262  RUNTIME.spawn_blocking(function)
12263}
12264
12265/// Submit blocking work without consuming it when the runtime is not accepting work.
12266///
12267/// Lifecycle owners can retain the returned closure and retry it after [`start`]
12268/// completes a runtime restart.
12269///
12270/// **On a wasm target, prefer this over [`spawn_blocking`]** -- see
12271/// [`try_spawn`] for why.
12272pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12273where
12274  F: FnOnce() -> T + Send + 'static,
12275  T: Send + 'static,
12276{
12277  RUNTIME.try_spawn_blocking(function)
12278}
12279
12280fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12281  let (backend, registration) = match controller.try_block_on_scope() {
12282    Ok(scope) => scope,
12283    Err((error, generation)) => {
12284      let _rejection = RejectedSubmissionGuard { controller };
12285      let _drop_context = RejectedSubmissionDropContext::enter();
12286      let _generation = generation.map(RuntimeGenerationGuard::enter);
12287      let _ = catch_unwind_contained(|| drop(future));
12288      panic!("{error}");
12289    }
12290  };
12291  let generation = backend.generation();
12292  let _registration = registration;
12293  let _generation = RuntimeGenerationGuard::enter(generation);
12294  let mut output = None;
12295  {
12296    let future = ContainedFuture::new(future, generation);
12297    let mut erased = std::pin::pin!(async {
12298      output = Some(future.await);
12299    });
12300    let _ = backend.block_on(erased.as_mut());
12301    // `erased` is destroyed before this scope exits, while the controller's
12302    // generation work and identity guards are live. `ContainedFuture` keeps a
12303    // poll panic from becoming a process-aborting double panic if the owned
12304    // future's destructor also unwinds.
12305  }
12306  output.expect("async runtime returned before the future completed")
12307}
12308
12309pub fn block_on<F: Future>(future: F) -> F::Output {
12310  block_on_with_controller(&RUNTIME, future)
12311}
12312
12313pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12314  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12315}
12316
12317/// Drive a borrowed future without consuming it when admission or shutdown fails.
12318///
12319/// **On a wasm target, prefer this over [`block_on`] / [`block_on_dyn`]** --
12320/// see [`try_spawn`] for why.
12321///
12322/// Under [`RuntimeFlavor::MultiThread`] this parks the CALLING thread on
12323/// `memory.atomic.wait32`, which a browser main thread is forbidden to do
12324/// (emnapi answers `napi_would_deadlock` there). The threaded wasm artifact is
12325/// a Node / WebContainer target today; inside a Worker it is fine.
12326pub fn try_block_on_dyn(
12327  future: Pin<&mut dyn Future<Output = ()>>,
12328) -> Result<(), RuntimeConfigError> {
12329  match RUNTIME.try_block_on(future)? {
12330    BlockOnOutcome::Completed => Ok(()),
12331    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12332      "the async runtime stopped before block_on completed its future".to_string(),
12333    )),
12334  }
12335}
12336
12337pub fn start() -> Result<(), RuntimeConfigError> {
12338  RUNTIME.start()
12339}
12340
12341/// Shut the async runtime down: stop accepting work, cancel what is queued,
12342/// wait for what is running, join the workers.
12343///
12344/// Equivalent to [`begin_shutdown`] followed by [`finish_shutdown`], and
12345/// unchanged in ordering and semantics. It reports the shutdown it performed
12346/// even when another thread completes that shutdown, or restarts the runtime,
12347/// between the two halves.
12348pub fn shutdown() -> Result<(), RuntimeConfigError> {
12349  RUNTIME.shutdown()
12350}
12351
12352/// Phase 1 of a two-phase shutdown. Publishes the stop, closes admission,
12353/// aborts registered tasks, drops queued work, drain-fires timers and wakes
12354/// parkers -- and never waits. Never, including for a rejected submission
12355/// whose destructor is still running on another thread: that wait belongs to
12356/// [`finish_shutdown`] too.
12357///
12358/// `Ok(true)` means the announced stop still owes a wait -- registered work
12359/// that is still live, or a rejected submission still being destroyed: let
12360/// the host loop turn, poll [`runtime_work_pending`] until it answers `false`
12361/// (or a bound expires), then call [`finish_shutdown`]. `Ok(false)` means
12362/// [`finish_shutdown`] has nothing to wait for.
12363///
12364/// For a host that cannot yield inside [`shutdown`] -- on the threaded WASI
12365/// artifact the call comes from the JS thread, which is also the only thread
12366/// that can give a running blocking closure the turn it is waiting for.
12367///
12368/// Submissions are rejected between the phases, and [`start`] must not be
12369/// called there: it waits for phase 2 to publish the stop. A second
12370/// `begin_shutdown` before [`finish_shutdown`] neither waits nor starts a
12371/// second shutdown; it just re-reports [`runtime_work_pending`]. Calls of the
12372/// two phases do not have to balance: [`finish_shutdown`] completes the most
12373/// recently announced stop and is idempotent.
12374pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12375  RUNTIME.begin_shutdown()
12376}
12377
12378/// Non-blocking poll for the window between [`begin_shutdown`] and
12379/// [`finish_shutdown`]: `false` means phase 2 will not wait for user work.
12380///
12381/// Answers for the running generation when no shutdown is outstanding.
12382///
12383/// It never waits for a lock either: when another thread holds one it reads
12384/// under, it answers `true` and the host polls again on its next turn. That
12385/// keeps a pool thread that died holding such a lock (a trap on
12386/// `wasm32-wasip1-threads` unwinds nothing) from parking the polling thread
12387/// for good.
12388pub fn runtime_work_pending() -> bool {
12389  RUNTIME.runtime_work_pending()
12390}
12391
12392/// Phase 2 of a two-phase shutdown: wait for the generation to go idle, join
12393/// the workers and publish the stop.
12394///
12395/// Completes the most recently announced stop, whichever thread performs the
12396/// join, and is idempotent: extra calls, and calls that race another thread
12397/// completing the same shutdown, answer `Ok(())`. A call that arrives after a
12398/// [`start`] finds nothing outstanding and is a no-op -- it never stops the
12399/// generation that restart published. It errors only when no [`begin_shutdown`]
12400/// has EVER been called on this runtime, and when called from work of the
12401/// generation being stopped. Calling it while [`runtime_work_pending`] still
12402/// answers `true` is allowed -- it then blocks, which is what [`shutdown`]
12403/// does.
12404pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12405  RUNTIME.finish_shutdown()
12406}
12407
12408fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12409  for failure in completion.failed_dispatches {
12410    RUNTIME.cancel_current_thread_dispatch(failure);
12411  }
12412  if completion.redispatch {
12413    RUNTIME.request_current_thread_drain();
12414  }
12415  // Every host entry point lands here (delivery acknowledgement or failure,
12416  // driver unregistration, explicit dispatch cancellation). Work a
12417  // waker-stack failure transition moved aside is cancelled on this clean
12418  // stack even when the completion itself carried nothing, e.g. an env
12419  // cleanup hook unregistering a driver whose deliveries all failed already.
12420  RUNTIME.retire_rejected_current_thread_work();
12421}
12422
12423/// Acknowledge that one exact host delivery callback returned the successful
12424/// claim result from [`drive_current_thread_tasks`].
12425///
12426/// This releases the host's single-flight slot and submits at most its latest
12427/// still-needed pending delivery. The registry verifies that this exact
12428/// delivery was claimed; a forged success for an unclaimed delivery is treated
12429/// as failure. A stale acknowledgement from an unregistered host or retired
12430/// attempt is a no-op.
12431pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12432  complete_current_thread_task_delivery(
12433    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12434  );
12435}
12436
12437/// Report that one exact host delivery callback failed.
12438///
12439/// Recovery begins only after no other live host attempt can still service the
12440/// same executor dispatch. The registration and delivery identities prevent a
12441/// stale failure from affecting another host or a re-registered generation.
12442pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12443  complete_current_thread_task_delivery(
12444    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12445  );
12446}
12447
12448/// Cancel an accepted CurrentThread host delivery that failed before it could
12449/// schedule a fresh turn.
12450///
12451/// `delivery` is the opaque registration-scoped driver capability,
12452/// not the executor's shared dispatch capability. Repeated or stale failures
12453/// are no-ops.
12454pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12455  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12456}
12457
12458/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
12459///
12460/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
12461/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
12462/// Returns a callback lease only when this exact delivery was claimed, admitted,
12463/// and driven. It returns `None` for stale/unregistered deliveries, before
12464/// backend creation, and on MultiThread. The host must retain the lease through
12465/// delivery acknowledgement/failure and callback payload destruction.
12466pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12467  RUNTIME.drive_current_thread_tasks(delivery)
12468}
12469
12470pub fn reset_metrics() {
12471  RUNTIME.metrics.reset();
12472}
12473
12474pub fn metrics() -> RuntimeMetricsSnapshot {
12475  let options = RUNTIME.options();
12476  RUNTIME.metrics.snapshot(&options)
12477}
12478
12479#[cfg(test)]
12480// The wasm lanes gate out whole classes of tests (see the three rules below),
12481// which strands their helper fns, statics and driver registries. That is the
12482// point of the gates, not a defect, and 100+ dead-code warnings would bury a
12483// real one in the `cargo check --tests` guard CI runs on both WASI triples.
12484#[cfg_attr(target_family = "wasm", allow(dead_code))]
12485mod tests {
12486  use super::*;
12487
12488  // Three gates in here do NOT mean "can this build create OS threads?", so
12489  // the `napi_runtime_os_threads` rewrite deliberately does not reach them.
12490  // Each was verified by running the suite on `wasm32-wasip1-threads`:
12491  //
12492  // * `#[cfg(not(target_family = "wasm"))]` on a test that re-execs itself
12493  //   through `std::process::Command`: `std::env::current_exe()` is
12494  //   `Unsupported` on WASI (22 tests).
12495  // * `#[cfg(not(target_family = "wasm"))]` on a test that asserts the
12496  //   DEFAULT flavor is MultiThread: every wasm target keeps CurrentThread by
12497  //   default, which `RuntimeOptions::default` still decides with
12498  //   `cfg!(target_family = "wasm")` (2 tests).
12499  // * `panic = "unwind"` on a test that drives a panic through
12500  //   `catch_unwind`: both WASI targets are `panic = "abort"`, so every
12501  //   containment path this crate has is inert there and the panic takes the
12502  //   process down instead of becoming a `JoinError` (43 tests). The
12503  //   containment code itself still compiles and runs; only the tests that
12504  //   PROVE containment need an unwinding target.
12505  //
12506  // `napi_runtime_os_threads` itself is also the right gate for a test whose
12507  // HARNESS spawns a `std::thread` even though the code under test does not:
12508  // threadless WASI answers `thread_spawn` with `Os { code: 58, Unsupported }`
12509  // (13 tests).
12510
12511  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12512  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12513    LazyLock::new(Mutex::default);
12514  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12515  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12516  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12517  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12518    LazyLock::new(Mutex::default);
12519  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12520  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12521    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12522  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12523    LazyLock::new(Mutex::default);
12524  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12525  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12526    CurrentThreadTaskDriverRegistry,
12527  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12528  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12529  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12530  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12531    LazyLock::new(Mutex::default);
12532  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12533    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12534  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12535    LazyLock::new(Mutex::default);
12536  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12537  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12538    LazyLock::new(Mutex::default);
12539  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12540    LazyLock::new(Mutex::default);
12541  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12542    CurrentThreadTaskDriverRegistry,
12543  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12544  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12545    LazyLock::new(Mutex::default);
12546  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12547  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12548    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12549  static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12550    CurrentThreadTaskDriverRegistry,
12551  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12552  static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12553    CurrentThreadTaskDriverRegistry,
12554  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12555  static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12556    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12557  static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12558    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12559  static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12560    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12561  static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12562    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12563  static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12564    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12565  static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12566    CurrentThreadTaskDriverRegistry,
12567  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12568  static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12569    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12570  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12571
12572  thread_local! {
12573    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12574      const { std::cell::Cell::new(0) };
12575    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12576      const { std::cell::Cell::new(0) };
12577  }
12578
12579  #[cfg(napi_runtime_os_threads)]
12580  struct StopPollProbe {
12581    polls: Arc<AtomicUsize>,
12582    first_poll: Option<std::sync::mpsc::Sender<()>>,
12583  }
12584
12585  #[cfg(napi_runtime_os_threads)]
12586  impl Future for StopPollProbe {
12587    type Output = ();
12588
12589    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12590      self.polls.fetch_add(1, Ordering::SeqCst);
12591      if let Some(first_poll) = self.first_poll.take() {
12592        first_poll.send(()).unwrap();
12593      }
12594      Poll::Pending
12595    }
12596  }
12597
12598  #[cfg(napi_runtime_os_threads)]
12599  struct PanickingJoinAwaiter {
12600    wakes: Arc<AtomicUsize>,
12601    drops: Arc<AtomicUsize>,
12602  }
12603
12604  #[cfg(napi_runtime_os_threads)]
12605  impl std::task::Wake for PanickingJoinAwaiter {
12606    fn wake(self: Arc<Self>) {
12607      self.wakes.fetch_add(1, Ordering::SeqCst);
12608      panic!("intentional JoinHandle awaiter wake panic");
12609    }
12610
12611    fn wake_by_ref(self: &Arc<Self>) {
12612      self.wakes.fetch_add(1, Ordering::SeqCst);
12613      panic!("intentional JoinHandle awaiter wake-by-ref panic");
12614    }
12615  }
12616
12617  #[cfg(napi_runtime_os_threads)]
12618  impl Drop for PanickingJoinAwaiter {
12619    fn drop(&mut self) {
12620      self.drops.fetch_add(1, Ordering::SeqCst);
12621      panic!("intentional JoinHandle awaiter destructor panic");
12622    }
12623  }
12624
12625  #[cfg(napi_runtime_os_threads)]
12626  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12627    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12628  }
12629
12630  #[cfg(napi_runtime_os_threads)]
12631  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12632  enum JoinAwaiterWakerEventKind {
12633    Clone,
12634    Wake,
12635    WakeByRef,
12636    Drop,
12637  }
12638
12639  #[cfg(napi_runtime_os_threads)]
12640  struct JoinAwaiterWakerState {
12641    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12642  }
12643
12644  #[cfg(napi_runtime_os_threads)]
12645  impl JoinAwaiterWakerState {
12646    fn record(&self, kind: JoinAwaiterWakerEventKind) {
12647      self
12648        .events
12649        .lock()
12650        .unwrap_or_else(std::sync::PoisonError::into_inner)
12651        .push((kind, active_runtime_generation()));
12652    }
12653
12654    fn clear(&self) {
12655      self
12656        .events
12657        .lock()
12658        .unwrap_or_else(std::sync::PoisonError::into_inner)
12659        .clear();
12660    }
12661
12662    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12663      self
12664        .events
12665        .lock()
12666        .unwrap_or_else(std::sync::PoisonError::into_inner)
12667        .clone()
12668    }
12669  }
12670
12671  #[cfg(napi_runtime_os_threads)]
12672  struct JoinAwaiterWakerToken {
12673    state: Arc<JoinAwaiterWakerState>,
12674  }
12675
12676  #[cfg(napi_runtime_os_threads)]
12677  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12678    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12679    token.state.record(JoinAwaiterWakerEventKind::Clone);
12680    let clone = Box::new(JoinAwaiterWakerToken {
12681      state: Arc::clone(&token.state),
12682    });
12683    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12684  }
12685
12686  #[cfg(napi_runtime_os_threads)]
12687  unsafe fn wake_join_awaiter_waker(data: *const ()) {
12688    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12689    token.state.record(JoinAwaiterWakerEventKind::Wake);
12690  }
12691
12692  #[cfg(napi_runtime_os_threads)]
12693  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12694    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12695    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12696  }
12697
12698  #[cfg(napi_runtime_os_threads)]
12699  unsafe fn drop_join_awaiter_waker(data: *const ()) {
12700    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12701    token.state.record(JoinAwaiterWakerEventKind::Drop);
12702  }
12703
12704  #[cfg(napi_runtime_os_threads)]
12705  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12706    clone_join_awaiter_waker,
12707    wake_join_awaiter_waker,
12708    wake_join_awaiter_waker_by_ref,
12709    drop_join_awaiter_waker,
12710  );
12711
12712  #[cfg(napi_runtime_os_threads)]
12713  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12714    let token = Box::new(JoinAwaiterWakerToken { state });
12715    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12716    unsafe { Waker::from_raw(raw) }
12717  }
12718
12719  #[cfg(napi_runtime_os_threads)]
12720  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12721    wait_until("the block_on driver to sleep", || {
12722      stop
12723        .parkers
12724        .lock()
12725        .unwrap_or_else(std::sync::PoisonError::into_inner)
12726        .iter()
12727        .filter_map(Weak::upgrade)
12728        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12729    });
12730  }
12731
12732  #[cfg(panic = "unwind")]
12733  #[test]
12734  fn unique_ids_fail_before_wraparound() {
12735    let counter = AtomicU64::new(u64::MAX - 1);
12736    assert_eq!(
12737      next_unique_id(&counter, "test id space exhausted"),
12738      u64::MAX - 1
12739    );
12740
12741    let exhausted = catch_unwind(AssertUnwindSafe(|| {
12742      next_unique_id(&counter, "test id space exhausted");
12743    }));
12744    assert!(exhausted.is_err());
12745    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12746  }
12747
12748  #[cfg(panic = "unwind")]
12749  #[test]
12750  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12751    let work = GenerationWork::new();
12752    {
12753      let mut state = work.state.lock().unwrap();
12754      state.next_task_id = u64::MAX;
12755    }
12756
12757    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12758    assert!(exhausted.is_err());
12759
12760    let state = work.state.lock().unwrap();
12761    assert_eq!(state.next_task_id, u64::MAX);
12762    assert_eq!(state.active, 0);
12763    assert!(state.abort_handles.is_empty());
12764  }
12765
12766  /// A waker that records, when its last reference drops, whether `work`'s
12767  /// state lock was free at that moment. `Abortable` parks the waker it is
12768  /// polled with in the abort state its handles share, so the waker drops
12769  /// exactly where the last `AbortHandle` of that task does.
12770  struct GenerationStateLockProbe {
12771    work: Weak<GenerationWork>,
12772    dropped_with_lock_free: Arc<Mutex<Option<bool>>>,
12773  }
12774
12775  #[expect(
12776    clippy::manual_noop_waker,
12777    reason = "the test observes the waker payload's Drop"
12778  )]
12779  impl std::task::Wake for GenerationStateLockProbe {
12780    fn wake(self: Arc<Self>) {}
12781  }
12782
12783  impl Drop for GenerationStateLockProbe {
12784    fn drop(&mut self) {
12785      let lock_free = self
12786        .work
12787        .upgrade()
12788        .is_some_and(|work| work.state.try_lock().is_ok());
12789      *self.dropped_with_lock_free.lock().unwrap() = Some(lock_free);
12790    }
12791  }
12792
12793  /// Register one async task on `work` and leave the handle `work` keeps as
12794  /// the only owner of its abort state, with a [`GenerationStateLockProbe`]
12795  /// parked in it.
12796  fn register_task_with_state_lock_probe(
12797    work: &Arc<GenerationWork>,
12798  ) -> (GenerationWorkGuard, Arc<Mutex<Option<bool>>>) {
12799    let (registration, guard) = work
12800      .try_register_async()
12801      .expect("an open generation must accept the task");
12802    let dropped_with_lock_free = Arc::new(Mutex::new(None));
12803    let waker = Waker::from(Arc::new(GenerationStateLockProbe {
12804      work: Arc::downgrade(work),
12805      dropped_with_lock_free: Arc::clone(&dropped_with_lock_free),
12806    }));
12807    let mut task = std::pin::pin!(Abortable::new(std::future::pending::<()>(), registration));
12808    assert!(
12809      task
12810        .as_mut()
12811        .poll(&mut Context::from_waker(&waker))
12812        .is_pending()
12813    );
12814    (guard, dropped_with_lock_free)
12815  }
12816
12817  #[test]
12818  fn generation_work_guard_drops_its_abort_handle_outside_the_state_lock() {
12819    // The last `AbortHandle` of a task frees its abort state and the waker
12820    // parked there, and a waker's drop is foreign code. Under the state lock,
12821    // anything that goes wrong in there -- on `wasm32-wasip1-threads`, a trap
12822    // in the allocator unwinds nothing -- leaves the lock held for good.
12823    let work = GenerationWork::new();
12824    let (guard, dropped_with_lock_free) = register_task_with_state_lock_probe(&work);
12825    assert_eq!(
12826      *dropped_with_lock_free.lock().unwrap(),
12827      None,
12828      "the handle the generation keeps must still own the abort state"
12829    );
12830
12831    drop(guard);
12832    assert_eq!(
12833      *dropped_with_lock_free.lock().unwrap(),
12834      Some(true),
12835      "the guard must drop the handle it removed after releasing the state lock"
12836    );
12837  }
12838
12839  #[test]
12840  fn close_and_abort_takes_the_abort_handles_out_of_the_state() {
12841    // `abort` consumes the parked waker, so what is left to free is the abort
12842    // state itself. Taking the handles out at close frees it after the lock
12843    // is released, and a guard retiring later finds nothing to free under it.
12844    let work = GenerationWork::new();
12845    let (registration, guard) = work
12846      .try_register_async()
12847      .expect("an open generation must accept the task");
12848    let task = Abortable::new(std::future::pending::<()>(), registration);
12849
12850    work.close_and_abort();
12851    assert!(task.is_aborted(), "close must abort the registered task");
12852    assert!(
12853      work.state.lock().unwrap().abort_handles.is_empty(),
12854      "close must take the handles out of the state, not leave them for the guards"
12855    );
12856    drop(task);
12857    drop(guard);
12858    assert_eq!(work.state.lock().unwrap().active, 0);
12859  }
12860
12861  #[cfg(panic = "unwind")]
12862  #[test]
12863  fn caught_panic_payload_destructor_runs() {
12864    struct DropSignal(Arc<AtomicBool>);
12865
12866    impl Drop for DropSignal {
12867      fn drop(&mut self) {
12868        self.0.store(true, Ordering::SeqCst);
12869      }
12870    }
12871
12872    let dropped = Arc::new(AtomicBool::new(false));
12873    let payload = DropSignal(Arc::clone(&dropped));
12874
12875    assert!(
12876      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12877      "the original panic must remain contained"
12878    );
12879    assert!(
12880      dropped.load(Ordering::SeqCst),
12881      "an ordinary caught panic payload must be destroyed"
12882    );
12883  }
12884
12885  #[cfg(panic = "unwind")]
12886  #[test]
12887  fn hostile_caught_panic_payload_destructor_remains_contained() {
12888    struct PanicOnDrop(Arc<AtomicBool>);
12889
12890    impl Drop for PanicOnDrop {
12891      fn drop(&mut self) {
12892        self.0.store(true, Ordering::SeqCst);
12893        panic!("caught panic payload destructor escaped");
12894      }
12895    }
12896
12897    let dropped = Arc::new(AtomicBool::new(false));
12898    let payload = PanicOnDrop(Arc::clone(&dropped));
12899    let result = catch_unwind(AssertUnwindSafe(|| {
12900      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12901    }));
12902
12903    assert!(
12904      result.is_ok(),
12905      "the payload destructor's nested panic must remain contained"
12906    );
12907    assert!(
12908      result.unwrap().is_none(),
12909      "the original panic must remain contained"
12910    );
12911    assert!(
12912      dropped.load(Ordering::SeqCst),
12913      "the hostile payload destructor must be attempted"
12914    );
12915  }
12916
12917  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12918    CurrentThreadHostDispatchResult::Accepted
12919  }
12920
12921  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12922    CurrentThreadHostDispatchResult::Unavailable
12923  }
12924
12925  fn count_current_thread_recovery_host_dispatch(
12926    _dispatch: u64,
12927  ) -> CurrentThreadHostDispatchResult {
12928    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12929    CurrentThreadHostDispatchResult::Accepted
12930  }
12931
12932  fn reset_current_thread_recovery_host_dispatches() {
12933    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12934  }
12935
12936  fn current_thread_recovery_host_dispatches() -> usize {
12937    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12938  }
12939
12940  fn accept_only_first_current_thread_host_dispatch(
12941    _dispatch: u64,
12942  ) -> CurrentThreadHostDispatchResult {
12943    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12944      let previous = dispatches.get();
12945      dispatches.set(previous + 1);
12946      previous == 0
12947    }) {
12948      CurrentThreadHostDispatchResult::Accepted
12949    } else {
12950      CurrentThreadHostDispatchResult::Unavailable
12951    }
12952  }
12953
12954  fn reset_current_thread_one_shot_host_dispatches() {
12955    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12956  }
12957
12958  fn current_thread_one_shot_host_dispatches() -> usize {
12959    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12960  }
12961
12962  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12963    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12964    CurrentThreadHostDispatchResult::Accepted
12965  }
12966
12967  fn count_current_thread_blocking_budget_host_dispatch(
12968    _dispatch: u64,
12969  ) -> CurrentThreadHostDispatchResult {
12970    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12971    CurrentThreadHostDispatchResult::Accepted
12972  }
12973
12974  fn count_current_thread_admission_host_dispatch(
12975    _dispatch: u64,
12976  ) -> CurrentThreadHostDispatchResult {
12977    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12978    CurrentThreadHostDispatchResult::Accepted
12979  }
12980
12981  fn count_current_thread_interleaved_recovery_host_dispatch(
12982    _dispatch: u64,
12983  ) -> CurrentThreadHostDispatchResult {
12984    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12985    CurrentThreadHostDispatchResult::Accepted
12986  }
12987
12988  fn dispatch_late_registration_current_thread_tasks(
12989    dispatch: u64,
12990  ) -> CurrentThreadHostDispatchResult {
12991    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12992  }
12993
12994  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12995    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12996      .lock()
12997      .unwrap()
12998      .push(dispatch);
12999    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
13000  }
13001
13002  fn dispatch_authoritative_panic_current_thread_tasks(
13003    dispatch: u64,
13004  ) -> CurrentThreadHostDispatchResult {
13005    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13006      .lock()
13007      .unwrap()
13008      .push(dispatch);
13009    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
13010      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
13011      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
13012      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
13013    }
13014  }
13015
13016  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
13017    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13018  }
13019
13020  fn dispatch_waker_stack_failure_current_thread_tasks(
13021    dispatch: u64,
13022  ) -> CurrentThreadHostDispatchResult {
13023    CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13024  }
13025
13026  fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
13027    dispatch: u64,
13028  ) -> CurrentThreadHostDispatchResult {
13029    CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13030  }
13031
13032  fn dispatch_submission_failure_current_thread_tasks(
13033    dispatch: u64,
13034  ) -> CurrentThreadHostDispatchResult {
13035    CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
13036  }
13037
13038  fn dispatch_retirement_turn_current_thread_tasks(
13039    dispatch: u64,
13040  ) -> CurrentThreadHostDispatchResult {
13041    CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
13042  }
13043
13044  fn dispatch_nested_retirement_current_thread_tasks(
13045    dispatch: u64,
13046  ) -> CurrentThreadHostDispatchResult {
13047    CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
13048  }
13049
13050  fn dispatch_sustained_producer_current_thread_tasks(
13051    dispatch: u64,
13052  ) -> CurrentThreadHostDispatchResult {
13053    CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
13054  }
13055
13056  fn dispatch_retirement_retry_current_thread_tasks(
13057    dispatch: u64,
13058  ) -> CurrentThreadHostDispatchResult {
13059    CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
13060  }
13061
13062  fn dispatch_final_retirement_offer_current_thread_tasks(
13063    dispatch: u64,
13064  ) -> CurrentThreadHostDispatchResult {
13065    CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
13066  }
13067
13068  fn dispatch_inline_final_offer_current_thread_tasks(
13069    dispatch: u64,
13070  ) -> CurrentThreadHostDispatchResult {
13071    CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
13072  }
13073
13074  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
13075    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13076      .lock()
13077      .unwrap()
13078      .push(dispatch);
13079    assert!(
13080      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13081      "injected CurrentThread host dispatch panic"
13082    );
13083    CurrentThreadHostDispatchResult::Accepted
13084  }
13085
13086  fn panic_once_current_thread_armed_republish_host_dispatch(
13087    dispatch: u64,
13088  ) -> CurrentThreadHostDispatchResult {
13089    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13090      .lock()
13091      .unwrap()
13092      .push(dispatch);
13093    assert!(
13094      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
13095      "injected armed CurrentThread host dispatch panic"
13096    );
13097    CurrentThreadHostDispatchResult::Accepted
13098  }
13099
13100  fn record_current_thread_republish_rearm_dispatch(
13101    dispatch: u64,
13102  ) -> CurrentThreadHostDispatchResult {
13103    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13104      .lock()
13105      .unwrap()
13106      .push(dispatch);
13107    CurrentThreadHostDispatchResult::Accepted
13108  }
13109
13110  fn reject_initial_current_thread_publication_once(
13111    dispatch: u64,
13112  ) -> CurrentThreadHostDispatchResult {
13113    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13114      .lock()
13115      .unwrap();
13116    dispatches.push(dispatch);
13117    if dispatches.len() == 1 {
13118      CurrentThreadHostDispatchResult::Unavailable
13119    } else {
13120      CurrentThreadHostDispatchResult::Accepted
13121    }
13122  }
13123
13124  fn reject_current_thread_replacement_publication_once(
13125    dispatch: u64,
13126  ) -> CurrentThreadHostDispatchResult {
13127    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13128      .lock()
13129      .unwrap();
13130    dispatches.push(dispatch);
13131    if dispatches.len() == 2 {
13132      CurrentThreadHostDispatchResult::Unavailable
13133    } else {
13134      CurrentThreadHostDispatchResult::Accepted
13135    }
13136  }
13137
13138  fn fail_current_thread_replacement_publication_once(
13139    dispatch: u64,
13140  ) -> CurrentThreadHostDispatchResult {
13141    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13142      .lock()
13143      .unwrap()
13144      .push(dispatch);
13145    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
13146  }
13147
13148  fn count_current_thread_shutdown_republish_host_dispatch(
13149    _dispatch: u64,
13150  ) -> CurrentThreadHostDispatchResult {
13151    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13152    CurrentThreadHostDispatchResult::Accepted
13153  }
13154
13155  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
13156    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
13157    CurrentThreadHostDispatchResult::Accepted
13158  }
13159
13160  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
13161    let pending = executor.dispatch_pending.load(Ordering::Acquire);
13162    if pending != 0 {
13163      return pending;
13164    }
13165    let dispatch = next_current_thread_dispatch_id();
13166    match executor.dispatch_pending.compare_exchange(
13167      0,
13168      dispatch,
13169      Ordering::AcqRel,
13170      Ordering::Acquire,
13171    ) {
13172      Ok(_) => dispatch,
13173      Err(pending) => pending,
13174    }
13175  }
13176
13177  fn current_thread_dispatch_failure(
13178    dispatch: u64,
13179    epoch: u64,
13180  ) -> CurrentThreadTaskDispatchFailure {
13181    CurrentThreadTaskDispatchFailure { dispatch, epoch }
13182  }
13183
13184  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
13185    let executor = controller
13186      .running_current_thread_executor()
13187      .expect("the test controller must have a running CurrentThread executor");
13188    current_thread_dispatch(&executor)
13189  }
13190
13191  #[test]
13192  fn current_thread_executor_drives_spawned_tasks() {
13193    let metrics = Arc::new(RuntimeMetrics::default());
13194    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
13195    let scheduler = Arc::clone(&executor);
13196    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
13197      scheduler.schedule(runnable);
13198    });
13199
13200    executor.schedule(runnable);
13201
13202    let mut output = None;
13203    {
13204      let mut future = std::pin::pin!(async {
13205        output = Some(task.await);
13206      });
13207      executor.block_on(future.as_mut());
13208    }
13209    assert_eq!(output, Some(42));
13210    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
13211    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
13212  }
13213
13214  #[cfg(napi_runtime_os_threads)]
13215  #[test]
13216  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13217    use std::sync::mpsc;
13218
13219    let executor = Arc::new(CurrentThreadExecutor::with_detection(
13220      Arc::new(RuntimeMetrics::default()),
13221      false,
13222      None,
13223    ));
13224    let polls = Arc::new(AtomicUsize::new(0));
13225    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13226    let (outcome_tx, outcome_rx) = mpsc::channel();
13227    let driver_executor = Arc::clone(&executor);
13228    let driver_polls = Arc::clone(&polls);
13229    let driver = std::thread::spawn(move || {
13230      let mut future = std::pin::pin!(StopPollProbe {
13231        polls: driver_polls,
13232        first_poll: Some(first_poll_tx)
13233      });
13234      outcome_tx
13235        .send(driver_executor.block_on(future.as_mut()))
13236        .unwrap();
13237    });
13238
13239    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13240    wait_for_generation_parker_to_sleep(&executor.stop);
13241    executor.stop.begin_shutdown();
13242
13243    assert_eq!(
13244      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13245      BlockOnOutcome::Stopped
13246    );
13247    driver.join().unwrap();
13248    assert_eq!(
13249      polls.load(Ordering::SeqCst),
13250      1,
13251      "a shutdown wake must be checked before the next CurrentThread user poll"
13252    );
13253  }
13254
13255  #[cfg(napi_runtime_os_threads)]
13256  #[test]
13257  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13258    use std::sync::mpsc;
13259
13260    let metrics = Arc::new(RuntimeMetrics::default());
13261    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13262      Arc::clone(&metrics),
13263      accept_current_thread_host_dispatch,
13264    ));
13265    let polls = Arc::new(AtomicUsize::new(0));
13266    let task_polls = Arc::clone(&polls);
13267    let (runnable, task) = async_task::spawn(
13268      async move {
13269        task_polls.fetch_add(1, Ordering::SeqCst);
13270      },
13271      |_| {},
13272    );
13273    executor.schedule(runnable);
13274    let dispatch = current_thread_dispatch(&executor);
13275
13276    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13277    let (release_claim_tx, release_claim_rx) = mpsc::channel();
13278    let driver_executor = Arc::clone(&executor);
13279    let driver = std::thread::spawn(move || {
13280      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13281        *slot.borrow_mut() = Some(Box::new(move || {
13282          claim_locked_tx.send(()).unwrap();
13283          release_claim_rx.recv().unwrap();
13284        }));
13285      });
13286      driver_executor.drive_host_turn_for_dispatch(dispatch);
13287    });
13288    claim_locked_rx
13289      .recv_timeout(Duration::from_secs(2))
13290      .expect("the admitted host turn must pause before its runnable claim");
13291
13292    executor.stop.publish_stopping();
13293    release_claim_tx.send(()).unwrap();
13294    driver.join().unwrap();
13295
13296    assert_eq!(
13297      polls.load(Ordering::SeqCst),
13298      0,
13299      "stop publication must outrank a later runnable claim by an already-admitted host turn"
13300    );
13301    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13302
13303    executor.begin_shutdown();
13304    assert!(
13305      futures::executor::block_on(task.fallible()).is_none(),
13306      "shutdown must cancel the runnable rejected by the stopped claim"
13307    );
13308    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13309  }
13310
13311  #[cfg(napi_runtime_os_threads)]
13312  #[test]
13313  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13314    use std::sync::mpsc;
13315
13316    let metrics = Arc::new(RuntimeMetrics::default());
13317    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13318      Arc::clone(&metrics),
13319      accept_current_thread_host_dispatch,
13320    ));
13321    let polls = Arc::new(AtomicUsize::new(0));
13322    let task_polls = Arc::clone(&polls);
13323    let (runnable, task) = async_task::spawn(
13324      async move {
13325        task_polls.fetch_add(1, Ordering::SeqCst);
13326      },
13327      |_| {},
13328    );
13329    executor.schedule(runnable);
13330
13331    let queue = executor
13332      .queue
13333      .lock()
13334      .unwrap_or_else(std::sync::PoisonError::into_inner);
13335    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13336    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13337    let cancelling_executor = Arc::clone(&executor);
13338    let cancellation = std::thread::spawn(move || {
13339      let scheduler = cancelling_executor
13340        .scheduler_idle_lock
13341        .lock()
13342        .unwrap_or_else(std::sync::PoisonError::into_inner);
13343      let cancelling = cancelling_executor
13344        .begin_failed_dispatch_cancellation()
13345        .expect("the test must publish terminal dispatch cancellation");
13346      drop(scheduler);
13347      cancellation_published_tx.send(()).unwrap();
13348      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13349      cancellation_done_tx.send(()).unwrap();
13350    });
13351    cancellation_published_rx
13352      .recv_timeout(Duration::from_secs(2))
13353      .expect("terminal dispatch cancellation must publish before queue draining");
13354
13355    let claimant_executor = Arc::clone(&executor);
13356    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13357    assert!(
13358      !claimant.join().unwrap(),
13359      "a runnable claim after terminal cancellation publication must be rejected"
13360    );
13361    assert_eq!(polls.load(Ordering::SeqCst), 0);
13362    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13363    assert!(
13364      matches!(
13365        cancellation_done_rx.try_recv(),
13366        Err(mpsc::TryRecvError::Empty)
13367      ),
13368      "terminal cancellation must still be waiting to drain the queued runnable"
13369    );
13370
13371    drop(queue);
13372    cancellation_done_rx
13373      .recv_timeout(Duration::from_secs(2))
13374      .expect("terminal cancellation must drain the rejected runnable");
13375    cancellation.join().unwrap();
13376    assert!(
13377      futures::executor::block_on(task.fallible()).is_none(),
13378      "terminal cancellation must cancel the runnable it prevented from starting"
13379    );
13380    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13381  }
13382
13383  #[cfg(napi_runtime_os_threads)]
13384  #[test]
13385  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13386    use std::sync::mpsc;
13387
13388    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13389    let polls = Arc::new(AtomicUsize::new(0));
13390    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13391    let (outcome_tx, outcome_rx) = mpsc::channel();
13392    let driver_executor = Arc::clone(&executor);
13393    let driver_polls = Arc::clone(&polls);
13394    let driver = std::thread::spawn(move || {
13395      let mut future = std::pin::pin!(StopPollProbe {
13396        polls: driver_polls,
13397        first_poll: Some(first_poll_tx)
13398      });
13399      outcome_tx
13400        .send(driver_executor.block_on(future.as_mut()))
13401        .unwrap();
13402    });
13403
13404    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13405    wait_for_generation_parker_to_sleep(&executor.stop);
13406    executor.stop.begin_shutdown();
13407
13408    assert_eq!(
13409      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13410      BlockOnOutcome::Stopped
13411    );
13412    driver.join().unwrap();
13413    assert_eq!(
13414      polls.load(Ordering::SeqCst),
13415      1,
13416      "a shutdown wake must be checked before the next parking block_on user poll"
13417    );
13418  }
13419
13420  #[cfg(napi_runtime_os_threads)]
13421  #[test]
13422  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13423    use std::sync::mpsc;
13424
13425    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13426    let polls = Arc::new(AtomicUsize::new(0));
13427    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13428    let (outcome_tx, outcome_rx) = mpsc::channel();
13429    let driver_executor = Arc::clone(&executor);
13430    let driver_polls = Arc::clone(&polls);
13431    let driver = std::thread::spawn(move || {
13432      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13433      let mut future = std::pin::pin!(StopPollProbe {
13434        polls: driver_polls,
13435        first_poll: Some(first_poll_tx)
13436      });
13437      outcome_tx
13438        .send(driver_executor.block_on(future.as_mut()))
13439        .unwrap();
13440    });
13441
13442    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13443    wait_for_generation_parker_to_sleep(&executor.stop);
13444    executor.stop.begin_shutdown();
13445
13446    assert_eq!(
13447      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13448      BlockOnOutcome::Stopped
13449    );
13450    driver.join().unwrap();
13451    assert_eq!(
13452      polls.load(Ordering::SeqCst),
13453      1,
13454      "a shutdown wake must be checked before the next cooperative user poll"
13455    );
13456  }
13457
13458  #[test]
13459  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13460    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13461      Arc::new(RuntimeMetrics::default()),
13462      accept_current_thread_host_dispatch,
13463    ));
13464    let pending = u64::MAX - 1;
13465    executor.dispatch_pending.store(pending, Ordering::Release);
13466    let mut allocations = 0;
13467
13468    executor.request_drain_with(|| {
13469      allocations += 1;
13470      u64::MAX
13471    });
13472
13473    assert_eq!(allocations, 0);
13474    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13475  }
13476
13477  #[cfg(napi_runtime_os_threads)]
13478  #[test]
13479  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13480    use std::sync::mpsc;
13481
13482    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13483      Arc::new(RuntimeMetrics::default()),
13484      accept_current_thread_host_dispatch,
13485    ));
13486    let dispatch = current_thread_dispatch(&executor);
13487    let scheduler = executor
13488      .scheduler_idle_lock
13489      .lock()
13490      .unwrap_or_else(std::sync::PoisonError::into_inner);
13491    let (claiming_tx, claiming_rx) = mpsc::channel();
13492    let (claimed_tx, claimed_rx) = mpsc::channel();
13493    let claiming_executor = Arc::clone(&executor);
13494    let claimant = std::thread::spawn(move || {
13495      claiming_tx.send(()).unwrap();
13496      claimed_tx
13497        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13498        .unwrap();
13499    });
13500    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13501
13502    executor.stop.begin_shutdown();
13503    drop(scheduler);
13504
13505    assert!(
13506      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13507      "an old-generation dispatch must not claim a host turn after stop becomes visible"
13508    );
13509    claimant.join().unwrap();
13510    assert_eq!(
13511      executor.dispatch_pending.load(Ordering::Acquire),
13512      dispatch,
13513      "rejecting the stopped claim must not consume its stale capability"
13514    );
13515  }
13516
13517  #[cfg(napi_runtime_os_threads)]
13518  #[test]
13519  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13520    use std::sync::mpsc;
13521
13522    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13523    let metrics = Arc::new(RuntimeMetrics::default());
13524    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13525      Arc::clone(&metrics),
13526      count_current_thread_stopped_host_dispatch,
13527    ));
13528    let scheduler = executor
13529      .scheduler_idle_lock
13530      .lock()
13531      .unwrap_or_else(std::sync::PoisonError::into_inner);
13532    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13533    let scheduling_executor = Arc::clone(&executor);
13534    let scheduling = std::thread::spawn(move || {
13535      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13536        *slot.borrow_mut() = Some(Box::new(move || {
13537          publication_blocked_tx.send(()).unwrap();
13538        }));
13539      });
13540      let scheduler = Arc::clone(&scheduling_executor);
13541      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13542        scheduler.schedule(runnable);
13543      });
13544      task.detach();
13545      scheduling_executor.schedule(runnable);
13546    });
13547
13548    publication_blocked_rx
13549      .recv_timeout(Duration::from_secs(2))
13550      .expect("dispatch publication must block behind the scheduler-idle mutex");
13551    executor.begin_shutdown();
13552    drop(scheduler);
13553    scheduling.join().unwrap();
13554    executor.wait_until_scheduler_idle();
13555
13556    assert_eq!(
13557      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13558      0,
13559      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13560    );
13561    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13562    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13563  }
13564
13565  #[cfg(napi_runtime_os_threads)]
13566  #[test]
13567  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13568    use std::{sync::mpsc, time::Duration};
13569
13570    let metrics = Arc::new(RuntimeMetrics::default());
13571    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13572      Arc::clone(&metrics),
13573      accept_current_thread_host_dispatch,
13574    ));
13575    let scheduler = Arc::clone(&executor);
13576    let completed = Arc::new(AtomicBool::new(false));
13577    let completed_task = Arc::clone(&completed);
13578    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13579    let shared = async move {
13580      wake_rx.await.expect("wake sender dropped");
13581    }
13582    .boxed()
13583    .shared();
13584    let (runnable, task) = async_task::spawn(
13585      async move {
13586        shared.await;
13587        completed_task.store(true, Ordering::SeqCst);
13588      },
13589      move |runnable| scheduler.schedule(runnable),
13590    );
13591
13592    executor.schedule(runnable);
13593    assert!(
13594      !completed.load(Ordering::SeqCst),
13595      "accepted host dispatch must defer the first poll"
13596    );
13597    executor.drive_host_turn();
13598
13599    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13600    std::thread::spawn(move || {
13601      wake_tx.send(()).expect("shared wake receiver dropped");
13602      send_returned_tx.send(()).unwrap();
13603    });
13604    send_returned_rx
13605      .recv_timeout(Duration::from_secs(1))
13606      .expect("Shared wake re-entered its own waker mutex through inline polling");
13607    assert!(
13608      !completed.load(Ordering::SeqCst),
13609      "the wake must remain queued until a fresh host turn"
13610    );
13611
13612    executor.drive_host_turn();
13613    futures::executor::block_on(task);
13614    assert!(completed.load(Ordering::SeqCst));
13615    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13616  }
13617
13618  #[cfg(napi_runtime_os_threads)]
13619  #[test]
13620  fn current_thread_hostless_wake_is_enqueue_only() {
13621    use std::{sync::mpsc, time::Duration};
13622
13623    struct LockOnSecondPoll {
13624      gate: Arc<Mutex<()>>,
13625      first: bool,
13626      waker: Option<mpsc::Sender<Waker>>,
13627      completed: Arc<AtomicBool>,
13628    }
13629
13630    impl Future for LockOnSecondPoll {
13631      type Output = ();
13632
13633      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13634        if self.first {
13635          self.first = false;
13636          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13637          return Poll::Pending;
13638        }
13639        drop(self.gate.lock().unwrap());
13640        self.completed.store(true, Ordering::SeqCst);
13641        Poll::Ready(())
13642      }
13643    }
13644
13645    let metrics = Arc::new(RuntimeMetrics::default());
13646    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13647    let gate = Arc::new(Mutex::new(()));
13648    let completed = Arc::new(AtomicBool::new(false));
13649    let (waker_tx, waker_rx) = mpsc::channel();
13650    let scheduler = Arc::clone(&executor);
13651    let (runnable, task) = async_task::spawn(
13652      LockOnSecondPoll {
13653        gate: Arc::clone(&gate),
13654        first: true,
13655        waker: Some(waker_tx),
13656        completed: Arc::clone(&completed),
13657      },
13658      move |runnable| scheduler.schedule(runnable),
13659    );
13660    executor.schedule(runnable);
13661    executor.drive_host_turn();
13662    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13663
13664    let (returned_tx, returned_rx) = mpsc::channel();
13665    let wake_gate = Arc::clone(&gate);
13666    let waking = std::thread::spawn(move || {
13667      let _held = wake_gate.lock().unwrap();
13668      waker.wake();
13669      returned_tx.send(()).unwrap();
13670    });
13671    returned_rx
13672      .recv_timeout(Duration::from_secs(1))
13673      .expect("hostless wake must enqueue without polling under the caller's lock");
13674    waking.join().unwrap();
13675    assert!(!completed.load(Ordering::SeqCst));
13676
13677    executor.drive_host_turn();
13678    futures::executor::block_on(task);
13679    assert!(completed.load(Ordering::SeqCst));
13680  }
13681
13682  #[cfg(napi_runtime_os_threads)]
13683  #[test]
13684  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13685    use std::sync::mpsc;
13686
13687    let controller = Arc::new(current_thread_controller(
13688      "current-thread-hostless-late-work",
13689    ));
13690    let backend = controller.backend();
13691    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13692      panic!("configured CurrentThread backend must create a CurrentThread executor");
13693    };
13694    let executor = Arc::clone(executor);
13695    drop(backend);
13696
13697    let (value_tx, value_rx) = oneshot::channel();
13698    let (done_tx, done_rx) = mpsc::channel();
13699    let block_on_controller = Arc::clone(&controller);
13700    let runner = std::thread::spawn(move || {
13701      let mut output = None;
13702      let outcome = {
13703        let mut future = std::pin::pin!(async {
13704          output = Some(value_rx.await.expect("scheduled sender must deliver"));
13705        });
13706        block_on_controller.block_on(future.as_mut())
13707      };
13708      done_tx.send((outcome, output)).unwrap();
13709    });
13710
13711    wait_until(
13712      "the hostless CurrentThread block_on driver to sleep",
13713      || {
13714        executor
13715          .stop
13716          .parkers
13717          .lock()
13718          .unwrap_or_else(std::sync::PoisonError::into_inner)
13719          .iter()
13720          .filter_map(Weak::upgrade)
13721          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13722      },
13723    );
13724
13725    controller
13726      .try_spawn_detached(async move {
13727        value_tx
13728          .send(37usize)
13729          .expect("block_on receiver must remain alive");
13730      })
13731      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13732
13733    let (outcome, output) = done_rx
13734      .recv_timeout(Duration::from_secs(2))
13735      .expect("queue publication must wake the sleeping explicit driver");
13736    assert_eq!(outcome, BlockOnOutcome::Completed);
13737    assert_eq!(output, Some(37));
13738    runner.join().unwrap();
13739    controller.shutdown().unwrap();
13740  }
13741
13742  #[cfg(napi_runtime_os_threads)]
13743  #[test]
13744  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13745    use std::sync::mpsc;
13746
13747    struct BlockingPoll {
13748      entered: Option<mpsc::Sender<()>>,
13749      release: mpsc::Receiver<()>,
13750    }
13751
13752    impl Future for BlockingPoll {
13753      type Output = ();
13754
13755      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13756        self.entered.take().unwrap().send(()).unwrap();
13757        self.release.recv().unwrap();
13758        Poll::Ready(())
13759      }
13760    }
13761
13762    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13763      Arc::new(RuntimeMetrics::default()),
13764      reject_current_thread_host_dispatch,
13765    ));
13766    let (value_tx, value_rx) = oneshot::channel();
13767    let (older_done_tx, older_done_rx) = mpsc::channel();
13768    let older_executor = Arc::clone(&executor);
13769    let older = std::thread::spawn(move || {
13770      let mut value = None;
13771      {
13772        let mut future = std::pin::pin!(async {
13773          value = Some(value_rx.await.unwrap());
13774        });
13775        assert_eq!(
13776          older_executor.block_on(future.as_mut()),
13777          BlockOnOutcome::Completed
13778        );
13779      }
13780      older_done_tx.send(value.unwrap()).unwrap();
13781    });
13782    wait_until("the older CurrentThread driver to sleep", || {
13783      executor
13784        .stop
13785        .parkers
13786        .lock()
13787        .unwrap_or_else(std::sync::PoisonError::into_inner)
13788        .iter()
13789        .filter_map(Weak::upgrade)
13790        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13791    });
13792
13793    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13794    let (release_newer_tx, release_newer_rx) = mpsc::channel();
13795    let newer_executor = Arc::clone(&executor);
13796    let newer = std::thread::spawn(move || {
13797      let mut future = std::pin::pin!(BlockingPoll {
13798        entered: Some(newer_entered_tx),
13799        release: release_newer_rx
13800      });
13801      assert_eq!(
13802        newer_executor.block_on(future.as_mut()),
13803        BlockOnOutcome::Completed
13804      );
13805    });
13806    newer_entered_rx
13807      .recv_timeout(Duration::from_secs(1))
13808      .expect("the newer driver must be blocked inside its poll");
13809
13810    let scheduler = Arc::clone(&executor);
13811    let (runnable, task) = async_task::spawn(
13812      async move {
13813        value_tx.send(41usize).unwrap();
13814      },
13815      move |runnable| scheduler.schedule(runnable),
13816    );
13817    task.detach();
13818    executor.schedule(runnable);
13819
13820    assert_eq!(
13821      older_done_rx
13822        .recv_timeout(Duration::from_secs(2))
13823        .expect("queue publication must also wake the older sleeping driver"),
13824      41
13825    );
13826    release_newer_tx.send(()).unwrap();
13827    older.join().unwrap();
13828    newer.join().unwrap();
13829  }
13830
13831  #[test]
13832  fn current_thread_shutdown_cancels_host_queued_task() {
13833    use std::sync::mpsc;
13834
13835    struct DropSignal(mpsc::Sender<()>);
13836
13837    impl Drop for DropSignal {
13838      fn drop(&mut self) {
13839        self.0.send(()).unwrap();
13840      }
13841    }
13842
13843    let metrics = Arc::new(RuntimeMetrics::default());
13844    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13845      Arc::clone(&metrics),
13846      accept_current_thread_host_dispatch,
13847    ));
13848    let scheduler = Arc::clone(&executor);
13849    let (dropped_tx, dropped_rx) = mpsc::channel();
13850    let signal = DropSignal(dropped_tx);
13851    let (runnable, task) = async_task::spawn(
13852      async move {
13853        let _signal = signal;
13854        futures::future::pending::<()>().await;
13855      },
13856      move |runnable| scheduler.schedule(runnable),
13857    );
13858
13859    executor.schedule(runnable);
13860    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13861    executor.begin_shutdown();
13862
13863    dropped_rx
13864      .recv_timeout(Duration::from_secs(1))
13865      .expect("shutdown must drop a task queued behind a host turn");
13866    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13867    assert!(
13868      futures::executor::block_on(task.fallible()).is_none(),
13869      "the queued task handle must resolve as cancelled"
13870    );
13871  }
13872
13873  #[test]
13874  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13875    reset_current_thread_recovery_host_dispatches();
13876    let metrics = Arc::new(RuntimeMetrics::default());
13877    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13878      Arc::clone(&metrics),
13879      count_current_thread_recovery_host_dispatch,
13880    ));
13881    let scheduler = Arc::clone(&executor);
13882    let completed = Arc::new(AtomicBool::new(false));
13883    let completed_task = Arc::clone(&completed);
13884    let (runnable, task) = async_task::spawn(
13885      async move {
13886        completed_task.store(true, Ordering::SeqCst);
13887      },
13888      move |runnable| scheduler.schedule(runnable),
13889    );
13890
13891    executor.schedule(runnable);
13892    assert_eq!(current_thread_recovery_host_dispatches(), 1);
13893    let lost_dispatch = current_thread_dispatch(&executor);
13894
13895    // Model another host registering while the first host still owns an
13896    // accepted delivery. The executor capability stays stable; the host
13897    // registry gives the new registration a distinct delivery token.
13898    executor.request_drain_if_queued();
13899    assert_eq!(
13900      current_thread_recovery_host_dispatches(),
13901      2,
13902      "the newly registered host must receive the pending executor dispatch"
13903    );
13904    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13905    executor.drive_host_turn_for_dispatch(lost_dispatch);
13906    futures::executor::block_on(task);
13907    assert!(completed.load(Ordering::SeqCst));
13908    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13909
13910    let second_completed = Arc::new(AtomicBool::new(false));
13911    let second_completed_task = Arc::clone(&second_completed);
13912    let second_scheduler = Arc::clone(&executor);
13913    let (second_runnable, second_task) = async_task::spawn(
13914      async move {
13915        second_completed_task.store(true, Ordering::SeqCst);
13916      },
13917      move |runnable| second_scheduler.schedule(runnable),
13918    );
13919    executor.schedule(second_runnable);
13920    assert_eq!(
13921      current_thread_recovery_host_dispatches(),
13922      3,
13923      "work queued after the stale callback must receive a new dispatch"
13924    );
13925    let third_dispatch = current_thread_dispatch(&executor);
13926
13927    // The already-consumed capability remains stale relative to dispatch 2 and
13928    // cannot service its work.
13929    executor.drive_host_turn_for_dispatch(lost_dispatch);
13930    assert!(!second_completed.load(Ordering::SeqCst));
13931    executor.drive_host_turn_for_dispatch(third_dispatch);
13932    futures::executor::block_on(second_task);
13933    assert!(second_completed.load(Ordering::SeqCst));
13934
13935    let third_scheduler = Arc::clone(&executor);
13936    let (third_runnable, third_task) =
13937      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13938    executor.schedule(third_runnable);
13939    assert_eq!(
13940      current_thread_recovery_host_dispatches(),
13941      4,
13942      "a stale callback must not leave the dispatch latch stuck"
13943    );
13944    executor.drive_host_turn();
13945    futures::executor::block_on(third_task);
13946  }
13947
13948  #[test]
13949  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13950    let metrics = Arc::new(RuntimeMetrics::default());
13951    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13952      Arc::clone(&metrics),
13953      accept_current_thread_host_dispatch,
13954    ));
13955    let scheduler = Arc::clone(&executor);
13956    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13957      scheduler.schedule(runnable)
13958    });
13959
13960    executor.schedule(runnable);
13961    let failed_dispatch = current_thread_dispatch(&executor);
13962    executor.cancel_host_dispatch_for_test(failed_dispatch);
13963    let recovery_dispatch = current_thread_dispatch(&executor);
13964    assert_eq!(
13965      executor.recovery_dispatch.load(Ordering::Acquire),
13966      recovery_dispatch
13967    );
13968
13969    executor.request_drain_if_queued();
13970    let new_host_dispatch = current_thread_dispatch(&executor);
13971    assert_eq!(new_host_dispatch, recovery_dispatch);
13972    assert_eq!(
13973      executor.recovery_dispatch.load(Ordering::Acquire),
13974      recovery_dispatch
13975    );
13976    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13977
13978    executor.drive_host_turn_for_dispatch(new_host_dispatch);
13979    assert_eq!(futures::executor::block_on(task), 17);
13980  }
13981
13982  #[cfg(napi_runtime_os_threads)]
13983  #[test]
13984  fn current_thread_host_admission_serializes_dispatch_publication() {
13985    use std::sync::mpsc;
13986
13987    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13988    let metrics = Arc::new(RuntimeMetrics::default());
13989    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13990      Arc::clone(&metrics),
13991      count_current_thread_admission_host_dispatch,
13992    ));
13993    let first_scheduler = Arc::clone(&executor);
13994    let (first_runnable, first_task) =
13995      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13996    first_task.detach();
13997    executor.schedule(first_runnable);
13998    let dispatch = current_thread_dispatch(&executor);
13999    assert_eq!(
14000      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14001      1
14002    );
14003
14004    let (claimed_tx, claimed_rx) = mpsc::channel();
14005    let (release_tx, release_rx) = mpsc::channel();
14006    let driver_executor = Arc::clone(&executor);
14007    let driver = std::thread::spawn(move || {
14008      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
14009        *slot.borrow_mut() = Some(Box::new(move || {
14010          claimed_tx.send(()).unwrap();
14011          release_rx.recv().unwrap();
14012        }));
14013      });
14014      driver_executor.drive_host_turn_for_dispatch(dispatch);
14015    });
14016    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14017
14018    let second_ran = Arc::new(AtomicBool::new(false));
14019    let second_ran_task = Arc::clone(&second_ran);
14020    let scheduling_executor = Arc::clone(&executor);
14021    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14022    let (scheduled_tx, scheduled_rx) = mpsc::channel();
14023    let scheduling = std::thread::spawn(move || {
14024      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14025        *slot.borrow_mut() = Some(Box::new(move || {
14026          publication_blocked_tx.send(()).unwrap();
14027        }));
14028      });
14029      let scheduler = Arc::clone(&scheduling_executor);
14030      let (runnable, task) = async_task::spawn(
14031        async move {
14032          second_ran_task.store(true, Ordering::SeqCst);
14033        },
14034        move |runnable| scheduler.schedule(runnable),
14035      );
14036      task.detach();
14037      scheduling_executor.schedule(runnable);
14038      scheduled_tx.send(()).unwrap();
14039    });
14040
14041    publication_blocked_rx
14042      .recv_timeout(Duration::from_secs(2))
14043      .unwrap();
14044    assert!(
14045      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
14046      "dispatch publication must wait until host admission publishes its draining role"
14047    );
14048    assert_eq!(
14049      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
14050      1,
14051      "the admission gap must not publish a duplicate host dispatch"
14052    );
14053
14054    release_tx.send(()).unwrap();
14055    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14056    scheduling.join().unwrap();
14057    driver.join().unwrap();
14058    if !second_ran.load(Ordering::SeqCst) {
14059      executor.drive_host_turn();
14060    }
14061    assert!(second_ran.load(Ordering::SeqCst));
14062    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14063  }
14064
14065  #[test]
14066  fn current_thread_host_turn_yields_after_runnable_budget() {
14067    struct SelfWakingFuture {
14068      remaining: usize,
14069    }
14070
14071    impl Future for SelfWakingFuture {
14072      type Output = ();
14073
14074      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14075        if self.remaining == 0 {
14076          return Poll::Ready(());
14077        }
14078        self.remaining -= 1;
14079        cx.waker().wake_by_ref();
14080        Poll::Pending
14081      }
14082    }
14083
14084    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14085    let metrics = Arc::new(RuntimeMetrics::default());
14086    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14087      Arc::clone(&metrics),
14088      count_current_thread_budget_host_dispatch,
14089    ));
14090    let scheduler = Arc::clone(&executor);
14091    let (runnable, task) = async_task::spawn(
14092      SelfWakingFuture {
14093        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14094      },
14095      move |runnable| scheduler.schedule(runnable),
14096    );
14097
14098    executor.schedule(runnable);
14099    executor.drive_host_turn();
14100
14101    assert_eq!(
14102      metrics.runnable_polls.load(Ordering::Relaxed),
14103      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
14104    );
14105    assert_eq!(
14106      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14107      2,
14108      "remaining hot work must continue through a fresh host turn"
14109    );
14110
14111    executor.drive_host_turn();
14112    futures::executor::block_on(task);
14113    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14114  }
14115
14116  #[cfg(napi_runtime_os_threads)]
14117  #[test]
14118  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
14119    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
14120
14121    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14122    let metrics = Arc::new(RuntimeMetrics::default());
14123    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14124      Arc::clone(&metrics),
14125      count_current_thread_blocking_budget_host_dispatch,
14126    ));
14127    let work = GenerationWork::new();
14128    let observed = Arc::new(Mutex::new(Vec::new()));
14129
14130    let task_executor = Arc::clone(&executor);
14131    let wake_executor = Arc::clone(&executor);
14132    let producer_executor = Arc::clone(&executor);
14133    let producer_work = Arc::clone(&work);
14134    let producer_observed = Arc::clone(&observed);
14135    let outer_registration = work
14136      .try_register_work()
14137      .expect("the outer blocking job must be accepted");
14138    let (runnable, task) = async_task::spawn(
14139      async move {
14140        let queued = task_executor
14141          .schedule_blocking(
14142            move || {
14143              std::thread::spawn(move || {
14144                (0..QUEUED_BLOCKING_JOBS)
14145                  .map(|index| {
14146                    let registration = producer_work
14147                      .try_register_work()
14148                      .expect("queued blocking work must be accepted");
14149                    let observed = Arc::clone(&producer_observed);
14150                    producer_executor.schedule_blocking(
14151                      move || {
14152                        observed
14153                          .lock()
14154                          .unwrap_or_else(std::sync::PoisonError::into_inner)
14155                          .push(index);
14156                      },
14157                      registration,
14158                    )
14159                  })
14160                  .collect::<Vec<_>>()
14161              })
14162              .join()
14163              .expect("the blocking producer must finish")
14164            },
14165            outer_registration,
14166          )
14167          .await
14168          .expect("the inline blocking job must finish");
14169        for handle in queued {
14170          handle.await.expect("queued blocking work must finish");
14171        }
14172      },
14173      move |runnable| wake_executor.schedule(runnable),
14174    );
14175
14176    executor.schedule(runnable);
14177    executor.drive_host_turn();
14178
14179    let first_turn = observed
14180      .lock()
14181      .unwrap_or_else(std::sync::PoisonError::into_inner)
14182      .clone();
14183    assert!(
14184      !first_turn.is_empty(),
14185      "the first host turn must service queued blocking work"
14186    );
14187    assert!(
14188      first_turn.len() < QUEUED_BLOCKING_JOBS,
14189      "inline lane release must not drain the entire blocking FIFO in one host turn"
14190    );
14191    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
14192    assert_eq!(
14193      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
14194      2,
14195      "remaining blocking work must continue through a fresh host turn"
14196    );
14197
14198    let mut continuation_turns = 0;
14199    while observed
14200      .lock()
14201      .unwrap_or_else(std::sync::PoisonError::into_inner)
14202      .len()
14203      < QUEUED_BLOCKING_JOBS
14204    {
14205      continuation_turns += 1;
14206      assert!(
14207        continuation_turns <= QUEUED_BLOCKING_JOBS,
14208        "queued blocking work failed to make bounded host-turn progress"
14209      );
14210      executor.drive_host_turn();
14211    }
14212    executor.drive_host_turn();
14213    futures::executor::block_on(task);
14214
14215    assert_eq!(
14216      *observed
14217        .lock()
14218        .unwrap_or_else(std::sync::PoisonError::into_inner),
14219      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14220      "blocking jobs must preserve FIFO order across host turns"
14221    );
14222    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14223  }
14224
14225  #[cfg(napi_runtime_os_threads)]
14226  #[test]
14227  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14228    use std::sync::mpsc;
14229
14230    struct SelfWakingFuture {
14231      remaining: usize,
14232    }
14233
14234    impl Future for SelfWakingFuture {
14235      type Output = ();
14236
14237      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14238        if self.remaining == 0 {
14239          return Poll::Ready(());
14240        }
14241        self.remaining -= 1;
14242        cx.waker().wake_by_ref();
14243        Poll::Pending
14244      }
14245    }
14246
14247    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14248    let old_generation = controller.backend().generation();
14249    controller
14250      .try_spawn_detached(SelfWakingFuture {
14251        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14252      })
14253      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14254    let dispatch = controller_current_thread_dispatch(&controller);
14255
14256    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14257    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14258    let driver_controller = Arc::clone(&controller);
14259    let driver = std::thread::spawn(move || {
14260      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14261        *slot.borrow_mut() = Some(Box::new(move || {
14262          dispatch_entered_tx.send(()).unwrap();
14263          release_dispatch_rx.recv().unwrap();
14264        }));
14265      });
14266      driver_controller.drive_current_thread_dispatch(dispatch, false);
14267    });
14268    dispatch_entered_rx
14269      .recv_timeout(Duration::from_secs(2))
14270      .expect("the bounded host turn must begin publishing its continuation");
14271
14272    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14273    let shutdown_controller = Arc::clone(&controller);
14274    let shutdown = std::thread::spawn(move || {
14275      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14276    });
14277    wait_until("CurrentThread shutdown to leave Running", || {
14278      let state = controller
14279        .state
14280        .lock()
14281        .unwrap_or_else(std::sync::PoisonError::into_inner);
14282      !matches!(
14283        &state.lifecycle,
14284        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14285      )
14286    });
14287
14288    let (restart_tx, restart_rx) = mpsc::channel();
14289    let restart_controller = Arc::clone(&controller);
14290    let restart = std::thread::spawn(move || {
14291      let result = restart_controller
14292        .start()
14293        .map(|()| restart_controller.backend().generation());
14294      restart_tx.send(result).unwrap();
14295    });
14296
14297    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14298    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14299    let shutdown_overlapped = early_shutdown.is_some();
14300    let restart_overlapped = early_restart.is_some();
14301    release_dispatch_tx.send(()).unwrap();
14302    driver.join().unwrap();
14303
14304    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14305      shutdown_rx
14306        .recv_timeout(Duration::from_secs(2))
14307        .expect("shutdown must finish after continuation publication retires")
14308    });
14309    shutdown_result.unwrap();
14310    let new_generation = early_restart.unwrap_or_else(|| {
14311      restart_rx
14312        .recv_timeout(Duration::from_secs(2))
14313        .expect("restart must follow continuation publication and shutdown")
14314    });
14315    let new_generation = new_generation.unwrap();
14316    shutdown.join().unwrap();
14317    restart.join().unwrap();
14318
14319    assert!(
14320      !shutdown_overlapped,
14321      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14322    );
14323    assert!(
14324      !restart_overlapped,
14325      "restart must not overlap an old-generation continuation dispatch"
14326    );
14327    assert_ne!(new_generation, old_generation);
14328    controller.shutdown().unwrap();
14329  }
14330
14331  #[cfg(napi_runtime_os_threads)]
14332  #[test]
14333  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14334    use std::sync::mpsc;
14335
14336    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14337    let old_generation = controller.backend().generation();
14338    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14339    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14340    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14341    let submit_controller = Arc::clone(&controller);
14342    let reentrant_controller = Arc::clone(&controller);
14343    let submitter = std::thread::spawn(move || {
14344      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14345        *slot.borrow_mut() = Some(Box::new(move || {
14346          reentrant_shutdown_tx
14347            .send(reentrant_controller.shutdown())
14348            .unwrap();
14349          dispatch_entered_tx.send(()).unwrap();
14350          release_dispatch_rx.recv().unwrap();
14351        }));
14352      });
14353      submit_controller
14354        .try_spawn_detached(std::future::pending::<()>())
14355        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14356    });
14357
14358    let reentrant_shutdown = reentrant_shutdown_rx
14359      .recv_timeout(Duration::from_secs(2))
14360      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14361    dispatch_entered_rx
14362      .recv_timeout(Duration::from_secs(2))
14363      .expect("the initial host dispatch publication must reach the injected pause");
14364
14365    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14366    let shutdown_controller = Arc::clone(&controller);
14367    let shutdown = std::thread::spawn(move || {
14368      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14369    });
14370    wait_until("CurrentThread shutdown to leave Running", || {
14371      let state = controller
14372        .state
14373        .lock()
14374        .unwrap_or_else(std::sync::PoisonError::into_inner);
14375      !matches!(
14376        &state.lifecycle,
14377        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14378      )
14379    });
14380
14381    let (restart_tx, restart_rx) = mpsc::channel();
14382    let restart_controller = Arc::clone(&controller);
14383    let restart = std::thread::spawn(move || {
14384      let result = restart_controller
14385        .start()
14386        .map(|()| restart_controller.backend().generation());
14387      restart_tx.send(result).unwrap();
14388    });
14389
14390    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14391    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14392    let shutdown_overlapped = early_shutdown.is_some();
14393    let restart_overlapped = early_restart.is_some();
14394    release_dispatch_tx.send(()).unwrap();
14395    submitter.join().unwrap();
14396
14397    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14398      shutdown_rx
14399        .recv_timeout(Duration::from_secs(2))
14400        .expect("shutdown must finish after initial dispatch publication retires")
14401    });
14402    shutdown_result.unwrap();
14403    let new_generation = early_restart.unwrap_or_else(|| {
14404      restart_rx
14405        .recv_timeout(Duration::from_secs(2))
14406        .expect("restart must follow initial dispatch publication and shutdown")
14407    });
14408    let new_generation = new_generation.unwrap();
14409    shutdown.join().unwrap();
14410    restart.join().unwrap();
14411
14412    let reentrant_error = reentrant_shutdown
14413      .expect_err("dispatch publication must be generation-scoped scheduler work");
14414    assert!(
14415      reentrant_error
14416        .to_string()
14417        .contains("work running on that runtime")
14418    );
14419    assert!(
14420      !shutdown_overlapped,
14421      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14422    );
14423    assert!(
14424      !restart_overlapped,
14425      "restart must not overlap an old-generation host dispatch call"
14426    );
14427    assert_ne!(new_generation, old_generation);
14428    controller.shutdown().unwrap();
14429  }
14430
14431  #[cfg(panic = "unwind")]
14432  #[test]
14433  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14434    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14435      .lock()
14436      .unwrap()
14437      .clear();
14438    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14439    let metrics = Arc::new(RuntimeMetrics::default());
14440    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14441      Arc::clone(&metrics),
14442      panic_once_current_thread_host_dispatch,
14443    ));
14444    let scheduler = Arc::clone(&executor);
14445    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14446      scheduler.schedule(runnable)
14447    });
14448
14449    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14450    assert!(
14451      publication.is_err(),
14452      "the injected host dispatch panic must unwind"
14453    );
14454    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14455    {
14456      let scheduler = executor
14457        .scheduler_idle_lock
14458        .lock()
14459        .unwrap_or_else(std::sync::PoisonError::into_inner);
14460      assert_eq!(scheduler.active_dispatch_calls, 0);
14461      assert!(scheduler.dispatch_publications.is_empty());
14462    }
14463
14464    executor.request_drain_if_queued();
14465
14466    assert_ne!(dispatch, 0);
14467    assert_eq!(
14468      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14469        .lock()
14470        .unwrap(),
14471      vec![dispatch, dispatch],
14472      "a later host registration must retry the same pending capability"
14473    );
14474    {
14475      let scheduler = executor
14476        .scheduler_idle_lock
14477        .lock()
14478        .unwrap_or_else(std::sync::PoisonError::into_inner);
14479      assert_eq!(scheduler.active_dispatch_calls, 0);
14480      assert!(scheduler.dispatch_publications.is_empty());
14481    }
14482    executor.drive_host_turn_for_dispatch(dispatch);
14483    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14484    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14485  }
14486
14487  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14488  #[test]
14489  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14490    use std::sync::mpsc;
14491
14492    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14493      .lock()
14494      .unwrap()
14495      .clear();
14496    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14497    let metrics = Arc::new(RuntimeMetrics::default());
14498    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14499      Arc::clone(&metrics),
14500      panic_once_current_thread_armed_republish_host_dispatch,
14501    ));
14502    let scheduler = Arc::clone(&executor);
14503    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14504      scheduler.schedule(runnable)
14505    });
14506
14507    let (called_tx, called_rx) = mpsc::channel();
14508    let (release_tx, release_rx) = mpsc::channel();
14509    let publishing_executor = Arc::clone(&executor);
14510    let publication = std::thread::spawn(move || {
14511      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14512        *slot.borrow_mut() = Some(Box::new(move || {
14513          called_tx.send(()).unwrap();
14514          release_rx.recv().unwrap();
14515        }));
14516      });
14517      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14518    });
14519    called_rx
14520      .recv_timeout(Duration::from_secs(2))
14521      .expect("the first publication must pause before calling the host dispatcher");
14522
14523    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14524    executor.request_drain_if_queued();
14525    let publication_state = {
14526      let scheduler = executor
14527        .scheduler_idle_lock
14528        .lock()
14529        .unwrap_or_else(std::sync::PoisonError::into_inner);
14530      (
14531        scheduler.active_dispatch_calls,
14532        scheduler.dispatch_publications.len(),
14533        scheduler.dispatch_publications[0].dispatch,
14534        scheduler.dispatch_publications[0].republish_requested,
14535      )
14536    };
14537    release_tx.send(()).unwrap();
14538
14539    let publication = publication.join().unwrap();
14540    assert!(
14541      publication.is_err(),
14542      "the original host-dispatch panic must resume"
14543    );
14544    assert_ne!(dispatch, 0);
14545    assert_eq!(publication_state, (1, 1, dispatch, true));
14546    assert_eq!(
14547      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14548        .lock()
14549        .unwrap(),
14550      vec![dispatch, dispatch],
14551      "the armed request must retry automatically before the original panic resumes"
14552    );
14553    {
14554      let scheduler = executor
14555        .scheduler_idle_lock
14556        .lock()
14557        .unwrap_or_else(std::sync::PoisonError::into_inner);
14558      assert_eq!(scheduler.active_dispatch_calls, 0);
14559      assert!(scheduler.dispatch_publications.is_empty());
14560    }
14561    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14562    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14563
14564    executor.drive_host_turn_for_dispatch(dispatch);
14565    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14566    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14567  }
14568
14569  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14570  #[test]
14571  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14572    use std::sync::mpsc;
14573
14574    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14575      .lock()
14576      .unwrap()
14577      .clear();
14578    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14579    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14580    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14581    {
14582      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14583        .state
14584        .lock()
14585        .unwrap_or_else(std::sync::PoisonError::into_inner);
14586      assert!(state.entries.is_empty());
14587      assert!(state.dispatches.is_empty());
14588    }
14589    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14590      .register(Arc::new(FailDuringAuthoritativePanicPublication));
14591    let metrics = Arc::new(RuntimeMetrics::default());
14592    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14593      Arc::clone(&metrics),
14594      dispatch_authoritative_panic_current_thread_tasks,
14595      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14596    ));
14597    let scheduler = Arc::clone(&executor);
14598    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14599      scheduler.schedule(runnable)
14600    });
14601
14602    let (called_tx, called_rx) = mpsc::channel();
14603    let (release_tx, release_rx) = mpsc::channel();
14604    let publishing_executor = Arc::clone(&executor);
14605    let publication = std::thread::spawn(move || {
14606      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14607        *slot.borrow_mut() = Some(Box::new(move || {
14608          called_tx.send(()).unwrap();
14609          release_rx.recv().unwrap();
14610        }));
14611      });
14612      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14613    });
14614    called_rx
14615      .recv_timeout(Duration::from_secs(2))
14616      .expect("the first publication must pause before raising its panic");
14617
14618    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14619    executor.request_drain_if_queued();
14620    release_tx.send(()).unwrap();
14621
14622    let payload = publication
14623      .join()
14624      .unwrap()
14625      .expect_err("the first host-dispatch panic must resume");
14626    assert!(
14627      payload.is::<FirstHostileCurrentThreadPanic>(),
14628      "the first panic must remain authoritative over nested recovery"
14629    );
14630    assert_eq!(
14631      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14632      1,
14633      "the later hostile payload must be discarded under containment"
14634    );
14635    assert_eq!(
14636      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14637      0,
14638      "the authoritative payload must remain owned by the resumed unwind"
14639    );
14640    discard_panic_payload(payload);
14641    assert_eq!(
14642      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14643      1
14644    );
14645
14646    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14647      .lock()
14648      .unwrap()
14649      .clone();
14650    assert_eq!(dispatches.len(), 3);
14651    let replacement_dispatch = dispatches[2];
14652    assert_eq!(dispatches[0], failed_dispatch);
14653    assert_eq!(dispatches[1], failed_dispatch);
14654    assert_ne!(replacement_dispatch, failed_dispatch);
14655    assert_eq!(
14656      executor.dispatch_pending.load(Ordering::Acquire),
14657      replacement_dispatch
14658    );
14659    assert_eq!(
14660      executor.recovery_dispatch.load(Ordering::Acquire),
14661      replacement_dispatch
14662    );
14663    {
14664      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14665        .state
14666        .lock()
14667        .unwrap_or_else(std::sync::PoisonError::into_inner);
14668      assert!(
14669        !state.dispatches.contains_key(&failed_dispatch),
14670        "nested recovery unwind must retire the exact failed epoch"
14671      );
14672      assert!(!state.dispatches.contains_key(&replacement_dispatch));
14673    }
14674
14675    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14676    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14677    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14678      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14679    executor.request_drain_if_queued();
14680
14681    assert_eq!(
14682      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14683        .lock()
14684        .unwrap(),
14685      vec![
14686        failed_dispatch,
14687        failed_dispatch,
14688        replacement_dispatch,
14689        replacement_dispatch
14690      ],
14691      "retry must retain the replacement reserved before the nested panic"
14692    );
14693    let delivery = healthy.deliveries()[0];
14694    assert_eq!(
14695      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14696      Some(replacement_dispatch)
14697    );
14698    let mut host_turn = executor
14699      .try_admit_host_turn(replacement_dispatch)
14700      .expect("the retried replacement delivery must be admitted");
14701    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14702    host_turn.drive();
14703    assert!(
14704      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14705        .finish_delivery(delivery, false)
14706        .failed_dispatches
14707        .is_empty()
14708    );
14709    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14710    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14711
14712    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14713    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14714      .state
14715      .lock()
14716      .unwrap_or_else(std::sync::PoisonError::into_inner);
14717    assert!(state.entries.is_empty());
14718    assert!(state.dispatches.is_empty());
14719  }
14720
14721  #[cfg(napi_runtime_os_threads)]
14722  #[test]
14723  fn current_thread_shutdown_suppresses_queued_republish() {
14724    use std::sync::mpsc;
14725
14726    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14727    let metrics = Arc::new(RuntimeMetrics::default());
14728    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14729      Arc::clone(&metrics),
14730      count_current_thread_shutdown_republish_host_dispatch,
14731    ));
14732    let scheduler = Arc::clone(&executor);
14733    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14734      scheduler.schedule(runnable)
14735    });
14736
14737    let (returned_tx, returned_rx) = mpsc::channel();
14738    let (release_tx, release_rx) = mpsc::channel();
14739    let publishing_executor = Arc::clone(&executor);
14740    let publication = std::thread::spawn(move || {
14741      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14742        *slot.borrow_mut() = Some(Box::new(move || {
14743          returned_tx.send(()).unwrap();
14744          release_rx.recv().unwrap();
14745        }));
14746      });
14747      publishing_executor.schedule(runnable);
14748    });
14749    returned_rx
14750      .recv_timeout(Duration::from_secs(2))
14751      .expect("the initial publication must reach its completion barrier");
14752
14753    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14754    executor.request_drain_if_queued();
14755    let publication_state = {
14756      let scheduler = executor
14757        .scheduler_idle_lock
14758        .lock()
14759        .unwrap_or_else(std::sync::PoisonError::into_inner);
14760      (
14761        scheduler.active_dispatch_calls,
14762        scheduler.dispatch_publications.len(),
14763        scheduler.dispatch_publications[0].dispatch,
14764        scheduler.dispatch_publications[0].republish_requested,
14765      )
14766    };
14767    executor.begin_shutdown();
14768    release_tx.send(()).unwrap();
14769    publication.join().unwrap();
14770    executor.wait_until_scheduler_idle();
14771
14772    assert_ne!(dispatch, 0);
14773    assert_eq!(publication_state, (1, 1, dispatch, true));
14774    assert_eq!(
14775      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14776      1
14777    );
14778    {
14779      let scheduler = executor
14780        .scheduler_idle_lock
14781        .lock()
14782        .unwrap_or_else(std::sync::PoisonError::into_inner);
14783      assert_eq!(scheduler.active_dispatch_calls, 0);
14784      assert!(scheduler.dispatch_publications.is_empty());
14785    }
14786    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14787    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14788    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14789    assert_eq!(futures::executor::block_on(task.fallible()), None);
14790  }
14791
14792  #[cfg(napi_runtime_os_threads)]
14793  #[test]
14794  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14795    use std::sync::mpsc;
14796
14797    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14798      .lock()
14799      .unwrap()
14800      .clear();
14801    let metrics = Arc::new(RuntimeMetrics::default());
14802    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14803      Arc::clone(&metrics),
14804      record_current_thread_republish_rearm_dispatch,
14805    ));
14806    let scheduler = Arc::clone(&executor);
14807    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14808      scheduler.schedule(runnable)
14809    });
14810
14811    let (first_returned_tx, first_returned_rx) = mpsc::channel();
14812    let (release_first_tx, release_first_rx) = mpsc::channel();
14813    let (second_returned_tx, second_returned_rx) = mpsc::channel();
14814    let (release_second_tx, release_second_rx) = mpsc::channel();
14815    let publishing_executor = Arc::clone(&executor);
14816    let publication = std::thread::spawn(move || {
14817      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14818        *slot.borrow_mut() = Some(Box::new(move || {
14819          first_returned_tx.send(()).unwrap();
14820          release_first_rx.recv().unwrap();
14821          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14822            *slot.borrow_mut() = Some(Box::new(move || {
14823              second_returned_tx.send(()).unwrap();
14824              release_second_rx.recv().unwrap();
14825            }));
14826          });
14827        }));
14828      });
14829      publishing_executor.schedule(runnable);
14830    });
14831    first_returned_rx
14832      .recv_timeout(Duration::from_secs(2))
14833      .expect("the first publication must reach its completion barrier");
14834
14835    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14836    executor.request_drain_if_queued();
14837    executor.request_drain_if_queued();
14838    let first_publication_state = {
14839      let scheduler = executor
14840        .scheduler_idle_lock
14841        .lock()
14842        .unwrap_or_else(std::sync::PoisonError::into_inner);
14843      (
14844        scheduler.active_dispatch_calls,
14845        scheduler.dispatch_publications.len(),
14846        scheduler.dispatch_publications[0].dispatch,
14847        scheduler.dispatch_publications[0].republish_requested,
14848      )
14849    };
14850    release_first_tx.send(()).unwrap();
14851    second_returned_rx
14852      .recv_timeout(Duration::from_secs(2))
14853      .expect("the second publication must reach its completion barrier");
14854
14855    executor.request_drain_if_queued();
14856    executor.request_drain_if_queued();
14857    let second_publication_state = {
14858      let scheduler = executor
14859        .scheduler_idle_lock
14860        .lock()
14861        .unwrap_or_else(std::sync::PoisonError::into_inner);
14862      (
14863        scheduler.active_dispatch_calls,
14864        scheduler.dispatch_publications.len(),
14865        scheduler.dispatch_publications[0].dispatch,
14866        scheduler.dispatch_publications[0].republish_requested,
14867      )
14868    };
14869    release_second_tx.send(()).unwrap();
14870    publication.join().unwrap();
14871
14872    assert_ne!(dispatch, 0);
14873    assert_eq!(first_publication_state, (1, 1, dispatch, true));
14874    assert_eq!(second_publication_state, (1, 1, dispatch, true));
14875    assert_eq!(
14876      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14877      vec![dispatch, dispatch, dispatch],
14878      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14879    );
14880    {
14881      let scheduler = executor
14882        .scheduler_idle_lock
14883        .lock()
14884        .unwrap_or_else(std::sync::PoisonError::into_inner);
14885      assert_eq!(scheduler.active_dispatch_calls, 0);
14886      assert!(scheduler.dispatch_publications.is_empty());
14887    }
14888    executor.drive_host_turn_for_dispatch(dispatch);
14889    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14890    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14891  }
14892
14893  #[cfg(napi_runtime_os_threads)]
14894  #[test]
14895  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14896    use std::sync::mpsc;
14897
14898    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14899      .lock()
14900      .unwrap()
14901      .clear();
14902    let metrics = Arc::new(RuntimeMetrics::default());
14903    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14904      Arc::clone(&metrics),
14905      reject_initial_current_thread_publication_once,
14906    ));
14907    let scheduler = Arc::clone(&executor);
14908    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14909      scheduler.schedule(runnable)
14910    });
14911
14912    let (returned_tx, returned_rx) = mpsc::channel();
14913    let (release_tx, release_rx) = mpsc::channel();
14914    let publishing_executor = Arc::clone(&executor);
14915    let publication = std::thread::spawn(move || {
14916      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14917        *slot.borrow_mut() = Some(Box::new(move || {
14918          returned_tx.send(()).unwrap();
14919          release_rx.recv().unwrap();
14920        }));
14921      });
14922      publishing_executor.schedule(runnable);
14923    });
14924    returned_rx
14925      .recv_timeout(Duration::from_secs(2))
14926      .expect("the unavailable initial publication must reach its completion barrier");
14927
14928    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14929    executor.request_drain_if_queued();
14930    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14931      .lock()
14932      .unwrap()
14933      .len();
14934
14935    release_tx.send(()).unwrap();
14936    publication.join().unwrap();
14937
14938    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14939      .lock()
14940      .unwrap()
14941      .clone();
14942    assert_ne!(dispatch, 0);
14943    assert_eq!(
14944      attempts_while_unavailable_is_paused, 1,
14945      "a racing host request must join the in-flight publication instead of retiring it independently"
14946    );
14947    assert_eq!(
14948      dispatches,
14949      vec![dispatch, dispatch],
14950      "the publication owner must retry the same internal capability after the new host joins"
14951    );
14952    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14953    executor.drive_host_turn_for_dispatch(dispatch);
14954    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14955    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14956  }
14957
14958  #[cfg(napi_runtime_os_threads)]
14959  #[test]
14960  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14961    use std::sync::mpsc;
14962
14963    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14964      .lock()
14965      .unwrap()
14966      .clear();
14967    let metrics = Arc::new(RuntimeMetrics::default());
14968    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14969      Arc::clone(&metrics),
14970      reject_current_thread_replacement_publication_once,
14971    ));
14972    let scheduler = Arc::clone(&executor);
14973    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14974      scheduler.schedule(runnable)
14975    });
14976    executor.schedule(runnable);
14977    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14978    assert_ne!(failed_dispatch, 0);
14979
14980    let (returned_tx, returned_rx) = mpsc::channel();
14981    let (release_tx, release_rx) = mpsc::channel();
14982    let cancelling_executor = Arc::clone(&executor);
14983    let cancellation = std::thread::spawn(move || {
14984      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14985        *slot.borrow_mut() = Some(Box::new(move || {
14986          returned_tx.send(()).unwrap();
14987          release_rx.recv().unwrap();
14988        }));
14989      });
14990      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14991    });
14992    returned_rx
14993      .recv_timeout(Duration::from_secs(2))
14994      .expect("the unavailable replacement publication must reach its completion barrier");
14995
14996    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14997    executor.request_drain_if_queued();
14998    let attempts_while_unavailable_is_paused =
14999      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15000        .lock()
15001        .unwrap()
15002        .len();
15003
15004    release_tx.send(()).unwrap();
15005    cancellation.join().unwrap();
15006
15007    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
15008      .lock()
15009      .unwrap()
15010      .clone();
15011    assert_ne!(replacement_dispatch, 0);
15012    assert_ne!(replacement_dispatch, failed_dispatch);
15013    assert_eq!(
15014      attempts_while_unavailable_is_paused, 2,
15015      "a racing host request must join the in-flight replacement publication"
15016    );
15017    assert_eq!(
15018      dispatches,
15019      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15020      "republication must reuse the sole replacement capability instead of allocating another"
15021    );
15022    assert_eq!(
15023      executor.dispatch_pending.load(Ordering::Acquire),
15024      replacement_dispatch
15025    );
15026    assert_eq!(
15027      executor.recovery_dispatch.load(Ordering::Acquire),
15028      replacement_dispatch
15029    );
15030    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15031
15032    executor.drive_host_turn_for_dispatch(replacement_dispatch);
15033    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
15034    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15035    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15036  }
15037
15038  #[cfg(napi_runtime_os_threads)]
15039  #[test]
15040  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
15041    use std::sync::mpsc;
15042
15043    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15044      .lock()
15045      .unwrap()
15046      .clear();
15047    {
15048      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15049        .state
15050        .lock()
15051        .unwrap_or_else(std::sync::PoisonError::into_inner);
15052      assert!(state.entries.is_empty());
15053      assert!(state.dispatches.is_empty());
15054    }
15055    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15056    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15057      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15058    let metrics = Arc::new(RuntimeMetrics::default());
15059    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15060      Arc::clone(&metrics),
15061      fail_current_thread_replacement_publication_once,
15062      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
15063    ));
15064    let scheduler = Arc::clone(&executor);
15065    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
15066      scheduler.schedule(runnable)
15067    });
15068    executor.schedule(runnable);
15069    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15070    assert_ne!(failed_dispatch, 0);
15071    let initial_delivery = initial.deliveries()[0];
15072    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15073      .finish_delivery(initial_delivery, true);
15074    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
15075      panic!("the initial accepted delivery must report one exact failure");
15076    };
15077    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
15078    let failed_dispatch_failure = *failed_dispatch_failure;
15079    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
15080    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15081      .register(Arc::new(FailDuringReplacementRepublishPublication));
15082
15083    let (returned_tx, returned_rx) = mpsc::channel();
15084    let (release_tx, release_rx) = mpsc::channel();
15085    let cancelling_executor = Arc::clone(&executor);
15086    let cancellation = std::thread::spawn(move || {
15087      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15088        *slot.borrow_mut() = Some(Box::new(move || {
15089          returned_tx.send(()).unwrap();
15090          release_rx.recv().unwrap();
15091        }));
15092      });
15093      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
15094    });
15095    returned_rx
15096      .recv_timeout(Duration::from_secs(2))
15097      .expect("the failed replacement publication must reach its completion barrier");
15098
15099    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15100    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15101    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15102      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15103    executor.request_drain_if_queued();
15104    let publication_state = {
15105      let scheduler = executor
15106        .scheduler_idle_lock
15107        .lock()
15108        .unwrap_or_else(std::sync::PoisonError::into_inner);
15109      (
15110        scheduler.active_dispatch_calls,
15111        scheduler.dispatch_publications.len(),
15112        scheduler.dispatch_publications[0].dispatch,
15113        scheduler.dispatch_publications[0].republish_requested,
15114      )
15115    };
15116    release_tx.send(()).unwrap();
15117    cancellation.join().unwrap();
15118
15119    assert_ne!(replacement_dispatch, 0);
15120    assert_ne!(replacement_dispatch, failed_dispatch);
15121    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
15122    assert_eq!(
15123      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
15124        .lock()
15125        .unwrap(),
15126      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15127      "the newly armed host request must receive the exact replacement before cancellation"
15128    );
15129    assert_eq!(
15130      executor.dispatch_pending.load(Ordering::Acquire),
15131      replacement_dispatch
15132    );
15133    assert_eq!(
15134      executor.recovery_dispatch.load(Ordering::Acquire),
15135      replacement_dispatch
15136    );
15137    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
15138
15139    let delivery = healthy.deliveries()[0];
15140    assert_eq!(delivery.dispatch, replacement_dispatch);
15141    assert_eq!(
15142      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15143        .claim_delivery(delivery.capability()),
15144      Some(replacement_dispatch)
15145    );
15146    let mut host_turn = executor
15147      .try_admit_host_turn(replacement_dispatch)
15148      .expect("the rearmed replacement delivery must be admitted");
15149    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15150    host_turn.drive();
15151    assert!(
15152      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15153        .finish_delivery(delivery, false)
15154        .failed_dispatches
15155        .is_empty()
15156    );
15157    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
15158    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15159    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15160
15161    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
15162    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
15163    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
15164      .state
15165      .lock()
15166      .unwrap_or_else(std::sync::PoisonError::into_inner);
15167    assert!(state.entries.is_empty());
15168    assert!(state.dispatches.is_empty());
15169  }
15170
15171  #[cfg(napi_runtime_os_threads)]
15172  #[test]
15173  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
15174    use std::sync::mpsc;
15175
15176    {
15177      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15178        .state
15179        .lock()
15180        .unwrap_or_else(std::sync::PoisonError::into_inner);
15181      assert!(state.entries.is_empty());
15182      assert!(state.dispatches.is_empty());
15183    }
15184    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
15185    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15186      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
15187    let metrics = Arc::new(RuntimeMetrics::default());
15188    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15189      Arc::clone(&metrics),
15190      dispatch_stale_failure_current_thread_tasks,
15191      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
15192    ));
15193    let scheduler = Arc::clone(&executor);
15194    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
15195      scheduler.schedule(runnable)
15196    });
15197    executor.schedule(runnable);
15198
15199    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15200    let initial_delivery = initial.deliveries()[0];
15201    let delayed_completion =
15202      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
15203    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
15204      panic!("the initial accepted delivery must report one exact failure");
15205    };
15206    assert_eq!(
15207      *delayed_failure,
15208      current_thread_dispatch_failure(dispatch, 0)
15209    );
15210    let delayed_failure = *delayed_failure;
15211
15212    let (returned_tx, returned_rx) = mpsc::channel();
15213    let (release_tx, release_rx) = mpsc::channel();
15214    let publishing_executor = Arc::clone(&executor);
15215    let publication = std::thread::spawn(move || {
15216      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15217        *slot.borrow_mut() = Some(Box::new(move || {
15218          returned_tx.send(()).unwrap();
15219          release_rx.recv().unwrap();
15220        }));
15221      });
15222      publishing_executor.request_drain_if_queued();
15223    });
15224    returned_rx
15225      .recv_timeout(Duration::from_secs(2))
15226      .expect("the old failed epoch must pause before executor recovery");
15227
15228    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15229    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15230      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15231    executor.request_drain_if_queued();
15232    release_tx.send(()).unwrap();
15233    publication.join().unwrap();
15234
15235    let healthy_delivery = healthy.deliveries()[0];
15236    let delayed_initial_delivery = initial.deliveries()[1];
15237    {
15238      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15239        .state
15240        .lock()
15241        .unwrap_or_else(std::sync::PoisonError::into_inner);
15242      let dispatch_state = state
15243        .dispatches
15244        .get(&dispatch)
15245        .expect("the rearmed dispatch must remain registered");
15246      assert_eq!(dispatch_state.epoch, 1);
15247      assert!(!dispatch_state.is_cancelling());
15248    }
15249
15250    executor.cancel_host_dispatch(delayed_failure);
15251
15252    assert_eq!(
15253      executor.dispatch_pending.load(Ordering::Acquire),
15254      dispatch,
15255      "the delayed old-epoch failure must not consume the rearmed executor capability"
15256    );
15257    assert_eq!(
15258      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15259        .state
15260        .lock()
15261        .unwrap_or_else(std::sync::PoisonError::into_inner)
15262        .dispatches
15263        .get(&dispatch)
15264        .map(|state| (state.epoch, state.phase)),
15265      Some((1, CurrentThreadDispatchPhase::Pending)),
15266      "the delayed old-epoch cancellation must not remove the accepted rearm"
15267    );
15268
15269    assert_eq!(
15270      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15271      Some(dispatch)
15272    );
15273    let mut host_turn = executor
15274      .try_admit_host_turn(dispatch)
15275      .expect("the current-epoch delivery must be admitted");
15276    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15277    host_turn.drive();
15278    assert!(
15279      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15280        .finish_delivery(healthy_delivery, false)
15281        .failed_dispatches
15282        .is_empty()
15283    );
15284    assert!(
15285      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15286        .finish_delivery(delayed_initial_delivery, false)
15287        .failed_dispatches
15288        .is_empty()
15289    );
15290    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15291    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15292
15293    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15294    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15295    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15296      .state
15297      .lock()
15298      .unwrap_or_else(std::sync::PoisonError::into_inner);
15299    assert!(state.entries.is_empty());
15300    assert!(state.dispatches.is_empty());
15301  }
15302
15303  #[cfg(napi_runtime_os_threads)]
15304  #[test]
15305  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15306    use std::sync::mpsc;
15307
15308    {
15309      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15310        .state
15311        .lock()
15312        .unwrap_or_else(std::sync::PoisonError::into_inner);
15313      assert!(state.entries.is_empty());
15314      assert!(state.dispatches.is_empty());
15315    }
15316    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15317      .register(Arc::new(FailDuringLateRegistrationPublication));
15318
15319    let metrics = Arc::new(RuntimeMetrics::default());
15320    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15321      Arc::clone(&metrics),
15322      dispatch_late_registration_current_thread_tasks,
15323      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15324    ));
15325    let scheduler = Arc::clone(&executor);
15326    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15327      scheduler.schedule(runnable)
15328    });
15329    metrics.runnable_scheduled();
15330    executor
15331      .queue
15332      .lock()
15333      .unwrap_or_else(std::sync::PoisonError::into_inner)
15334      .runnables
15335      .push_back(runnable);
15336    let replacement_dispatch = next_current_thread_dispatch_id();
15337    executor
15338      .recovery_dispatch
15339      .store(replacement_dispatch, Ordering::Release);
15340    executor
15341      .dispatch_pending
15342      .store(replacement_dispatch, Ordering::Release);
15343
15344    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15345    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15346    let publishing_executor = Arc::clone(&executor);
15347    let publication = std::thread::spawn(move || {
15348      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15349        *slot.borrow_mut() = Some(Box::new(move || {
15350          owner_retired_tx.send(()).unwrap();
15351          request_blocked_rx
15352            .recv_timeout(Duration::from_secs(2))
15353            .expect("the binding-like drain request must wait for the failure transition");
15354        }));
15355      });
15356      publishing_executor.request_drain_if_queued();
15357    });
15358    owner_retired_rx
15359      .recv_timeout(Duration::from_secs(2))
15360      .expect("the failed replacement publication must retire its owner");
15361
15362    // Match the binding sequence: publish the host registration first, then
15363    // request service for work that may have accumulated before registration.
15364    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15365    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15366      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15367    let requesting_executor = Arc::clone(&executor);
15368    let request = std::thread::spawn(move || {
15369      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15370        *slot.borrow_mut() = Some(Box::new(move || {
15371          request_blocked_tx.send(()).unwrap();
15372        }));
15373      });
15374      requesting_executor.request_drain_if_queued();
15375    });
15376
15377    publication.join().unwrap();
15378    request.join().unwrap();
15379
15380    assert!(
15381      healthy.deliveries().is_empty(),
15382      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15383    );
15384    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15385    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15386    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15387    assert_eq!(futures::executor::block_on(task.fallible()), None);
15388    {
15389      let scheduler = executor
15390        .scheduler_idle_lock
15391        .lock()
15392        .unwrap_or_else(std::sync::PoisonError::into_inner);
15393      assert_eq!(scheduler.active_dispatch_calls, 0);
15394      assert!(scheduler.dispatch_publications.is_empty());
15395    }
15396
15397    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15398
15399    let scheduler = Arc::clone(&executor);
15400    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15401      scheduler.schedule(runnable)
15402    });
15403    executor.schedule(later_runnable);
15404    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15405    let later_delivery = healthy.deliveries()[0];
15406    assert_eq!(
15407      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15408      Some(later_dispatch)
15409    );
15410    let mut host_turn = executor
15411      .try_admit_host_turn(later_dispatch)
15412      .expect("the later exact delivery must be admitted");
15413    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15414    host_turn.drive();
15415    assert!(
15416      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15417        .finish_delivery(later_delivery, false)
15418        .failed_dispatches
15419        .is_empty()
15420    );
15421    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15422    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15423
15424    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15425    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15426      .state
15427      .lock()
15428      .unwrap_or_else(std::sync::PoisonError::into_inner);
15429    assert!(state.entries.is_empty());
15430    assert!(state.dispatches.is_empty());
15431  }
15432
15433  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15434  #[test]
15435  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15436  {
15437    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15438      .lock()
15439      .unwrap()
15440      .clear();
15441    {
15442      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15443        .state
15444        .lock()
15445        .unwrap_or_else(std::sync::PoisonError::into_inner);
15446      assert!(state.entries.is_empty());
15447      assert!(state.dispatches.is_empty());
15448    }
15449    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15450      .next_delivery
15451      .store(0, Ordering::Relaxed);
15452    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15453      .register(Arc::new(FailDuringNestedUnwindPublication));
15454
15455    let metrics = Arc::new(RuntimeMetrics::default());
15456    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15457      Arc::clone(&metrics),
15458      dispatch_nested_unwind_current_thread_tasks,
15459      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15460    ));
15461    let scheduler = Arc::clone(&executor);
15462    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15463      scheduler.schedule(runnable)
15464    });
15465
15466    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15467      *slot.borrow_mut() = Some(Box::new(|| {
15468        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15469          .next_delivery
15470          .store(u64::MAX, Ordering::Relaxed);
15471      }));
15472    });
15473    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15474    assert!(
15475      publication.is_err(),
15476      "replacement delivery-id exhaustion must unwind through the outer failed publication"
15477    );
15478
15479    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15480      .lock()
15481      .unwrap()
15482      .clone();
15483    assert_eq!(dispatches.len(), 2);
15484    let failed_dispatch = dispatches[0];
15485    let replacement_dispatch = dispatches[1];
15486    assert_ne!(failed_dispatch, replacement_dispatch);
15487    assert_eq!(
15488      executor.dispatch_pending.load(Ordering::Acquire),
15489      replacement_dispatch
15490    );
15491    assert_eq!(
15492      executor.recovery_dispatch.load(Ordering::Acquire),
15493      replacement_dispatch
15494    );
15495    {
15496      let scheduler = executor
15497        .scheduler_idle_lock
15498        .lock()
15499        .unwrap_or_else(std::sync::PoisonError::into_inner);
15500      assert_eq!(scheduler.active_dispatch_calls, 0);
15501      assert!(scheduler.dispatch_publications.is_empty());
15502    }
15503    {
15504      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15505        .state
15506        .lock()
15507        .unwrap_or_else(std::sync::PoisonError::into_inner);
15508      assert!(
15509        !state.dispatches.contains_key(&failed_dispatch),
15510        "outer unwind cleanup must retire the exact old cancelling dispatch"
15511      );
15512      assert!(
15513        !state.dispatches.contains_key(&replacement_dispatch),
15514        "the replacement publication guard must release its aborted registry state"
15515      );
15516    }
15517
15518    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15519      .next_delivery
15520      .store(1, Ordering::Relaxed);
15521    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15522    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15523    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15524      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15525
15526    executor.request_drain_if_queued();
15527
15528    assert_eq!(
15529      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15530      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15531      "retry must reuse the replacement reserved before the nested unwind"
15532    );
15533    let delivery = healthy.deliveries()[0];
15534    assert_eq!(delivery.dispatch, replacement_dispatch);
15535    assert_eq!(
15536      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15537      Some(replacement_dispatch)
15538    );
15539    let mut host_turn = executor
15540      .try_admit_host_turn(replacement_dispatch)
15541      .expect("the retried replacement delivery must be admitted");
15542    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15543    host_turn.drive();
15544    assert!(
15545      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15546        .finish_delivery(delivery, false)
15547        .failed_dispatches
15548        .is_empty()
15549    );
15550    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15551    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15552
15553    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15554    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15555      .state
15556      .lock()
15557      .unwrap_or_else(std::sync::PoisonError::into_inner);
15558    assert!(state.entries.is_empty());
15559    assert!(state.dispatches.is_empty());
15560  }
15561
15562  #[test]
15563  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15564    reset_current_thread_recovery_host_dispatches();
15565    let metrics = Arc::new(RuntimeMetrics::default());
15566    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15567      Arc::clone(&metrics),
15568      count_current_thread_recovery_host_dispatch,
15569    ));
15570
15571    let scheduler = Arc::clone(&executor);
15572    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15573      scheduler.schedule(runnable)
15574    });
15575    executor.schedule(runnable);
15576    assert_eq!(current_thread_recovery_host_dispatches(), 1);
15577    let failed_dispatch = current_thread_dispatch(&executor);
15578
15579    // Model the host scheduler throwing after Rust accepted the callback but
15580    // before JavaScript could queue a fresh turn.
15581    executor.cancel_host_dispatch_for_test(failed_dispatch);
15582    assert_eq!(
15583      current_thread_recovery_host_dispatches(),
15584      2,
15585      "cancellation must request one replacement without another submission"
15586    );
15587    let replacement_dispatch = current_thread_dispatch(&executor);
15588    assert_ne!(replacement_dispatch, failed_dispatch);
15589    assert_eq!(
15590      executor.recovery_dispatch.load(Ordering::Acquire),
15591      replacement_dispatch
15592    );
15593
15594    executor.drive_host_turn_for_dispatch(replacement_dispatch);
15595    assert_eq!(futures::executor::block_on(task), 1);
15596    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15597    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15598  }
15599
15600  #[test]
15601  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15602    reset_current_thread_recovery_host_dispatches();
15603    let metrics = Arc::new(RuntimeMetrics::default());
15604    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15605      Arc::clone(&metrics),
15606      count_current_thread_recovery_host_dispatch,
15607    ));
15608    let backend =
15609      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15610
15611    let registration = backend
15612      .work
15613      .try_register_async()
15614      .expect("the backend must accept work");
15615    let task = spawn_registered(
15616      &backend,
15617      Arc::clone(&metrics),
15618      std::future::pending::<()>(),
15619      registration,
15620    );
15621    let failed_dispatch = current_thread_dispatch(&executor);
15622    executor.cancel_host_dispatch_for_test(failed_dispatch);
15623    let replacement_dispatch = current_thread_dispatch(&executor);
15624
15625    executor.cancel_host_dispatch_for_test(replacement_dispatch);
15626
15627    assert_eq!(
15628      current_thread_recovery_host_dispatches(),
15629      2,
15630      "a second scheduler failure must not create an unbounded retry loop"
15631    );
15632    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15633    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15634    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15635    assert_eq!(
15636      futures::executor::block_on(task).unwrap_err().to_string(),
15637      "async runtime stopped before the task completed",
15638      "terminal host failure must settle the original operation as cancelled"
15639    );
15640    assert_eq!(backend.work.state.lock().unwrap().active, 0);
15641
15642    let later_registration = backend
15643      .work
15644      .try_register_async()
15645      .expect("terminal cancellation must reopen acceptance");
15646    let later_task = spawn_registered(
15647      &backend,
15648      Arc::clone(&metrics),
15649      async { 2usize },
15650      later_registration,
15651    );
15652    assert_eq!(
15653      current_thread_recovery_host_dispatches(),
15654      3,
15655      "terminal cancellation must not permanently close the executor"
15656    );
15657    executor.drive_host_turn();
15658    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15659  }
15660
15661  /// Regression test for the hazard shape rolldown hit when a `worker_threads`
15662  /// Worker running watch mode is torn down on the CurrentThread flavor: the
15663  /// cleanup-hook dispatch failure cancels the queued coordinator runnable,
15664  /// whose JoinHandle is wrapped in `futures::Shared`. `Shared`'s notifier
15665  /// wakes its awaiters while holding its `wakers` mutex; the parked awaiter's
15666  /// schedule is rejected by the cancellation sweep, and dropping that
15667  /// runnable inline destroys the awaiter's `Shared` clone, whose destructor
15668  /// re-locks `wakers` on the same thread.
15669  #[cfg(napi_runtime_os_threads)]
15670  #[test]
15671  fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15672    use std::{sync::mpsc, time::Duration};
15673
15674    let (done_tx, done_rx) = mpsc::channel();
15675    std::thread::spawn(move || {
15676      let metrics = Arc::new(RuntimeMetrics::default());
15677      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15678        Arc::clone(&metrics),
15679        accept_current_thread_host_dispatch,
15680      ));
15681
15682      // Task A: its JoinHandle is shared, exactly like a watcher close handle.
15683      let scheduler_a = Arc::clone(&executor);
15684      let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15685        scheduler_a.schedule(runnable)
15686      });
15687      let shared = async move { task_a.fallible().await }.boxed().shared();
15688      let shared_for_b = shared.clone();
15689
15690      // Task B: awaits a clone of that Shared and parks after one poll.
15691      let scheduler_b = Arc::clone(&executor);
15692      let (runnable_b, task_b) = async_task::spawn(
15693        async move {
15694          shared_for_b.await;
15695        },
15696        move |runnable| scheduler_b.schedule(runnable),
15697      );
15698      executor.schedule(runnable_b);
15699      executor.drive_host_turn();
15700      assert_eq!(
15701        metrics.queued_runnables.load(Ordering::Relaxed),
15702        0,
15703        "B must be parked and unscheduled after its first poll"
15704      );
15705
15706      // A's runnable is queued (woken, not yet polled) when the host dispatch
15707      // fails twice, so the terminal cancellation sweep drops it.
15708      executor.schedule(runnable_a);
15709      let failed_dispatch = current_thread_dispatch(&executor);
15710      executor.cancel_host_dispatch_for_test(failed_dispatch);
15711      let replacement_dispatch = current_thread_dispatch(&executor);
15712      executor.cancel_host_dispatch_for_test(replacement_dispatch);
15713
15714      assert!(
15715        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15716        "the cancellation sweep must reopen the executor"
15717      );
15718      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15719      assert!(
15720        futures::executor::block_on(task_b.fallible()).is_none(),
15721        "B's rejected wake must still cancel B"
15722      );
15723      drop(shared);
15724      done_tx.send(()).unwrap();
15725    });
15726    done_rx
15727      .recv_timeout(Duration::from_secs(5))
15728      .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15729  }
15730
15731  /// Regression test for the closed-queue sibling of the hazard above. A wake
15732  /// can be in flight on any thread when shutdown starts: `async-task` marks
15733  /// the task `SCHEDULED` before it invokes the schedule closure, so the abort
15734  /// that `close_and_abort` issues for that task coalesces with the in-flight
15735  /// wake and returns, `begin_shutdown` closes the queue without waiting for
15736  /// the closure to land, and the resumed closure is rejected by the closed
15737  /// queue. That rejection must not drop the runnable inline either: the
15738  /// waker here is still inside `futures::Shared`'s notifier, holding its
15739  /// `wakers` mutex, and the rejected future owns a clone of the same `Shared`.
15740  #[cfg(napi_runtime_os_threads)]
15741  #[test]
15742  fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15743    use std::{
15744      sync::{Barrier, mpsc},
15745      time::Duration,
15746    };
15747
15748    let (done_tx, done_rx) = mpsc::channel();
15749    std::thread::spawn(move || {
15750      let metrics = Arc::new(RuntimeMetrics::default());
15751      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15752        Arc::clone(&metrics),
15753        accept_current_thread_host_dispatch,
15754      ));
15755      let backend =
15756        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15757      let generation = backend.generation();
15758
15759      // Task A: registered like production work; its JoinHandle is shared.
15760      let (abort_a, guard_a) = backend
15761        .work
15762        .try_register_async()
15763        .expect("the backend must accept work");
15764      let scheduler_a = Arc::clone(&executor);
15765      let (runnable_a, task_a) = async_task::spawn(
15766        RegisteredTaskFuture::new(
15767          Abortable::new(std::future::pending::<()>(), abort_a),
15768          guard_a,
15769          generation,
15770        ),
15771        move |runnable| scheduler_a.schedule(runnable),
15772      );
15773      let shared = async move { task_a.fallible().await.is_some() }
15774        .boxed()
15775        .shared();
15776      let shared_for_b = shared.clone();
15777
15778      // Task B: registered and abortable too, awaiting a clone of that Shared.
15779      // Its schedule closure pauses before it reaches the executor, exactly
15780      // where a waker thread may be descheduled while shutdown runs.
15781      let (abort_b, guard_b) = backend
15782        .work
15783        .try_register_async()
15784        .expect("the backend must accept work");
15785      let wake_in_flight = Arc::new(Barrier::new(2));
15786      let wake_release = Arc::new(Barrier::new(2));
15787      let scheduler_b = Arc::clone(&executor);
15788      let (runnable_b, task_b) = async_task::spawn(
15789        RegisteredTaskFuture::new(
15790          Abortable::new(
15791            async move {
15792              shared_for_b.await;
15793            },
15794            abort_b,
15795          ),
15796          guard_b,
15797          generation,
15798        ),
15799        {
15800          let wake_in_flight = Arc::clone(&wake_in_flight);
15801          let wake_release = Arc::clone(&wake_release);
15802          move |runnable| {
15803            wake_in_flight.wait();
15804            wake_release.wait();
15805            scheduler_b.schedule(runnable);
15806          }
15807        },
15808      );
15809      executor.schedule(runnable_b);
15810      executor.drive_host_turn();
15811      assert_eq!(
15812        metrics.queued_runnables.load(Ordering::Relaxed),
15813        0,
15814        "B must be parked and unscheduled after its first poll"
15815      );
15816
15817      // Cancel A on another thread: closing A notifies its awaiter, and the
15818      // Shared notifier wakes B while holding `wakers`. B's schedule closure
15819      // then parks at the barrier with that lock still held.
15820      let waker_thread = std::thread::spawn(move || drop(runnable_a));
15821      wake_in_flight.wait();
15822
15823      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
15824      // the abort wake is a no-op; the queue then closes.
15825      backend.begin_shutdown();
15826      wake_release.wait();
15827      backend.wait_until_idle();
15828
15829      waker_thread
15830        .join()
15831        .expect("the waker thread must not panic");
15832      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15833      assert!(
15834        futures::executor::block_on(task_b.fallible()).is_none(),
15835        "B's rejected wake must still cancel B"
15836      );
15837      assert_eq!(backend.work.state.lock().unwrap().active, 0);
15838      drop(shared);
15839      done_tx.send(()).unwrap();
15840    });
15841    done_rx
15842      .recv_timeout(Duration::from_secs(5))
15843      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15844  }
15845
15846  /// Regression test for the terminal-failure sibling of the two hazards
15847  /// above. Task B awaits a `futures::Shared` and parks. Completing the Shared
15848  /// wakes B from inside its notifier, which holds `wakers`, and `schedule`
15849  /// publishes a host dispatch on that same stack. The host fails the
15850  /// delivery before `dispatch` returns, and fails the one replacement the
15851  /// same way, so the terminal failure transition runs while the waker's
15852  /// stack is still under `wakers`. Nothing may be dropped there: B's future
15853  /// owns a clone of the same `Shared`, whose destructor re-locks `wakers`.
15854  /// The transition moves B aside and reopens; the next host turn cancels B.
15855  #[cfg(napi_runtime_os_threads)]
15856  #[test]
15857  fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15858    use std::{sync::mpsc, time::Duration};
15859
15860    let (done_tx, done_rx) = mpsc::channel();
15861    std::thread::spawn(move || {
15862      {
15863        let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15864          .state
15865          .lock()
15866          .unwrap_or_else(std::sync::PoisonError::into_inner);
15867        assert!(state.entries.is_empty());
15868        assert!(state.dispatches.is_empty());
15869      }
15870      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15871        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15872      ));
15873      let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15874        .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15875      let metrics = Arc::new(RuntimeMetrics::default());
15876      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15877        Arc::clone(&metrics),
15878        dispatch_waker_stack_failure_current_thread_tasks,
15879        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15880      ));
15881
15882      // Task B awaits a clone of a Shared and parks after its first poll.
15883      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15884      let shared = rx.map(|_| ()).boxed().shared();
15885      let shared_for_b = shared.clone();
15886      let scheduler_b = Arc::clone(&executor);
15887      let (runnable_b, task_b) = async_task::spawn(
15888        async move {
15889          shared_for_b.await;
15890        },
15891        move |runnable| scheduler_b.schedule(runnable),
15892      );
15893      executor.schedule(runnable_b);
15894
15895      // Service the accepted delivery the way `drive_current_thread_tasks`
15896      // and `acknowledge_current_thread_task_delivery` do.
15897      let delivery = driver.deliveries()[0];
15898      let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15899        .claim_delivery(delivery.capability())
15900        .expect("the accepted delivery must be claimable");
15901      let mut host_turn = executor
15902        .try_admit_host_turn(dispatch)
15903        .expect("the claimed delivery must be admitted");
15904      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15905      host_turn.drive();
15906      drop(host_turn);
15907      let completion =
15908        CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15909      assert!(completion.failed_dispatches.is_empty());
15910      assert!(!completion.redispatch);
15911      assert_eq!(
15912        metrics.queued_runnables.load(Ordering::Relaxed),
15913        0,
15914        "B must be parked and unscheduled after its first poll"
15915      );
15916
15917      // From now on every accepted delivery fails before `dispatch` returns.
15918      driver.fail.store(true, Ordering::SeqCst);
15919      // Completing the Shared wakes B from inside its notifier, under
15920      // `wakers`; B's schedule publishes the dispatch and its replacement on
15921      // this stack, both fail, and the terminal transition runs here.
15922      tx.send(()).expect("B must still hold its Shared clone");
15923      assert_eq!(
15924        driver.deliveries().len(),
15925        // The initial service, the wake's dispatch and its one replacement,
15926        // then the retirement turns and the final offer -- this host refuses
15927        // every one of them.
15928        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15929        "the wake must publish the dispatch, its one replacement, and the \
15930         retirement turns the terminal transition owes itself"
15931      );
15932      assert!(
15933        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15934        "the terminal failure must reopen the executor before the waker returns"
15935      );
15936      assert!(
15937        executor.has_rejected_work(),
15938        "B must be moved aside, not dropped on the waker's stack"
15939      );
15940      assert_eq!(
15941        metrics.queued_runnables.load(Ordering::Relaxed),
15942        0,
15943        "the moved runnable carries its queued debit"
15944      );
15945      let mut task_b = task_b.fallible();
15946      assert!(
15947        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15948        "B must still be pending: nothing may be dropped on the waker's stack"
15949      );
15950
15951      // The next host turn (what `drive_current_thread_tasks` runs for the
15952      // delivery a later submission publishes) cancels B on its own stack.
15953      executor.drive_host_turn();
15954      assert!(!executor.has_rejected_work());
15955      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15956      assert!(
15957        poll_ready_within(&mut task_b, Duration::from_secs(5))
15958          .expect("the host turn must cancel B")
15959          .is_none(),
15960        "the terminal failure must still cancel B"
15961      );
15962      drop(shared);
15963
15964      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15965      let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15966        .state
15967        .lock()
15968        .unwrap_or_else(std::sync::PoisonError::into_inner);
15969      assert!(state.entries.is_empty());
15970      assert!(state.dispatches.is_empty());
15971      done_tx.send(()).unwrap();
15972    });
15973    done_rx
15974      .recv_timeout(Duration::from_secs(5))
15975      .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15976  }
15977
15978  /// Poll `future` with a no-op waker until it is ready or `timeout` passes.
15979  /// The tests below drive the executor by hand, so readiness is produced on
15980  /// this thread and a wait can only fail by design, never by scheduling.
15981  #[cfg(napi_runtime_os_threads)]
15982  fn poll_ready_within<F: Future + Unpin>(
15983    future: &mut F,
15984    timeout: std::time::Duration,
15985  ) -> Option<F::Output> {
15986    let deadline = std::time::Instant::now() + timeout;
15987    let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15988    loop {
15989      if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15990        return Some(output);
15991      }
15992      if std::time::Instant::now() >= deadline {
15993        return None;
15994      }
15995      std::thread::sleep(std::time::Duration::from_millis(1));
15996    }
15997  }
15998
15999  /// Liveness regression test for the transition above. After the waker-stack
16000  /// terminal failure the driver stays registered and becomes healthy again.
16001  /// Work submitted from then on is new work: its schedule must publish a
16002  /// fresh delivery, and the host turn that services it cancels the work that
16003  /// was queued when the failure happened (B) before it runs the new task.
16004  /// Nothing else prompts the executor: no drain request, no re-registration,
16005  /// no shutdown.
16006  #[cfg(napi_runtime_os_threads)]
16007  #[test]
16008  fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
16009    use std::{sync::mpsc, time::Duration};
16010
16011    const WAIT: Duration = Duration::from_secs(5);
16012    let (done_tx, done_rx) = mpsc::channel();
16013    std::thread::spawn(move || {
16014      let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
16015      {
16016        let state = registry
16017          .state
16018          .lock()
16019          .unwrap_or_else(std::sync::PoisonError::into_inner);
16020        assert!(state.entries.is_empty());
16021        assert!(state.dispatches.is_empty());
16022      }
16023      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16024      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16025      let metrics = Arc::new(RuntimeMetrics::default());
16026      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16027        Arc::clone(&metrics),
16028        dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
16029        registry,
16030      ));
16031
16032      // Service one accepted delivery the way the host does.
16033      let service = |index: usize| {
16034        let delivery = driver.deliveries()[index];
16035        let dispatch = registry
16036          .claim_delivery(delivery.capability())
16037          .expect("the accepted delivery must be claimable");
16038        let mut host_turn = executor
16039          .try_admit_host_turn(dispatch)
16040          .expect("the claimed delivery must be admitted");
16041        registry.mark_serviced(dispatch);
16042        host_turn.drive();
16043        drop(host_turn);
16044        let completion = registry.finish_delivery(delivery, false);
16045        assert!(completion.failed_dispatches.is_empty());
16046        assert!(!completion.redispatch);
16047      };
16048
16049      // Task B awaits a clone of a Shared and parks after its first poll.
16050      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16051      let shared = rx.map(|_| ()).boxed().shared();
16052      let shared_for_b = shared.clone();
16053      let scheduler_b = Arc::clone(&executor);
16054      let (runnable_b, task_b) = async_task::spawn(
16055        async move {
16056          shared_for_b.await;
16057        },
16058        move |runnable| scheduler_b.schedule(runnable),
16059      );
16060      executor.schedule(runnable_b);
16061      service(0);
16062      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16063
16064      // The waker-stack terminal failure: the wake's dispatch and its one
16065      // replacement both fail before `dispatch` returns.
16066      driver.fail.store(true, Ordering::SeqCst);
16067      tx.send(()).expect("B must still hold its Shared clone");
16068      // The dispatch, its one replacement, and the retirement turns the
16069      // transition publishes for itself -- which this still-failing host
16070      // loses too, down to the final offer.
16071      const AFTER_WAKE: usize =
16072        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16073      assert_eq!(driver.deliveries().len(), AFTER_WAKE);
16074      let mut task_b = task_b.fallible();
16075      assert!(
16076        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16077        "B must still be pending: nothing may be dropped on the waker's stack"
16078      );
16079
16080      // The host is healthy again and still registered. A later submission
16081      // must publish a delivery on its own.
16082      driver.fail.store(false, Ordering::SeqCst);
16083      let scheduler_later = Arc::clone(&executor);
16084      let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
16085        scheduler_later.schedule(runnable)
16086      });
16087      executor.schedule(runnable_later);
16088      assert_eq!(
16089        driver.deliveries().len(),
16090        AFTER_WAKE + 1,
16091        "a submission after the terminal failure must publish a fresh delivery"
16092      );
16093      assert!(
16094        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16095        "the executor must be open for the healthy host"
16096      );
16097
16098      // The host services that delivery: the pre-failure work is cancelled
16099      // and the new task runs.
16100      service(AFTER_WAKE);
16101      let mut task_later = task_later.fallible();
16102      assert_eq!(
16103        poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
16104        Some(42),
16105        "the later task must complete on the delivered host turn"
16106      );
16107      assert!(
16108        poll_ready_within(&mut task_b, WAIT)
16109          .expect("B must have been cancelled by the host turn")
16110          .is_none(),
16111        "the terminal failure must still cancel the work queued when it happened"
16112      );
16113      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16114      drop(shared);
16115
16116      registry.unregister(registration);
16117      let state = registry
16118        .state
16119        .lock()
16120        .unwrap_or_else(std::sync::PoisonError::into_inner);
16121      assert!(state.entries.is_empty());
16122      assert!(state.dispatches.is_empty());
16123      done_tx.send(()).unwrap();
16124    });
16125    done_rx
16126      .recv_timeout(WAIT)
16127      .expect("the healthy host never got a delivery for the later submission");
16128  }
16129
16130  /// Regression test for the continuation a WAKE owes itself.
16131  ///
16132  /// `6d452174` gave an ordinary submission its own continuation, but a
16133  /// genuine wake still had none. B parks on a `futures::Shared`; completing
16134  /// the Shared wakes B from inside its notifier, which holds `wakers`, so
16135  /// `schedule` publishes on a stack where nothing may be destroyed. The host
16136  /// loses that publication and the one replacement before `dispatch`
16137  /// returns, and the terminal transition moves B into `queue.rejected` and
16138  /// reopens -- correctly dropping nothing.
16139  ///
16140  /// What was missing is what happens next. The transition consumed the
16141  /// executor's one outstanding host request and left the queue empty, so
16142  /// `has_serviceable_work` is false and nothing published again: during
16143  /// normal operation there is no generation idle waiter, no host turn
16144  /// follows the failed publications, and B's `JoinHandle` and
16145  /// `GenerationWorkGuard` stayed live forever. Nothing in this test supplies
16146  /// an unrelated entry -- no later submission, no drain request, no
16147  /// unregistration, no shutdown. The host only services the delivery the
16148  /// transition published for ITSELF.
16149  #[cfg(napi_runtime_os_threads)]
16150  #[test]
16151  fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
16152    use std::{sync::mpsc, time::Duration};
16153
16154    const WAIT: Duration = Duration::from_secs(5);
16155    let (done_tx, done_rx) = mpsc::channel();
16156    std::thread::spawn(move || {
16157      let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
16158      {
16159        let state = registry
16160          .state
16161          .lock()
16162          .unwrap_or_else(std::sync::PoisonError::into_inner);
16163        assert!(state.entries.is_empty());
16164        assert!(state.dispatches.is_empty());
16165      }
16166      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16167      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16168      let metrics = Arc::new(RuntimeMetrics::default());
16169      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16170        Arc::clone(&metrics),
16171        dispatch_retirement_turn_current_thread_tasks,
16172        registry,
16173      ));
16174
16175      // Service one accepted delivery the way the host does.
16176      let service = |index: usize| {
16177        let delivery = driver.deliveries()[index];
16178        let dispatch = registry
16179          .claim_delivery(delivery.capability())
16180          .expect("the accepted delivery must be claimable");
16181        let mut host_turn = executor
16182          .try_admit_host_turn(dispatch)
16183          .expect("the claimed delivery must be admitted");
16184        registry.mark_serviced(dispatch);
16185        host_turn.drive();
16186        drop(host_turn);
16187        let completion = registry.finish_delivery(delivery, false);
16188        assert!(completion.failed_dispatches.is_empty());
16189        assert!(!completion.redispatch);
16190      };
16191
16192      // Task B awaits a clone of a Shared and parks after its first poll.
16193      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16194      let shared = rx.map(|_| ()).boxed().shared();
16195      let shared_for_b = shared.clone();
16196      let scheduler_b = Arc::clone(&executor);
16197      let (runnable_b, task_b) = async_task::spawn(
16198        async move {
16199          shared_for_b.await;
16200        },
16201        move |runnable| scheduler_b.schedule(runnable),
16202      );
16203      executor.schedule(runnable_b);
16204      service(0);
16205      assert_eq!(
16206        metrics.queued_runnables.load(Ordering::Relaxed),
16207        0,
16208        "B must be parked and unscheduled after its first poll"
16209      );
16210
16211      // Exactly the wake's publication and its one replacement are lost; the
16212      // host is healthy again from the third delivery on.
16213      driver.fail_budget.store(2, Ordering::SeqCst);
16214      tx.send(()).expect("B must still hold its Shared clone");
16215
16216      assert_eq!(
16217        driver.deliveries().len(),
16218        4,
16219        "the wake must publish the dispatch, its one replacement, and the \
16220         retirement turn the terminal transition owes itself; without that \
16221         third publication no host turn ever follows and B stays pending \
16222         forever"
16223      );
16224      assert!(
16225        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16226        "the terminal failure must reopen the executor before the waker returns"
16227      );
16228      assert!(
16229        executor.has_rejected_work(),
16230        "B must be moved aside, not dropped on the waker's stack"
16231      );
16232      assert_eq!(
16233        metrics.queued_runnables.load(Ordering::Relaxed),
16234        0,
16235        "the moved runnable carries its queued debit"
16236      );
16237      let mut task_b = task_b.fallible();
16238      assert!(
16239        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16240        "B must still be pending: nothing may be dropped on the waker's stack"
16241      );
16242
16243      // The ONLY continuation: the host serves the turn the transition asked
16244      // for. No submission, no drain request, no unregistration, no shutdown.
16245      service(3);
16246      assert!(
16247        !executor.has_rejected_work(),
16248        "the retirement turn must cancel the moved work on its own stack"
16249      );
16250      assert!(
16251        poll_ready_within(&mut task_b, WAIT)
16252          .expect("the retirement turn must cancel B")
16253          .is_none(),
16254        "the terminal failure must still cancel the work queued when it happened"
16255      );
16256      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16257      drop(shared);
16258
16259      registry.unregister(registration);
16260      let state = registry
16261        .state
16262        .lock()
16263        .unwrap_or_else(std::sync::PoisonError::into_inner);
16264      assert!(state.entries.is_empty());
16265      assert!(state.dispatches.is_empty());
16266      done_tx.send(()).unwrap();
16267    });
16268    done_rx.recv_timeout(WAIT).expect(
16269      "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16270       `queue.rejected` and its JoinHandle never settled",
16271    );
16272  }
16273
16274  /// Regression test for the continuation a NESTED terminal failure owes.
16275  ///
16276  /// `fc4d8e3b` gave a waker-stack terminal failure a continuation of its own:
16277  /// it publishes one fresh host dispatch, and the host turn that services it
16278  /// is the clean stack that retires the moved work. That publication calls
16279  /// the host driver again, so a one-shot bounds it -- and the one-shot used
16280  /// to DISCARD the nested request instead of handing it back:
16281  ///
16282  ///   thread W (waker)                     | thread T (producer)
16283  ///   -------------------------------------+---------------------------
16284  ///   Shared notifier wakes B -> schedule  |
16285  ///   publish D1 .. host fails             |
16286  ///   publish D2 (replacement) .. fails    |
16287  ///   terminal: B -> queue.rejected, reopen|
16288  ///   retirement scope ENTER, publish D3 --+--> schedule(C) -> queue.runnables
16289  ///                                        |    request_drain -> Coalesced
16290  ///   host fails D3, sees C serviceable    |
16291  ///   publish D4 (replacement) .. fails    |
16292  ///   terminal: C -> queue.rejected, reopen|
16293  ///   retirement scope already ACTIVE      |
16294  ///     -> returned, publishing NOTHING    |
16295  ///
16296  /// Four deliveries during the wake, every one of them lost, no host turn at
16297  /// all, and both B and C sat in `queue.rejected` with their `JoinHandle`s
16298  /// pending and their `GenerationWorkGuard`s live -- even though the host is
16299  /// healthy for its very next delivery. The nested request now goes back to
16300  /// the frame that owns the publication, which publishes a fifth delivery for
16301  /// it once D3 has unwound. Nothing in this test supplies an unrelated entry:
16302  /// no later submission, no drain request, no unregistration, no shutdown.
16303  #[cfg(napi_runtime_os_threads)]
16304  #[test]
16305  fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16306    use std::{sync::mpsc, time::Duration};
16307
16308    const WAIT: Duration = Duration::from_secs(5);
16309    let (done_tx, done_rx) = mpsc::channel();
16310    std::thread::spawn(move || {
16311      let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16312      {
16313        let state = registry
16314          .state
16315          .lock()
16316          .unwrap_or_else(std::sync::PoisonError::into_inner);
16317        assert!(state.entries.is_empty());
16318        assert!(state.dispatches.is_empty());
16319      }
16320      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16321      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16322      let metrics = Arc::new(RuntimeMetrics::default());
16323      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16324        Arc::clone(&metrics),
16325        dispatch_nested_retirement_current_thread_tasks,
16326        registry,
16327      ));
16328
16329      // Service one accepted delivery the way the host does.
16330      let service = |index: usize| {
16331        let delivery = driver.deliveries()[index];
16332        let dispatch = registry
16333          .claim_delivery(delivery.capability())
16334          .expect("the accepted delivery must be claimable");
16335        let mut host_turn = executor
16336          .try_admit_host_turn(dispatch)
16337          .expect("the claimed delivery must be admitted");
16338        registry.mark_serviced(dispatch);
16339        host_turn.drive();
16340        drop(host_turn);
16341        let completion = registry.finish_delivery(delivery, false);
16342        assert!(completion.failed_dispatches.is_empty());
16343        assert!(!completion.redispatch);
16344      };
16345
16346      // Task B awaits a clone of a Shared and parks after its first poll, so
16347      // its wake arrives from inside the notifier's `wakers` lock.
16348      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16349      let shared = rx.map(|_| ()).boxed().shared();
16350      let shared_for_b = shared.clone();
16351      let scheduler_b = Arc::clone(&executor);
16352      let (runnable_b, task_b) = async_task::spawn(
16353        async move {
16354          shared_for_b.await;
16355        },
16356        move |runnable| scheduler_b.schedule(runnable),
16357      );
16358      executor.schedule(runnable_b);
16359      service(0);
16360      assert_eq!(
16361        metrics.queued_runnables.load(Ordering::Relaxed),
16362        0,
16363        "B must be parked and unscheduled after its first poll"
16364      );
16365
16366      // Task C is queued by ANOTHER thread, from inside the retirement turn's
16367      // publication. The executor is reopened by then, so C lands in
16368      // `queue.runnables`; the publication already owns `dispatch_pending`, so
16369      // C's own drain request coalesces and adds no delivery of its own.
16370      let scheduler_c = Arc::clone(&executor);
16371      let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16372        scheduler_c.schedule(runnable);
16373      });
16374      let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16375      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16376      let producer_executor = Arc::clone(&executor);
16377      let producer = std::thread::spawn(move || {
16378        let runnable = gate_rx
16379          .recv()
16380          .expect("the retirement turn must open the window");
16381        producer_executor.schedule(runnable);
16382        queued_tx.send(()).expect("W must still be waiting");
16383      });
16384      driver.during_delivery(3, move || {
16385        gate_tx.send(runnable_c).expect("the producer must be live");
16386        queued_rx
16387          .recv_timeout(WAIT)
16388          .expect("the producer must queue C before the retirement turn fails");
16389      });
16390
16391      // Every delivery of the interleaving is lost: the wake's publication,
16392      // its one replacement, the retirement turn the terminal transition
16393      // publishes for itself, and the replacement that turn's failure reserves
16394      // once it sees C queued. The host is healthy again from the sixth.
16395      driver.fail_budget.store(4, Ordering::SeqCst);
16396      tx.send(()).expect("B must still hold its Shared clone");
16397      producer.join().expect("the producer must not panic");
16398
16399      assert_eq!(
16400        driver.deliveries().len(),
16401        6,
16402        "the nested terminal transition may not publish on this stack, but the \
16403         retirement request it hands back must be serviced after the outer \
16404         publication unwinds; without that sixth delivery no host turn ever \
16405         follows and both B and C stay pending forever"
16406      );
16407      assert!(
16408        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16409        "both terminal failures must reopen the executor before the waker returns"
16410      );
16411      assert!(
16412        executor.has_rejected_work(),
16413        "B and C must be moved aside, not dropped on the waker's stack"
16414      );
16415      assert_eq!(
16416        metrics.queued_runnables.load(Ordering::Relaxed),
16417        0,
16418        "the moved runnables carry their queued debit"
16419      );
16420      let mut task_b = task_b.fallible();
16421      let mut task_c = task_c.fallible();
16422      assert!(
16423        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16424        "B must still be pending: nothing may be dropped on the waker's stack"
16425      );
16426      assert!(
16427        poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16428        "C must still be pending: nothing may be dropped on the waker's stack"
16429      );
16430
16431      // The ONLY continuation: the host serves the delivery the retirement
16432      // request produced. No submission, no drain request, no unregistration,
16433      // no shutdown.
16434      service(5);
16435      assert!(
16436        !executor.has_rejected_work(),
16437        "the retirement turn must cancel both moved runnables on its own stack"
16438      );
16439      assert!(
16440        poll_ready_within(&mut task_b, WAIT)
16441          .expect("the retirement turn must cancel B")
16442          .is_none(),
16443        "the terminal failure must still cancel the work queued when it happened"
16444      );
16445      assert!(
16446        poll_ready_within(&mut task_c, WAIT)
16447          .expect("the retirement turn must cancel C")
16448          .is_none(),
16449        "the nested terminal failure must cancel the work it moved aside too"
16450      );
16451      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16452      drop(shared);
16453
16454      registry.unregister(registration);
16455      let state = registry
16456        .state
16457        .lock()
16458        .unwrap_or_else(std::sync::PoisonError::into_inner);
16459      assert!(state.entries.is_empty());
16460      assert!(state.dispatches.is_empty());
16461      done_tx.send(()).unwrap();
16462    });
16463    done_rx.recv_timeout(WAIT).expect(
16464      "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16465       runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16466    );
16467  }
16468
16469  /// Regression test for the BOUND on the retirement turns `e2a8ed9c` opened.
16470  ///
16471  /// That commit made the frame owning a waker-stack retirement publication
16472  /// loop while nested terminal transitions kept handing work back. Its
16473  /// termination argument -- every turn consumes one distinct live task --
16474  /// bounds the turns by the live tasks, which is no bound at all in wall
16475  /// clock: a producer that keeps submitting keeps supplying the runnable each
16476  /// nested transition needs.
16477  ///
16478  ///   thread W (waker)                     | thread T (producer)
16479  ///   -------------------------------------+---------------------------
16480  ///   Shared notifier wakes B -> schedule  |
16481  ///   publish D1 --------------------------+--> schedule(C1) -> runnables
16482  ///   host fails D1 synchronously          |    request_drain -> Coalesced
16483  ///   publish D2 (replacement) ------------+--> schedule(C2)
16484  ///   host fails D2 synchronously          |
16485  ///   terminal: C1,C2 -> rejected, reopen  |
16486  ///   retirement turn 1: publish D3 -------+--> schedule(C3)   ... and so on
16487  ///
16488  /// Every turn is two more host callbacks and two more producer submissions,
16489  /// so the turns ran for exactly as long as the producer did: measured on
16490  /// `e2a8ed9c`, 32768 submissions produced 32769 deliveries inside one wake,
16491  /// and a producer left running for 400ms held this single `schedule`
16492  /// callback -- and `futures::Shared`'s `wakers` mutex under it -- for 432ms.
16493  ///
16494  /// The owner frame now takes at most `REJECTED_WORK_RETIREMENT_ATTEMPTS`
16495  /// turns a producer can interleave with, and then makes the one offer a
16496  /// producer cannot inflate (`publish_final_rejected_work_retirement`). This
16497  /// host refuses that one too, so what is left is the honest end state for a
16498  /// host that never accepts: the work waits, undropped, for a depth-0 entry.
16499  /// Nothing here supplies one until `wait_until_idle` -- no submission, no
16500  /// drain request, no unregistration -- and that is what
16501  /// `current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget`
16502  /// pins from the other side, where the host accepts the final offer.
16503  #[cfg(napi_runtime_os_threads)]
16504  #[test]
16505  fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16506    use std::{sync::mpsc, time::Duration};
16507
16508    const WAIT: Duration = Duration::from_secs(5);
16509    /// Far more submissions than the turns may ever spend, so what stops them
16510    /// is the executor's budget and not the producer running dry.
16511    const PRODUCER_BUDGET: usize = 4096;
16512    /// One publication and its one replacement for the wake itself, two more
16513    /// for each retirement turn, and the single final offer.
16514    const BOUNDED_DELIVERIES: usize =
16515      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16516
16517    let (done_tx, done_rx) = mpsc::channel();
16518    std::thread::spawn(move || {
16519      let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16520      {
16521        let state = registry
16522          .state
16523          .lock()
16524          .unwrap_or_else(std::sync::PoisonError::into_inner);
16525        assert!(state.entries.is_empty());
16526        assert!(state.dispatches.is_empty());
16527      }
16528      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16529      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16530      let metrics = Arc::new(RuntimeMetrics::default());
16531      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16532        Arc::clone(&metrics),
16533        dispatch_sustained_producer_current_thread_tasks,
16534        registry,
16535      ));
16536      let backend =
16537        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16538
16539      // Service one accepted delivery the way the host does.
16540      let service = |index: usize| {
16541        let delivery = driver.deliveries()[index];
16542        let dispatch = registry
16543          .claim_delivery(delivery.capability())
16544          .expect("the accepted delivery must be claimable");
16545        let mut host_turn = executor
16546          .try_admit_host_turn(dispatch)
16547          .expect("the claimed delivery must be admitted");
16548        registry.mark_serviced(dispatch);
16549        host_turn.drive();
16550        drop(host_turn);
16551        let completion = registry.finish_delivery(delivery, false);
16552        assert!(completion.failed_dispatches.is_empty());
16553        assert!(!completion.redispatch);
16554      };
16555
16556      // B awaits a clone of a Shared and parks after its first poll, so its
16557      // wake arrives from inside the notifier's `wakers` lock. It is a
16558      // registered task, so its `GenerationWorkGuard` is observable.
16559      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16560      let shared = rx.map(|_| ()).boxed().shared();
16561      let shared_for_b = shared.clone();
16562      let work_registration = backend
16563        .work
16564        .try_register_async()
16565        .expect("the backend must accept work");
16566      let task_b = spawn_registered(
16567        &backend,
16568        Arc::clone(&metrics),
16569        async move {
16570          shared_for_b.await;
16571        },
16572        work_registration,
16573      );
16574      service(0);
16575      assert_eq!(
16576        metrics.queued_runnables.load(Ordering::Relaxed),
16577        0,
16578        "B must be parked and unscheduled after its first poll"
16579      );
16580      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16581
16582      // The producer queues one runnable from inside every publication. The
16583      // publication already owns `dispatch_pending`, so each of those drain
16584      // requests coalesces and adds no delivery of its own -- every delivery
16585      // below is one the waker's stack published.
16586      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16587      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16588      let producer_executor = Arc::clone(&executor);
16589      let producer = std::thread::spawn(move || {
16590        let mut submissions = 0usize;
16591        while queue_one_rx.recv().is_ok() {
16592          let scheduler = Arc::clone(&producer_executor);
16593          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16594            scheduler.schedule(runnable);
16595          });
16596          task.detach();
16597          producer_executor.schedule(runnable);
16598          submissions += 1;
16599          if queued_tx.send(()).is_err() {
16600            break;
16601          }
16602        }
16603        submissions
16604      });
16605      driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16606
16607      // From here the host fails every delivery synchronously, on the calling
16608      // thread, and never recovers.
16609      driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16610      let before = driver.deliveries().len();
16611      tx.send(()).expect("B must still hold its Shared clone");
16612      let during_wake = driver.deliveries().len() - before;
16613      let unspent = driver.disarm_producer();
16614      let submissions = producer.join().expect("the producer must not panic");
16615
16616      assert_eq!(
16617        during_wake, BOUNDED_DELIVERIES,
16618        "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16619         on the waker's stack plus its one final offer; without a budget it \
16620         services the producer's stream instead and this wake never returns"
16621      );
16622      assert_eq!(
16623        submissions, during_wake,
16624        "the producer must have queued a runnable during every publication"
16625      );
16626      assert!(
16627        unspent > 0,
16628        "the producer must still have been willing to submit when the turns stopped"
16629      );
16630      assert!(
16631        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16632        "every terminal failure, and the final offer, must reopen the executor \
16633         before the waker returns"
16634      );
16635      assert!(
16636        executor.has_rejected_work(),
16637        "the moved work must be parked, not dropped on the waker's stack"
16638      );
16639      assert_eq!(
16640        executor.dispatch_pending.load(Ordering::Acquire),
16641        0,
16642        "a host that refused every offer including the final one leaves none \
16643         outstanding: this is the documented end state for a host that never accepts"
16644      );
16645      assert_eq!(
16646        metrics.queued_runnables.load(Ordering::Relaxed),
16647        0,
16648        "the moved runnables carry their queued debit"
16649      );
16650      let mut task_b = Box::pin(task_b);
16651      assert!(
16652        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16653        "B must still be pending: nothing may be dropped on the waker's stack"
16654      );
16655      assert_eq!(
16656        backend.work.state.lock().unwrap().active,
16657        1,
16658        "B's generation guard rides with its parked runnable"
16659      );
16660
16661      // The first depth-0 entry retires it, and no `GenerationWorkGuard`
16662      // outlives that wait. Nothing above supplied this entry: no submission,
16663      // no drain request, no unregistration.
16664      backend.wait_until_idle();
16665      assert!(!executor.has_rejected_work());
16666      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16667      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16668      assert!(
16669        poll_ready_within(&mut task_b, WAIT)
16670          .expect("the idle wait must cancel B")
16671          .is_err(),
16672        "the terminal failure must still cancel the work queued when it happened"
16673      );
16674      drop(shared);
16675
16676      registry.unregister(registration);
16677      let state = registry
16678        .state
16679        .lock()
16680        .unwrap_or_else(std::sync::PoisonError::into_inner);
16681      assert!(state.entries.is_empty());
16682      assert!(state.dispatches.is_empty());
16683      done_tx.send(()).unwrap();
16684    });
16685    done_rx.recv_timeout(WAIT).expect(
16686      "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16687       the wake never returned",
16688    );
16689  }
16690
16691  /// Regression test for the offer the attempt budget may not swallow -- the
16692  /// reviewer's interleaving, with a FINITE producer.
16693  ///
16694  /// `fd471429` capped the retirement turns at
16695  /// `REJECTED_WORK_RETIREMENT_ATTEMPTS` and argued that exhausting the budget
16696  /// strands nothing, because the producer that drove it to exhaustion
16697  /// supplies the next continuation itself with its next ordinary submission.
16698  /// It does not, if it has just made its last one:
16699  ///
16700  ///   thread W (waker)                     | thread T (producer, budget 18)
16701  ///   -------------------------------------+---------------------------
16702  ///   ... turns 1..7 ...                   | ... C3..C16 ...
16703  ///   turn 8: publish D17 -----------------+--> schedule(C17)
16704  ///   host fails D17, sees C17 serviceable |
16705  ///   publish D18 (replacement) -----------+--> schedule(C18)  <- last one
16706  ///   host fails D18                       |    (producer now quiescent)
16707  ///   terminal #9: C18 -> rejected, reopen |
16708  ///   budget spent -> turns stop           |
16709  ///
16710  /// The host is healthy from its next delivery and stays registered, but no
16711  /// dispatch is outstanding, `queue.rejected` holds B and every C, and
16712  /// nothing publishes again. Measured on `fd471429`: 18 deliveries during the
16713  /// wake, `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle`
16714  /// pending, its future never dropped, its `GenerationWorkGuard` still
16715  /// active, and no delivery afterwards.
16716  ///
16717  /// The budget bounds what a producer can inflate; it may not swallow the
16718  /// continuation itself. After it, one final offer is made with the
16719  /// terminal-cancellation claim held, so nothing can interleave with it and
16720  /// it needs no budget of its own -- and this host accepts it. That host turn
16721  /// is the depth-0 stack that retires everything the wake moved aside.
16722  /// Nothing here supplies any other entry: no submission after the wake, no
16723  /// drain request, no unregistration, no shutdown.
16724  #[cfg(napi_runtime_os_threads)]
16725  #[test]
16726  fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16727    use std::{sync::mpsc, time::Duration};
16728
16729    const WAIT: Duration = Duration::from_secs(5);
16730    /// Every delivery the budget can pay for, and not one more: the producer's
16731    /// last runnable is queued inside the last of them and it then goes
16732    /// quiescent.
16733    const BUDGETED_DELIVERIES: usize =
16734      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16735
16736    let (done_tx, done_rx) = mpsc::channel();
16737    std::thread::spawn(move || {
16738      struct DropFlag(Arc<AtomicBool>);
16739      impl Drop for DropFlag {
16740        fn drop(&mut self) {
16741          self.0.store(true, Ordering::SeqCst);
16742        }
16743      }
16744
16745      let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16746      {
16747        let state = registry
16748          .state
16749          .lock()
16750          .unwrap_or_else(std::sync::PoisonError::into_inner);
16751        assert!(state.entries.is_empty());
16752        assert!(state.dispatches.is_empty());
16753      }
16754      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16755      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16756      let metrics = Arc::new(RuntimeMetrics::default());
16757      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16758        Arc::clone(&metrics),
16759        dispatch_final_retirement_offer_current_thread_tasks,
16760        registry,
16761      ));
16762      let backend =
16763        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16764
16765      let service = |index: usize| {
16766        let delivery = driver.deliveries()[index];
16767        let dispatch = registry
16768          .claim_delivery(delivery.capability())
16769          .expect("the accepted delivery must be claimable");
16770        let mut host_turn = executor
16771          .try_admit_host_turn(dispatch)
16772          .expect("the claimed delivery must be admitted");
16773        registry.mark_serviced(dispatch);
16774        host_turn.drive();
16775        drop(host_turn);
16776        let completion = registry.finish_delivery(delivery, false);
16777        assert!(completion.failed_dispatches.is_empty());
16778        assert!(!completion.redispatch);
16779      };
16780
16781      let dropped = Arc::new(AtomicBool::new(false));
16782      let flag = DropFlag(Arc::clone(&dropped));
16783      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16784      let shared = rx.map(|_| ()).boxed().shared();
16785      let shared_for_b = shared.clone();
16786      let work_registration = backend
16787        .work
16788        .try_register_async()
16789        .expect("the backend must accept work");
16790      let task_b = spawn_registered(
16791        &backend,
16792        Arc::clone(&metrics),
16793        async move {
16794          let _flag = flag;
16795          shared_for_b.await;
16796        },
16797        work_registration,
16798      );
16799      service(0);
16800      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16801      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16802
16803      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16804      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16805      let producer_executor = Arc::clone(&executor);
16806      let producer = std::thread::spawn(move || {
16807        let mut submissions = 0usize;
16808        while queue_one_rx.recv().is_ok() {
16809          let scheduler = Arc::clone(&producer_executor);
16810          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16811            scheduler.schedule(runnable);
16812          });
16813          task.detach();
16814          producer_executor.schedule(runnable);
16815          submissions += 1;
16816          if queued_tx.send(()).is_err() {
16817            break;
16818          }
16819        }
16820        submissions
16821      });
16822      // The producer feeds exactly the deliveries the budget can pay for, and
16823      // the host refuses exactly those: it is healthy from the next one.
16824      driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16825      driver
16826        .fail_budget
16827        .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16828
16829      let before = driver.deliveries().len();
16830      tx.send(()).expect("B must still hold its Shared clone");
16831      let during_wake = driver.deliveries().len() - before;
16832      let unspent = driver.disarm_producer();
16833      let submissions = producer.join().expect("the producer must not panic");
16834
16835      assert_eq!(
16836        during_wake,
16837        BUDGETED_DELIVERIES + 1,
16838        "the budget must be followed by the one offer a producer cannot \
16839         inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16840         deliveries and never asks the host that would have accepted"
16841      );
16842      assert_eq!(
16843        submissions, BUDGETED_DELIVERIES,
16844        "the producer must have queued a runnable during every budgeted \
16845         publication and gone quiescent before the final offer"
16846      );
16847      assert_eq!(
16848        unspent, 0,
16849        "the producer must have run dry exactly as the budget ran out"
16850      );
16851      assert_eq!(
16852        driver.fail_budget.load(Ordering::SeqCst),
16853        0,
16854        "the host must have refused every budgeted delivery and no more"
16855      );
16856      assert!(
16857        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16858        "the final offer must reopen the executor before the waker returns"
16859      );
16860      assert!(
16861        executor.has_rejected_work(),
16862        "nothing may be dropped on the waker's stack"
16863      );
16864      assert_ne!(
16865        executor.dispatch_pending.load(Ordering::Acquire),
16866        0,
16867        "the wake must leave a dispatch outstanding: that accepted delivery is \
16868         the continuation the parked work has"
16869      );
16870      let mut task_b = Box::pin(task_b);
16871      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16872      assert!(!dropped.load(Ordering::SeqCst));
16873      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16874
16875      // The ONLY continuation: the host serves the final offer. No submission,
16876      // no drain request, no unregistration, no shutdown.
16877      service(driver.deliveries().len() - 1);
16878      assert!(
16879        !executor.has_rejected_work(),
16880        "that host turn is a depth-0 stack and must retire everything the wake \
16881         moved aside"
16882      );
16883      assert!(
16884        poll_ready_within(&mut task_b, WAIT)
16885          .expect("the final offer's host turn must cancel B")
16886          .is_err()
16887      );
16888      assert!(
16889        dropped.load(Ordering::SeqCst),
16890        "B's future must be destroyed, on the host's stack and not the waker's"
16891      );
16892      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16893      assert_eq!(
16894        backend.work.state.lock().unwrap().active,
16895        0,
16896        "no GenerationWorkGuard may outlive the retirement"
16897      );
16898      drop(shared);
16899
16900      registry.unregister(registration);
16901      let state = registry
16902        .state
16903        .lock()
16904        .unwrap_or_else(std::sync::PoisonError::into_inner);
16905      assert!(state.entries.is_empty());
16906      assert!(state.dispatches.is_empty());
16907      done_tx.send(()).unwrap();
16908    });
16909    done_rx.recv_timeout(WAIT).expect(
16910      "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16911    );
16912  }
16913
16914  /// Regression test for the turn a REFUSED offer owes, with no producer at
16915  /// all.
16916  ///
16917  /// Until this commit the turns were driven by a thread-local flag that only
16918  /// a NESTED terminal transition set. An offer the host simply refused with
16919  /// an empty queue reserves no replacement, so it reached no transition and
16920  /// set no flag, and the owner frame returned after a single offer with seven
16921  /// attempts unspent -- the same stranded state the budget's exhaustion
16922  /// leaves, reached with nothing racing at all. Measured before: 3 deliveries
16923  /// during the wake, `dispatch_pending` 0, B parked in `queue.rejected` with
16924  /// its guard held, and a host that was healthy for its very next delivery
16925  /// never asked for one.
16926  ///
16927  /// What a turn owes is executor state, not a message: work parked and no
16928  /// dispatch outstanding. Reading that instead of a flag makes both routes
16929  /// one route, and this host -- which refuses the wake's publication, its
16930  /// replacement and the first offer, and accepts the second -- is served.
16931  #[cfg(napi_runtime_os_threads)]
16932  #[test]
16933  fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16934    use std::{sync::mpsc, time::Duration};
16935
16936    const WAIT: Duration = Duration::from_secs(5);
16937    let (done_tx, done_rx) = mpsc::channel();
16938    std::thread::spawn(move || {
16939      struct DropFlag(Arc<AtomicBool>);
16940      impl Drop for DropFlag {
16941        fn drop(&mut self) {
16942          self.0.store(true, Ordering::SeqCst);
16943        }
16944      }
16945
16946      let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16947      {
16948        let state = registry
16949          .state
16950          .lock()
16951          .unwrap_or_else(std::sync::PoisonError::into_inner);
16952        assert!(state.entries.is_empty());
16953        assert!(state.dispatches.is_empty());
16954      }
16955      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16956      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16957      let metrics = Arc::new(RuntimeMetrics::default());
16958      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16959        Arc::clone(&metrics),
16960        dispatch_retirement_retry_current_thread_tasks,
16961        registry,
16962      ));
16963      let backend =
16964        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16965
16966      let service = |index: usize| {
16967        let delivery = driver.deliveries()[index];
16968        let dispatch = registry
16969          .claim_delivery(delivery.capability())
16970          .expect("the accepted delivery must be claimable");
16971        let mut host_turn = executor
16972          .try_admit_host_turn(dispatch)
16973          .expect("the claimed delivery must be admitted");
16974        registry.mark_serviced(dispatch);
16975        host_turn.drive();
16976        drop(host_turn);
16977        let completion = registry.finish_delivery(delivery, false);
16978        assert!(completion.failed_dispatches.is_empty());
16979        assert!(!completion.redispatch);
16980      };
16981
16982      let dropped = Arc::new(AtomicBool::new(false));
16983      let flag = DropFlag(Arc::clone(&dropped));
16984      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16985      let shared = rx.map(|_| ()).boxed().shared();
16986      let shared_for_b = shared.clone();
16987      let work_registration = backend
16988        .work
16989        .try_register_async()
16990        .expect("the backend must accept work");
16991      let task_b = spawn_registered(
16992        &backend,
16993        Arc::clone(&metrics),
16994        async move {
16995          let _flag = flag;
16996          shared_for_b.await;
16997        },
16998        work_registration,
16999      );
17000      service(0);
17001      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17002      assert_eq!(backend.work.state.lock().unwrap().active, 1);
17003
17004      // The wake's publication, its one replacement, and the first retirement
17005      // offer are lost. The host is healthy from the second offer on, and no
17006      // producer ever runs: every offer after the first is refused with an
17007      // empty queue, which reserves no replacement and reaches no transition.
17008      let before = driver.deliveries().len();
17009      driver.fail_budget.store(3, Ordering::SeqCst);
17010      tx.send(()).expect("B must still hold its Shared clone");
17011      let during_wake = driver.deliveries().len() - before;
17012
17013      assert_eq!(
17014        during_wake, 4,
17015        "a refused offer owes another turn just as a nested transition does: \
17016         work is parked and no dispatch is outstanding"
17017      );
17018      assert_ne!(
17019        executor.dispatch_pending.load(Ordering::Acquire),
17020        0,
17021        "the accepted second offer is the continuation the parked work has"
17022      );
17023      assert!(executor.has_rejected_work());
17024      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17025      let mut task_b = Box::pin(task_b);
17026      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17027      assert!(!dropped.load(Ordering::SeqCst));
17028
17029      // The ONLY continuation: the host serves the offer it accepted.
17030      service(driver.deliveries().len() - 1);
17031      assert!(!executor.has_rejected_work());
17032      assert!(
17033        poll_ready_within(&mut task_b, WAIT)
17034          .expect("the accepted offer's host turn must cancel B")
17035          .is_err()
17036      );
17037      assert!(dropped.load(Ordering::SeqCst));
17038      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17039      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17040      drop(shared);
17041
17042      registry.unregister(registration);
17043      let state = registry
17044        .state
17045        .lock()
17046        .unwrap_or_else(std::sync::PoisonError::into_inner);
17047      assert!(state.entries.is_empty());
17048      assert!(state.dispatches.is_empty());
17049      done_tx.send(()).unwrap();
17050    });
17051    done_rx
17052      .recv_timeout(WAIT)
17053      .expect("a refused retirement offer left B stranded with a healthy host");
17054  }
17055
17056  /// Regression test for the continuation the FINAL offer owes when the host
17057  /// services it from inside `dispatch`.
17058  ///
17059  /// `6cfe2002` makes one last offer after the attempt budget with the
17060  /// terminal-cancellation claim held, so nothing can inflate it, and then
17061  /// reopens and returns. A driver may claim, drive and acknowledge a delivery
17062  /// inside `dispatch` -- the registry supports that synchronous completion,
17063  /// and `RacingTaskDriver` in this module drives one inline -- and such a turn
17064  /// on THIS offer can do nothing at all:
17065  /// `retire_rejected_work_off_waker_stack` returns at nonzero schedule depth,
17066  /// and `take_runnable` returns `None` while `cancelling_failed_dispatch` is
17067  /// held. It only consumes `dispatch_pending`. The offer then returned with
17068  /// the executor reopened, no dispatch outstanding and `queue.rejected` still
17069  /// holding B. Measured before this commit: 11 deliveries during the wake,
17070  /// `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle` pending, its
17071  /// future never dropped, its `GenerationWorkGuard` still active -- and not
17072  /// one delivery afterwards, with the driver registered and healthy.
17073  ///
17074  /// An offer the host ACCEPTED but could not act on leaves exactly the state
17075  /// a refused one leaves, and the invariant is the one this branch already
17076  /// reads off the executor: while work is parked in `queue.rejected` and no
17077  /// dispatch is outstanding, this stack still owes a continuation. So the
17078  /// offer reports whether the host accepted it, and an accepted offer whose
17079  /// dispatch was consumed is followed by ONE ordinary publication -- the
17080  /// executor is open again, so that is the path `5857092f` reopened it for.
17081  /// It is one publication and not a loop, and a nested terminal transition
17082  /// inside it finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held and
17083  /// publishes nothing.
17084  ///
17085  /// Nothing here supplies any other entry: no submission after the wake, no
17086  /// drain request, no unregistration, no shutdown.
17087  #[cfg(napi_runtime_os_threads)]
17088  #[test]
17089  fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
17090    use std::{sync::mpsc, time::Duration};
17091
17092    const WAIT: Duration = Duration::from_secs(5);
17093    /// The wake's publication, its one replacement, and every budgeted turn.
17094    const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
17095
17096    let (done_tx, done_rx) = mpsc::channel();
17097    std::thread::spawn(move || {
17098      struct DropFlag(Arc<AtomicBool>);
17099      impl Drop for DropFlag {
17100        fn drop(&mut self) {
17101          self.0.store(true, Ordering::SeqCst);
17102        }
17103      }
17104
17105      let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
17106      {
17107        let state = registry
17108          .state
17109          .lock()
17110          .unwrap_or_else(std::sync::PoisonError::into_inner);
17111        assert!(state.entries.is_empty());
17112        assert!(state.dispatches.is_empty());
17113      }
17114      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
17115      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17116      let metrics = Arc::new(RuntimeMetrics::default());
17117      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17118        Arc::clone(&metrics),
17119        dispatch_inline_final_offer_current_thread_tasks,
17120        registry,
17121      ));
17122      let backend =
17123        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17124
17125      let service = |index: usize| {
17126        let delivery = driver.deliveries()[index];
17127        let dispatch = registry
17128          .claim_delivery(delivery.capability())
17129          .expect("the accepted delivery must be claimable");
17130        let mut host_turn = executor
17131          .try_admit_host_turn(dispatch)
17132          .expect("the claimed delivery must be admitted");
17133        registry.mark_serviced(dispatch);
17134        host_turn.drive();
17135        drop(host_turn);
17136        let completion = registry.finish_delivery(delivery, false);
17137        assert!(completion.failed_dispatches.is_empty());
17138        assert!(!completion.redispatch);
17139      };
17140
17141      let dropped = Arc::new(AtomicBool::new(false));
17142      let flag = DropFlag(Arc::clone(&dropped));
17143      let (tx, rx) = futures::channel::oneshot::channel::<()>();
17144      let shared = rx.map(|_| ()).boxed().shared();
17145      let shared_for_b = shared.clone();
17146      let work_registration = backend
17147        .work
17148        .try_register_async()
17149        .expect("the backend must accept work");
17150      let task_b = spawn_registered(
17151        &backend,
17152        Arc::clone(&metrics),
17153        async move {
17154          let _flag = flag;
17155          shared_for_b.await;
17156        },
17157        work_registration,
17158      );
17159      service(0);
17160      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17161      assert_eq!(backend.work.state.lock().unwrap().active, 1);
17162
17163      // The wake's publication, its one replacement and every budgeted turn
17164      // are refused. The delivery right after them is the final offer, and the
17165      // host claims, drives and acknowledges THAT one from inside `dispatch`:
17166      // on the waker's stack, with the terminal-cancellation claim held.
17167      let before = driver.deliveries().len();
17168      let serviced_inline = Arc::new(AtomicBool::new(false));
17169      {
17170        let executor = Arc::clone(&executor);
17171        let hook_driver = Arc::clone(&driver);
17172        let serviced_inline = Arc::clone(&serviced_inline);
17173        driver.during_delivery(before + REFUSED_DELIVERIES, move || {
17174          let delivery = *hook_driver
17175            .deliveries()
17176            .last()
17177            .expect("the final offer must be recorded before the hook runs");
17178          let dispatch = registry
17179            .claim_delivery(delivery.capability())
17180            .expect("the final offer must be claimable");
17181          let mut host_turn = executor
17182            .try_admit_host_turn(dispatch)
17183            .expect("the claimed final offer must be admitted");
17184          registry.mark_serviced(dispatch);
17185          host_turn.drive();
17186          drop(host_turn);
17187          let completion = registry.finish_delivery(delivery, false);
17188          assert!(completion.failed_dispatches.is_empty());
17189          serviced_inline.store(true, Ordering::SeqCst);
17190        });
17191      }
17192      driver
17193        .fail_budget
17194        .store(REFUSED_DELIVERIES, Ordering::SeqCst);
17195      tx.send(()).expect("B must still hold its Shared clone");
17196      let during_wake = driver.deliveries().len() - before;
17197
17198      assert!(
17199        serviced_inline.load(Ordering::SeqCst),
17200        "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
17201      );
17202      assert_eq!(
17203        during_wake,
17204        REFUSED_DELIVERIES + 2,
17205        "an accepted offer whose host turn could not act owes the same continuation a refused \
17206         one owes: work parked and no dispatch outstanding"
17207      );
17208      assert_ne!(
17209        executor.dispatch_pending.load(Ordering::Acquire),
17210        0,
17211        "the publication that follows the consumed offer is the continuation the parked work has"
17212      );
17213      assert!(executor.has_rejected_work());
17214      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17215      let mut task_b = Box::pin(task_b);
17216      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17217      assert!(!dropped.load(Ordering::SeqCst));
17218
17219      // The ONLY continuation: the host serves it on a clean stack.
17220      service(driver.deliveries().len() - 1);
17221      assert!(!executor.has_rejected_work());
17222      assert!(
17223        poll_ready_within(&mut task_b, WAIT)
17224          .expect("the host turn that follows the consumed offer must cancel B")
17225          .is_err()
17226      );
17227      assert!(dropped.load(Ordering::SeqCst));
17228      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17229      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17230      drop(shared);
17231
17232      registry.unregister(registration);
17233      let state = registry
17234        .state
17235        .lock()
17236        .unwrap_or_else(std::sync::PoisonError::into_inner);
17237      assert!(state.entries.is_empty());
17238      assert!(state.dispatches.is_empty());
17239      done_tx.send(()).unwrap();
17240    });
17241    done_rx.recv_timeout(WAIT).expect(
17242      "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17243    );
17244  }
17245
17246  /// Regression test for the continuation an ordinary `spawn` owes itself.
17247  /// `async-task` runs one scheduler callback for two different things: a
17248  /// wake, which any `Waker` can raise from any stack, and the first schedule
17249  /// of a freshly submitted task, which `spawn` makes on its caller's own
17250  /// stack. Only the wake can arrive under a foreign lock. When the host fails
17251  /// a submission's publication and its one replacement synchronously, the
17252  /// terminal transition is that submission's ONLY continuation: it produced
17253  /// no host turn, `complete_current_thread_task_delivery` has already run,
17254  /// and nothing else will reach `retire_rejected_work`. Treating the frame as
17255  /// a waker's parked the runnable in `queue.rejected` with the driver
17256  /// registered and healthy again, so the `JoinHandle` stayed pending forever
17257  /// and its `GenerationWorkGuard` stayed active. Nothing here supplies a
17258  /// later entry: no drain request, no host turn, no unregistration, no
17259  /// shutdown.
17260  #[cfg(napi_runtime_os_threads)]
17261  #[test]
17262  fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17263    use std::time::Duration;
17264
17265    let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17266    {
17267      let state = registry
17268        .state
17269        .lock()
17270        .unwrap_or_else(std::sync::PoisonError::into_inner);
17271      assert!(state.entries.is_empty());
17272      assert!(state.dispatches.is_empty());
17273    }
17274    let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17275    let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17276    let metrics = Arc::new(RuntimeMetrics::default());
17277    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17278      Arc::clone(&metrics),
17279      dispatch_submission_failure_current_thread_tasks,
17280      registry,
17281    ));
17282    let backend =
17283      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17284
17285    // Every accepted delivery fails before `dispatch` returns, the way a
17286    // closing threadsafe function does.
17287    driver.fail.store(true, Ordering::SeqCst);
17288
17289    let work_registration = backend
17290      .work
17291      .try_register_async()
17292      .expect("the backend must accept work");
17293    let task = spawn_registered(
17294      &backend,
17295      Arc::clone(&metrics),
17296      std::future::pending::<()>(),
17297      work_registration,
17298    );
17299
17300    assert_eq!(
17301      driver.deliveries().len(),
17302      2,
17303      "the submission must publish the dispatch and exactly one replacement"
17304    );
17305    assert!(
17306      !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17307      "the terminal failure must reopen the executor"
17308    );
17309    assert!(
17310      !executor.has_rejected_work(),
17311      "a submission's own stack is not a waker's: the rejected work must be        cancelled here, because nothing else will"
17312    );
17313    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17314    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17315
17316    let mut task = Box::pin(task);
17317    assert_eq!(
17318      poll_ready_within(&mut task, Duration::ZERO)
17319        .expect("the submission must settle without any later entry")
17320        .unwrap_err()
17321        .to_string(),
17322      "async runtime stopped before the task completed",
17323      "terminal host failure must settle the submitted operation as cancelled"
17324    );
17325
17326    registry.unregister(registration);
17327    let state = registry
17328      .state
17329      .lock()
17330      .unwrap_or_else(std::sync::PoisonError::into_inner);
17331    assert!(state.entries.is_empty());
17332    assert!(state.dispatches.is_empty());
17333  }
17334
17335  /// MultiThread twin of
17336  /// `current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline`.
17337  /// The same wake is in flight when shutdown starts: B's schedule closure
17338  /// runs inside `futures::Shared`'s notifier, under its `wakers` mutex, and
17339  /// B's future owns a clone of that `Shared`. `MultiThreadExecutor::schedule`
17340  /// has no closed-queue branch: the late runnable is pushed to the injector
17341  /// (or the worker's LIFO slot) exactly like any other, and the drainer that
17342  /// claims it under the published stop cancels it on its own stack
17343  /// (`claim_runnable` -> `RunnableClaim::Cancel` -> `cancel_runnable`). That
17344  /// stack never holds `wakers`, so the drop only waits for the waker thread
17345  /// to leave the notifier. This pins that shape: a closed-queue rejection
17346  /// added to `schedule` that dropped the runnable inline would deadlock
17347  /// here, exactly as the CurrentThread executor did.
17348  #[cfg(napi_runtime_os_threads)]
17349  #[test]
17350  fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17351    use std::{
17352      sync::{Barrier, mpsc},
17353      time::Duration,
17354    };
17355
17356    let (done_tx, done_rx) = mpsc::channel();
17357    std::thread::spawn(move || {
17358      let metrics = Arc::new(RuntimeMetrics::default());
17359      let options = RuntimeOptions {
17360        flavor: RuntimeFlavor::MultiThread,
17361        worker_threads: 2,
17362        max_blocking_tasks: 1,
17363        thread_name_prefix: "mt-inline-drop".to_string(),
17364        park_deadline: None,
17365        drain_linger: DEFAULT_DRAIN_LINGER,
17366      };
17367      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17368      let backend =
17369        RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17370      let generation = backend.generation();
17371
17372      // Task A: registered like production work; its JoinHandle is shared.
17373      let (abort_a, guard_a) = backend
17374        .work
17375        .try_register_async()
17376        .expect("the backend must accept work");
17377      let scheduler_a = Arc::clone(&executor);
17378      let (runnable_a, task_a) = async_task::spawn(
17379        RegisteredTaskFuture::new(
17380          Abortable::new(std::future::pending::<()>(), abort_a),
17381          guard_a,
17382          generation,
17383        ),
17384        move |runnable| scheduler_a.schedule(runnable),
17385      );
17386      let shared = async move { task_a.fallible().await.is_some() }
17387        .boxed()
17388        .shared();
17389      let shared_for_b = shared.clone();
17390
17391      // Task B: registered and abortable too, awaiting a clone of that Shared.
17392      // Its schedule closure pauses before it reaches the executor, exactly
17393      // where a waker thread may be descheduled while shutdown runs.
17394      let (abort_b, guard_b) = backend
17395        .work
17396        .try_register_async()
17397        .expect("the backend must accept work");
17398      let wake_in_flight = Arc::new(Barrier::new(2));
17399      let wake_release = Arc::new(Barrier::new(2));
17400      let scheduler_b = Arc::clone(&executor);
17401      let (runnable_b, task_b) = async_task::spawn(
17402        RegisteredTaskFuture::new(
17403          Abortable::new(
17404            async move {
17405              shared_for_b.await;
17406            },
17407            abort_b,
17408          ),
17409          guard_b,
17410          generation,
17411        ),
17412        {
17413          let wake_in_flight = Arc::clone(&wake_in_flight);
17414          let wake_release = Arc::clone(&wake_release);
17415          move |runnable| {
17416            wake_in_flight.wait();
17417            wake_release.wait();
17418            scheduler_b.schedule(runnable);
17419          }
17420        },
17421      );
17422      // The first poll runs on a pool drainer; wait until B has parked on the
17423      // Shared and left the drainer, so the wake below is a real re-schedule
17424      // from the notifier rather than a wake-while-running that the drainer
17425      // would re-publish from its own stack.
17426      executor.schedule(runnable_b);
17427      wait_until("B parked after its first poll", || {
17428        metrics.runnable_polls.load(Ordering::SeqCst) == 1
17429          && metrics.active_runnables.load(Ordering::SeqCst) == 0
17430          && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17431      });
17432
17433      // Cancel A on another thread: closing A notifies its awaiter, and the
17434      // Shared notifier wakes B while holding `wakers`. B's schedule closure
17435      // then parks at the barrier with that lock still held.
17436      let waker_thread = std::thread::spawn(move || drop(runnable_a));
17437      wake_in_flight.wait();
17438
17439      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
17440      // the abort wake is a no-op; the stop is then published.
17441      backend.begin_shutdown();
17442      wake_release.wait();
17443      backend.wait_until_idle();
17444
17445      waker_thread
17446        .join()
17447        .expect("the waker thread must not panic");
17448      assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17449      assert!(
17450        futures::executor::block_on(task_b.fallible()).is_none(),
17451        "B's late wake must still cancel B"
17452      );
17453      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17454      drop(shared);
17455      done_tx.send(()).unwrap();
17456    });
17457    done_rx
17458      .recv_timeout(Duration::from_secs(5))
17459      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17460  }
17461
17462  #[cfg(napi_runtime_os_threads)]
17463  #[test]
17464  fn current_thread_recovery_reservation_blocks_new_host_publication() {
17465    use std::sync::mpsc;
17466
17467    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17468    let metrics = Arc::new(RuntimeMetrics::default());
17469    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17470      Arc::clone(&metrics),
17471      count_current_thread_interleaved_recovery_host_dispatch,
17472    ));
17473    let backend =
17474      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17475
17476    let registration = backend
17477      .work
17478      .try_register_async()
17479      .expect("the backend must accept work");
17480    let task = spawn_registered(
17481      &backend,
17482      Arc::clone(&metrics),
17483      std::future::pending::<()>(),
17484      registration,
17485    );
17486    let failed_dispatch = current_thread_dispatch(&executor);
17487    assert_eq!(
17488      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17489      1
17490    );
17491
17492    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17493    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17494    let cancelling_executor = Arc::clone(&executor);
17495    let cancellation = std::thread::spawn(move || {
17496      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17497        *slot.borrow_mut() = Some(Box::new(move || {
17498          recovery_cleared_tx.send(()).unwrap();
17499          release_recovery_rx.recv().unwrap();
17500        }));
17501      });
17502      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17503    });
17504    recovery_cleared_rx
17505      .recv_timeout(Duration::from_secs(2))
17506      .expect("failed dispatch cancellation must pause after consuming the old capability");
17507
17508    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17509    let (publication_done_tx, publication_done_rx) = mpsc::channel();
17510    let publishing_executor = Arc::clone(&executor);
17511    let publication = std::thread::spawn(move || {
17512      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17513        *slot.borrow_mut() = Some(Box::new(move || {
17514          publication_blocked_tx.send(()).unwrap();
17515        }));
17516      });
17517      publishing_executor.request_drain_if_queued();
17518      publication_done_tx.send(()).unwrap();
17519    });
17520
17521    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17522      release_recovery_tx.send(()).unwrap();
17523      cancellation.join().unwrap();
17524      publication.join().unwrap();
17525      panic!("a newly registered host must wait for recovery reservation: {error}");
17526    }
17527    assert!(
17528      matches!(
17529        publication_done_rx.try_recv(),
17530        Err(mpsc::TryRecvError::Empty)
17531      ),
17532      "the new host must not publish in the consumed-to-recovery gap"
17533    );
17534    release_recovery_tx.send(()).unwrap();
17535
17536    cancellation.join().unwrap();
17537    publication_done_rx
17538      .recv_timeout(Duration::from_secs(2))
17539      .unwrap();
17540    publication.join().unwrap();
17541
17542    let replacement_dispatch = current_thread_dispatch(&executor);
17543    assert_ne!(replacement_dispatch, failed_dispatch);
17544    assert_eq!(
17545      executor.recovery_dispatch.load(Ordering::Acquire),
17546      replacement_dispatch
17547    );
17548    assert_eq!(
17549      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17550      3,
17551      "the original publication and two host attempts for its one replacement must run"
17552    );
17553
17554    executor.cancel_host_dispatch_for_test(replacement_dispatch);
17555
17556    assert_eq!(
17557      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17558      3,
17559      "failure of the tagged replacement must cancel work without a second replacement"
17560    );
17561    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17562    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17563    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17564    assert_eq!(
17565      futures::executor::block_on(task).unwrap_err().to_string(),
17566      "async runtime stopped before the task completed"
17567    );
17568
17569    let later_registration = backend
17570      .work
17571      .try_register_async()
17572      .expect("terminal cancellation must reopen acceptance");
17573    let later_task = spawn_registered(
17574      &backend,
17575      Arc::clone(&metrics),
17576      async { 2usize },
17577      later_registration,
17578    );
17579    assert_eq!(
17580      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17581      4
17582    );
17583    executor.drive_host_turn();
17584    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17585  }
17586
17587  #[cfg(napi_runtime_os_threads)]
17588  #[test]
17589  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17590    use std::sync::mpsc;
17591
17592    struct BlockingDrop {
17593      entered: mpsc::Sender<()>,
17594      release: mpsc::Receiver<()>,
17595    }
17596
17597    impl Drop for BlockingDrop {
17598      fn drop(&mut self) {
17599        self.entered.send(()).unwrap();
17600        self.release.recv().unwrap();
17601      }
17602    }
17603
17604    reset_current_thread_recovery_host_dispatches();
17605    let metrics = Arc::new(RuntimeMetrics::default());
17606    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17607      Arc::clone(&metrics),
17608      count_current_thread_recovery_host_dispatch,
17609    ));
17610    let backend =
17611      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17612
17613    let holder_registration = backend
17614      .work
17615      .try_register_work()
17616      .expect("the backend must accept the blocking holder");
17617    let (holder_started_tx, holder_started_rx) = mpsc::channel();
17618    let (release_holder_tx, release_holder_rx) = mpsc::channel();
17619    let holder_executor = Arc::clone(&executor);
17620    let holder = std::thread::spawn(move || {
17621      let handle = holder_executor.schedule_blocking(
17622        move || {
17623          holder_started_tx.send(()).unwrap();
17624          release_holder_rx.recv().unwrap();
17625        },
17626        holder_registration,
17627      );
17628      futures::executor::block_on(handle).unwrap();
17629    });
17630    holder_started_rx
17631      .recv_timeout(Duration::from_secs(2))
17632      .expect("the first blocking call must hold the CurrentThread lane");
17633
17634    let cancelled_registration = backend
17635      .work
17636      .try_register_work()
17637      .expect("the backend must accept queued blocking work");
17638    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17639    let (release_drop_tx, release_drop_rx) = mpsc::channel();
17640    let blocking_drop = BlockingDrop {
17641      entered: drop_entered_tx,
17642      release: release_drop_rx,
17643    };
17644    let cancelled = executor.schedule_blocking(
17645      move || {
17646        let _blocking_drop = blocking_drop;
17647      },
17648      cancelled_registration,
17649    );
17650
17651    let async_registration = backend
17652      .work
17653      .try_register_async()
17654      .expect("the backend must accept queued async work");
17655    let pending = spawn_registered(
17656      &backend,
17657      Arc::clone(&metrics),
17658      std::future::pending::<()>(),
17659      async_registration,
17660    );
17661    let failed_dispatch = current_thread_dispatch(&executor);
17662    executor.cancel_host_dispatch_for_test(failed_dispatch);
17663    let replacement_dispatch = current_thread_dispatch(&executor);
17664
17665    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17666    let cancelling_executor = Arc::clone(&executor);
17667    let cancellation = std::thread::spawn(move || {
17668      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17669      cancel_done_tx.send(()).unwrap();
17670    });
17671    drop_entered_rx
17672      .recv_timeout(Duration::from_secs(2))
17673      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17674    let active_during_drop = backend.work.state.lock().unwrap().active;
17675    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17676
17677    release_drop_tx.send(()).unwrap();
17678    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17679    cancellation.join().unwrap();
17680
17681    assert_eq!(
17682      active_during_drop, 2,
17683      "the blocking capture's work registration must remain active until its destructor returns"
17684    );
17685    assert!(
17686      cancellation_blocked,
17687      "terminal cancellation must wait for capture destruction"
17688    );
17689    assert!(futures::executor::block_on(cancelled).is_err());
17690    assert!(futures::executor::block_on(pending).is_err());
17691    assert_eq!(backend.work.state.lock().unwrap().active, 1);
17692
17693    release_holder_tx.send(()).unwrap();
17694    holder.join().unwrap();
17695    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17696  }
17697
17698  #[test]
17699  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17700    reset_current_thread_one_shot_host_dispatches();
17701    let metrics = Arc::new(RuntimeMetrics::default());
17702    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17703      Arc::clone(&metrics),
17704      accept_only_first_current_thread_host_dispatch,
17705    ));
17706
17707    let scheduler = Arc::clone(&executor);
17708    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17709      scheduler.schedule(runnable)
17710    });
17711    executor.schedule(runnable);
17712    let failed_dispatch = current_thread_dispatch(&executor);
17713
17714    executor.cancel_host_dispatch_for_test(failed_dispatch);
17715
17716    assert_eq!(
17717      current_thread_one_shot_host_dispatches(),
17718      2,
17719      "the original and its one bounded replacement must be attempted"
17720    );
17721    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17722    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17723    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17724    assert!(
17725      futures::executor::block_on(task.fallible()).is_none(),
17726      "an unavailable replacement host must settle the original task as cancelled"
17727    );
17728  }
17729
17730  enum TestCurrentThreadTaskDriverBehavior {
17731    Healthy,
17732    PanicInLivenessAndSweep,
17733    PanicInDispatchAndSweep,
17734  }
17735
17736  struct TestCurrentThreadTaskDriver {
17737    behavior: TestCurrentThreadTaskDriverBehavior,
17738    dispatched: AtomicBool,
17739  }
17740
17741  #[derive(Default)]
17742  struct ManualCurrentThreadTaskDriver {
17743    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17744  }
17745
17746  struct FailDuringAuthoritativePanicPublication;
17747  struct FailDuringLateRegistrationPublication;
17748  struct FailDuringNestedUnwindPublication;
17749  struct FailDuringReplacementRepublishPublication;
17750  struct FirstHostileCurrentThreadPanic;
17751  struct LaterHostileCurrentThreadPanic;
17752
17753  impl Drop for FirstHostileCurrentThreadPanic {
17754    fn drop(&mut self) {
17755      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17756      panic!("the first retained CurrentThread panic payload was dropped");
17757    }
17758  }
17759
17760  impl Drop for LaterHostileCurrentThreadPanic {
17761    fn drop(&mut self) {
17762      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17763      panic!("a later CurrentThread panic payload escaped containment");
17764    }
17765  }
17766
17767  impl ManualCurrentThreadTaskDriver {
17768    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17769      self.deliveries.lock().unwrap().clone()
17770    }
17771  }
17772
17773  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17774    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17775      self.deliveries.lock().unwrap().push(delivery);
17776      true
17777    }
17778  }
17779
17780  /// Accepts every delivery; once `fail` is set it also fails the accepted
17781  /// delivery before `dispatch` returns, from a helper thread it joins, the
17782  /// way a host whose env is tearing down reports a threadsafe-function call
17783  /// that cannot land.
17784  struct SynchronouslyFailingCurrentThreadTaskDriver {
17785    registry: &'static CurrentThreadTaskDriverRegistry,
17786    fail: AtomicBool,
17787    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17788  }
17789
17790  impl SynchronouslyFailingCurrentThreadTaskDriver {
17791    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17792      Self {
17793        registry,
17794        fail: AtomicBool::new(false),
17795        deliveries: Mutex::default(),
17796      }
17797    }
17798
17799    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17800      self.deliveries.lock().unwrap().clone()
17801    }
17802  }
17803
17804  impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17805    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17806      self.deliveries.lock().unwrap().push(delivery);
17807      if self.fail.load(Ordering::SeqCst) {
17808        let registry = self.registry;
17809        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17810          .join()
17811          .expect("the failing helper must not panic");
17812        assert!(
17813          completion.failed_dispatches.is_empty(),
17814          "the failed delivery must remain provisional until its publication retires"
17815        );
17816      }
17817      true
17818    }
17819  }
17820
17821  /// Accepts every delivery, and fails the first `fail_budget` of them before
17822  /// `dispatch` returns -- from a helper thread it joins, exactly like
17823  /// `SynchronouslyFailingCurrentThreadTaskDriver`. Once the budget is spent
17824  /// the host is healthy again, so the retirement turn a waker-stack terminal
17825  /// failure publishes for itself is accepted instead of lost.
17826  /// A one-shot the test runs from inside a chosen delivery; see
17827  /// `BoundedFailureCurrentThreadTaskDriver::during_delivery`.
17828  type DeliveryHook = Box<dyn FnOnce() + Send>;
17829
17830  struct BoundedFailureCurrentThreadTaskDriver {
17831    registry: &'static CurrentThreadTaskDriverRegistry,
17832    fail_budget: AtomicUsize,
17833    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17834    during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17835  }
17836
17837  impl BoundedFailureCurrentThreadTaskDriver {
17838    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17839      Self {
17840        registry,
17841        fail_budget: AtomicUsize::new(0),
17842        deliveries: Mutex::default(),
17843        during_delivery: Mutex::default(),
17844      }
17845    }
17846
17847    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17848      self.deliveries.lock().unwrap().clone()
17849    }
17850
17851    /// Run `hook` from inside the `index`-th delivery (0-based over every
17852    /// delivery this driver receives), before that delivery is failed. This is
17853    /// the window another thread needs to queue work while a publication is in
17854    /// flight.
17855    fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17856      *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17857    }
17858  }
17859
17860  impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17861    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17862      let index = {
17863        let mut deliveries = self.deliveries.lock().unwrap();
17864        deliveries.push(delivery);
17865        deliveries.len() - 1
17866      };
17867      let hook = {
17868        let mut slot = self.during_delivery.lock().unwrap();
17869        if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17870          slot.take().map(|(_, hook)| hook)
17871        } else {
17872          None
17873        }
17874      };
17875      if let Some(hook) = hook {
17876        hook();
17877      }
17878      let budget = self.fail_budget.load(Ordering::SeqCst);
17879      if budget != 0 {
17880        self.fail_budget.store(budget - 1, Ordering::SeqCst);
17881        let registry = self.registry;
17882        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17883          .join()
17884          .expect("the failing helper must not panic");
17885        assert!(
17886          completion.failed_dispatches.is_empty(),
17887          "the failed delivery must remain provisional until its publication retires"
17888        );
17889      }
17890      true
17891    }
17892  }
17893
17894  /// Accepts every delivery and, while `fail` is set, fails it synchronously
17895  /// from inside `dispatch` on the calling thread -- the option
17896  /// [`CurrentThreadTaskDriver::dispatch`] documents for an SPI host. While a
17897  /// producer is armed it also rendezvouses with the producer thread from
17898  /// inside every delivery, so exactly one runnable is queued while each
17899  /// publication is in flight.
17900  struct SustainedProducerGate {
17901    remaining: usize,
17902    queue_one: std::sync::mpsc::Sender<()>,
17903    queued: std::sync::mpsc::Receiver<()>,
17904  }
17905
17906  struct SustainedProducerCurrentThreadTaskDriver {
17907    registry: &'static CurrentThreadTaskDriverRegistry,
17908    /// Deliveries still to be failed: `usize::MAX` fails every one of them,
17909    /// `n` fails the next `n` and is healthy after, `0` is healthy.
17910    fail_budget: AtomicUsize,
17911    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17912    producer: Mutex<Option<SustainedProducerGate>>,
17913  }
17914
17915  impl SustainedProducerCurrentThreadTaskDriver {
17916    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17917      Self {
17918        registry,
17919        fail_budget: AtomicUsize::new(0),
17920        deliveries: Mutex::default(),
17921        producer: Mutex::default(),
17922      }
17923    }
17924
17925    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17926      self.deliveries.lock().unwrap().clone()
17927    }
17928
17929    fn arm_producer(
17930      &self,
17931      remaining: usize,
17932      queue_one: std::sync::mpsc::Sender<()>,
17933      queued: std::sync::mpsc::Receiver<()>,
17934    ) {
17935      *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17936        remaining,
17937        queue_one,
17938        queued,
17939      });
17940    }
17941
17942    /// Close the gate and report how many submissions the producer was still
17943    /// willing to make.
17944    fn disarm_producer(&self) -> usize {
17945      self
17946        .producer
17947        .lock()
17948        .unwrap()
17949        .take()
17950        .map_or(0, |gate| gate.remaining)
17951    }
17952  }
17953
17954  impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17955    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17956      self.deliveries.lock().unwrap().push(delivery);
17957      {
17958        let mut slot = self.producer.lock().unwrap();
17959        if let Some(gate) = slot.as_mut()
17960          && gate.remaining != 0
17961        {
17962          gate.remaining -= 1;
17963          gate.queue_one.send(()).expect("the producer must be live");
17964          gate
17965            .queued
17966            .recv_timeout(std::time::Duration::from_secs(5))
17967            .expect("the producer must queue one runnable per publication");
17968        }
17969      }
17970      let budget = self.fail_budget.load(Ordering::SeqCst);
17971      if budget != 0 {
17972        if budget != usize::MAX {
17973          self.fail_budget.store(budget - 1, Ordering::SeqCst);
17974        }
17975        let completion = self.registry.finish_delivery(delivery, true);
17976        assert!(
17977          completion.failed_dispatches.is_empty(),
17978          "the failed delivery must remain provisional until its publication retires"
17979        );
17980      }
17981      true
17982    }
17983  }
17984
17985  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17986    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17987      let completion =
17988        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17989      assert!(
17990        completion.failed_dispatches.is_empty(),
17991        "the failed delivery must remain provisional until its publication retires"
17992      );
17993      true
17994    }
17995  }
17996
17997  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17998    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17999      let completion =
18000        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
18001      assert!(
18002        completion.failed_dispatches.is_empty(),
18003        "the failed delivery must remain provisional until its publication retires"
18004      );
18005      true
18006    }
18007  }
18008
18009  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
18010    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18011      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
18012      assert!(
18013        completion.failed_dispatches.is_empty(),
18014        "the failed delivery must remain provisional until its publication retires"
18015      );
18016      true
18017    }
18018  }
18019
18020  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
18021    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18022      let completion =
18023        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
18024      assert!(
18025        completion.failed_dispatches.is_empty(),
18026        "the failed delivery must remain provisional until its publication retires"
18027      );
18028      true
18029    }
18030  }
18031
18032  struct ReentrantCurrentThreadTaskDriver {
18033    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18034    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
18035  }
18036
18037  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
18038    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18039      true
18040    }
18041
18042    fn is_live(&self) -> bool {
18043      let registration = *self.registration.lock().unwrap();
18044      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
18045        registry.unregister(id);
18046      }
18047      false
18048    }
18049  }
18050
18051  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
18052    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18053    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18054  }
18055
18056  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
18057    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18058      true
18059    }
18060
18061    fn is_live(&self) -> bool {
18062      if let Some(registry) = self.registry.upgrade() {
18063        for registration in self.registrations.lock().unwrap().iter().copied() {
18064          let completion = registry.unregister(registration);
18065          assert!(completion.failed_dispatches.is_empty());
18066          assert!(!completion.redispatch);
18067        }
18068      }
18069      false
18070    }
18071  }
18072
18073  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18074    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18075    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
18076    drops: Arc<AtomicUsize>,
18077  }
18078
18079  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18080    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18081      if let Some(registry) = self.registry.upgrade() {
18082        for registration in self.registrations.lock().unwrap().iter().copied() {
18083          let completion = registry.unregister(registration);
18084          assert!(completion.failed_dispatches.is_empty());
18085          assert!(!completion.redispatch);
18086        }
18087      }
18088      true
18089    }
18090  }
18091
18092  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18093    fn drop(&mut self) {
18094      self.drops.fetch_add(1, Ordering::SeqCst);
18095      panic!("intentional reentrant CurrentThread task-driver destructor panic");
18096    }
18097  }
18098
18099  struct PanickingDropCurrentThreadTaskDriver {
18100    drops: Arc<AtomicUsize>,
18101  }
18102
18103  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
18104    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18105      true
18106    }
18107  }
18108
18109  impl Drop for PanickingDropCurrentThreadTaskDriver {
18110    fn drop(&mut self) {
18111      self.drops.fetch_add(1, Ordering::SeqCst);
18112      panic!("intentional CurrentThread task-driver destructor panic");
18113    }
18114  }
18115
18116  struct ReentrantCurrentThreadTaskDriverDrop {
18117    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18118    fallback: CurrentThreadTaskDriverId,
18119  }
18120
18121  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
18122    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18123      true
18124    }
18125  }
18126
18127  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
18128    fn drop(&mut self) {
18129      if let Some(registry) = self.registry.upgrade() {
18130        registry.unregister(self.fallback);
18131      }
18132    }
18133  }
18134
18135  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
18136    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18137      assert!(
18138        !matches!(
18139          self.behavior,
18140          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
18141        ),
18142        "intentional task-driver dispatch panic"
18143      );
18144      self.dispatched.store(true, Ordering::SeqCst);
18145      true
18146    }
18147
18148    fn is_live(&self) -> bool {
18149      assert!(
18150        !matches!(
18151          self.behavior,
18152          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
18153        ),
18154        "intentional task-driver liveness panic"
18155      );
18156      true
18157    }
18158
18159    fn on_swept(&self) {
18160      assert!(
18161        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
18162        "intentional task-driver sweep panic"
18163      );
18164    }
18165  }
18166
18167  #[cfg(panic = "unwind")]
18168  #[test]
18169  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
18170    let registry = CurrentThreadTaskDriverRegistry::default();
18171    let fallback = Arc::new(TestCurrentThreadTaskDriver {
18172      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18173      dispatched: AtomicBool::new(false),
18174    });
18175    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18176    registry.register(Arc::new(TestCurrentThreadTaskDriver {
18177      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
18178      dispatched: AtomicBool::new(false),
18179    }));
18180    registry.register(Arc::new(TestCurrentThreadTaskDriver {
18181      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
18182      dispatched: AtomicBool::new(false),
18183    }));
18184
18185    assert_eq!(
18186      registry.dispatch(37),
18187      CurrentThreadHostDispatchResult::Accepted,
18188      "a panicking newest host must fall back to a live host"
18189    );
18190    assert!(fallback.dispatched.load(Ordering::SeqCst));
18191  }
18192
18193  #[cfg(panic = "unwind")]
18194  #[test]
18195  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
18196    let registry = CurrentThreadTaskDriverRegistry::default();
18197    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
18198    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
18199    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18200    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18201    registry
18202      .next_delivery
18203      .store(u64::MAX - 1, Ordering::Relaxed);
18204
18205    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
18206    assert!(
18207      publication.is_err(),
18208      "delivery-id exhaustion on the second host must unwind the publication"
18209    );
18210    {
18211      let state = registry
18212        .state
18213        .lock()
18214        .unwrap_or_else(std::sync::PoisonError::into_inner);
18215      assert!(
18216        !state.dispatches.contains_key(&38),
18217        "unwind cleanup must release the exact registry publication barrier"
18218      );
18219      assert!(
18220        state
18221          .entries
18222          .values()
18223          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18224        "failed transactional reservation must leave every physical host slot clean"
18225      );
18226    }
18227    assert!(first.deliveries().is_empty());
18228    assert!(second.deliveries().is_empty());
18229
18230    registry.next_delivery.store(0, Ordering::Relaxed);
18231    assert_eq!(
18232      registry.dispatch(38),
18233      CurrentThreadHostDispatchResult::Accepted
18234    );
18235    let first_delivery = first.deliveries()[0];
18236    let second_delivery = second.deliveries()[0];
18237    assert_eq!(
18238      registry.claim_delivery(first_delivery.capability()),
18239      Some(38)
18240    );
18241    assert_eq!(
18242      registry.claim_delivery(second_delivery.capability()),
18243      Some(38)
18244    );
18245    registry.mark_serviced(38);
18246    assert!(
18247      registry
18248        .finish_delivery(first_delivery, false)
18249        .failed_dispatches
18250        .is_empty()
18251    );
18252    assert!(
18253      registry
18254        .finish_delivery(second_delivery, false)
18255        .failed_dispatches
18256        .is_empty()
18257    );
18258
18259    assert_eq!(
18260      registry.dispatch(39),
18261      CurrentThreadHostDispatchResult::Accepted
18262    );
18263    let first_later = first.deliveries()[1];
18264    let second_later = second.deliveries()[1];
18265    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18266    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18267    registry.mark_serviced(39);
18268    assert!(
18269      registry
18270        .finish_delivery(first_later, false)
18271        .failed_dispatches
18272        .is_empty()
18273    );
18274    assert!(
18275      registry
18276        .finish_delivery(second_later, false)
18277        .failed_dispatches
18278        .is_empty()
18279    );
18280    assert!(
18281      registry
18282        .state
18283        .lock()
18284        .unwrap_or_else(std::sync::PoisonError::into_inner)
18285        .dispatches
18286        .is_empty()
18287    );
18288    let state = registry
18289      .state
18290      .lock()
18291      .unwrap_or_else(std::sync::PoisonError::into_inner);
18292    assert!(
18293      state
18294        .entries
18295        .values()
18296        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18297    );
18298  }
18299
18300  #[test]
18301  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18302    let registry = CurrentThreadTaskDriverRegistry::default();
18303    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18304    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18305    let dispatch = 40;
18306
18307    assert_eq!(
18308      registry.dispatch(dispatch),
18309      CurrentThreadHostDispatchResult::Accepted
18310    );
18311    let failed_delivery = failed.deliveries()[0];
18312    let failure = current_thread_dispatch_failure(dispatch, 0);
18313    assert_eq!(
18314      registry
18315        .finish_delivery(failed_delivery, true)
18316        .failed_dispatches,
18317      vec![failure]
18318    );
18319    registry.unregister(failed_id);
18320
18321    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18322    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18323    assert!(
18324      registry.rearm_failed_publication(failure),
18325      "a closed unreferenced dispatch must reopen for its armed publication owner"
18326    );
18327    assert_eq!(
18328      registry.dispatch(dispatch),
18329      CurrentThreadHostDispatchResult::Accepted
18330    );
18331    let replacement_delivery = replacement.deliveries()[0];
18332    assert_eq!(replacement_delivery.dispatch, dispatch);
18333    assert_eq!(
18334      registry.claim_delivery(replacement_delivery.capability()),
18335      Some(dispatch)
18336    );
18337    registry.mark_serviced(dispatch);
18338    assert!(
18339      registry
18340        .finish_delivery(replacement_delivery, false)
18341        .failed_dispatches
18342        .is_empty()
18343    );
18344    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18345  }
18346
18347  #[test]
18348  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18349    struct RacingTaskDriver {
18350      executor: Arc<CurrentThreadExecutor>,
18351      drives: bool,
18352      dispatches: Mutex<Vec<u64>>,
18353    }
18354
18355    impl CurrentThreadTaskDriver for RacingTaskDriver {
18356      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18357        self.dispatches.lock().unwrap().push(delivery.dispatch);
18358        if self.drives {
18359          self
18360            .executor
18361            .drive_host_turn_for_dispatch(delivery.dispatch);
18362        }
18363        true
18364      }
18365    }
18366
18367    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18368      Arc::new(RuntimeMetrics::default()),
18369      accept_current_thread_host_dispatch,
18370    ));
18371    let polls = Arc::new(AtomicUsize::new(0));
18372    let task_polls = Arc::clone(&polls);
18373    let scheduler = Arc::clone(&executor);
18374    let (runnable, task) = async_task::spawn(
18375      async move {
18376        task_polls.fetch_add(1, Ordering::SeqCst);
18377      },
18378      move |runnable| scheduler.schedule(runnable),
18379    );
18380    executor.schedule(runnable);
18381    let dispatch = current_thread_dispatch(&executor);
18382
18383    let registry = CurrentThreadTaskDriverRegistry::default();
18384    let responsive = Arc::new(RacingTaskDriver {
18385      executor: Arc::clone(&executor),
18386      drives: true,
18387      dispatches: Mutex::new(Vec::new()),
18388    });
18389    let starved = Arc::new(RacingTaskDriver {
18390      executor: Arc::clone(&executor),
18391      drives: false,
18392      dispatches: Mutex::new(Vec::new()),
18393    });
18394    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18395    // The newest host is intentionally live but unresponsive.
18396    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18397
18398    assert_eq!(
18399      registry.dispatch(dispatch),
18400      CurrentThreadHostDispatchResult::Accepted
18401    );
18402    assert_eq!(
18403      responsive.dispatches.lock().unwrap().as_slice(),
18404      &[dispatch]
18405    );
18406    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18407    assert_eq!(
18408      polls.load(Ordering::SeqCst),
18409      1,
18410      "the responsive older host must win even when the newest host is starved"
18411    );
18412
18413    // The delayed callback from the starved host carries the same consumed
18414    // capability and cannot poll the task a second time.
18415    executor.drive_host_turn_for_dispatch(dispatch);
18416    assert_eq!(polls.load(Ordering::SeqCst), 1);
18417    futures::executor::block_on(task);
18418  }
18419
18420  #[test]
18421  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18422    const HOSTS: usize = 1_024;
18423
18424    let registry = CurrentThreadTaskDriverRegistry::default();
18425    let mut ids = Vec::with_capacity(HOSTS);
18426    let mut drivers = Vec::with_capacity(HOSTS);
18427    for _ in 0..HOSTS {
18428      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18429      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18430      drivers.push(driver);
18431    }
18432
18433    let dispatch = u64::MAX - 300;
18434    assert_eq!(
18435      registry.dispatch(dispatch),
18436      CurrentThreadHostDispatchResult::Accepted
18437    );
18438    let deliveries = drivers
18439      .iter()
18440      .map(|driver| driver.deliveries()[0])
18441      .collect::<Vec<_>>();
18442    {
18443      let state = registry
18444        .state
18445        .lock()
18446        .unwrap_or_else(std::sync::PoisonError::into_inner);
18447      assert_eq!(state.delivery_drivers.len(), HOSTS);
18448      assert_eq!(
18449        state
18450          .dispatches
18451          .get(&dispatch)
18452          .map(|state| state.references),
18453        Some(HOSTS)
18454      );
18455      assert_eq!(
18456        state
18457          .ordered_snapshot()
18458          .iter()
18459          .map(|(id, _)| *id)
18460          .collect::<Vec<_>>(),
18461        ids,
18462        "keyed driver lookup must retain registration order"
18463      );
18464    }
18465
18466    for delivery in &deliveries {
18467      assert_eq!(
18468        registry.claim_delivery(delivery.capability()),
18469        Some(dispatch)
18470      );
18471    }
18472    registry.mark_serviced(dispatch);
18473    for delivery in deliveries {
18474      assert!(
18475        registry
18476          .finish_delivery(delivery, false)
18477          .failed_dispatches
18478          .is_empty()
18479      );
18480    }
18481    {
18482      let state = registry
18483        .state
18484        .lock()
18485        .unwrap_or_else(std::sync::PoisonError::into_inner);
18486      assert!(state.delivery_drivers.is_empty());
18487      assert!(state.dispatches.is_empty());
18488    }
18489
18490    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18491    for id in &removed {
18492      let completion = registry.unregister(*id);
18493      assert!(completion.failed_dispatches.is_empty());
18494      assert!(!completion.redispatch);
18495    }
18496    let expected = ids
18497      .iter()
18498      .copied()
18499      .filter(|id| !removed.contains(id))
18500      .collect::<Vec<_>>();
18501    let live = registry.live().live;
18502    assert_eq!(
18503      live
18504        .as_slice()
18505        .iter()
18506        .map(|(id, _)| *id)
18507        .collect::<Vec<_>>(),
18508      expected,
18509      "O(1) removals must preserve the relative order of surviving host registrations"
18510    );
18511    drop(live);
18512
18513    for id in expected {
18514      registry.unregister(id);
18515    }
18516    let state = registry
18517      .state
18518      .lock()
18519      .unwrap_or_else(std::sync::PoisonError::into_inner);
18520    assert!(state.entries.is_empty());
18521    assert!(state.oldest.is_none());
18522    assert!(state.newest.is_none());
18523  }
18524
18525  #[test]
18526  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18527    let registry = CurrentThreadTaskDriverRegistry::default();
18528    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18529    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18530    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18531    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18532    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18533    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18534
18535    let dispatch = 41;
18536    assert_eq!(
18537      registry.dispatch(dispatch),
18538      CurrentThreadHostDispatchResult::Accepted
18539    );
18540    let failing_delivery = failing.deliveries()[0];
18541    let delayed_delivery = delayed.deliveries()[0];
18542    let healthy_delivery = healthy.deliveries()[0];
18543    assert_eq!(failing_delivery.dispatch, dispatch);
18544    assert_eq!(delayed_delivery.dispatch, dispatch);
18545    assert_eq!(healthy_delivery.dispatch, dispatch);
18546
18547    assert!(
18548      registry
18549        .finish_delivery(failing_delivery, true)
18550        .failed_dispatches
18551        .is_empty(),
18552      "one host failure must not recover while other accepted attempts remain"
18553    );
18554    assert_eq!(
18555      registry.claim_delivery(healthy_delivery.capability()),
18556      Some(dispatch)
18557    );
18558    registry.mark_serviced(dispatch);
18559    assert!(
18560      registry
18561        .finish_delivery(healthy_delivery, false)
18562        .failed_dispatches
18563        .is_empty()
18564    );
18565    assert_eq!(
18566      registry.claim_delivery(delayed_delivery.capability()),
18567      None,
18568      "a delayed losing delivery must become stale after another host services the dispatch"
18569    );
18570    assert!(
18571      registry
18572        .finish_delivery(delayed_delivery, true)
18573        .failed_dispatches
18574        .is_empty(),
18575      "a stale losing failure must not recover or cancel a serviced dispatch"
18576    );
18577    assert!(
18578      registry.state.lock().unwrap().dispatches.is_empty(),
18579      "all per-dispatch acknowledgement state must retire"
18580    );
18581  }
18582
18583  #[test]
18584  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18585    let registry = CurrentThreadTaskDriverRegistry::default();
18586    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18587    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18588
18589    let dispatch = 56;
18590    assert_eq!(
18591      registry.dispatch(dispatch),
18592      CurrentThreadHostDispatchResult::Accepted
18593    );
18594    let delivery = no_op.deliveries()[0];
18595    let completion = registry.finish_delivery(delivery, false);
18596    let failure = current_thread_dispatch_failure(dispatch, 0);
18597    assert_eq!(
18598      completion.failed_dispatches,
18599      vec![failure],
18600      "a callback return without an exact capability claim must enter recovery"
18601    );
18602    assert!(
18603      registry
18604        .state
18605        .lock()
18606        .unwrap()
18607        .dispatches
18608        .get(&dispatch)
18609        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18610    );
18611    registry.finish_cancellation(failure);
18612    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18613  }
18614
18615  #[test]
18616  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18617    struct FailDuringPublication {
18618      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18619    }
18620
18621    impl CurrentThreadTaskDriver for FailDuringPublication {
18622      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18623        let completion = self
18624          .registry
18625          .upgrade()
18626          .expect("the registry must outlive publication")
18627          .finish_delivery(delivery, true);
18628        assert!(
18629          completion.failed_dispatches.is_empty(),
18630          "failure must remain provisional until the broadcast closes"
18631        );
18632        true
18633      }
18634    }
18635
18636    struct RejectProvisional;
18637
18638    impl CurrentThreadTaskDriver for RejectProvisional {
18639      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18640        false
18641      }
18642    }
18643
18644    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18645    registry.register(Arc::new(RejectProvisional));
18646    // Newest-first publication calls the accepted-then-failing host while the
18647    // rejecting host still has an unresolved provisional delivery.
18648    registry.register(Arc::new(FailDuringPublication {
18649      registry: Arc::downgrade(&registry),
18650    }));
18651
18652    let failure = current_thread_dispatch_failure(57, 0);
18653    assert_eq!(
18654      registry.dispatch(57),
18655      CurrentThreadHostDispatchResult::Failed(failure),
18656      "the final provisional rejection must expose the earlier accepted failure"
18657    );
18658    assert!(
18659      registry
18660        .state
18661        .lock()
18662        .unwrap()
18663        .dispatches
18664        .get(&57)
18665        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18666    );
18667    assert_eq!(
18668      registry.dispatch(57),
18669      CurrentThreadHostDispatchResult::Failed(failure),
18670      "a racing replacement publication must preserve terminal failure until cancellation runs"
18671    );
18672    registry.finish_cancellation(failure);
18673  }
18674
18675  #[test]
18676  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18677    struct RejectProvisional;
18678
18679    impl CurrentThreadTaskDriver for RejectProvisional {
18680      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18681        false
18682      }
18683    }
18684
18685    let registry = CurrentThreadTaskDriverRegistry::default();
18686    registry.register(Arc::new(RejectProvisional));
18687    registry.register(Arc::new(RejectProvisional));
18688
18689    assert_eq!(
18690      registry.dispatch(58),
18691      CurrentThreadHostDispatchResult::Unavailable
18692    );
18693    assert!(
18694      registry.state.lock().unwrap().dispatches.is_empty(),
18695      "a broadcast with no accepted attempt must not enter failure recovery"
18696    );
18697  }
18698
18699  #[test]
18700  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18701    let registry = CurrentThreadTaskDriverRegistry::default();
18702    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18703    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18704
18705    assert_eq!(
18706      registry.dispatch(1),
18707      CurrentThreadHostDispatchResult::Accepted
18708    );
18709    let first = blocked.deliveries()[0];
18710    for dispatch in 2..=256 {
18711      assert_eq!(
18712        registry.dispatch(dispatch),
18713        CurrentThreadHostDispatchResult::Accepted
18714      );
18715      assert_eq!(
18716        blocked.deliveries().len(),
18717        1,
18718        "a blocked host must never receive a second queued or in-flight callback"
18719      );
18720    }
18721    {
18722      let state = registry.state.lock().unwrap();
18723      let entry = state
18724        .entries
18725        .get(&blocked_id)
18726        .expect("the blocked host must remain registered");
18727      assert_eq!(entry.pending, Some(256));
18728      assert_eq!(
18729        state.dispatches.len(),
18730        2,
18731        "only the in-flight and latest pending dispatches may retain registry state"
18732      );
18733    }
18734
18735    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18736    registry.mark_serviced(1);
18737    let completion = registry.finish_delivery(first, false);
18738    assert!(completion.failed_dispatches.is_empty());
18739    assert!(completion.redispatch);
18740    assert_eq!(
18741      registry.dispatch(256),
18742      CurrentThreadHostDispatchResult::Accepted
18743    );
18744    let deliveries = blocked.deliveries();
18745    assert_eq!(deliveries.len(), 2);
18746    assert_eq!(
18747      deliveries[1].dispatch, 256,
18748      "completion must schedule exactly the latest coalesced capability"
18749    );
18750    assert_eq!(registry.claim_delivery(first.capability()), None);
18751    assert_eq!(
18752      registry.claim_delivery(deliveries[1].capability()),
18753      Some(256)
18754    );
18755    registry.mark_serviced(256);
18756    assert!(
18757      registry
18758        .finish_delivery(deliveries[1], false)
18759        .failed_dispatches
18760        .is_empty()
18761    );
18762    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18763  }
18764
18765  #[test]
18766  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18767    struct RejectAfterFirst {
18768      calls: AtomicUsize,
18769      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18770    }
18771
18772    impl CurrentThreadTaskDriver for RejectAfterFirst {
18773      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18774        self.deliveries.lock().unwrap().push(delivery);
18775        self.calls.fetch_add(1, Ordering::SeqCst) == 0
18776      }
18777    }
18778
18779    let registry = CurrentThreadTaskDriverRegistry::default();
18780    let host = Arc::new(RejectAfterFirst {
18781      calls: AtomicUsize::new(0),
18782      deliveries: Mutex::new(Vec::new()),
18783    });
18784    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18785    assert_eq!(
18786      registry.dispatch(11),
18787      CurrentThreadHostDispatchResult::Accepted
18788    );
18789    let first = host.deliveries.lock().unwrap()[0];
18790    assert_eq!(
18791      registry.dispatch(12),
18792      CurrentThreadHostDispatchResult::Accepted
18793    );
18794
18795    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18796    registry.mark_serviced(11);
18797    let completion = registry.finish_delivery(first, false);
18798    assert!(completion.redispatch);
18799    let failure = current_thread_dispatch_failure(12, 0);
18800    assert_eq!(
18801      registry.dispatch(12),
18802      CurrentThreadHostDispatchResult::Failed(failure),
18803      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18804    );
18805    assert!(
18806      registry
18807        .state
18808        .lock()
18809        .unwrap()
18810        .dispatches
18811        .get(&12)
18812        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18813    );
18814    registry.finish_cancellation(failure);
18815    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18816  }
18817
18818  #[test]
18819  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18820    let registry = CurrentThreadTaskDriverRegistry::default();
18821    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18822    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18823    assert_eq!(
18824      registry.dispatch(73),
18825      CurrentThreadHostDispatchResult::Accepted
18826    );
18827    let old_delivery = old.deliveries()[0];
18828
18829    let completion = registry.unregister(old_id);
18830    let failure = current_thread_dispatch_failure(73, 0);
18831    assert_eq!(
18832      completion.failed_dispatches,
18833      vec![failure],
18834      "unregistering the final accepting host must retire its exact dispatch"
18835    );
18836    registry.finish_cancellation(failure);
18837    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18838    let replacement_id =
18839      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18840    assert_ne!(replacement_id, old_id);
18841    assert_eq!(
18842      registry.dispatch(74),
18843      CurrentThreadHostDispatchResult::Accepted
18844    );
18845    let replacement_delivery = replacement.deliveries()[0];
18846    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18847
18848    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18849    assert!(
18850      registry
18851        .finish_delivery(old_delivery, true)
18852        .failed_dispatches
18853        .is_empty()
18854    );
18855    assert!(
18856      registry
18857        .fail_delivery(old_delivery.capability())
18858        .failed_dispatches
18859        .is_empty()
18860    );
18861    assert_eq!(
18862      registry.claim_delivery(replacement_delivery.capability()),
18863      Some(74)
18864    );
18865    registry.mark_serviced(74);
18866    assert!(
18867      registry
18868        .finish_delivery(replacement_delivery, false)
18869        .failed_dispatches
18870        .is_empty()
18871    );
18872    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18873  }
18874
18875  #[test]
18876  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18877    struct QueuePendingThenReject {
18878      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18879    }
18880
18881    impl CurrentThreadTaskDriver for QueuePendingThenReject {
18882      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18883        assert_eq!(delivery.dispatch, 81);
18884        let completion = self
18885          .registry
18886          .upgrade()
18887          .expect("the registry must outlive publication")
18888          .dispatch_with_completion(82);
18889        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18890        assert!(completion.failed_dispatches.is_empty());
18891        false
18892      }
18893    }
18894
18895    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18896    registry.register(Arc::new(QueuePendingThenReject {
18897      registry: Arc::downgrade(&registry),
18898    }));
18899
18900    let completion = registry.dispatch_with_completion(81);
18901    assert_eq!(
18902      completion.result,
18903      CurrentThreadHostDispatchResult::Unavailable
18904    );
18905    let failure = current_thread_dispatch_failure(82, 0);
18906    assert_eq!(
18907      completion.failed_dispatches,
18908      vec![failure],
18909      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18910    );
18911    assert!(
18912      registry
18913        .state
18914        .lock()
18915        .unwrap()
18916        .dispatches
18917        .get(&82)
18918        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18919    );
18920    registry.finish_cancellation(failure);
18921    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18922  }
18923
18924  #[test]
18925  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18926    let registry = CurrentThreadTaskDriverRegistry::default();
18927    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18928    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18929    assert_eq!(
18930      registry.dispatch(91),
18931      CurrentThreadHostDispatchResult::Accepted
18932    );
18933    let old_delivery = host.deliveries()[0];
18934    assert_eq!(
18935      registry.dispatch(92),
18936      CurrentThreadHostDispatchResult::Accepted
18937    );
18938
18939    registry.retire_for_shutdown();
18940    {
18941      let state = registry.state.lock().unwrap();
18942      assert!(state.dispatches.is_empty());
18943      let entry = state
18944        .entries
18945        .get(&host_id)
18946        .expect("the host must remain registered");
18947      assert_eq!(entry.pending, None);
18948      assert!(
18949        entry.delivery.is_some(),
18950        "the physical callback remains single-flight until it actually retires"
18951      );
18952    }
18953    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18954
18955    assert_eq!(
18956      registry.dispatch(93),
18957      CurrentThreadHostDispatchResult::Accepted
18958    );
18959    assert_eq!(host.deliveries().len(), 1);
18960    let completion = registry.finish_delivery(old_delivery, true);
18961    assert!(
18962      completion.failed_dispatches.is_empty(),
18963      "a retired-generation failure must not cancel restarted work"
18964    );
18965    assert!(completion.redispatch);
18966    assert_eq!(
18967      registry.dispatch(93),
18968      CurrentThreadHostDispatchResult::Accepted
18969    );
18970    let restarted_delivery = host.deliveries()[1];
18971    assert_eq!(restarted_delivery.dispatch, 93);
18972    assert_eq!(
18973      registry.claim_delivery(restarted_delivery.capability()),
18974      Some(93)
18975    );
18976    registry.mark_serviced(93);
18977    assert!(
18978      registry
18979        .finish_delivery(restarted_delivery, false)
18980        .failed_dispatches
18981        .is_empty()
18982    );
18983
18984    registry.unregister(host_id);
18985    let state = registry.state.lock().unwrap();
18986    assert!(state.entries.is_empty());
18987    assert!(state.dispatches.is_empty());
18988  }
18989
18990  #[cfg(napi_runtime_os_threads)]
18991  #[test]
18992  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18993    use std::{sync::mpsc, time::Duration};
18994
18995    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18996    let fallback = Arc::new(TestCurrentThreadTaskDriver {
18997      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18998      dispatched: AtomicBool::new(false),
18999    });
19000    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19001    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
19002      registry: Arc::downgrade(&registry),
19003      registration: Mutex::new(None),
19004    });
19005    let reentrant_id =
19006      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19007    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
19008
19009    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
19010    let selection_registry = Arc::clone(&registry);
19011    let selection_thread = std::thread::spawn(move || {
19012      selection_tx
19013        .send(selection_registry.current().map(|(id, _)| id))
19014        .unwrap();
19015    });
19016    assert_eq!(
19017      selection_rx
19018        .recv_timeout(Duration::from_secs(10))
19019        .expect("is_live re-entry deadlocked"),
19020      Some(fallback_id)
19021    );
19022    selection_thread.join().unwrap();
19023
19024    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
19025      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
19026        registry: Arc::downgrade(&registry),
19027        fallback: fallback_id,
19028      });
19029    let drop_id = registry.register(Arc::clone(&drop_driver));
19030    drop(drop_driver);
19031
19032    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
19033    let drop_registry = Arc::clone(&registry);
19034    let drop_thread = std::thread::spawn(move || {
19035      drop_registry.unregister(drop_id);
19036      drop_tx.send(()).unwrap();
19037    });
19038    drop_rx
19039      .recv_timeout(Duration::from_secs(10))
19040      .expect("driver destructor re-entry deadlocked");
19041    drop_thread.join().unwrap();
19042    assert!(
19043      registry.current().is_none(),
19044      "the destructor must unregister the fallback"
19045    );
19046  }
19047
19048  #[cfg(not(target_family = "wasm"))]
19049  #[test]
19050  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
19051    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
19052
19053    if std::env::var_os(CHILD_ENV).is_some() {
19054      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19055      let fallback = Arc::new(TestCurrentThreadTaskDriver {
19056        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
19057        dispatched: AtomicBool::new(false),
19058      });
19059      let fallback_id =
19060        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
19061      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
19062        registry: Arc::downgrade(&registry),
19063        registrations: Mutex::new(Vec::new()),
19064      });
19065      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19066
19067      let first_drops = Arc::new(AtomicUsize::new(0));
19068      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19069        drops: Arc::clone(&first_drops),
19070      });
19071      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19072      drop(first);
19073
19074      let second_drops = Arc::new(AtomicUsize::new(0));
19075      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19076        drops: Arc::clone(&second_drops),
19077      });
19078      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19079      drop(second);
19080      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
19081
19082      assert_eq!(
19083        registry.current().map(|(id, _)| id),
19084        Some(fallback_id),
19085        "selection must retry after reentrant removals and preserve the healthy fallback"
19086      );
19087      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19088      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19089      let state = registry
19090        .state
19091        .lock()
19092        .unwrap_or_else(std::sync::PoisonError::into_inner);
19093      assert_eq!(state.entries.len(), 1);
19094      assert!(state.entries.contains_key(&fallback_id));
19095      assert!(state.dispatches.is_empty());
19096      return;
19097    }
19098
19099    let output = std::process::Command::new(std::env::current_exe().unwrap())
19100      .arg("--exact")
19101      .arg(
19102        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
19103      )
19104      .arg("--nocapture")
19105      .env(CHILD_ENV, "1")
19106      .output()
19107      .expect("the hostile task-driver snapshot subprocess test must start");
19108    assert!(
19109      output.status.success(),
19110      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19111      output.status.code(),
19112      String::from_utf8_lossy(&output.stdout),
19113      String::from_utf8_lossy(&output.stderr)
19114    );
19115  }
19116
19117  #[cfg(not(target_family = "wasm"))]
19118  #[test]
19119  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
19120    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
19121
19122    if std::env::var_os(CHILD_ENV).is_some() {
19123      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
19124
19125      let first_drops = Arc::new(AtomicUsize::new(0));
19126      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
19127        drops: Arc::clone(&first_drops),
19128      });
19129      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
19130      drop(first);
19131
19132      let second_drops = Arc::new(AtomicUsize::new(0));
19133      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
19134        drops: Arc::clone(&second_drops),
19135      });
19136      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
19137      drop(second);
19138
19139      let reentrant_drops = Arc::new(AtomicUsize::new(0));
19140      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
19141        registry: Arc::downgrade(&registry),
19142        registrations: Mutex::new(Vec::new()),
19143        drops: Arc::clone(&reentrant_drops),
19144      });
19145      let reentrant_id =
19146        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
19147      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
19148      drop(reentrant);
19149
19150      let dispatch = 59;
19151      let failure = current_thread_dispatch_failure(dispatch, 0);
19152      assert_eq!(
19153        registry.dispatch(dispatch),
19154        CurrentThreadHostDispatchResult::Failed(failure),
19155        "reentrant removal of every provisional host must report accepted delivery failure"
19156      );
19157      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
19158      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
19159      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
19160      registry.finish_cancellation(failure);
19161      let state = registry
19162        .state
19163        .lock()
19164        .unwrap_or_else(std::sync::PoisonError::into_inner);
19165      assert!(state.entries.is_empty());
19166      assert!(state.dispatches.is_empty());
19167      return;
19168    }
19169
19170    let output = std::process::Command::new(std::env::current_exe().unwrap())
19171      .arg("--exact")
19172      .arg(
19173        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
19174      )
19175      .arg("--nocapture")
19176      .env(CHILD_ENV, "1")
19177      .output()
19178      .expect("the hostile task-driver delivery subprocess test must start");
19179    assert!(
19180      output.status.success(),
19181      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
19182      output.status.code(),
19183      String::from_utf8_lossy(&output.stdout),
19184      String::from_utf8_lossy(&output.stderr)
19185    );
19186  }
19187
19188  // Builds the `JoinHandleInner::Task` literal directly, whose `dependency`
19189  // field only exists where the MultiThread machinery is compiled in.
19190  #[cfg(napi_runtime_os_threads)]
19191  #[test]
19192  fn dropping_join_handle_detaches_task_like_tokio() {
19193    let completed = Arc::new(AtomicBool::new(false));
19194    let completed_task = Arc::clone(&completed);
19195    let (runnable, task) = async_task::spawn(
19196      async move {
19197        completed_task.store(true, Ordering::SeqCst);
19198        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
19199      },
19200      |_| {},
19201    );
19202    let handle = JoinHandle(JoinHandleInner::Task {
19203      task: task.fallible(),
19204      awaiter: None,
19205      dependency: Arc::new(TaskDependency::default()),
19206    });
19207
19208    drop(handle);
19209    runnable.run();
19210    assert!(completed.load(Ordering::SeqCst));
19211  }
19212
19213  #[cfg(not(target_family = "wasm"))]
19214  #[test]
19215  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19216    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19217
19218    if std::env::var_os(CHILD_ENV).is_some() {
19219      let wakes = Arc::new(AtomicUsize::new(0));
19220      let drops = Arc::new(AtomicUsize::new(0));
19221      let (runnable, task) = async_task::spawn(
19222        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19223        |_| {},
19224      );
19225      let mut handle = JoinHandle(JoinHandleInner::Task {
19226        task: task.fallible(),
19227        awaiter: None,
19228        dependency: Arc::new(TaskDependency::default()),
19229      });
19230      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19231      {
19232        let mut cx = Context::from_waker(&waker);
19233        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19234      }
19235      drop(waker);
19236
19237      runnable.run();
19238
19239      let waker = futures::task::noop_waker();
19240      let mut cx = Context::from_waker(&waker);
19241      assert!(matches!(
19242        Pin::new(&mut handle).poll(&mut cx),
19243        Poll::Ready(Ok(()))
19244      ));
19245      assert_eq!(
19246        drops.load(Ordering::SeqCst),
19247        1,
19248        "a completed handle must not retain the caller's awaiter"
19249      );
19250      drop(handle);
19251      assert!(wakes.load(Ordering::SeqCst) >= 1);
19252      assert_eq!(drops.load(Ordering::SeqCst), 1);
19253      return;
19254    }
19255
19256    let output = std::process::Command::new(std::env::current_exe().unwrap())
19257      .arg("--exact")
19258      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19259      .arg("--nocapture")
19260      .env(CHILD_ENV, "1")
19261      .output()
19262      .expect("the JoinHandle completion awaiter subprocess must start");
19263    assert!(
19264      output.status.success(),
19265      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19266      output.status.code(),
19267      String::from_utf8_lossy(&output.stdout),
19268      String::from_utf8_lossy(&output.stderr)
19269    );
19270  }
19271
19272  #[cfg(not(target_family = "wasm"))]
19273  #[test]
19274  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19275    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19276
19277    if std::env::var_os(CHILD_ENV).is_some() {
19278      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19279      let wakes = Arc::new(AtomicUsize::new(0));
19280      let drops = Arc::new(AtomicUsize::new(0));
19281      let mut handle = controller.spawn(std::future::pending::<()>());
19282      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19283      {
19284        let mut cx = Context::from_waker(&waker);
19285        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19286      }
19287      drop(waker);
19288
19289      controller
19290        .shutdown()
19291        .expect("shutdown must survive the hostile awaiter");
19292      drop(handle);
19293      assert!(wakes.load(Ordering::SeqCst) >= 1);
19294      assert_eq!(drops.load(Ordering::SeqCst), 1);
19295      return;
19296    }
19297
19298    let output = std::process::Command::new(std::env::current_exe().unwrap())
19299      .arg("--exact")
19300      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19301      .arg("--nocapture")
19302      .env(CHILD_ENV, "1")
19303      .output()
19304      .expect("the JoinHandle shutdown awaiter subprocess must start");
19305    assert!(
19306      output.status.success(),
19307      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19308      output.status.code(),
19309      String::from_utf8_lossy(&output.stdout),
19310      String::from_utf8_lossy(&output.stderr)
19311    );
19312  }
19313
19314  #[cfg(napi_runtime_os_threads)]
19315  #[test]
19316  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19317    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19318    let mut handle = controller.spawn(std::future::pending::<()>());
19319    let old_generation = controller.backend().generation();
19320    let state = Arc::new(JoinAwaiterWakerState {
19321      events: Mutex::new(Vec::new()),
19322    });
19323    let waker = {
19324      let _generation = RuntimeGenerationGuard::enter(old_generation);
19325      join_awaiter_waker(Arc::clone(&state))
19326    };
19327    {
19328      let _generation = RuntimeGenerationGuard::enter(old_generation);
19329      let mut cx = Context::from_waker(&waker);
19330      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19331    }
19332    {
19333      let _generation = RuntimeGenerationGuard::enter(old_generation);
19334      drop(waker);
19335    }
19336
19337    controller
19338      .shutdown()
19339      .expect("the first generation must stop");
19340    controller.start().expect("the runtime must restart");
19341    let new_generation = controller.backend().generation();
19342    assert_ne!(new_generation, old_generation);
19343    state.clear();
19344
19345    let replacement = futures::task::noop_waker();
19346    {
19347      let _generation = RuntimeGenerationGuard::enter(new_generation);
19348      let mut cx = Context::from_waker(&replacement);
19349      assert!(matches!(
19350        Pin::new(&mut handle).poll(&mut cx),
19351        Poll::Ready(Err(_))
19352      ));
19353    }
19354
19355    let events = state.snapshot();
19356    assert!(
19357      !events.is_empty(),
19358      "completion must retire the registered awaiter waker"
19359    );
19360    assert!(
19361      events.iter().all(|(kind, generation)| {
19362        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19363      }),
19364      "old-generation awaiter retirement escaped its generation: {events:?}"
19365    );
19366    controller
19367      .shutdown()
19368      .expect("the replacement generation must stop");
19369  }
19370
19371  #[cfg(napi_runtime_os_threads)]
19372  #[test]
19373  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19374    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19375    let mut handle = controller.spawn(std::future::pending::<()>());
19376    let old_generation = controller.backend().generation();
19377    let state = Arc::new(JoinAwaiterWakerState {
19378      events: Mutex::new(Vec::new()),
19379    });
19380    let waker = {
19381      let _generation = RuntimeGenerationGuard::enter(old_generation);
19382      join_awaiter_waker(Arc::clone(&state))
19383    };
19384    {
19385      let _generation = RuntimeGenerationGuard::enter(old_generation);
19386      let mut cx = Context::from_waker(&waker);
19387      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19388    }
19389    {
19390      let _generation = RuntimeGenerationGuard::enter(old_generation);
19391      drop(waker);
19392    }
19393
19394    controller
19395      .shutdown()
19396      .expect("the first generation must stop");
19397    controller.start().expect("the runtime must restart");
19398    let new_generation = controller.backend().generation();
19399    assert_ne!(new_generation, old_generation);
19400    state.clear();
19401
19402    {
19403      let _generation = RuntimeGenerationGuard::enter(new_generation);
19404      drop(handle);
19405    }
19406
19407    let events = state.snapshot();
19408    assert!(
19409      !events.is_empty(),
19410      "handle drop must retire the registered awaiter waker"
19411    );
19412    assert!(
19413      events.iter().all(|(kind, generation)| {
19414        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19415      }),
19416      "old-generation awaiter destruction escaped its generation: {events:?}"
19417    );
19418    controller
19419      .shutdown()
19420      .expect("the replacement generation must stop");
19421  }
19422
19423  #[cfg(napi_runtime_os_threads)]
19424  #[test]
19425  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19426    use std::{sync::mpsc, time::Duration};
19427
19428    let options = RuntimeOptions {
19429      flavor: RuntimeFlavor::MultiThread,
19430      worker_threads: 2,
19431      max_blocking_tasks: 1,
19432      thread_name_prefix: "worker-classification".to_string(),
19433      park_deadline: None,
19434      drain_linger: DEFAULT_DRAIN_LINGER,
19435    };
19436    let executor =
19437      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19438    let id = executor.id;
19439    let (tx, rx) = mpsc::sync_channel(1);
19440
19441    executor.pool.spawn(move || {
19442      tx.send((
19443        ON_POOL_WORKER.with(std::cell::Cell::get),
19444        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19445      ))
19446      .unwrap();
19447    });
19448
19449    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19450    assert_eq!(
19451      worker,
19452      Some(id),
19453      "nested Rayon work must use cooperative block_on"
19454    );
19455    assert_eq!(
19456      driver, None,
19457      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19458    );
19459  }
19460
19461  #[cfg(napi_runtime_os_threads)]
19462  #[test]
19463  fn multi_thread_block_on_does_not_park_pool_worker() {
19464    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
19465    // future depending on pool-scheduled blocking work must not park its worker.
19466    // If every worker parks this way, no worker remains to pick up the blocking
19467    // jobs they await.
19468    use std::sync::{Barrier, mpsc};
19469    use std::time::Duration;
19470
19471    let (done_tx, done_rx) = mpsc::channel();
19472    let runner = std::thread::spawn(move || {
19473      let metrics = Arc::new(RuntimeMetrics::default());
19474      let options = RuntimeOptions {
19475        flavor: RuntimeFlavor::MultiThread,
19476        worker_threads: 2,
19477        max_blocking_tasks: 2,
19478        thread_name_prefix: "rd1-test".to_string(),
19479        park_deadline: None,
19480        drain_linger: DEFAULT_DRAIN_LINGER,
19481      };
19482      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19483
19484      let task_count = 2usize;
19485      // Guarantees every task is being polled on its own pool worker *before*
19486      // any of them parks in `block_on`, so the deadlock is deterministic.
19487      let barrier = Arc::new(Barrier::new(task_count));
19488
19489      let mut tasks = Vec::new();
19490      for _ in 0..task_count {
19491        let exec = Arc::clone(&executor);
19492        let barrier = Arc::clone(&barrier);
19493        let body = async move {
19494          barrier.wait();
19495          let inner_exec = Arc::clone(&exec);
19496          let mut inner = std::pin::pin!(async move {
19497            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19498            assert_eq!(value, 7);
19499          });
19500          // Synchronous `block_on` from inside a pool-worker task: the vector.
19501          exec.block_on(inner.as_mut());
19502        };
19503        let scheduler = Arc::clone(&executor);
19504        let (runnable, task) =
19505          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19506        executor.schedule(runnable);
19507        tasks.push(task);
19508      }
19509
19510      for task in tasks {
19511        futures::executor::block_on(task);
19512      }
19513      let _ = done_tx.send(());
19514    });
19515
19516    match done_rx.recv_timeout(Duration::from_secs(10)) {
19517      Ok(()) => runner.join().unwrap(),
19518      Err(error) => panic!(
19519        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19520      ),
19521    }
19522  }
19523
19524  #[cfg(napi_runtime_os_threads)]
19525  #[test]
19526  fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19527    // Idle-linger residence bound (adversarial rayon-starvation shape).
19528    //
19529    // W=2 pool. Worker B runs a blocking closure that spawns a raw rayon job
19530    // R onto the SAME pool (it lands in worker B's rayon deque) and then
19531    // cooperatively block_on's R's completion -- so worker B never returns to
19532    // rayon's scheduler. Worker A's drain frame runs the queues empty and
19533    // lingers on its own parker. A producer then publishes one trivial
19534    // runnable every ~200us (< the 500us idle budget), stopping only after R
19535    // ran. Every wake pops the MOST recently registered parker -- the
19536    // lingering drainer, which re-registers on top after each task -- and
19537    // every executed task clears the idle deadline, so without a bound on the
19538    // frame's TOTAL lingering residence worker A never exits its drain frame,
19539    // never returns its thread to rayon, and R (which only an idle rayon
19540    // worker can steal) starves indefinitely: producer never stops, worker B
19541    // never unparks. The frame residence bound must force the drainer out
19542    // through `finish_draining` (exit-and-respawn through rayon's queue is
19543    // exactly the interleaving point that lets R run), well inside the
19544    // watchdog below.
19545    use std::sync::mpsc;
19546
19547    for round in 0..3 {
19548      let metrics = Arc::new(RuntimeMetrics::default());
19549      let options = RuntimeOptions {
19550        flavor: RuntimeFlavor::MultiThread,
19551        worker_threads: 2,
19552        max_blocking_tasks: 1,
19553        thread_name_prefix: format!("linger-starve-{round}"),
19554        park_deadline: None,
19555        drain_linger: DEFAULT_DRAIN_LINGER,
19556      };
19557      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19558
19559      let r_done = Arc::new(AtomicBool::new(false));
19560      let give_up = Arc::new(AtomicBool::new(false));
19561      let (done_tx, done_rx) = mpsc::channel::<Duration>();
19562
19563      // T1 pins worker A until the cooperative driver (worker B, inside the
19564      // blocking closure's re-entrant block_on) has parked. Worker A's drain
19565      // frame then observes empty queues and starts lingering AFTER the
19566      // cooperative driver registered, making the lingering drainer the
19567      // most-recently-registered (i.e. wake-preferred) parker.
19568      let t1_executor = Arc::clone(&executor);
19569      let t1_started = Arc::new(AtomicBool::new(false));
19570      let t1_started_setter = Arc::clone(&t1_started);
19571      let t1_body = async move {
19572        t1_started_setter.store(true, Ordering::SeqCst);
19573        let deadline = Instant::now() + Duration::from_secs(10);
19574        while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19575          if Instant::now() >= deadline {
19576            // Give the worker back; the watchdog below reports the failure.
19577            return;
19578          }
19579          std::hint::spin_loop();
19580        }
19581      };
19582      let t1_scheduler = Arc::clone(&executor);
19583      let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19584      executor.schedule(t1_runnable);
19585      wait_until("the worker-pinning task to start", || {
19586        t1_started.load(Ordering::SeqCst)
19587      });
19588
19589      // The blocking closure on worker B: spawn R, then block_on its result.
19590      let blocking_executor = Arc::clone(&executor);
19591      let r_done_setter = Arc::clone(&r_done);
19592      let started = Instant::now();
19593      let handle = executor.schedule_blocking(move || {
19594        let (r_tx, r_rx) = oneshot::channel::<()>();
19595        blocking_executor.pool.spawn(move || {
19596          r_done_setter.store(true, Ordering::SeqCst);
19597          let _ = r_tx.send(());
19598        });
19599        let mut wait_for_r = std::pin::pin!(async move {
19600          let _ = r_rx.await;
19601        });
19602        blocking_executor.block_on(wait_for_r.as_mut());
19603        let _ = done_tx.send(started.elapsed());
19604      });
19605
19606      // Wait until both the cooperative driver and the lingering drainer are
19607      // registered; if the linger expired first (R already ran), that round
19608      // completed without ever reaching the adversarial interleaving.
19609      let arm_deadline = Instant::now() + Duration::from_secs(15);
19610      while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19611        && !r_done.load(Ordering::SeqCst)
19612      {
19613        assert!(
19614          Instant::now() < arm_deadline,
19615          "timed out arming the linger-starvation interleaving"
19616        );
19617        std::thread::yield_now();
19618      }
19619
19620      // Producer: one trivial runnable every ~200us (spin-paced, so cadence
19621      // stays far below the 500us idle budget), until R completed.
19622      let producer_executor = Arc::clone(&executor);
19623      let producer_r_done = Arc::clone(&r_done);
19624      let producer_give_up = Arc::clone(&give_up);
19625      let producer = std::thread::spawn(move || {
19626        let cadence = Duration::from_micros(200);
19627        let mut next_tick = Instant::now();
19628        while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19629          let scheduler = Arc::clone(&producer_executor);
19630          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19631          producer_executor.schedule(runnable);
19632          task.detach();
19633          next_tick += cadence;
19634          while Instant::now() < next_tick {
19635            std::hint::spin_loop();
19636          }
19637        }
19638      });
19639
19640      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19641      give_up.store(true, Ordering::SeqCst);
19642      producer.join().unwrap();
19643      match outcome {
19644        Ok(elapsed) => {
19645          futures::executor::block_on(handle).expect("blocking closure join");
19646          drop(t1_task);
19647          eprintln!("round {round}: completed in {elapsed:?}");
19648        }
19649        Err(error) => panic!(
19650          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19651           sub-budget periodic work must not keep a drain frame resident forever"
19652        ),
19653      }
19654    }
19655  }
19656
19657  #[cfg(napi_runtime_os_threads)]
19658  #[test]
19659  #[ignore = "microbenchmark: run explicitly in release mode"]
19660  fn parked_drivers_registry_cycle_microbench() {
19661    // Isolates the registry traffic of one steady-state linger cycle at
19662    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
19663    // drainer deregisters (the entry is already gone, so the position scan
19664    // walks the WHOLE vector and fails), then re-registers (push). Also
19665    // measures the timeout-path deregister (entry present) and a
19666    // wake_one_owner_dependency sweep over dependency-less lingerers.
19667    for &n in &[8usize, 64, 256] {
19668      let registry = ParkedDrivers::default();
19669      let parkers: Vec<Arc<DriverParker>> =
19670        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19671      for parker in &parkers {
19672        registry.register_with_role(parker, true, None);
19673      }
19674      let hot = &parkers[n - 1];
19675
19676      let iters = 2_000_000u32;
19677      let start = Instant::now();
19678      for _ in 0..iters {
19679        assert!(registry.wake_one());
19680        registry.deregister(hot);
19681        assert!(hot.consume_permit());
19682        registry.register_with_role(hot, true, None);
19683      }
19684      let wake_cycle = start.elapsed() / iters;
19685
19686      let start = Instant::now();
19687      for _ in 0..iters {
19688        registry.deregister(hot);
19689        registry.register_with_role(hot, true, None);
19690      }
19691      let timeout_cycle = start.elapsed() / iters;
19692
19693      let owner = BlockingOwnerToken {
19694        executor_id: u64::MAX,
19695        frame: u64::MAX,
19696      };
19697      let start = Instant::now();
19698      for _ in 0..iters {
19699        assert!(!registry.wake_one_owner_dependency(owner));
19700      }
19701      let owner_sweep = start.elapsed() / iters;
19702
19703      eprintln!(
19704        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19705         timeout-deregister+re-register {timeout_cycle:?}, \
19706         owner-dependency sweep {owner_sweep:?}"
19707      );
19708    }
19709  }
19710
19711  #[cfg(napi_runtime_os_threads)]
19712  #[test]
19713  fn multi_thread_block_on_from_caller_thread_still_parks() {
19714    // The non-pool (napi caller) path must keep using the plain parking
19715    // `block_on`; here we just assert it drives a ready future to completion.
19716    let metrics = Arc::new(RuntimeMetrics::default());
19717    let options = RuntimeOptions {
19718      flavor: RuntimeFlavor::MultiThread,
19719      worker_threads: 3,
19720      max_blocking_tasks: 2,
19721      thread_name_prefix: "rd1-caller".to_string(),
19722      park_deadline: None,
19723      drain_linger: DEFAULT_DRAIN_LINGER,
19724    };
19725    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19726
19727    let mut output = None;
19728    let mut future = std::pin::pin!(async {
19729      output = Some(41usize + 1);
19730    });
19731    executor.block_on(future.as_mut());
19732    assert_eq!(output, Some(42));
19733  }
19734
19735  #[cfg(napi_runtime_os_threads)]
19736  #[test]
19737  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19738    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
19739    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
19740    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
19741    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
19742    // This pins ONLY the cross-thread wake path: the pool task completing must wake
19743    // the parked caller's waker so `block_on` returns the pool-computed value.
19744    //
19745    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
19746    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
19747    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
19748    // parking waker is registered on the task), signals "caller parked" to a releaser
19749    // thread. The releaser then -- and only then -- releases the gate, so the pool
19750    // task completes strictly AFTER the caller registered its waker. This forecloses
19751    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
19752    // caller's first poll, because its gate is opened only in response to that first
19753    // `Pending` poll. The completion is thus forced to travel the real cross-thread
19754    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
19755    // if that wake is broken the caller parks forever and the bounded recv_timeout
19756    // below fails loudly instead of hanging the suite.
19757    //
19758    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
19759    // caller-is-pool-worker case, which has dedicated reentrancy tests.
19760    use std::sync::mpsc;
19761    use std::time::Duration;
19762
19763    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
19764    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
19765    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
19766    // is already registered before the gate can open.
19767    struct CallerProbe {
19768      task: Task<usize>,
19769      parked_tx: Option<mpsc::Sender<()>>,
19770    }
19771    impl Future for CallerProbe {
19772      type Output = usize;
19773      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19774        // `Task` is `Unpin`; poll it through a fresh `Pin`.
19775        let poll = Pin::new(&mut self.task).poll(cx);
19776        if poll.is_pending()
19777          && let Some(tx) = self.parked_tx.take()
19778        {
19779          // Waker is now registered on `task`; release the gate is safe.
19780          let _ = tx.send(());
19781        }
19782        poll
19783      }
19784    }
19785
19786    let (done_tx, done_rx) = mpsc::channel();
19787    let runner = std::thread::spawn(move || {
19788      let metrics = Arc::new(RuntimeMetrics::default());
19789      let options = RuntimeOptions {
19790        flavor: RuntimeFlavor::MultiThread,
19791        worker_threads: 2,
19792        max_blocking_tasks: 2,
19793        thread_name_prefix: "rd10-caller".to_string(),
19794        park_deadline: None,
19795        drain_linger: DEFAULT_DRAIN_LINGER,
19796      };
19797      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19798
19799      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
19800      // worker registers the gate waker and returns `Pending`. When the releaser sends
19801      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
19802      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
19803      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19804      let scheduler = Arc::clone(&executor);
19805      let (runnable, task) = async_task::spawn(
19806        async move { gate_rx.await.expect("gate sender dropped") },
19807        move |r| {
19808          scheduler.schedule(r);
19809        },
19810      );
19811      executor.schedule(runnable);
19812
19813      // Releaser thread: opens the gate ONLY after the caller has parked (registered
19814      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
19815      // completes, so completion must travel the cross-thread wake path.
19816      let (parked_tx, parked_rx) = mpsc::channel::<()>();
19817      let releaser = std::thread::spawn(move || {
19818        if parked_rx.recv().is_ok() {
19819          let _ = gate_tx.send(42usize);
19820        }
19821      });
19822
19823      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
19824      // `futures::executor::block_on` rather than driving the queue cooperatively.
19825      let mut output = None;
19826      {
19827        let mut future = std::pin::pin!(async {
19828          output = Some(
19829            CallerProbe {
19830              task,
19831              parked_tx: Some(parked_tx),
19832            }
19833            .await,
19834          );
19835        });
19836        executor.block_on(future.as_mut());
19837      }
19838
19839      releaser.join().unwrap();
19840      assert_eq!(
19841        output,
19842        Some(42usize),
19843        "block_on must return the pool-computed value"
19844      );
19845      let _ = done_tx.send(());
19846    });
19847
19848    match done_rx.recv_timeout(Duration::from_secs(10)) {
19849      Ok(()) => runner.join().unwrap(),
19850      Err(error) => panic!(
19851        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19852      ),
19853    }
19854  }
19855
19856  #[cfg(napi_runtime_os_threads)]
19857  #[test]
19858  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19859    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
19860    // before the work that will wake it is scheduled. Once every pool worker
19861    // is parked this way, `ensure_drainer` cannot spawn a replacement because
19862    // the parked workers are still counted as active drainers. A later
19863    // `schedule()` must wake a parked driver directly so the runnable can run.
19864    use std::sync::{Barrier, mpsc};
19865    use std::time::Duration;
19866
19867    let (done_tx, done_rx) = mpsc::channel();
19868    let runner = std::thread::spawn(move || {
19869      let metrics = Arc::new(RuntimeMetrics::default());
19870      let options = RuntimeOptions {
19871        flavor: RuntimeFlavor::MultiThread,
19872        worker_threads: 2,
19873        max_blocking_tasks: 2,
19874        thread_name_prefix: "rd1-park".to_string(),
19875        park_deadline: None,
19876        drain_linger: DEFAULT_DRAIN_LINGER,
19877      };
19878      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19879
19880      let task_count = 2usize;
19881      let barrier = Arc::new(Barrier::new(task_count));
19882
19883      let mut senders = Vec::new();
19884      let mut tasks = Vec::new();
19885      for _ in 0..task_count {
19886        let (tx, rx) = oneshot::channel::<usize>();
19887        senders.push(tx);
19888        let exec = Arc::clone(&executor);
19889        let barrier = Arc::clone(&barrier);
19890        let body = async move {
19891          // Make sure both pool workers are occupied before either parks.
19892          barrier.wait();
19893          // Child task whose wake routes through `schedule()`: the oneshot
19894          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
19895          let child_exec = Arc::clone(&exec);
19896          let (child_runnable, child_task) =
19897            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19898              child_exec.schedule(r)
19899            });
19900          exec.schedule(child_runnable);
19901          let mut inner = std::pin::pin!(async move {
19902            assert_eq!(child_task.await, 9usize);
19903          });
19904          exec.block_on(inner.as_mut());
19905        };
19906        let scheduler = Arc::clone(&executor);
19907        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19908        executor.schedule(runnable);
19909        tasks.push(task);
19910      }
19911
19912      // Give both workers time to poll their child Pending and park in block_on.
19913      std::thread::sleep(Duration::from_millis(200));
19914      for tx in senders {
19915        let _ = tx.send(9usize);
19916      }
19917
19918      for task in tasks {
19919        futures::executor::block_on(task);
19920      }
19921      let _ = done_tx.send(());
19922    });
19923
19924    match done_rx.recv_timeout(Duration::from_secs(10)) {
19925      Ok(()) => runner.join().unwrap(),
19926      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19927    }
19928  }
19929
19930  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
19931  /// Used to observe executor-internal states (e.g. "both drivers are
19932  /// registered as parked") without sleeps. The bound is a HANG detector,
19933  /// not a latency assertion: starved CI runners can stall fresh threads for
19934  /// hundreds of milliseconds, so it is deliberately generous.
19935  #[cfg(napi_runtime_os_threads)]
19936  fn wait_until(what: &str, condition: impl Fn() -> bool) {
19937    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19938    while !condition() {
19939      assert!(
19940        std::time::Instant::now() < deadline,
19941        "timed out waiting for: {what}"
19942      );
19943      std::thread::yield_now();
19944    }
19945  }
19946
19947  /// Bounded join for harness threads that already signalled completion of
19948  /// their assertions: everything left on the thread is teardown (executor
19949  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
19950  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
19951  /// into a silent libtest-worker hang for the full CI step timeout (observed
19952  /// on Windows in run 29195160953: several park-deadline-family tests never
19953  /// completed while every in-test assertion had already passed). Fail loudly
19954  /// and NAME the awaited phase instead.
19955  #[cfg(napi_runtime_os_threads)]
19956  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19957    let deadline = std::time::Instant::now() + timeout;
19958    while !handle.is_finished() {
19959      assert!(
19960        std::time::Instant::now() < deadline,
19961        "thread wedged past its {timeout:?} bound while: {what}"
19962      );
19963      std::thread::sleep(Duration::from_millis(10));
19964    }
19965    handle.join().unwrap();
19966  }
19967
19968  #[cfg(napi_runtime_os_threads)]
19969  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19970    executor: &Arc<MultiThreadExecutor>,
19971    start_counter: &AtomicU64,
19972    claim: impl FnOnce() -> T + Send + 'static,
19973  ) -> T {
19974    use std::sync::mpsc;
19975
19976    let (claimed_tx, claimed_rx) = mpsc::channel();
19977    let (release_claim_tx, release_claim_rx) = mpsc::channel();
19978    let (started_tx, started_rx) = mpsc::channel();
19979    let (release_start_tx, release_start_rx) = mpsc::channel();
19980    let starts_before = start_counter.load(Ordering::SeqCst);
19981    let worker = std::thread::spawn(move || {
19982      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19983        *slot.borrow_mut() = Some(Box::new(move || {
19984          claimed_tx.send(()).unwrap();
19985          release_claim_rx.recv().unwrap();
19986        }));
19987      });
19988      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19989        *slot.borrow_mut() = Some(Box::new(move || {
19990          started_tx.send(()).unwrap();
19991          release_start_rx.recv().unwrap();
19992        }));
19993      });
19994      claim()
19995    });
19996    claimed_rx
19997      .recv_timeout(Duration::from_secs(2))
19998      .expect("the scheduler claim must pause after removing work");
19999
20000    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20001    let verdict = executor.try_begin_deadlock_verdict();
20002    let claim_verdict_rejected = verdict.is_none();
20003    drop(verdict);
20004    release_claim_tx.send(()).unwrap();
20005
20006    started_rx
20007      .recv_timeout(Duration::from_secs(2))
20008      .expect("the scheduler claim must pause after publishing its start");
20009    let admission_active_after_start =
20010      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
20011    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
20012    let verdict = executor.try_begin_deadlock_verdict();
20013    let start_verdict_rejected = verdict.is_none();
20014    drop(verdict);
20015    release_start_tx.send(()).unwrap();
20016    let output = worker.join().unwrap();
20017
20018    assert!(
20019      admission_active,
20020      "removing scheduler work must retain a detector admission until start accounting"
20021    );
20022    assert!(
20023      claim_verdict_rejected,
20024      "a deadline verdict must not close while removed work has not published its start"
20025    );
20026    assert!(
20027      start_published,
20028      "work start accounting must publish before admission retires"
20029    );
20030    assert!(
20031      admission_active_after_start,
20032      "the detector admission must remain active until after start accounting"
20033    );
20034    assert!(
20035      start_verdict_rejected,
20036      "a deadline verdict must not close between start accounting and admission retirement"
20037    );
20038    output
20039  }
20040
20041  #[cfg(napi_runtime_os_threads)]
20042  #[test]
20043  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
20044    let executor =
20045      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
20046
20047    let fifo_ran = Arc::new(AtomicBool::new(false));
20048    let fifo_task_ran = Arc::clone(&fifo_ran);
20049    let (fifo, fifo_task) = async_task::spawn(
20050      async move {
20051        fifo_task_ran.store(true, Ordering::SeqCst);
20052      },
20053      |_| {},
20054    );
20055    executor.metrics.runnable_scheduled();
20056    executor.push_fifo_raw(fifo);
20057    let fifo_executor = Arc::clone(&executor);
20058    assert!(run_claim_with_deadlock_gate_assertion(
20059      &executor,
20060      &executor.metrics.runnable_polls,
20061      move || {
20062        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20063        fifo_executor.run_one_fifo_runnable()
20064      }
20065    ));
20066    futures::executor::block_on(fifo_task);
20067    assert!(fifo_ran.load(Ordering::SeqCst));
20068
20069    let fifo_order = Arc::new(Mutex::new(Vec::new()));
20070    let mut fifo_tasks = Vec::new();
20071    for name in ["first", "second"] {
20072      let task_order = Arc::clone(&fifo_order);
20073      let (runnable, task) = async_task::spawn(
20074        async move {
20075          task_order.lock().unwrap().push(name);
20076        },
20077        |_| {},
20078      );
20079      executor.metrics.runnable_scheduled();
20080      executor.push_fifo_raw(runnable);
20081      fifo_tasks.push(task);
20082    }
20083    for _ in 0..2 {
20084      let fifo_executor = Arc::clone(&executor);
20085      assert!(run_claim_with_deadlock_gate_assertion(
20086        &executor,
20087        &executor.metrics.runnable_polls,
20088        move || {
20089          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20090          fifo_executor.run_one_runnable()
20091        }
20092      ));
20093    }
20094    for task in fifo_tasks {
20095      futures::executor::block_on(task);
20096    }
20097    assert_eq!(
20098      *fifo_order.lock().unwrap(),
20099      ["first", "second"],
20100      "the detector-enabled normal fallback must preserve FIFO order"
20101    );
20102
20103    let lifo_ran = Arc::new(AtomicBool::new(false));
20104    let lifo_task_ran = Arc::clone(&lifo_ran);
20105    let (lifo, lifo_task) = async_task::spawn(
20106      async move {
20107        lifo_task_ran.store(true, Ordering::SeqCst);
20108      },
20109      |_| {},
20110    );
20111    executor.metrics.runnable_scheduled();
20112    let lifo_executor = Arc::clone(&executor);
20113    assert!(run_claim_with_deadlock_gate_assertion(
20114      &executor,
20115      &executor.metrics.runnable_polls,
20116      move || {
20117        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
20118        LIFO_SLOT.with(|slot| {
20119          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
20120        });
20121        lifo_executor.run_one_runnable()
20122      }
20123    ));
20124    futures::executor::block_on(lifo_task);
20125    assert!(lifo_ran.load(Ordering::SeqCst));
20126
20127    executor.begin_shutdown();
20128    executor.wait_until_scheduler_idle();
20129  }
20130
20131  #[cfg(napi_runtime_os_threads)]
20132  #[test]
20133  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
20134    let executor =
20135      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
20136
20137    let fifo_ran = Arc::new(AtomicBool::new(false));
20138    let fifo_job_ran = Arc::clone(&fifo_ran);
20139    executor.metrics.blocking_scheduled();
20140    executor
20141      .blocking_queue
20142      .lock()
20143      .unwrap()
20144      .push(QueuedBlocking {
20145        id: BlockingJobId(u64::MAX - 90),
20146        run: Box::new(move || {
20147          fifo_job_ran.store(true, Ordering::SeqCst);
20148        }),
20149      });
20150    let fifo_executor = Arc::clone(&executor);
20151    assert!(run_claim_with_deadlock_gate_assertion(
20152      &executor,
20153      &executor.metrics.blocking_tasks_started,
20154      move || {
20155        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
20156        fifo_executor.run_one_blocking()
20157      }
20158    ));
20159    assert!(fifo_ran.load(Ordering::SeqCst));
20160
20161    let owner = executor.fresh_owner_token();
20162    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
20163    let exact_job = BlockingJobId(u64::MAX - 91);
20164    let exact_ran = Arc::new(AtomicBool::new(false));
20165    let exact_job_ran = Arc::clone(&exact_ran);
20166    executor.metrics.blocking_scheduled();
20167    executor
20168      .blocking_queue
20169      .lock()
20170      .unwrap()
20171      .push(QueuedBlocking {
20172        id: exact_job,
20173        run: Box::new(move || {
20174          exact_job_ran.store(true, Ordering::SeqCst);
20175        }),
20176      });
20177    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
20178    dependency.set_owned(BlockingDependency {
20179      job: exact_job,
20180      owner: Some(owner),
20181    });
20182    let exact_executor = Arc::clone(&executor);
20183    assert!(run_claim_with_deadlock_gate_assertion(
20184      &executor,
20185      &executor.metrics.blocking_tasks_started,
20186      move || {
20187        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
20188        let _owner = BlockingOwnerGuard::enter(Some(owner));
20189        exact_executor.try_owned_blocking_over_cap(&dependency)
20190      }
20191    ));
20192    assert!(exact_ran.load(Ordering::SeqCst));
20193
20194    drop(active_owner);
20195    executor.begin_shutdown();
20196    executor.wait_until_scheduler_idle();
20197  }
20198
20199  #[cfg(napi_runtime_os_threads)]
20200  #[test]
20201  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
20202    // Empty-registry behavior: with nothing registered, `wake_one` must return
20203    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
20204    // the common case, since queue-wakes fire on every schedule while drivers
20205    // park only in rare re-entrant `block_on`). Proxy for "did not take the
20206    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
20207    // anyway, while (control) a wake_one issued with a parker registered must
20208    // take the slow path and block on that held lock until it is released.
20209    use std::sync::mpsc;
20210    use std::time::Duration;
20211
20212    let registry = Arc::new(ParkedDrivers::default());
20213
20214    // Fast path: empty registry -> wake_one completes although the lock is held.
20215    let guard = registry.parked.lock().unwrap();
20216    let (done_tx, done_rx) = mpsc::channel();
20217    let waker_thread = {
20218      let registry = Arc::clone(&registry);
20219      std::thread::spawn(move || {
20220        done_tx.send(registry.wake_one()).unwrap();
20221      })
20222    };
20223    let woke = done_rx
20224      .recv_timeout(Duration::from_secs(5))
20225      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20226    assert!(!woke, "an empty registry has nobody to wake");
20227    waker_thread.join().unwrap();
20228    drop(guard);
20229
20230    // Control: with a parker registered the SAME held lock must block
20231    // wake_one (slow path). This proves the fast path above genuinely skipped
20232    // the lock rather than wake_one never locking at all.
20233    let parker = Arc::new(DriverParker::default());
20234    registry.register(&parker);
20235    let guard = registry.parked.lock().unwrap();
20236    let (done_tx, done_rx) = mpsc::channel();
20237    let waker_thread = {
20238      let registry = Arc::clone(&registry);
20239      std::thread::spawn(move || {
20240        done_tx.send(registry.wake_one()).unwrap();
20241      })
20242    };
20243    assert!(
20244      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20245      "wake_one with a registered parker must take the slow path (block on the held lock)"
20246    );
20247    drop(guard);
20248    let woke = done_rx
20249      .recv_timeout(Duration::from_secs(5))
20250      .expect("slow-path wake_one must complete once the lock is released");
20251    assert!(
20252      woke,
20253      "the slow path must pop and wake the registered parker"
20254    );
20255    waker_thread.join().unwrap();
20256    // The popped parker received its permit: a park now returns immediately
20257    // instead of sleeping (bounded by the test harness if broken).
20258    parker.park();
20259    assert_eq!(
20260      registry.count.load(Ordering::SeqCst),
20261      0,
20262      "wake_one deregisters what it pops"
20263    );
20264  }
20265
20266  #[cfg(napi_runtime_os_threads)]
20267  #[test]
20268  fn driver_parker_permit_granted_while_running_is_not_lost() {
20269    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
20270    // parked) must be stored as a permit that makes the next `park` return
20271    // immediately; multiple such wakes coalesce into ONE permit.
20272    use std::sync::mpsc;
20273    use std::time::Duration;
20274
20275    let parker = Arc::new(DriverParker::default());
20276    // Two wakes while "running" (nobody parked yet).
20277    parker.unpark();
20278    parker.unpark();
20279
20280    let (tx, rx) = mpsc::channel();
20281    let driver = {
20282      let parker = Arc::clone(&parker);
20283      std::thread::spawn(move || {
20284        parker.park(); // must consume the stored permit without sleeping
20285        tx.send(1).unwrap();
20286        parker.park(); // no permit left: must genuinely sleep
20287        tx.send(2).unwrap();
20288      })
20289    };
20290
20291    assert_eq!(
20292      rx.recv_timeout(Duration::from_secs(5))
20293        .expect("a stored permit must not be lost"),
20294      1
20295    );
20296    assert!(
20297      rx.recv_timeout(Duration::from_millis(300)).is_err(),
20298      "two pre-park wakes must coalesce into one permit (second park must sleep)"
20299    );
20300    parker.unpark();
20301    assert_eq!(
20302      rx.recv_timeout(Duration::from_secs(5))
20303        .expect("unpark must wake a sleeping driver"),
20304      2
20305    );
20306    driver.join().unwrap();
20307  }
20308
20309  #[cfg(napi_runtime_os_threads)]
20310  #[test]
20311  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20312    use std::sync::mpsc;
20313
20314    let parker = Arc::new(DriverParker::default());
20315    assert!(
20316      Instant::now().checked_add(Duration::MAX).is_none(),
20317      "Duration::MAX must exercise the unrepresentable-deadline path"
20318    );
20319    let (done_tx, done_rx) = mpsc::channel();
20320    let driver = {
20321      let parker = Arc::clone(&parker);
20322      std::thread::spawn(move || {
20323        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20324      })
20325    };
20326    wait_until("the unbounded timeout driver to sleep", || {
20327      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20328    });
20329    assert!(
20330      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20331      "an unrepresentable timeout must not expire immediately"
20332    );
20333
20334    let unparker = {
20335      let parker = Arc::clone(&parker);
20336      std::thread::spawn(move || parker.unpark())
20337    };
20338    assert!(
20339      done_rx
20340        .recv_timeout(Duration::from_secs(1))
20341        .expect("an unrepresentable timeout must remain wakeable"),
20342      "a permit must be reported as a wake"
20343    );
20344    unparker.join().unwrap();
20345    driver.join().unwrap();
20346  }
20347
20348  #[cfg(napi_runtime_os_threads)]
20349  #[test]
20350  fn parked_drivers_wake_one_wakes_exactly_one() {
20351    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
20352    // other stays parked. Targeted wakes must neither wake everyone nor lose
20353    // the wake.
20354    use std::sync::mpsc;
20355    use std::time::Duration;
20356
20357    let registry = Arc::new(ParkedDrivers::default());
20358    let (woke_tx, woke_rx) = mpsc::channel();
20359    let mut drivers = Vec::new();
20360    for index in 0..2 {
20361      let registry = Arc::clone(&registry);
20362      let woke_tx = woke_tx.clone();
20363      drivers.push(std::thread::spawn(move || {
20364        let parker = Arc::new(DriverParker::default());
20365        registry.register(&parker);
20366        parker.park();
20367        registry.deregister(&parker);
20368        woke_tx.send(index).unwrap();
20369      }));
20370    }
20371    wait_until("both drivers registered as parked", || {
20372      registry.count.load(Ordering::SeqCst) == 2
20373    });
20374
20375    assert!(registry.wake_one(), "wake_one must wake a parked driver");
20376    woke_rx
20377      .recv_timeout(Duration::from_secs(5))
20378      .expect("exactly one driver must wake");
20379    assert!(
20380      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20381      "the second driver must stay parked after a single wake_one"
20382    );
20383    assert_eq!(
20384      registry.count.load(Ordering::SeqCst),
20385      1,
20386      "one driver must remain registered"
20387    );
20388
20389    assert!(
20390      registry.wake_one(),
20391      "a second wake_one must wake the remaining driver"
20392    );
20393    woke_rx
20394      .recv_timeout(Duration::from_secs(5))
20395      .expect("the second driver must wake");
20396    for driver in drivers {
20397      driver.join().unwrap();
20398    }
20399  }
20400
20401  #[cfg(napi_runtime_os_threads)]
20402  #[test]
20403  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20404    use std::sync::mpsc;
20405
20406    struct ReenterRegistryOnDrop {
20407      registry: Arc<ParkedDrivers>,
20408      dropped: mpsc::Sender<()>,
20409    }
20410
20411    #[expect(
20412      clippy::manual_noop_waker,
20413      reason = "the test observes the waker payload's Drop"
20414    )]
20415    impl std::task::Wake for ReenterRegistryOnDrop {
20416      fn wake(self: Arc<Self>) {}
20417    }
20418
20419    impl Drop for ReenterRegistryOnDrop {
20420      fn drop(&mut self) {
20421        let parker = Arc::new(DriverParker::default());
20422        self.registry.register(&parker);
20423        self.registry.deregister(&parker);
20424        self.dropped.send(()).unwrap();
20425      }
20426    }
20427
20428    let registry = Arc::new(ParkedDrivers::default());
20429    let dependency = Arc::new(TaskDependency::default());
20430    let (dropped_tx, dropped_rx) = mpsc::channel();
20431    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20432      registry: Arc::clone(&registry),
20433      dropped: dropped_tx,
20434    }));
20435    dependency.register_waiter(&waiter);
20436    drop(waiter);
20437
20438    let parker = Arc::new(DriverParker::default());
20439    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20440    drop(dependency);
20441
20442    let retiring_registry = Arc::clone(&registry);
20443    let retiring_parker = Arc::clone(&parker);
20444    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20445    dropped_rx
20446      .recv_timeout(Duration::from_secs(1))
20447      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20448    retiring.join().unwrap();
20449  }
20450
20451  #[cfg(napi_runtime_os_threads)]
20452  #[test]
20453  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20454    use std::sync::mpsc;
20455
20456    let registry = Arc::new(ParkedDrivers::default());
20457    let owner = BlockingOwnerToken {
20458      executor_id: u64::MAX - 70,
20459      frame: u64::MAX - 71,
20460    };
20461    let dependency = Arc::new(TaskDependency::default());
20462    dependency.set_owned(BlockingDependency {
20463      job: BlockingJobId(u64::MAX - 72),
20464      owner: Some(owner),
20465    });
20466    let parker = Arc::new(DriverParker::default());
20467    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20468
20469    let (predicate_tx, predicate_rx) = mpsc::channel();
20470    let (release_tx, release_rx) = mpsc::channel();
20471    let selecting_registry = Arc::clone(&registry);
20472    let selector = std::thread::spawn(move || {
20473      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20474        *slot.borrow_mut() = Some(Box::new(move || {
20475          predicate_tx.send(()).unwrap();
20476          release_rx.recv().unwrap();
20477        }));
20478      });
20479      selecting_registry.wake_one_owner_dependency(owner)
20480    });
20481
20482    predicate_rx
20483      .recv_timeout(Duration::from_secs(2))
20484      .expect("owner selection must reach the dependency predicate");
20485    let registry_was_unlocked = registry.parked.try_lock().is_ok();
20486    release_tx.send(()).unwrap();
20487    assert!(
20488      selector.join().unwrap(),
20489      "the exact-owner parker must still be selected"
20490    );
20491
20492    assert!(
20493      registry_was_unlocked,
20494      "owner dependency predicates must not run while the parked-driver registry is locked"
20495    );
20496    assert!(parker.consume_permit());
20497    assert_eq!(parker.take_owner_handoff(), Some(owner));
20498  }
20499
20500  #[cfg(napi_runtime_os_threads)]
20501  #[test]
20502  fn owner_handoff_skips_newer_untagged_dependency() {
20503    let registry = ParkedDrivers::default();
20504    let owner = BlockingOwnerToken {
20505      executor_id: u64::MAX - 1,
20506      frame: u64::MAX - 2,
20507    };
20508    let owned_dependency = Arc::new(TaskDependency::default());
20509    owned_dependency.set_owned(BlockingDependency {
20510      job: BlockingJobId(u64::MAX - 3),
20511      owner: Some(owner),
20512    });
20513    let untagged_dependency = Arc::new(TaskDependency::default());
20514    untagged_dependency.set_owned(BlockingDependency {
20515      job: BlockingJobId(u64::MAX - 4),
20516      owner: None,
20517    });
20518    let owned = Arc::new(DriverParker::default());
20519    let untagged = Arc::new(DriverParker::default());
20520
20521    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20522    // Register the unrelated dependency last so an unfiltered LIFO retry
20523    // would consume the exact owner handoff.
20524    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20525
20526    assert!(registry.wake_one_owner_dependency(owner));
20527    assert!(
20528      owned.consume_permit(),
20529      "the exact owner dependency must receive the retry"
20530    );
20531    assert!(
20532      !untagged.consume_permit(),
20533      "an untagged dependency must not consume an exact owner handoff"
20534    );
20535    registry.deregister(&untagged);
20536  }
20537
20538  #[cfg(napi_runtime_os_threads)]
20539  #[test]
20540  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20541    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20542    let owner = executor.fresh_owner_token();
20543    let valid_dependency = Arc::new(TaskDependency::default());
20544    valid_dependency.set_owned(BlockingDependency {
20545      job: BlockingJobId(u64::MAX - 45),
20546      owner: Some(owner),
20547    });
20548    let withdrawn_dependency = Arc::new(TaskDependency::default());
20549    withdrawn_dependency.set_owned(BlockingDependency {
20550      job: BlockingJobId(u64::MAX - 46),
20551      owner: Some(owner),
20552    });
20553    let valid = Arc::new(DriverParker::default());
20554    let withdrawn = Arc::new(DriverParker::default());
20555
20556    executor
20557      .parked_drivers
20558      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20559    executor.parked_drivers.register_with_role(
20560      &withdrawn,
20561      true,
20562      Some(Arc::clone(&withdrawn_dependency)),
20563    );
20564    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20565    withdrawn_dependency.clear();
20566
20567    assert_eq!(
20568      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20569      Some(false),
20570      "a selected dependency withdrawn before retry must forward the owner handoff"
20571    );
20572    assert!(
20573      valid.consume_permit(),
20574      "the next live same-owner dependency must receive the forwarded wake"
20575    );
20576    assert_eq!(
20577      valid.take_owner_handoff(),
20578      Some(owner),
20579      "the forwarded wake must retain the exact owner identity"
20580    );
20581  }
20582
20583  #[cfg(napi_runtime_os_threads)]
20584  #[test]
20585  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20586    use std::sync::mpsc;
20587
20588    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20589    let owner = executor.fresh_owner_token();
20590    let fallback_dependency = Arc::new(TaskDependency::default());
20591    fallback_dependency.set_owned(BlockingDependency {
20592      job: BlockingJobId(u64::MAX - 47),
20593      owner: Some(owner),
20594    });
20595    let exiting_dependency = Arc::new(TaskDependency::default());
20596    exiting_dependency.set_owned(BlockingDependency {
20597      job: BlockingJobId(u64::MAX - 48),
20598      owner: Some(owner),
20599    });
20600    let fallback = Arc::new(DriverParker::default());
20601    let exiting = Arc::new(DriverParker::default());
20602
20603    executor.parked_drivers.register_with_role(
20604      &fallback,
20605      true,
20606      Some(Arc::clone(&fallback_dependency)),
20607    );
20608    executor.parked_drivers.register_with_role(
20609      &exiting,
20610      true,
20611      Some(Arc::clone(&exiting_dependency)),
20612    );
20613
20614    let (selected_tx, selected_rx) = mpsc::channel();
20615    let (release_selection_tx, release_selection_rx) = mpsc::channel();
20616    let selector_executor = Arc::clone(&executor);
20617    let selector = std::thread::spawn(move || {
20618      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20619        *slot.borrow_mut() = Some(Box::new(move || {
20620          selected_tx.send(()).unwrap();
20621          release_selection_rx.recv().unwrap();
20622        }));
20623      });
20624      assert!(
20625        selector_executor
20626          .parked_drivers
20627          .wake_one_owner_dependency(owner)
20628      );
20629    });
20630    selected_rx
20631      .recv_timeout(Duration::from_secs(2))
20632      .expect("selection must pause after removing the exact-owner parker");
20633
20634    let (exited_tx, exited_rx) = mpsc::channel();
20635    let exiting_executor = Arc::clone(&executor);
20636    let exiting_driver = Arc::clone(&exiting);
20637    let driver = std::thread::spawn(move || {
20638      exiting_driver.park();
20639      exiting_executor.parked_drivers.deregister(&exiting_driver);
20640      exiting_executor.forward_owner_handoff(&exiting_driver);
20641      exited_tx.send(()).unwrap();
20642    });
20643    exiting.unpark();
20644    exited_rx
20645      .recv_timeout(Duration::from_secs(2))
20646      .expect("the direct future wake must let the selected driver exit");
20647
20648    release_selection_tx.send(()).unwrap();
20649    selector.join().unwrap();
20650    driver.join().unwrap();
20651
20652    assert!(
20653      fallback.consume_permit(),
20654      "an exiting selected driver must forward the owner handoff to the next dependency"
20655    );
20656    assert_eq!(
20657      fallback.take_owner_handoff(),
20658      Some(owner),
20659      "the forwarded handoff must retain the exact owner identity"
20660    );
20661  }
20662
20663  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20664  #[test]
20665  fn cooperative_panic_forwards_pending_owner_handoff() {
20666    use std::sync::mpsc;
20667
20668    struct PanicAfterWake {
20669      dependency: BlockingDependency,
20670      first_poll: Option<mpsc::Sender<()>>,
20671    }
20672
20673    impl Future for PanicAfterWake {
20674      type Output = ();
20675
20676      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20677        if let Some(first_poll) = self.first_poll.take() {
20678          // This shape never retires its publication -- the driver panics
20679          // instead -- so the returned token is dropped straight away.
20680          let mut published = PublishedBlockingDependencies::default();
20681          record_blocking_dependency(&mut published, self.dependency);
20682          first_poll.send(()).unwrap();
20683          return Poll::Pending;
20684        }
20685        panic!("intentional cooperative driver panic after owner handoff");
20686      }
20687    }
20688
20689    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20690    let owner = executor.fresh_owner_token();
20691    let fallback_dependency = Arc::new(TaskDependency::default());
20692    fallback_dependency.set_owned(BlockingDependency {
20693      job: BlockingJobId(u64::MAX - 49),
20694      owner: Some(owner),
20695    });
20696    let fallback = Arc::new(DriverParker::default());
20697    executor.parked_drivers.register_with_role(
20698      &fallback,
20699      true,
20700      Some(Arc::clone(&fallback_dependency)),
20701    );
20702
20703    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20704    let (panicked_tx, panicked_rx) = mpsc::channel();
20705    let driver_executor = Arc::clone(&executor);
20706    let driver = std::thread::spawn(move || {
20707      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20708      let mut future = std::pin::pin!(PanicAfterWake {
20709        dependency: BlockingDependency {
20710          job: BlockingJobId(u64::MAX - 50),
20711          owner: Some(owner)
20712        },
20713        first_poll: Some(first_poll_tx),
20714      });
20715      let panicked = catch_unwind(AssertUnwindSafe(|| {
20716        driver_executor.block_on(future.as_mut());
20717      }))
20718      .is_err();
20719      panicked_tx.send(panicked).unwrap();
20720    });
20721
20722    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20723    wait_until("the panicking cooperative driver to park", || {
20724      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20725    });
20726    assert!(
20727      executor.parked_drivers.wake_one_owner_dependency(owner),
20728      "the exact owner handoff must select the cooperative driver"
20729    );
20730    assert!(
20731      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20732      "the test future must unwind from its second poll"
20733    );
20734    driver.join().unwrap();
20735
20736    assert!(
20737      fallback.consume_permit(),
20738      "panic cleanup must forward the unconsumed owner handoff"
20739    );
20740    assert_eq!(
20741      fallback.take_owner_handoff(),
20742      Some(owner),
20743      "panic cleanup must preserve the exact owner identity"
20744    );
20745  }
20746
20747  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20748  #[test]
20749  fn cooperative_panic_flushes_lifo_slot() {
20750    struct ScheduleThenPanic {
20751      executor: Arc<MultiThreadExecutor>,
20752      ran: Arc<AtomicBool>,
20753    }
20754
20755    impl Future for ScheduleThenPanic {
20756      type Output = ();
20757
20758      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20759        let ran = Arc::clone(&self.ran);
20760        let scheduler = Arc::clone(&self.executor);
20761        let (runnable, task) = async_task::spawn(
20762          async move {
20763            ran.store(true, Ordering::SeqCst);
20764          },
20765          move |runnable| scheduler.schedule(runnable),
20766        );
20767        self.executor.schedule(runnable);
20768        task.detach();
20769        panic!("intentional cooperative driver panic with a populated LIFO slot");
20770      }
20771    }
20772
20773    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20774    executor
20775      .active_drainers
20776      .store(executor.max_drainers, Ordering::SeqCst);
20777    let ran = Arc::new(AtomicBool::new(false));
20778    let panicked = {
20779      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20780      let mut future = std::pin::pin!(ScheduleThenPanic {
20781        executor: Arc::clone(&executor),
20782        ran: Arc::clone(&ran),
20783      });
20784      catch_unwind(AssertUnwindSafe(|| {
20785        executor.block_on(future.as_mut());
20786      }))
20787      .is_err()
20788    };
20789
20790    assert!(panicked, "the test future must unwind from its first poll");
20791    assert!(
20792      executor.pop_lifo_slot().is_none(),
20793      "panic cleanup must empty the LIFO slot"
20794    );
20795    assert_eq!(
20796      executor.injector.len(),
20797      1,
20798      "panic cleanup must move the slot runnable to the shared FIFO"
20799    );
20800    assert!(
20801      executor.run_one(),
20802      "the flushed runnable must remain executable"
20803    );
20804    assert!(ran.load(Ordering::SeqCst));
20805    executor.active_drainers.store(0, Ordering::SeqCst);
20806  }
20807
20808  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20809  #[test]
20810  fn cooperative_panic_compensates_absorbed_queue_wake() {
20811    use std::sync::mpsc;
20812
20813    struct PanicAfterWake {
20814      first_poll: Option<mpsc::Sender<()>>,
20815    }
20816
20817    impl Future for PanicAfterWake {
20818      type Output = ();
20819
20820      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20821        if let Some(first_poll) = self.first_poll.take() {
20822          first_poll.send(()).unwrap();
20823          return Poll::Pending;
20824        }
20825        panic!("intentional cooperative driver panic after a queue wake");
20826      }
20827    }
20828
20829    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20830    executor
20831      .active_drainers
20832      .store(executor.max_drainers, Ordering::SeqCst);
20833    let fallback = Arc::new(DriverParker::default());
20834    executor.parked_drivers.register(&fallback);
20835    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20836    let (panicked_tx, panicked_rx) = mpsc::channel();
20837    let driver_executor = Arc::clone(&executor);
20838    let driver = std::thread::spawn(move || {
20839      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20840      let mut future = std::pin::pin!(PanicAfterWake {
20841        first_poll: Some(first_poll_tx)
20842      });
20843      let panicked = catch_unwind(AssertUnwindSafe(|| {
20844        driver_executor.block_on(future.as_mut());
20845      }))
20846      .is_err();
20847      panicked_tx.send(panicked).unwrap();
20848    });
20849
20850    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20851    wait_until(
20852      "the panicking cooperative driver to park after the fallback",
20853      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20854    );
20855
20856    let ran = Arc::new(AtomicBool::new(false));
20857    let ran_task = Arc::clone(&ran);
20858    let scheduler = Arc::clone(&executor);
20859    let (runnable, task) = async_task::spawn(
20860      async move {
20861        ran_task.store(true, Ordering::SeqCst);
20862      },
20863      move |runnable| scheduler.schedule(runnable),
20864    );
20865    task.detach();
20866    executor.metrics.runnable_scheduled();
20867    executor.push_fifo_raw(runnable);
20868    assert!(
20869      executor.parked_drivers.wake_one(),
20870      "the queued runnable's wake must be absorbed by the cooperative driver"
20871    );
20872
20873    assert!(
20874      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20875      "the test future must unwind after absorbing the queue wake"
20876    );
20877    driver.join().unwrap();
20878    assert!(
20879      fallback.consume_permit(),
20880      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20881    );
20882    assert!(
20883      executor.run_one(),
20884      "the queued runnable must remain executable"
20885    );
20886    assert!(ran.load(Ordering::SeqCst));
20887    executor.active_drainers.store(0, Ordering::SeqCst);
20888  }
20889
20890  #[cfg(napi_runtime_os_threads)]
20891  #[test]
20892  fn multi_thread_future_wake_targets_its_own_parked_driver() {
20893    // Future-wake targeting invariant: a future-wake must reach the driver that
20894    // AWAITS that future, not "some parked driver". D1 parks first awaiting
20895    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
20896    // though D2 parked more recently -- a future-wake misrouted through the
20897    // registry (which pops most-recently-parked) would wake D2, leave D1
20898    // parked forever, and trip the bounded timeout. The old notify_all
20899    // broadcast never distinguished drivers, so this pins the new obligation.
20900    use std::sync::mpsc;
20901    use std::time::Duration;
20902
20903    let (done_tx, done_rx) = mpsc::channel();
20904    let runner = std::thread::spawn(move || {
20905      let metrics = Arc::new(RuntimeMetrics::default());
20906      let options = RuntimeOptions {
20907        flavor: RuntimeFlavor::MultiThread,
20908        worker_threads: 2,
20909        max_blocking_tasks: 2,
20910        thread_name_prefix: "wake-target".to_string(),
20911        park_deadline: None,
20912        drain_linger: DEFAULT_DRAIN_LINGER,
20913      };
20914      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20915
20916      let (tx1, rx1) = oneshot::channel::<usize>();
20917      let (tx2, rx2) = oneshot::channel::<usize>();
20918      let t1_done = Arc::new(AtomicBool::new(false));
20919      let t2_done = Arc::new(AtomicBool::new(false));
20920
20921      // T1: parks FIRST, awaiting rx1.
20922      let t1_exec = Arc::clone(&executor);
20923      let t1_flag = Arc::clone(&t1_done);
20924      let t1_body = async move {
20925        let mut inner = std::pin::pin!(async move {
20926          assert_eq!(rx1.await.unwrap(), 7usize);
20927        });
20928        t1_exec.block_on(inner.as_mut());
20929        t1_flag.store(true, Ordering::SeqCst);
20930      };
20931      let sched1 = Arc::clone(&executor);
20932      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20933      executor.schedule(runnable1);
20934      wait_until("driver 1 parked", || {
20935        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20936      });
20937
20938      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
20939      // `schedule`, so no wake_one can pop the already-parked D1) and a
20940      // drainer is spawned for it on the free second worker.
20941      let t2_exec = Arc::clone(&executor);
20942      let t2_flag = Arc::clone(&t2_done);
20943      let t2_body = async move {
20944        let mut inner = std::pin::pin!(async move {
20945          assert_eq!(rx2.await.unwrap(), 9usize);
20946        });
20947        t2_exec.block_on(inner.as_mut());
20948        t2_flag.store(true, Ordering::SeqCst);
20949      };
20950      let sched2 = Arc::clone(&executor);
20951      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20952      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20953      executor.push_fifo_raw(runnable2);
20954      executor.ensure_drainer();
20955      wait_until("driver 2 parked", || {
20956        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20957      });
20958
20959      // Complete D1's future. The wake must land on D1 (first-parked), the
20960      // driver that owns rx1's waker -- not on the more recently parked D2.
20961      tx1.send(7usize).unwrap();
20962      wait_until("T1 completed via its own driver's wake", || {
20963        t1_done.load(Ordering::SeqCst)
20964      });
20965      futures::executor::block_on(task1);
20966      assert!(
20967        !t2_done.load(Ordering::SeqCst),
20968        "D2 must still be parked: its future was never completed"
20969      );
20970      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
20971      // linger): its linger parker joins the registry alongside D2 for the
20972      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
20973      // in the window between the cooperative parker's wake-deregistration
20974      // and the drain frame's linger registration, then 2 while lingering,
20975      // then 1 again after expiry -- so a `count == 1` wait can return
20976      // during the transient window and prove nothing about the linger.
20977      // Observe the lifecycle itself instead, via the test counters that
20978      // `drain` maintains: the linger parker must REGISTER, and its idle
20979      // budget must EXPIRE (`drain_linger_expiries` is incremented only
20980      // after the expiry path deregistered the parker, and no further work
20981      // exists to start another episode), after which the registry has
20982      // deterministically settled: EXACTLY the untargeted driver D2 remains
20983      // (a misrouted wake would have left D1 cooperatively parked instead,
20984      // already excluded by the `t1_done` wait above, and a linger parker
20985      // stranded in the registry would read 2 here).
20986      wait_until(
20987        "the retiring drain frame registered its linger parker",
20988        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20989      );
20990      wait_until("the lingering drainer's idle budget expired", || {
20991        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20992      });
20993      assert_eq!(
20994        executor.parked_drivers.count.load(Ordering::SeqCst),
20995        1,
20996        "exactly the untargeted driver must remain parked after the linger expired"
20997      );
20998      assert!(
20999        !t2_done.load(Ordering::SeqCst),
21000        "D2 must still be parked after the lingering drainer expired"
21001      );
21002
21003      // Teardown: release D2 as well.
21004      tx2.send(9usize).unwrap();
21005      futures::executor::block_on(task2);
21006      assert!(t2_done.load(Ordering::SeqCst));
21007      let _ = done_tx.send(());
21008    });
21009
21010    match done_rx.recv_timeout(Duration::from_secs(10)) {
21011      Ok(()) => runner.join().unwrap(),
21012      Err(error) => panic!(
21013        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
21014      ),
21015    }
21016  }
21017
21018  #[cfg(napi_runtime_os_threads)]
21019  #[test]
21020  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
21021    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
21022    // with queue work remaining must hand the wake on because it may have
21023    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
21024    // is parked as a cooperative driver, a runnable is pushed without a wake,
21025    // and the test thread forces a ready future through the cooperative branch.
21026    // Exit compensation is the ONLY mechanism left that can wake the parked
21027    // worker to drain the runnable.
21028    use std::sync::mpsc;
21029    use std::time::Duration;
21030
21031    let (done_tx, done_rx) = mpsc::channel();
21032    let runner = std::thread::spawn(move || {
21033      let metrics = Arc::new(RuntimeMetrics::default());
21034      let options = RuntimeOptions {
21035        flavor: RuntimeFlavor::MultiThread,
21036        worker_threads: 1,
21037        max_blocking_tasks: 1,
21038        thread_name_prefix: "wake-compensate".to_string(),
21039        park_deadline: None,
21040        drain_linger: DEFAULT_DRAIN_LINGER,
21041      };
21042      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21043
21044      // Park the sole worker as a cooperative driver awaiting a gate.
21045      let (gate_tx, gate_rx) = oneshot::channel::<()>();
21046      let td_exec = Arc::clone(&executor);
21047      let td_body = async move {
21048        let mut inner = std::pin::pin!(async move {
21049          gate_rx.await.unwrap();
21050        });
21051        td_exec.block_on(inner.as_mut());
21052      };
21053      let sched = Arc::clone(&executor);
21054      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
21055      executor.schedule(td_runnable);
21056      wait_until("the sole driver parked", || {
21057        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21058      });
21059
21060      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
21061      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
21062      let (ran_tx, ran_rx) = mpsc::channel::<()>();
21063      let sched_stranded = Arc::clone(&executor);
21064      let (stranded_runnable, stranded_task) = async_task::spawn(
21065        async move {
21066          ran_tx.send(()).unwrap();
21067        },
21068        move |r| sched_stranded.schedule(r),
21069      );
21070      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21071      executor.push_fifo_raw(stranded_runnable);
21072      stranded_task.detach();
21073
21074      // Drive a ready future through the cooperative branch on THIS thread
21075      // (fake on-pool marker, as in the executor-scoping test). Its first
21076      // poll is Ready, so the loop performs no work itself -- only the exit
21077      // compensation can hand the queued runnable to the parked driver.
21078      {
21079        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21080        let mut ready = std::pin::pin!(async {});
21081        executor.block_on(ready.as_mut());
21082      }
21083
21084      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
21085        "exit compensation must wake the parked driver to run the stranded queued runnable",
21086      );
21087
21088      // Teardown: release the parked driver.
21089      gate_tx.send(()).unwrap();
21090      futures::executor::block_on(td_task);
21091      let _ = done_tx.send(());
21092    });
21093
21094    match done_rx.recv_timeout(Duration::from_secs(10)) {
21095      Ok(()) => runner.join().unwrap(),
21096      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
21097    }
21098  }
21099
21100  #[cfg(napi_runtime_os_threads)]
21101  #[test]
21102  fn cooperative_exit_compensates_blocking_only_residue() {
21103    use std::sync::mpsc;
21104
21105    struct Controlled {
21106      ready: Arc<AtomicBool>,
21107      entered: Option<mpsc::Sender<()>>,
21108    }
21109
21110    impl Future for Controlled {
21111      type Output = ();
21112
21113      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21114        if let Some(entered) = self.entered.take() {
21115          entered.send(()).unwrap();
21116        }
21117        if self.ready.load(Ordering::SeqCst) {
21118          Poll::Ready(())
21119        } else {
21120          Poll::Pending
21121        }
21122      }
21123    }
21124
21125    let options = RuntimeOptions {
21126      flavor: RuntimeFlavor::MultiThread,
21127      worker_threads: 2,
21128      max_blocking_tasks: 1,
21129      thread_name_prefix: "blocking-exit-compensation".to_string(),
21130      park_deadline: None,
21131      drain_linger: DEFAULT_DRAIN_LINGER,
21132    };
21133    let executor =
21134      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21135    let ready = Arc::new(AtomicBool::new(false));
21136    let (entered_tx, entered_rx) = mpsc::channel();
21137    let (returned_tx, returned_rx) = mpsc::channel();
21138    let (release_tx, release_rx) = mpsc::channel();
21139    let task_exec = Arc::clone(&executor);
21140    let task_ready = Arc::clone(&ready);
21141    let scheduler = Arc::clone(&executor);
21142    let (runnable, task) = async_task::spawn(
21143      async move {
21144        let mut future = std::pin::pin!(Controlled {
21145          ready: task_ready,
21146          entered: Some(entered_tx)
21147        });
21148        task_exec.block_on(future.as_mut());
21149        returned_tx.send(()).unwrap();
21150        release_rx.recv().unwrap();
21151      },
21152      move |runnable| scheduler.schedule(runnable),
21153    );
21154    executor.schedule(runnable);
21155    entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
21156
21157    // The cooperative frame must be PARKED (registered) before the timer task
21158    // is scheduled, so that schedule's wake targets the frame instead of
21159    // spawning a second drainer. A second drainer would later LINGER as a
21160    // registered blocking-capable parker, and `wake_one_blocking` prefers the
21161    // most recently parked candidate: on a starved runner (observed on the
21162    // Windows CI) the linger parker absorbs the blocking wake below, the
21163    // drainer services the job, and the frame -- whose exit compensation is
21164    // the subject under test -- sleeps in its timer-bounded park until the
21165    // 120s heap timer, far past any sane recv bound.
21166    wait_until("the cooperative frame parked", || {
21167      executor.parked_drivers.count.load(Ordering::SeqCst) == 1
21168    });
21169
21170    let timer_exec = Arc::clone(&executor);
21171    let timer_scheduler = Arc::clone(&executor);
21172    let (timer_runnable, timer_task) = async_task::spawn(
21173      async move {
21174        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
21175      },
21176      move |runnable| timer_scheduler.schedule(runnable),
21177    );
21178    executor.schedule(timer_runnable);
21179    wait_until("the dedicated timekeeper parked", || {
21180      executor
21181        .timers
21182        .inner
21183        .lock()
21184        .unwrap()
21185        .timekeeper_parker
21186        .is_some()
21187    });
21188    // Re-parked after running the timer runnable: the blocking wake below
21189    // must find the frame registered (the sole candidate), so the frame
21190    // absorbs the wake, observes its ready future, and its EXIT COMPENSATION
21191    // services the blocking-only residue.
21192    wait_until(
21193      "the cooperative frame re-parked in its timer-bounded park",
21194      || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
21195    );
21196
21197    ready.store(true, Ordering::SeqCst);
21198    let (blocking_tx, blocking_rx) = mpsc::channel();
21199    let blocking = executor.schedule_blocking(move || {
21200      blocking_tx.send(()).unwrap();
21201    });
21202    // Generous hang-detector bounds, not latency assertions: starved CI
21203    // runners (msys2 CLANG64) can stall a woken thread for whole seconds.
21204    blocking_rx
21205      .recv_timeout(Duration::from_secs(10))
21206      .expect("blocking-only residue must be serviced by a Rayon worker");
21207    returned_rx
21208      .recv_timeout(Duration::from_secs(10))
21209      .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
21210    release_tx.send(()).unwrap();
21211    futures::executor::block_on(task);
21212    futures::executor::block_on(blocking).unwrap();
21213    executor.shutdown_timers();
21214    futures::executor::block_on(timer_task);
21215  }
21216
21217  #[cfg(napi_runtime_os_threads)]
21218  #[test]
21219  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21220    let options = RuntimeOptions {
21221      flavor: RuntimeFlavor::MultiThread,
21222      worker_threads: 2,
21223      max_blocking_tasks: 1,
21224      thread_name_prefix: "stopped-exit-compensation".to_string(),
21225      park_deadline: None,
21226      drain_linger: DEFAULT_DRAIN_LINGER,
21227    };
21228    let executor =
21229      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21230    let ran = Arc::new(AtomicBool::new(false));
21231    let blocking_ran = Arc::clone(&ran);
21232
21233    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
21234    // become observable while this job remains queued, matching the original
21235    // begin_shutdown ordering window exactly.
21236    executor
21237      .active_drainers
21238      .store(executor.max_drainers, Ordering::SeqCst);
21239    let blocking = executor.schedule_blocking(move || {
21240      blocking_ran.store(true, Ordering::SeqCst);
21241    });
21242    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21243
21244    executor.stop.begin_shutdown();
21245    executor.compensate_queued_work_wake(true);
21246    let ran_after_stop = ran.load(Ordering::SeqCst);
21247
21248    executor.active_drainers.store(0, Ordering::SeqCst);
21249    executor.begin_shutdown();
21250    let blocking_result = futures::executor::block_on(blocking);
21251
21252    assert!(
21253      !ran_after_stop,
21254      "exit compensation started queued blocking work after stop"
21255    );
21256    assert!(
21257      blocking_result.is_err(),
21258      "shutdown must cancel the queued blocking job"
21259    );
21260  }
21261
21262  #[cfg(napi_runtime_os_threads)]
21263  #[test]
21264  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21265    // Targeted-wake stress: N rounds of {producer: publish work,
21266    // wake_one()} racing {consumer: check work, register, re-check, park} --
21267    // the exact schedule-vs-park protocol `schedule` and
21268    // `cooperative_block_on` run. This walks the interleaving at every offset
21269    // the OS scheduler produces; a single lost wakeup parks the consumer
21270    // forever with work pending, and the bounded recv_timeout turns that into
21271    // a loud failure instead of a hung suite. (No loom in this repo; std
21272    // threads.)
21273    use std::sync::mpsc;
21274    use std::time::Duration;
21275
21276    const ROUNDS: usize = 20_000;
21277
21278    let (done_tx, done_rx) = mpsc::channel();
21279    let registry = Arc::new(ParkedDrivers::default());
21280    let work = Arc::new(AtomicBool::new(false));
21281
21282    let consumer = {
21283      let registry = Arc::clone(&registry);
21284      let work = Arc::clone(&work);
21285      std::thread::spawn(move || {
21286        let parker = Arc::new(DriverParker::default());
21287        for _ in 0..ROUNDS {
21288          loop {
21289            if work.swap(false, Ordering::SeqCst) {
21290              break;
21291            }
21292            // The cooperative park protocol: register FIRST, then re-check,
21293            // then park (see `ParkedDrivers::wake_one`).
21294            registry.register(&parker);
21295            if work.load(Ordering::SeqCst) {
21296              registry.deregister(&parker);
21297              continue;
21298            }
21299            parker.park();
21300            registry.deregister(&parker);
21301          }
21302        }
21303        done_tx.send(()).unwrap();
21304      })
21305    };
21306
21307    for _ in 0..ROUNDS {
21308      work.store(true, Ordering::SeqCst);
21309      // The schedule side: publish work, then wake one parked driver. When
21310      // wake_one misses (consumer not registered yet), the consumer's
21311      // post-register re-check must see the work instead.
21312      let _ = registry.wake_one();
21313      while work.load(Ordering::SeqCst) {
21314        std::hint::spin_loop();
21315      }
21316    }
21317
21318    done_rx
21319      .recv_timeout(Duration::from_secs(30))
21320      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21321    consumer.join().unwrap();
21322  }
21323
21324  #[cfg(napi_runtime_os_threads)]
21325  #[test]
21326  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21327    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
21328    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
21329    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
21330    let options = RuntimeOptions {
21331      flavor: RuntimeFlavor::MultiThread,
21332      worker_threads: 2,
21333      max_blocking_tasks: 2,
21334      thread_name_prefix: "lifo-order".to_string(),
21335      park_deadline: None,
21336      drain_linger: DEFAULT_DRAIN_LINGER,
21337    };
21338    let executor =
21339      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21340    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21341
21342    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
21343    // could race the manual `run_one` calls below).
21344    let old_order = Arc::clone(&order);
21345    let sched = Arc::clone(&executor);
21346    let (old_runnable, old_task) = async_task::spawn(
21347      async move {
21348        old_order.lock().unwrap().push("old");
21349      },
21350      move |r| sched.schedule(r),
21351    );
21352    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21353    executor.push_fifo_raw(old_runnable);
21354    old_task.detach();
21355
21356    // Simulate being on this executor's pool worker: the schedule below must
21357    // take the slot path (which never touches the FIFO nor spawns a drainer).
21358    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21359    let new_order = Arc::clone(&order);
21360    let sched = Arc::clone(&executor);
21361    let (new_runnable, new_task) = async_task::spawn(
21362      async move {
21363        new_order.lock().unwrap().push("new");
21364      },
21365      move |r| sched.schedule(r),
21366    );
21367    executor.schedule(new_runnable);
21368    new_task.detach();
21369    assert_eq!(
21370      executor.injector.len(),
21371      1,
21372      "the slot-path schedule must not push to the shared FIFO"
21373    );
21374
21375    assert!(executor.run_one(), "run_one must pop the slot first");
21376    assert!(executor.run_one(), "run_one must then pop the FIFO");
21377    assert!(!executor.run_one(), "no work must remain");
21378    assert_eq!(
21379      *order.lock().unwrap(),
21380      vec!["new", "old"],
21381      "the slot runnable (newest) must run before older FIFO work"
21382    );
21383  }
21384
21385  #[cfg(napi_runtime_os_threads)]
21386  #[test]
21387  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21388    let options = RuntimeOptions {
21389      flavor: RuntimeFlavor::MultiThread,
21390      worker_threads: 2,
21391      max_blocking_tasks: 1,
21392      thread_name_prefix: "fifo-owner".to_string(),
21393      park_deadline: None,
21394      drain_linger: DEFAULT_DRAIN_LINGER,
21395    };
21396    let executor =
21397      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21398    let observed_owner = Arc::new(Mutex::new(None));
21399    let observed = Arc::clone(&observed_owner);
21400    let (runnable, task) = async_task::spawn(
21401      async move {
21402        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21403      },
21404      |_| {},
21405    );
21406    task.detach();
21407    executor.metrics.runnable_scheduled();
21408    executor.push_fifo_raw(runnable);
21409    executor.active_drainers.store(1, Ordering::Release);
21410
21411    let owner = executor.fresh_owner_token();
21412    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21413    Arc::clone(&executor).drain();
21414
21415    assert_eq!(
21416      *observed_owner.lock().unwrap(),
21417      Some(None),
21418      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21419    );
21420    assert_eq!(
21421      BLOCKING_OWNER.with(std::cell::Cell::get),
21422      Some(owner),
21423      "the ambient blocking owner must be restored after the runnable"
21424    );
21425  }
21426
21427  #[cfg(napi_runtime_os_threads)]
21428  #[test]
21429  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21430    // Drain integration: with one worker, task P scheduling
21431    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
21432    // jumping ahead of the older queued T1/T2.
21433    use std::sync::mpsc;
21434    use std::time::Duration;
21435
21436    let (done_tx, done_rx) = mpsc::channel();
21437    let runner = std::thread::spawn(move || {
21438      let options = RuntimeOptions {
21439        flavor: RuntimeFlavor::MultiThread,
21440        worker_threads: 1,
21441        max_blocking_tasks: 1,
21442        thread_name_prefix: "lifo-drain".to_string(),
21443        park_deadline: None,
21444        drain_linger: DEFAULT_DRAIN_LINGER,
21445      };
21446      let executor =
21447        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21448      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21449
21450      let p_exec = Arc::clone(&executor);
21451      let p_order = Arc::clone(&order);
21452      let p_body = async move {
21453        p_order.lock().unwrap().push("P");
21454        // Scheduled from the pool worker mid-drain: must land in the slot.
21455        let c_order = Arc::clone(&p_order);
21456        let sched = Arc::clone(&p_exec);
21457        let (c_runnable, c_task) = async_task::spawn(
21458          async move {
21459            c_order.lock().unwrap().push("C");
21460          },
21461          move |r| sched.schedule(r),
21462        );
21463        p_exec.schedule(c_runnable);
21464        c_task.detach();
21465      };
21466
21467      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
21468      // mirroring the `runnable_scheduled` accounting a real schedule does so
21469      // the queued-runnables counter stays balanced.
21470      let mut tasks = Vec::new();
21471      let sched = Arc::clone(&executor);
21472      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21473      executor.metrics.runnable_scheduled();
21474      executor.push_fifo_raw(p_runnable);
21475      tasks.push(p_task);
21476      for name in ["T1", "T2"] {
21477        let t_order = Arc::clone(&order);
21478        let sched = Arc::clone(&executor);
21479        let (t_runnable, t_task) = async_task::spawn(
21480          async move {
21481            t_order.lock().unwrap().push(name);
21482          },
21483          move |r| sched.schedule(r),
21484        );
21485        executor.metrics.runnable_scheduled();
21486        executor.push_fifo_raw(t_runnable);
21487        tasks.push(t_task);
21488      }
21489      executor.ensure_drainer();
21490
21491      for task in tasks {
21492        futures::executor::block_on(task);
21493      }
21494      wait_until("the detached child C ran", || {
21495        order.lock().unwrap().len() == 4
21496      });
21497      assert_eq!(
21498        *order.lock().unwrap(),
21499        vec!["P", "C", "T1", "T2"],
21500        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21501      );
21502      let _ = done_tx.send(());
21503    });
21504
21505    match done_rx.recv_timeout(Duration::from_secs(10)) {
21506      Ok(()) => runner.join().unwrap(),
21507      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21508    }
21509  }
21510
21511  #[cfg(napi_runtime_os_threads)]
21512  #[test]
21513  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21514    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
21515    // NEWEST takes the slot; the displaced occupant must land on the shared
21516    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
21517    // the displacement wake cannot spawn a real drainer racing the manual
21518    // `run_one` calls.
21519    let options = RuntimeOptions {
21520      flavor: RuntimeFlavor::MultiThread,
21521      worker_threads: 2,
21522      max_blocking_tasks: 2,
21523      thread_name_prefix: "lifo-displace".to_string(),
21524      park_deadline: None,
21525      drain_linger: DEFAULT_DRAIN_LINGER,
21526    };
21527    let executor =
21528      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21529    executor
21530      .active_drainers
21531      .store(executor.max_drainers, Ordering::SeqCst);
21532    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21533
21534    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21535    for name in ["a", "b"] {
21536      let n_order = Arc::clone(&order);
21537      let sched = Arc::clone(&executor);
21538      let (runnable, task) = async_task::spawn(
21539        async move {
21540          n_order.lock().unwrap().push(name);
21541        },
21542        move |r| sched.schedule(r),
21543      );
21544      executor.schedule(runnable);
21545      task.detach();
21546    }
21547    assert_eq!(
21548      executor.injector.len(),
21549      1,
21550      "the displaced occupant (a) must have been pushed to the shared FIFO"
21551    );
21552
21553    assert!(executor.run_one(), "run_one must pop the slot (b) first");
21554    assert!(
21555      executor.run_one(),
21556      "run_one must then pop the displaced (a) from the FIFO"
21557    );
21558    assert!(!executor.run_one(), "no work must remain");
21559    assert_eq!(
21560      *order.lock().unwrap(),
21561      vec!["b", "a"],
21562      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21563    );
21564    executor.active_drainers.store(0, Ordering::SeqCst);
21565  }
21566
21567  #[cfg(napi_runtime_os_threads)]
21568  #[test]
21569  fn lifo_slot_is_executor_scoped() {
21570    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
21571    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
21572    // scheduling onto executor B must bypass the slot (B's FIFO); and with
21573    // A's runnable IN the slot, B must neither pop nor displace it.
21574    let options = RuntimeOptions {
21575      flavor: RuntimeFlavor::MultiThread,
21576      worker_threads: 2,
21577      max_blocking_tasks: 2,
21578      thread_name_prefix: "lifo-scope".to_string(),
21579      park_deadline: None,
21580      drain_linger: DEFAULT_DRAIN_LINGER,
21581    };
21582    let exec1 =
21583      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21584    let exec2 =
21585      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21586    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21587    exec1
21588      .active_drainers
21589      .store(exec1.max_drainers, Ordering::SeqCst);
21590    exec2
21591      .active_drainers
21592      .store(exec2.max_drainers, Ordering::SeqCst);
21593    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21594
21595    // As exec1's worker: r1 goes to the slot, tagged exec1.
21596    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21597    let r1_order = Arc::clone(&order);
21598    let sched = Arc::clone(&exec1);
21599    let (r1, t1) = async_task::spawn(
21600      async move {
21601        r1_order.lock().unwrap().push("one");
21602      },
21603      move |r| sched.schedule(r),
21604    );
21605    exec1.schedule(r1);
21606    t1.detach();
21607    assert!(
21608      exec1.injector.is_empty(),
21609      "r1 must sit in the slot, not exec1's FIFO"
21610    );
21611
21612    {
21613      // Nested: now act as exec2's worker while exec1's runnable occupies the
21614      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
21615      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21616      let r2_order = Arc::clone(&order);
21617      let sched = Arc::clone(&exec2);
21618      let (r2, t2) = async_task::spawn(
21619        async move {
21620          r2_order.lock().unwrap().push("two");
21621        },
21622        move |r| sched.schedule(r),
21623      );
21624      exec2.schedule(r2);
21625      t2.detach();
21626      assert_eq!(
21627        exec2.injector.len(),
21628        1,
21629        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21630      );
21631      assert!(
21632        exec2.pop_lifo_slot().is_none(),
21633        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21634      );
21635      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21636    }
21637
21638    // Back as exec1's worker: its slot entry is intact and runnable.
21639    assert!(
21640      exec1.run_one(),
21641      "exec1's slot entry must have survived the foreign activity"
21642    );
21643    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21644    exec1.active_drainers.store(0, Ordering::SeqCst);
21645    exec2.active_drainers.store(0, Ordering::SeqCst);
21646  }
21647
21648  #[cfg(napi_runtime_os_threads)]
21649  #[test]
21650  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21651    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
21652    // next FROM the pool worker (every link lands in the slot) crosses
21653    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
21654    // flushed to the FIFO before `finish_draining` (so its re-check sees the
21655    // runnable and re-arms a drainer); a missing flush strands the chain at
21656    // the first budget boundary (lost task + queued_runnables leak).
21657    use std::sync::mpsc;
21658    use std::time::Duration;
21659
21660    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
21661
21662    fn spawn_link(
21663      executor: &Arc<MultiThreadExecutor>,
21664      remaining: u64,
21665      counter: Arc<AtomicU64>,
21666      done: mpsc::Sender<()>,
21667    ) {
21668      let body_exec = Arc::clone(executor);
21669      let body = async move {
21670        counter.fetch_add(1, Ordering::SeqCst);
21671        if remaining > 0 {
21672          spawn_link(&body_exec, remaining - 1, counter, done);
21673        } else {
21674          done.send(()).unwrap();
21675        }
21676      };
21677      let sched = Arc::clone(executor);
21678      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21679      executor.schedule(runnable);
21680      task.detach();
21681    }
21682
21683    let (done_tx, done_rx) = mpsc::channel();
21684    let runner = std::thread::spawn(move || {
21685      let metrics = Arc::new(RuntimeMetrics::default());
21686      let options = RuntimeOptions {
21687        flavor: RuntimeFlavor::MultiThread,
21688        worker_threads: 1,
21689        max_blocking_tasks: 1,
21690        thread_name_prefix: "lifo-budget".to_string(),
21691        park_deadline: None,
21692        drain_linger: DEFAULT_DRAIN_LINGER,
21693      };
21694      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21695
21696      let counter = Arc::new(AtomicU64::new(0));
21697      let (chain_tx, chain_rx) = mpsc::channel();
21698      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21699
21700      chain_rx
21701        .recv_timeout(Duration::from_secs(10))
21702        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21703      assert_eq!(
21704        counter.load(Ordering::SeqCst),
21705        CHAIN,
21706        "every chain link must have run"
21707      );
21708      wait_until("runnable counters settle to zero (no slot leak)", || {
21709        metrics.queued_runnables.load(Ordering::Relaxed) == 0
21710          && metrics.active_runnables.load(Ordering::Relaxed) == 0
21711      });
21712      let _ = done_tx.send(());
21713    });
21714
21715    match done_rx.recv_timeout(Duration::from_secs(15)) {
21716      Ok(()) => runner.join().unwrap(),
21717      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21718    }
21719  }
21720
21721  #[cfg(napi_runtime_os_threads)]
21722  #[test]
21723  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21724    // Direct flush primitive test: pin the flush behavior used at drain
21725    // exits, before cooperative parks and by the unwind guard -- a
21726    // slot-resident runnable moves to the shared FIFO with its task still
21727    // completable, never dropped. (The pre-park call is defensive: `run_one`
21728    // pops the slot before any park is reachable, so this primitive is its
21729    // only observable seam.)
21730    let options = RuntimeOptions {
21731      flavor: RuntimeFlavor::MultiThread,
21732      worker_threads: 2,
21733      max_blocking_tasks: 2,
21734      thread_name_prefix: "lifo-flush".to_string(),
21735      park_deadline: None,
21736      drain_linger: DEFAULT_DRAIN_LINGER,
21737    };
21738    let executor =
21739      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21740    executor
21741      .active_drainers
21742      .store(executor.max_drainers, Ordering::SeqCst);
21743    let ran = Arc::new(AtomicBool::new(false));
21744
21745    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21746    let ran_flag = Arc::clone(&ran);
21747    let sched = Arc::clone(&executor);
21748    let (runnable, task) = async_task::spawn(
21749      async move {
21750        ran_flag.store(true, Ordering::SeqCst);
21751      },
21752      move |r| sched.schedule(r),
21753    );
21754    executor.schedule(runnable);
21755    task.detach();
21756    assert!(
21757      executor.injector.is_empty(),
21758      "the runnable must start in the slot"
21759    );
21760
21761    executor.flush_lifo_slot();
21762    assert!(
21763      executor.pop_lifo_slot().is_none(),
21764      "the slot must be empty after a flush"
21765    );
21766    assert_eq!(
21767      executor.injector.len(),
21768      1,
21769      "the flushed runnable must be on the shared FIFO"
21770    );
21771    assert!(executor.run_one(), "the flushed runnable must still run");
21772    assert!(
21773      ran.load(Ordering::SeqCst),
21774      "the flushed task must complete, not be dropped"
21775    );
21776    executor.active_drainers.store(0, Ordering::SeqCst);
21777  }
21778
21779  #[cfg(napi_runtime_os_threads)]
21780  #[test]
21781  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21782    // Blocking-owner isolation must cover the LIFO slot path: a runnable
21783    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
21784    // borrow the driving owner frame's over-cap privilege; the ambient owner
21785    // is restored afterwards.
21786    let options = RuntimeOptions {
21787      flavor: RuntimeFlavor::MultiThread,
21788      worker_threads: 2,
21789      max_blocking_tasks: 2,
21790      thread_name_prefix: "lifo-owner".to_string(),
21791      park_deadline: None,
21792      drain_linger: DEFAULT_DRAIN_LINGER,
21793    };
21794    let executor =
21795      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21796    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21797
21798    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21799
21800    // Install the slot entry from RUNNABLE context (no ambient owner), the
21801    // only context that may use the slot because schedules issued under an
21802    // owner frame are diverted to the shared FIFO.
21803    let observed_slot = Arc::clone(&observed);
21804    let sched = Arc::clone(&executor);
21805    let (runnable, task) = async_task::spawn(
21806      async move {
21807        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21808      },
21809      move |r| sched.schedule(r),
21810    );
21811    executor.schedule(runnable);
21812    task.detach();
21813    assert!(
21814      executor.injector.is_empty(),
21815      "the runnable must sit in the slot"
21816    );
21817
21818    // The POP can still happen while an owner frame is ambient (a cooperative
21819    // `block_on` entered from a blocking closure drives `run_one` with the
21820    // closure's token on the stack); the slot branch must clear it.
21821    let token = executor.fresh_owner_token();
21822    let _owner = BlockingOwnerGuard::enter(Some(token));
21823    assert!(executor.run_one(), "the slot runnable must run");
21824    assert_eq!(
21825      *observed.lock().unwrap(),
21826      Some(None),
21827      "a slot runnable must observe a CLEARED blocking owner"
21828    );
21829    assert_eq!(
21830      BLOCKING_OWNER.with(std::cell::Cell::get),
21831      Some(token),
21832      "the ambient owner frame must be restored after the slot runnable"
21833    );
21834  }
21835
21836  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
21837  // each other on every poll. Scheduled from a pool worker, each wake lands in
21838  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
21839  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
21840  // its partner's stored waker so both complete.
21841  #[cfg(napi_runtime_os_threads)]
21842  struct PingPongShared {
21843    count: AtomicU64,
21844    stop: AtomicBool,
21845    stop_after: u64,
21846    wakers: Mutex<[Option<std::task::Waker>; 2]>,
21847  }
21848
21849  #[cfg(napi_runtime_os_threads)]
21850  struct PingPong {
21851    shared: Arc<PingPongShared>,
21852    me: usize,
21853  }
21854
21855  #[cfg(napi_runtime_os_threads)]
21856  impl Future for PingPong {
21857    type Output = ();
21858    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21859      let shared = &self.shared;
21860      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21861      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21862        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21863        if let Some(partner) = partner {
21864          partner.wake();
21865        }
21866        return Poll::Ready(());
21867      }
21868      let mut wakers = shared.wakers.lock().unwrap();
21869      wakers[self.me] = Some(cx.waker().clone());
21870      if let Some(partner) = wakers[1 - self.me].take() {
21871        drop(wakers);
21872        partner.wake();
21873      }
21874      Poll::Pending
21875    }
21876  }
21877
21878  #[cfg(napi_runtime_os_threads)]
21879  fn spawn_ping_pong_pair(
21880    executor: &Arc<MultiThreadExecutor>,
21881    shared: &Arc<PingPongShared>,
21882  ) -> Vec<Task<()>> {
21883    let mut tasks = Vec::new();
21884    for me in 0..2 {
21885      let sched = Arc::clone(executor);
21886      let (runnable, task) = async_task::spawn(
21887        PingPong {
21888          shared: Arc::clone(shared),
21889          me,
21890        },
21891        move |r| sched.schedule(r),
21892      );
21893      executor.schedule(runnable);
21894      tasks.push(task);
21895    }
21896    tasks
21897  }
21898
21899  #[cfg(napi_runtime_os_threads)]
21900  #[test]
21901  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
21902    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
21903    // a hot wake/await pair holds a worker for at most one budget window
21904    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
21905    // a third task queued behind an unbounded ping-pong pair must still run;
21906    // if slot pops bypassed the budget the drain would never exit and T3
21907    // would starve forever (caught by the bounded timeout).
21908    use std::sync::mpsc;
21909    use std::time::Duration;
21910
21911    let (done_tx, done_rx) = mpsc::channel();
21912    let runner = std::thread::spawn(move || {
21913      let options = RuntimeOptions {
21914        flavor: RuntimeFlavor::MultiThread,
21915        worker_threads: 1,
21916        max_blocking_tasks: 0,
21917        thread_name_prefix: "lifo-streak".to_string(),
21918        park_deadline: None,
21919        drain_linger: DEFAULT_DRAIN_LINGER,
21920      };
21921      let executor =
21922        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21923
21924      let shared = Arc::new(PingPongShared {
21925        count: AtomicU64::new(0),
21926        stop: AtomicBool::new(false),
21927        stop_after: u64::MAX,
21928        wakers: Mutex::new([None, None]),
21929      });
21930      let pair = spawn_ping_pong_pair(&executor, &shared);
21931
21932      // T3: queued on the shared FIFO behind the hot pair.
21933      let (t3_tx, t3_rx) = mpsc::channel::<()>();
21934      let sched = Arc::clone(&executor);
21935      let (t3_runnable, t3_task) = async_task::spawn(
21936        async move {
21937          t3_tx.send(()).unwrap();
21938        },
21939        move |r| sched.schedule(r),
21940      );
21941      executor.schedule(t3_runnable);
21942
21943      t3_rx
21944        .recv_timeout(Duration::from_secs(10))
21945        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21946      assert!(
21947        shared.count.load(Ordering::SeqCst) >= 32,
21948        "the pair must have been bouncing through the slot before T3 got its turn"
21949      );
21950
21951      // Teardown: stop the pair and release whichever side is waiting.
21952      shared.stop.store(true, Ordering::SeqCst);
21953      let stored: Vec<_> = shared
21954        .wakers
21955        .lock()
21956        .unwrap()
21957        .iter_mut()
21958        .filter_map(Option::take)
21959        .collect();
21960      for waker in stored {
21961        waker.wake();
21962      }
21963      for task in pair {
21964        futures::executor::block_on(task);
21965      }
21966      futures::executor::block_on(t3_task);
21967      let _ = done_tx.send(());
21968    });
21969
21970    match done_rx.recv_timeout(Duration::from_secs(15)) {
21971      Ok(()) => runner.join().unwrap(),
21972      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21973    }
21974  }
21975
21976  #[cfg(napi_runtime_os_threads)]
21977  #[test]
21978  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21979    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
21980    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
21981    // pair must not turn that priority into a permanent wedge. The budget
21982    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
21983    // blocking queue, and the queued blocking job completes once the pair
21984    // finishes its (long) run on the SOLE worker.
21985    use std::sync::mpsc;
21986    use std::time::Duration;
21987
21988    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
21989
21990    let (done_tx, done_rx) = mpsc::channel();
21991    let runner = std::thread::spawn(move || {
21992      let options = RuntimeOptions {
21993        flavor: RuntimeFlavor::MultiThread,
21994        worker_threads: 1,
21995        max_blocking_tasks: 1,
21996        thread_name_prefix: "lifo-blocking".to_string(),
21997        park_deadline: None,
21998        drain_linger: DEFAULT_DRAIN_LINGER,
21999      };
22000      let executor =
22001        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22002
22003      let shared = Arc::new(PingPongShared {
22004        count: AtomicU64::new(0),
22005        stop: AtomicBool::new(false),
22006        stop_after: BOUNCES,
22007        wakers: Mutex::new([None, None]),
22008      });
22009      let pair = spawn_ping_pong_pair(&executor, &shared);
22010
22011      // Queued while the pair keeps the slot hot on the only worker.
22012      let blocking = executor.schedule_blocking(|| 11usize);
22013
22014      assert_eq!(
22015        futures::executor::block_on(blocking).unwrap(),
22016        11,
22017        "the queued blocking job must complete despite the hot LIFO pair"
22018      );
22019      for task in pair {
22020        futures::executor::block_on(task);
22021      }
22022      assert!(
22023        shared.count.load(Ordering::SeqCst) >= BOUNCES,
22024        "the pair must have actually run its full bounce quota"
22025      );
22026      let _ = done_tx.send(());
22027    });
22028
22029    match done_rx.recv_timeout(Duration::from_secs(15)) {
22030      Ok(()) => runner.join().unwrap(),
22031      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
22032    }
22033  }
22034
22035  #[cfg(napi_runtime_os_threads)]
22036  #[test]
22037  fn infinite_hot_runnable_yields_to_blocking_fifo() {
22038    use std::sync::mpsc;
22039
22040    struct HotFuture {
22041      stop: Arc<AtomicBool>,
22042    }
22043
22044    impl Future for HotFuture {
22045      type Output = ();
22046
22047      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22048        if self.stop.load(Ordering::SeqCst) {
22049          Poll::Ready(())
22050        } else {
22051          cx.waker().wake_by_ref();
22052          Poll::Pending
22053        }
22054      }
22055    }
22056
22057    let options = RuntimeOptions {
22058      flavor: RuntimeFlavor::MultiThread,
22059      worker_threads: 2,
22060      max_blocking_tasks: 1,
22061      thread_name_prefix: "blocking-fairness".to_string(),
22062      park_deadline: None,
22063      drain_linger: DEFAULT_DRAIN_LINGER,
22064    };
22065    let executor =
22066      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22067    let stop = Arc::new(AtomicBool::new(false));
22068    let scheduler = Arc::clone(&executor);
22069    let (runnable, task) = async_task::spawn(
22070      HotFuture {
22071        stop: Arc::clone(&stop),
22072      },
22073      move |r| scheduler.schedule(r),
22074    );
22075    task.detach();
22076    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
22077    executor
22078      .blocking_queue
22079      .lock()
22080      .unwrap()
22081      .push(QueuedBlocking {
22082        id: BlockingJobId(u64::MAX),
22083        run: Box::new(move || blocking_tx.send(()).unwrap()),
22084      });
22085
22086    let _driver = OnPoolWorkerGuard::enter(executor.id);
22087    executor.schedule(runnable);
22088    let mut runnable_streak = 0;
22089    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
22090      assert!(executor.run_one_fair(&mut runnable_streak));
22091    }
22092    blocking_rx
22093      .try_recv()
22094      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
22095
22096    stop.store(true, Ordering::SeqCst);
22097    assert!(executor.run_one_fair(&mut runnable_streak));
22098  }
22099
22100  #[cfg(napi_runtime_os_threads)]
22101  #[test]
22102  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22103    use std::sync::mpsc;
22104
22105    struct HotFuture {
22106      stop: Arc<AtomicBool>,
22107    }
22108
22109    impl Future for HotFuture {
22110      type Output = ();
22111
22112      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22113        if self.stop.load(Ordering::SeqCst) {
22114          Poll::Ready(())
22115        } else {
22116          cx.waker().wake_by_ref();
22117          Poll::Pending
22118        }
22119      }
22120    }
22121
22122    let options = RuntimeOptions {
22123      flavor: RuntimeFlavor::MultiThread,
22124      worker_threads: 1,
22125      max_blocking_tasks: 1,
22126      thread_name_prefix: "exact-dependency-fairness".to_string(),
22127      park_deadline: None,
22128      drain_linger: DEFAULT_DRAIN_LINGER,
22129    };
22130    let executor =
22131      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22132    let stop = Arc::new(AtomicBool::new(false));
22133    let (queued_tx, queued_rx) = mpsc::channel();
22134    let (exact_tx, exact_rx) = mpsc::channel();
22135    let owner_executor = Arc::clone(&executor);
22136    let owner_stop = Arc::clone(&stop);
22137    let owner = executor.schedule_blocking(move || {
22138      let scheduler = Arc::clone(&owner_executor);
22139      let hot_stop = Arc::clone(&owner_stop);
22140      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22141        scheduler.schedule(runnable);
22142      });
22143      owner_executor.schedule(runnable);
22144
22145      let exact_stop = Arc::clone(&owner_stop);
22146      let exact = owner_executor.schedule_blocking(move || {
22147        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22148        41usize
22149      });
22150      queued_tx.send(()).unwrap();
22151
22152      let mut value = None;
22153      {
22154        let mut future = std::pin::pin!(async {
22155          value = Some(exact.await.unwrap());
22156          hot_task.await;
22157        });
22158        assert_eq!(
22159          owner_executor.block_on(future.as_mut()),
22160          BlockOnOutcome::Completed
22161        );
22162      }
22163      value.unwrap()
22164    });
22165
22166    queued_rx
22167      .recv_timeout(Duration::from_secs(2))
22168      .expect("the exact dependency must be queued behind the saturated owner");
22169    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22170    stop.store(true, Ordering::SeqCst);
22171    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
22172    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22173    assert_eq!(
22174      exact_before_stop,
22175      Some(true),
22176      "a hot local runnable must yield to the exact dependency before it is stopped"
22177    );
22178  }
22179
22180  #[cfg(napi_runtime_os_threads)]
22181  #[test]
22182  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
22183    use std::sync::mpsc;
22184
22185    struct BlockingPoll {
22186      started: Option<mpsc::Sender<()>>,
22187      released: Arc<(Mutex<bool>, Condvar)>,
22188    }
22189
22190    impl Future for BlockingPoll {
22191      type Output = ();
22192
22193      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22194        if let Some(started) = self.started.take() {
22195          started.send(()).unwrap();
22196        }
22197        let (released, changed) = &*self.released;
22198        let mut released = released
22199          .lock()
22200          .unwrap_or_else(std::sync::PoisonError::into_inner);
22201        while !*released {
22202          released = changed
22203            .wait(released)
22204            .unwrap_or_else(std::sync::PoisonError::into_inner);
22205        }
22206        Poll::Ready(())
22207      }
22208    }
22209
22210    let options = RuntimeOptions {
22211      flavor: RuntimeFlavor::MultiThread,
22212      worker_threads: 3,
22213      max_blocking_tasks: 2,
22214      thread_name_prefix: "exact-before-last-slot".to_string(),
22215      park_deadline: None,
22216      drain_linger: DEFAULT_DRAIN_LINGER,
22217    };
22218    let executor =
22219      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22220
22221    let released = Arc::new((Mutex::new(false), Condvar::new()));
22222    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22223    let mut blockers = Vec::new();
22224    for _ in 0..2 {
22225      let scheduler = Arc::clone(&executor);
22226      let blocker_released = Arc::clone(&released);
22227      let started = blocker_started_tx.clone();
22228      let (runnable, task) = async_task::spawn(
22229        BlockingPoll {
22230          started: Some(started),
22231          released: blocker_released,
22232        },
22233        move |runnable| scheduler.schedule(runnable),
22234      );
22235      executor.schedule(runnable);
22236      blockers.push(task);
22237    }
22238    drop(blocker_started_tx);
22239    for _ in 0..2 {
22240      blocker_started_rx
22241        .recv_timeout(Duration::from_secs(2))
22242        .expect("two workers must be gated before the owner starts");
22243    }
22244
22245    let dependency_started = Arc::new(AtomicBool::new(false));
22246    let owner_executor = Arc::clone(&executor);
22247    let owner_dependency_started = Arc::clone(&dependency_started);
22248    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22249    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22250    let owner_signal = Arc::clone(&dependency_signal);
22251    let owner = executor.schedule_blocking(move || {
22252      let unrelated_signal = Arc::clone(&owner_signal);
22253      let unrelated_started = Arc::clone(&owner_dependency_started);
22254      let unrelated = owner_executor.schedule_blocking(move || {
22255        let observed = unrelated_started.load(Ordering::SeqCst);
22256        unrelated_observed_tx.send(observed).unwrap();
22257        let (ready, changed) = &*unrelated_signal;
22258        let mut ready = ready
22259          .lock()
22260          .unwrap_or_else(std::sync::PoisonError::into_inner);
22261        while !*ready {
22262          ready = changed
22263            .wait(ready)
22264            .unwrap_or_else(std::sync::PoisonError::into_inner);
22265        }
22266      });
22267
22268      let exact_signal = Arc::clone(&owner_signal);
22269      let exact_started = Arc::clone(&owner_dependency_started);
22270      let exact = owner_executor.schedule_blocking(move || {
22271        exact_started.store(true, Ordering::SeqCst);
22272        let (ready, changed) = &*exact_signal;
22273        *ready
22274          .lock()
22275          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22276        changed.notify_all();
22277        41usize
22278      });
22279
22280      let mut output = None;
22281      {
22282        let mut future = std::pin::pin!(async {
22283          output = Some(exact.await.unwrap());
22284          unrelated.await.unwrap();
22285        });
22286        assert_eq!(
22287          owner_executor.block_on(future.as_mut()),
22288          BlockOnOutcome::Completed
22289        );
22290      }
22291      output.unwrap()
22292    });
22293
22294    let (owner_done_tx, owner_done_rx) = mpsc::channel();
22295    let owner_waiter = std::thread::spawn(move || {
22296      owner_done_tx
22297        .send(futures::executor::block_on(owner))
22298        .unwrap();
22299    });
22300    let (completed_while_gated, owner_result) =
22301      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22302        Ok(result) => (true, result),
22303        Err(_) => {
22304          // Let a broken implementation recover so the regression fails
22305          // deterministically instead of leaving blocked Rayon workers behind.
22306          let (released, changed) = &*released;
22307          *released
22308            .lock()
22309            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22310          changed.notify_all();
22311          (
22312            false,
22313            owner_done_rx
22314              .recv_timeout(Duration::from_secs(2))
22315              .expect("releasing the other workers must let the broken ordering recover"),
22316          )
22317        }
22318      };
22319    let unrelated_observed = unrelated_observed_rx
22320      .recv_timeout(Duration::from_secs(2))
22321      .expect("the unrelated FIFO job must eventually run");
22322
22323    {
22324      let (released, changed) = &*released;
22325      *released
22326        .lock()
22327        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22328      changed.notify_all();
22329    }
22330    owner_waiter.join().unwrap();
22331    for blocker in blockers {
22332      futures::executor::block_on(blocker);
22333    }
22334    executor.begin_shutdown();
22335    executor.wait_until_scheduler_idle();
22336
22337    assert!(
22338      completed_while_gated,
22339      "the exact dependency must complete while unrelated workers remain gated"
22340    );
22341    assert_eq!(owner_result.unwrap(), 41);
22342    assert!(
22343      unrelated_observed,
22344      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22345    );
22346  }
22347
22348  #[cfg(napi_runtime_os_threads)]
22349  #[test]
22350  fn blocking_saturation_preserves_a_runnable_execution_lane() {
22351    use std::{sync::mpsc, time::Duration};
22352
22353    let options = RuntimeOptions {
22354      flavor: RuntimeFlavor::MultiThread,
22355      worker_threads: 2,
22356      max_blocking_tasks: 2,
22357      thread_name_prefix: "blocking-reserve".to_string(),
22358      park_deadline: None,
22359      drain_linger: DEFAULT_DRAIN_LINGER,
22360    }
22361    .validate()
22362    .expect("production options must reserve a runnable lane");
22363    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22364    let executor =
22365      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22366    let (release_tx, release_rx) = mpsc::channel();
22367    let (started_tx, started_rx) = mpsc::channel();
22368    let first = executor.schedule_blocking(move || {
22369      started_tx.send(()).unwrap();
22370      release_rx.recv().unwrap();
22371    });
22372    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22373
22374    let (queued_tx, queued_rx) = mpsc::channel();
22375    let second = executor.schedule_blocking(move || {
22376      queued_tx.send(()).unwrap();
22377    });
22378    assert!(
22379      queued_rx.try_recv().is_err(),
22380      "the second blocking job must remain queued while the sole blocking slot is occupied"
22381    );
22382
22383    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22384    let scheduler = Arc::clone(&executor);
22385    let (runnable, task) = async_task::spawn(
22386      async move {
22387        runnable_tx.send(()).unwrap();
22388      },
22389      move |r| scheduler.schedule(r),
22390    );
22391    task.detach();
22392    executor.schedule(runnable);
22393    runnable_rx
22394      .recv_timeout(Duration::from_secs(1))
22395      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22396
22397    release_tx.send(()).unwrap();
22398    futures::executor::block_on(first).unwrap();
22399    futures::executor::block_on(second).unwrap();
22400    queued_rx
22401      .recv_timeout(Duration::from_secs(1))
22402      .expect("queued blocking work must run after the occupied slot is released");
22403  }
22404
22405  #[cfg(napi_runtime_os_threads)]
22406  #[test]
22407  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22408    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
22409    // schedule same-executor work, which lands in THIS worker's LIFO slot --
22410    // no FIFO push, no wake. The Ready exit of
22411    // `cooperative_block_on` must flush the slot: the caller may wait on that
22412    // child synchronously (and the enclosing drain frame can be arbitrarily
22413    // far away -- here it does not exist at all, via the fake on-pool
22414    // marker), so a stranded slot entry is an invisible, dead task.
22415    use std::sync::mpsc;
22416    use std::time::Duration;
22417
22418    struct SpawnOnReady {
22419      executor: Arc<MultiThreadExecutor>,
22420      child_tx: Option<mpsc::Sender<u64>>,
22421    }
22422    impl Future for SpawnOnReady {
22423      type Output = ();
22424      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22425        let child_tx = self.child_tx.take().expect("polled once");
22426        let sched = Arc::clone(&self.executor);
22427        let (runnable, task) = async_task::spawn(
22428          async move {
22429            child_tx.send(21).unwrap();
22430          },
22431          move |r| sched.schedule(r),
22432        );
22433        // On-pool + same executor + no ambient owner: lands in the slot.
22434        self.executor.schedule(runnable);
22435        task.detach();
22436        Poll::Ready(())
22437      }
22438    }
22439
22440    let (done_tx, done_rx) = mpsc::channel();
22441    let runner = std::thread::spawn(move || {
22442      let options = RuntimeOptions {
22443        flavor: RuntimeFlavor::MultiThread,
22444        worker_threads: 2,
22445        max_blocking_tasks: 2,
22446        thread_name_prefix: "exit-flush".to_string(),
22447        park_deadline: None,
22448        drain_linger: DEFAULT_DRAIN_LINGER,
22449      };
22450      let executor =
22451        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22452
22453      let (child_tx, child_rx) = mpsc::channel::<u64>();
22454      {
22455        // The test-only on-pool marker runs the cooperative branch on THIS
22456        // thread, with NO enclosing drain loop to bail the slot out.
22457        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22458        let mut future = std::pin::pin!(SpawnOnReady {
22459          executor: Arc::clone(&executor),
22460          child_tx: Some(child_tx)
22461        });
22462        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22463      }
22464
22465      // Synchronous bounded wait, NOT via the executor: only the exit flush
22466      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
22467      // complete the child.
22468      let value = child_rx
22469        .recv_timeout(Duration::from_secs(5))
22470        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22471      assert_eq!(value, 21);
22472      let _ = done_tx.send(());
22473    });
22474
22475    match done_rx.recv_timeout(Duration::from_secs(10)) {
22476      Ok(()) => runner.join().unwrap(),
22477      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22478    }
22479  }
22480
22481  /// Perpetual same-executor chain: each link schedules its successor from
22482  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
22483  /// raised. `hops` counts executed links.
22484  #[cfg(napi_runtime_os_threads)]
22485  fn spawn_hot_chain(
22486    executor: &Arc<MultiThreadExecutor>,
22487    stop: Arc<AtomicBool>,
22488    hops: Arc<AtomicU64>,
22489  ) {
22490    if stop.load(Ordering::SeqCst) {
22491      return;
22492    }
22493    let link_exec = Arc::clone(executor);
22494    let body = async move {
22495      hops.fetch_add(1, Ordering::SeqCst);
22496      spawn_hot_chain(&link_exec, stop, hops);
22497    };
22498    let sched = Arc::clone(executor);
22499    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22500    executor.schedule(runnable);
22501    task.detach();
22502  }
22503
22504  #[cfg(napi_runtime_os_threads)]
22505  #[test]
22506  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22507    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
22508    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
22509    // the FIFO, so a hot chain (every link schedules its successor into the
22510    // slot) keeps run_one succeeding forever and the FIFO is never reached
22511    // from this thread. On the direct 1-worker test topology a FIFO task that
22512    // completes the awaited future then never runs: deadlock. The budgeted
22513    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
22514    // gets its turn.
22515    use std::sync::mpsc;
22516    use std::time::Duration;
22517
22518    let (done_tx, done_rx) = mpsc::channel();
22519    let runner = std::thread::spawn(move || {
22520      let options = RuntimeOptions {
22521        flavor: RuntimeFlavor::MultiThread,
22522        worker_threads: 1,
22523        // This locality test intentionally bypasses RuntimeOptions::validate
22524        // to isolate one physical worker without admitting blocking work.
22525        max_blocking_tasks: 0,
22526        thread_name_prefix: "coop-budget".to_string(),
22527        park_deadline: None,
22528        drain_linger: DEFAULT_DRAIN_LINGER,
22529      };
22530      let executor =
22531        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22532
22533      let stop = Arc::new(AtomicBool::new(false));
22534      let hops = Arc::new(AtomicU64::new(0));
22535
22536      // M runs on the sole worker: it queues F (the task that completes the
22537      // awaited future) on the FIFO, heats the slot with the chain, then
22538      // block_on's the value F produces.
22539      let m_exec = Arc::clone(&executor);
22540      let m_stop = Arc::clone(&stop);
22541      let m_hops = Arc::clone(&hops);
22542      let m_body = async move {
22543        let (tx, rx) = oneshot::channel::<u64>();
22544        // Schedule F first: it takes the slot momentarily...
22545        let sched_f = Arc::clone(&m_exec);
22546        let (f_runnable, f_task) = async_task::spawn(
22547          async move {
22548            let _ = tx.send(7);
22549          },
22550          move |r| sched_f.schedule(r),
22551        );
22552        m_exec.schedule(f_runnable);
22553        f_task.detach();
22554        // ...then the chain starter displaces it to the FIFO and keeps the
22555        // slot hot from here on.
22556        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22557
22558        let mut inner = std::pin::pin!(async move {
22559          assert_eq!(
22560            rx.await.unwrap(),
22561            7,
22562            "F must run and complete the awaited future"
22563          );
22564        });
22565        m_exec.block_on(inner.as_mut());
22566        m_stop.store(true, Ordering::SeqCst);
22567      };
22568      let sched_m = Arc::clone(&executor);
22569      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22570      executor.schedule(m_runnable);
22571
22572      futures::executor::block_on(m_task);
22573      assert!(
22574        hops.load(Ordering::SeqCst) >= 64,
22575        "the chain must have been genuinely hot (>= one budget window) before F ran"
22576      );
22577      let _ = done_tx.send(());
22578    });
22579
22580    match done_rx.recv_timeout(Duration::from_secs(10)) {
22581      Ok(()) => runner.join().unwrap(),
22582      Err(error) => panic!(
22583        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22584      ),
22585    }
22586  }
22587
22588  #[cfg(napi_runtime_os_threads)]
22589  #[test]
22590  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22591    use std::sync::mpsc;
22592
22593    struct RefillLifo {
22594      executor: Arc<MultiThreadExecutor>,
22595      fifo_ran: Arc<AtomicBool>,
22596    }
22597
22598    impl Future for RefillLifo {
22599      type Output = ();
22600
22601      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22602        if self.fifo_ran.load(Ordering::SeqCst) {
22603          return Poll::Ready(());
22604        }
22605        let scheduler = Arc::clone(&self.executor);
22606        let (runnable, task) =
22607          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22608        self.executor.schedule(runnable);
22609        task.detach();
22610        Poll::Pending
22611      }
22612    }
22613
22614    let (done_tx, done_rx) = mpsc::channel();
22615    let runner = std::thread::spawn(move || {
22616      let options = RuntimeOptions {
22617        flavor: RuntimeFlavor::MultiThread,
22618        worker_threads: 1,
22619        max_blocking_tasks: 0,
22620        thread_name_prefix: "forced-fifo".to_string(),
22621        park_deadline: None,
22622        drain_linger: DEFAULT_DRAIN_LINGER,
22623      };
22624      let executor =
22625        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22626      executor
22627        .active_drainers
22628        .store(executor.max_drainers, Ordering::SeqCst);
22629
22630      let fifo_ran = Arc::new(AtomicBool::new(false));
22631      let fifo_flag = Arc::clone(&fifo_ran);
22632      let scheduler = Arc::clone(&executor);
22633      let (fifo, task) = async_task::spawn(
22634        async move {
22635          fifo_flag.store(true, Ordering::SeqCst);
22636        },
22637        move |runnable| scheduler.schedule(runnable),
22638      );
22639      task.detach();
22640      executor.metrics.runnable_scheduled();
22641      executor.push_fifo_raw(fifo);
22642
22643      {
22644        let _driver = OnPoolWorkerGuard::enter(executor.id);
22645        let mut future = std::pin::pin!(RefillLifo {
22646          executor: Arc::clone(&executor),
22647          fifo_ran: Arc::clone(&fifo_ran)
22648        });
22649        executor.block_on(future.as_mut());
22650        while executor.run_one() {}
22651      }
22652      executor.active_drainers.store(0, Ordering::SeqCst);
22653      assert!(fifo_ran.load(Ordering::SeqCst));
22654      done_tx.send(()).unwrap();
22655    });
22656    done_rx
22657      .recv_timeout(Duration::from_secs(3))
22658      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22659    runner.join().unwrap();
22660  }
22661
22662  #[cfg(napi_runtime_os_threads)]
22663  #[test]
22664  fn blocking_closure_spawn_is_not_stranded_in_slot() {
22665    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
22666    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
22667    // same-executor task and then waits for it synchronously would deadlock if
22668    // the child were slotted because the slot only drains after the closure
22669    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
22670    // push to the shared FIFO with a wake.
22671    use std::sync::mpsc;
22672    use std::time::Duration;
22673
22674    let (done_tx, done_rx) = mpsc::channel();
22675    let runner = std::thread::spawn(move || {
22676      let options = RuntimeOptions {
22677        flavor: RuntimeFlavor::MultiThread,
22678        worker_threads: 2,
22679        max_blocking_tasks: 2,
22680        thread_name_prefix: "blk-spawn-wait".to_string(),
22681        park_deadline: None,
22682        drain_linger: DEFAULT_DRAIN_LINGER,
22683      };
22684      let executor =
22685        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22686
22687      // Closure spawns a child and BLOCKS on the channel the child must fill.
22688      // A spare worker exists; only a real FIFO push + wake can reach it.
22689      let blk_exec = Arc::clone(&executor);
22690      let handle = executor.schedule_blocking(move || {
22691        let (tx, rx) = mpsc::channel::<u64>();
22692        let sched = Arc::clone(&blk_exec);
22693        let (runnable, task) = async_task::spawn(
22694          async move {
22695            tx.send(21).unwrap();
22696          },
22697          move |r| sched.schedule(r),
22698        );
22699        blk_exec.schedule(runnable);
22700        task.detach();
22701        rx.recv_timeout(Duration::from_secs(5))
22702          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22703      });
22704      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22705
22706      // Variant: the closure exits WITHOUT waiting; the child must still
22707      // complete promptly (no stranding on the closure-exit path either).
22708      let (v_tx, v_rx) = mpsc::channel::<u64>();
22709      let v_exec = Arc::clone(&executor);
22710      let v_handle = executor.schedule_blocking(move || {
22711        let sched = Arc::clone(&v_exec);
22712        let (runnable, task) = async_task::spawn(
22713          async move {
22714            v_tx.send(42).unwrap();
22715          },
22716          move |r| sched.schedule(r),
22717        );
22718        v_exec.schedule(runnable);
22719        task.detach();
22720        1u64
22721      });
22722      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22723      assert_eq!(
22724        v_rx
22725          .recv_timeout(Duration::from_secs(5))
22726          .expect("fire-and-forget child must complete"),
22727        42
22728      );
22729      let _ = done_tx.send(());
22730    });
22731
22732    match done_rx.recv_timeout(Duration::from_secs(15)) {
22733      Ok(()) => runner.join().unwrap(),
22734      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22735    }
22736  }
22737
22738  #[cfg(napi_runtime_os_threads)]
22739  #[test]
22740  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22741    // Saturated nested-blocking invariant: a blocking closure re-enters
22742    // `block_on` and awaits another `spawn_blocking` while
22743    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
22744    // The driver must not park while still holding its blocking slot, because
22745    // the inner job would then have no capacity to run.
22746    use std::sync::{Barrier, mpsc};
22747    use std::time::Duration;
22748
22749    let (done_tx, done_rx) = mpsc::channel();
22750    let runner = std::thread::spawn(move || {
22751      let metrics = Arc::new(RuntimeMetrics::default());
22752      let options = RuntimeOptions {
22753        flavor: RuntimeFlavor::MultiThread,
22754        worker_threads: 2,
22755        max_blocking_tasks: 2,
22756        thread_name_prefix: "rd1-nested-blk".to_string(),
22757        park_deadline: None,
22758        drain_linger: DEFAULT_DRAIN_LINGER,
22759      };
22760      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22761
22762      let outer_count = 2usize; // saturates max_blocking_tasks
22763      let barrier = Arc::new(Barrier::new(outer_count));
22764
22765      let mut handles = Vec::new();
22766      for _ in 0..outer_count {
22767        let exec = Arc::clone(&executor);
22768        let barrier = Arc::clone(&barrier);
22769        let handle = executor.schedule_blocking(move || {
22770          // Both outer blocking jobs hold a slot before either nests.
22771          barrier.wait();
22772          let inner_exec = Arc::clone(&exec);
22773          let mut value = 0usize;
22774          {
22775            let mut inner = std::pin::pin!(async {
22776              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22777            });
22778            exec.block_on(inner.as_mut());
22779          }
22780          value
22781        });
22782        handles.push(handle);
22783      }
22784
22785      for handle in handles {
22786        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22787      }
22788      let _ = done_tx.send(());
22789    });
22790
22791    match done_rx.recv_timeout(Duration::from_secs(10)) {
22792      Ok(()) => runner.join().unwrap(),
22793      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22794    }
22795  }
22796
22797  #[cfg(napi_runtime_os_threads)]
22798  #[test]
22799  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22800    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22801    let owner = executor.fresh_owner_token();
22802    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22803    let job = BlockingJobId(u64::MAX - 5);
22804    let dependency = TaskDependency::default();
22805    dependency.set_owned(BlockingDependency { job, owner: None });
22806    let ran = Arc::new(AtomicBool::new(false));
22807    let job_ran = Arc::clone(&ran);
22808    executor
22809      .blocking_queue
22810      .lock()
22811      .unwrap()
22812      .push(QueuedBlocking {
22813        id: job,
22814        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22815      });
22816
22817    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22818    assert!(
22819      !executor.try_owned_blocking_over_cap(&dependency),
22820      "an untagged worker must not infer ancestry from a sole active owner"
22821    );
22822    assert!(!ran.load(Ordering::SeqCst));
22823    assert!(
22824      executor
22825        .blocking_queue
22826        .lock()
22827        .unwrap()
22828        .jobs
22829        .contains_key(&job),
22830      "rejected owner inference must leave the exact job queued"
22831    );
22832  }
22833
22834  #[cfg(napi_runtime_os_threads)]
22835  #[test]
22836  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22837    use std::sync::{Barrier, mpsc};
22838
22839    let options = RuntimeOptions {
22840      flavor: RuntimeFlavor::MultiThread,
22841      worker_threads: 2,
22842      max_blocking_tasks: 2,
22843      thread_name_prefix: "exact-owner-frame".to_string(),
22844      park_deadline: None,
22845      drain_linger: DEFAULT_DRAIN_LINGER,
22846    };
22847    let executor =
22848      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22849    let owners_saturated = Arc::new(Barrier::new(3));
22850    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22851    let begin_lending = Arc::new(Barrier::new(3));
22852    let release_dependencies = Arc::new(Barrier::new(3));
22853    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22854    let mut owners = Vec::new();
22855
22856    for owner_index in 0..2 {
22857      let owner_executor = Arc::clone(&executor);
22858      let owners_saturated = Arc::clone(&owners_saturated);
22859      let dependency_queued_tx = dependency_queued_tx.clone();
22860      let begin_lending = Arc::clone(&begin_lending);
22861      let release_dependencies = Arc::clone(&release_dependencies);
22862      let dependency_started_tx = dependency_started_tx.clone();
22863      owners.push(executor.schedule_blocking(move || {
22864        let owner_thread = std::thread::current().id();
22865        owners_saturated.wait();
22866        let child_started = dependency_started_tx.clone();
22867        let child_release = Arc::clone(&release_dependencies);
22868        let child = owner_executor.schedule_blocking(move || {
22869          assert_eq!(
22870            std::thread::current().id(),
22871            owner_thread,
22872            "an owner frame must execute only its own exact dependency on its lent lane"
22873          );
22874          child_started.send(owner_index).unwrap();
22875          child_release.wait();
22876          owner_index
22877        });
22878        dependency_queued_tx.send(()).unwrap();
22879        begin_lending.wait();
22880
22881        let mut result = None;
22882        {
22883          let mut future = std::pin::pin!(async {
22884            result = Some(child.await.unwrap());
22885          });
22886          assert_eq!(
22887            owner_executor.block_on(future.as_mut()),
22888            BlockOnOutcome::Completed
22889          );
22890        }
22891        result.unwrap()
22892      }));
22893    }
22894    drop(dependency_queued_tx);
22895    drop(dependency_started_tx);
22896    owners_saturated.wait();
22897
22898    for _ in 0..2 {
22899      dependency_queued_rx
22900        .recv_timeout(Duration::from_secs(2))
22901        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
22902    }
22903    let (unrelated_tx, unrelated_rx) = mpsc::channel();
22904    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
22905    begin_lending.wait();
22906
22907    let mut started = Vec::new();
22908    for _ in 0..2 {
22909      started.push(
22910        dependency_started_rx
22911          .recv_timeout(Duration::from_secs(2))
22912          .expect("each owner must service its own exact dependency"),
22913      );
22914    }
22915    started.sort_unstable();
22916    assert_eq!(started, vec![0, 1]);
22917    assert!(
22918      unrelated_rx.try_recv().is_err(),
22919      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22920    );
22921
22922    release_dependencies.wait();
22923    for (owner_index, owner) in owners.into_iter().enumerate() {
22924      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22925    }
22926    unrelated_rx
22927      .recv_timeout(Duration::from_secs(2))
22928      .expect("the unrelated job must run after an owner releases a counted lane");
22929    futures::executor::block_on(unrelated).unwrap();
22930  }
22931
22932  #[cfg(napi_runtime_os_threads)]
22933  #[test]
22934  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22935    use std::sync::{Mutex, mpsc};
22936
22937    const DEPENDENCY_COUNT: usize = 4;
22938
22939    let options = RuntimeOptions {
22940      flavor: RuntimeFlavor::MultiThread,
22941      worker_threads: 3,
22942      max_blocking_tasks: 1,
22943      thread_name_prefix: "parked-unrelated-worker".to_string(),
22944      park_deadline: None,
22945      drain_linger: DEFAULT_DRAIN_LINGER,
22946    };
22947    let executor =
22948      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22949    let (parked_tx, parked_rx) = mpsc::channel();
22950    let (release_parked_tx, release_parked_rx) = mpsc::channel();
22951    executor.pool.spawn(move || {
22952      parked_tx.send(()).unwrap();
22953      release_parked_rx.recv().unwrap();
22954    });
22955    parked_rx
22956      .recv_timeout(Duration::from_secs(2))
22957      .expect("the unrelated Rayon worker must remain occupied");
22958
22959    let owner_executor = Arc::clone(&executor);
22960    let owner = executor.schedule_blocking(move || {
22961      let owner = BLOCKING_OWNER
22962        .with(std::cell::Cell::get)
22963        .expect("the blocking closure must carry its exact owner frame");
22964      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22965      let results = Arc::new(Mutex::new(Vec::new()));
22966      let descendant_results = Arc::clone(&results);
22967      let descendant_executor = Arc::clone(&owner_executor);
22968      rayon::scope(move |scope| {
22969        scope.spawn(move |_| {
22970          assert_eq!(
22971            BLOCKING_OWNER.with(std::cell::Cell::get),
22972            None,
22973            "the parked owner must force its descendant onto the remaining worker"
22974          );
22975          let _owner = BlockingOwnerGuard::enter(Some(owner));
22976          descendant_started_tx.send(()).unwrap();
22977          for value in 0..DEPENDENCY_COUNT {
22978            let handle = descendant_executor.schedule_blocking(move || value);
22979            let mut result = None;
22980            {
22981              let mut future = std::pin::pin!(async {
22982                result = Some(handle.await.unwrap());
22983              });
22984              assert_eq!(
22985                descendant_executor.block_on(future.as_mut()),
22986                BlockOnOutcome::Completed
22987              );
22988            }
22989            descendant_results.lock().unwrap().push(result.unwrap());
22990          }
22991        });
22992        descendant_started_rx
22993          .recv_timeout(Duration::from_secs(2))
22994          .expect("the nested descendant must start on the only unoccupied worker");
22995      });
22996      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22997    });
22998
22999    assert_eq!(
23000      futures::executor::block_on(owner).unwrap(),
23001      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
23002    );
23003
23004    let (idle_tx, idle_rx) = mpsc::channel();
23005    let idle_executor = Arc::clone(&executor);
23006    let idle_waiter = std::thread::spawn(move || {
23007      idle_executor.begin_shutdown();
23008      idle_executor.wait_until_scheduler_idle();
23009      idle_tx.send(()).unwrap();
23010    });
23011    idle_rx
23012      .recv_timeout(Duration::from_secs(2))
23013      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
23014
23015    release_parked_tx.send(()).unwrap();
23016    idle_waiter.join().unwrap();
23017  }
23018
23019  #[cfg(napi_runtime_os_threads)]
23020  #[test]
23021  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
23022    use std::sync::{Condvar, Mutex, mpsc};
23023
23024    let options = RuntimeOptions {
23025      flavor: RuntimeFlavor::MultiThread,
23026      worker_threads: 3,
23027      max_blocking_tasks: 1,
23028      thread_name_prefix: "same-owner-rearm".to_string(),
23029      park_deadline: None,
23030      drain_linger: DEFAULT_DRAIN_LINGER,
23031    };
23032    let executor =
23033      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23034    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
23035    let (job_started_tx, job_started_rx) = mpsc::channel();
23036    let owner_executor = Arc::clone(&executor);
23037    let owner_releases = Arc::clone(&releases);
23038    let owner = executor.schedule_blocking(move || {
23039      let owner = BLOCKING_OWNER
23040        .with(std::cell::Cell::get)
23041        .expect("the blocking closure must carry its exact owner frame");
23042      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
23043      let results = Arc::new(Mutex::new(Vec::new()));
23044      let scope_results = Arc::clone(&results);
23045      rayon::scope(move |scope| {
23046        for index in 0..2 {
23047          let descendant_ready_tx = descendant_ready_tx.clone();
23048          let descendant_executor = Arc::clone(&owner_executor);
23049          let descendant_results = Arc::clone(&scope_results);
23050          let job_started_tx = job_started_tx.clone();
23051          let releases = Arc::clone(&owner_releases);
23052          scope.spawn(move |_| {
23053            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
23054            let _owner = BlockingOwnerGuard::enter(Some(owner));
23055            descendant_ready_tx.send(()).unwrap();
23056            let handle = descendant_executor.schedule_blocking(move || {
23057              job_started_tx.send(index).unwrap();
23058              let (released, changed) = &*releases;
23059              let mut state = released.lock().unwrap();
23060              while !state[index] {
23061                state = changed.wait(state).unwrap();
23062              }
23063              index
23064            });
23065            let mut result = None;
23066            {
23067              let mut future = std::pin::pin!(async {
23068                result = Some(handle.await.unwrap());
23069              });
23070              assert_eq!(
23071                descendant_executor.block_on(future.as_mut()),
23072                BlockOnOutcome::Completed
23073              );
23074            }
23075            descendant_results.lock().unwrap().push(result.unwrap());
23076          });
23077        }
23078        drop(descendant_ready_tx);
23079        for _ in 0..2 {
23080          descendant_ready_rx
23081            .recv_timeout(Duration::from_secs(2))
23082            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
23083        }
23084      });
23085      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
23086    });
23087
23088    let first = job_started_rx
23089      .recv_timeout(Duration::from_secs(2))
23090      .expect("one exact dependency must borrow the owner lane");
23091    assert!(
23092      job_started_rx
23093        .recv_timeout(Duration::from_millis(100))
23094        .is_err(),
23095      "one owner frame must not service two dependencies concurrently"
23096    );
23097    let unrelated_parker = Arc::new(DriverParker::default());
23098    executor
23099      .parked_drivers
23100      .register_with_role(&unrelated_parker, true, None);
23101    {
23102      let (released, changed) = &*releases;
23103      released.lock().unwrap()[first] = true;
23104      changed.notify_all();
23105    }
23106
23107    let second = job_started_rx
23108      .recv_timeout(Duration::from_secs(2))
23109      .expect("completion must rearm one parked same-owner dependency");
23110    assert_ne!(second, first);
23111    assert!(
23112      !unrelated_parker.consume_permit(),
23113      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
23114    );
23115    executor.parked_drivers.deregister(&unrelated_parker);
23116    {
23117      let (released, changed) = &*releases;
23118      released.lock().unwrap()[second] = true;
23119      changed.notify_all();
23120    }
23121
23122    let mut results = futures::executor::block_on(owner).unwrap();
23123    results.sort_unstable();
23124    assert_eq!(results, vec![0, 1]);
23125  }
23126
23127  #[cfg(napi_runtime_os_threads)]
23128  #[test]
23129  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
23130    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
23131    let owner = executor.fresh_owner_token();
23132    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23133    let failed_dependency = Arc::new(TaskDependency::default());
23134    failed_dependency.set_owned(BlockingDependency {
23135      job: BlockingJobId(u64::MAX - 21),
23136      owner: Some(owner),
23137    });
23138    let parked_dependency = Arc::new(TaskDependency::default());
23139    parked_dependency.set_owned(BlockingDependency {
23140      job: BlockingJobId(u64::MAX - 22),
23141      owner: Some(owner),
23142    });
23143    let parker = Arc::new(DriverParker::default());
23144
23145    let queue = executor.blocking_queue.lock().unwrap();
23146    let claimant_executor = Arc::clone(&executor);
23147    let claimant_dependency = Arc::clone(&failed_dependency);
23148    let claimant = std::thread::spawn(move || {
23149      let _owner = BlockingOwnerGuard::enter(Some(owner));
23150      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
23151    });
23152    wait_until("the failed claimant to reserve the owner lane", || {
23153      !executor.active_blocking_owners.is_available(owner)
23154    });
23155    executor
23156      .parked_drivers
23157      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
23158    failed_dependency.clear();
23159    drop(queue);
23160
23161    assert!(
23162      !claimant.join().unwrap(),
23163      "the cancelled dependency must not execute"
23164    );
23165    assert!(
23166      parker.consume_permit(),
23167      "releasing a failed exact claim must hand the owner lane to another parked dependency"
23168    );
23169  }
23170
23171  #[cfg(napi_runtime_os_threads)]
23172  #[test]
23173  fn exact_owner_lending_does_not_start_blocking_after_stop() {
23174    use std::sync::mpsc;
23175
23176    let options = RuntimeOptions {
23177      flavor: RuntimeFlavor::MultiThread,
23178      worker_threads: 2,
23179      max_blocking_tasks: 1,
23180      thread_name_prefix: "stopped-exact-owner".to_string(),
23181      park_deadline: None,
23182      drain_linger: DEFAULT_DRAIN_LINGER,
23183    };
23184    let executor =
23185      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23186    let ran = Arc::new(AtomicBool::new(false));
23187    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
23188    let (release_claim_tx, release_claim_rx) = mpsc::channel();
23189    let owner_executor = Arc::clone(&executor);
23190    let child_ran = Arc::clone(&ran);
23191    let owner = executor.schedule_blocking(move || {
23192      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
23193        *slot.borrow_mut() = Some(Box::new(move || {
23194          claim_entered_tx.send(()).unwrap();
23195          release_claim_rx.recv().unwrap();
23196        }));
23197      });
23198      let child = owner_executor.schedule_blocking(move || {
23199        child_ran.store(true, Ordering::SeqCst);
23200      });
23201      let mut future = std::pin::pin!(async move {
23202        let _ = child.await;
23203      });
23204      owner_executor.block_on(future.as_mut())
23205    });
23206
23207    claim_entered_rx
23208      .recv_timeout(Duration::from_secs(2))
23209      .expect("the cooperative driver must pause before the exact-owner queue claim");
23210    assert_eq!(
23211      executor.blocking_queue.lock().unwrap().jobs.len(),
23212      1,
23213      "the exact dependency must remain queued before stop publication"
23214    );
23215
23216    // Pin the reported race: the cooperative driver already passed its loop's
23217    // stop check, then shutdown publishes stop before the exact queue claim.
23218    executor.stop.begin_shutdown();
23219    release_claim_tx.send(()).unwrap();
23220
23221    assert_eq!(
23222      futures::executor::block_on(owner).unwrap(),
23223      BlockOnOutcome::Stopped,
23224      "the cooperative driver must stop instead of executing its queued dependency"
23225    );
23226    assert!(
23227      !ran.load(Ordering::SeqCst),
23228      "exact-owner lending started blocking work after stop"
23229    );
23230    assert_eq!(
23231      executor.blocking_queue.lock().unwrap().jobs.len(),
23232      1,
23233      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23234    );
23235
23236    executor.begin_shutdown();
23237    executor.wait_until_scheduler_idle();
23238    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23239    assert!(!ran.load(Ordering::SeqCst));
23240  }
23241
23242  #[cfg(napi_runtime_os_threads)]
23243  #[test]
23244  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23245    const DEPENDENCY_COUNT: usize = 1024;
23246
23247    let options = RuntimeOptions {
23248      flavor: RuntimeFlavor::MultiThread,
23249      worker_threads: 2,
23250      max_blocking_tasks: 1,
23251      thread_name_prefix: "linear-owner-lending".to_string(),
23252      park_deadline: None,
23253      drain_linger: DEFAULT_DRAIN_LINGER,
23254    };
23255    let executor =
23256      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23257    let completed = Arc::new(AtomicUsize::new(0));
23258
23259    for index in 0..DEPENDENCY_COUNT {
23260      let owner = executor.fresh_owner_token();
23261      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23262      let dependency = TaskDependency::new(u64::MAX, Some(owner));
23263      let blocking = BlockingDependency {
23264        job: BlockingJobId(u64::MAX - index as u64),
23265        owner: Some(owner),
23266      };
23267      dependency.set_owned(blocking);
23268      let completed = Arc::clone(&completed);
23269      executor
23270        .blocking_queue
23271        .lock()
23272        .unwrap()
23273        .push(QueuedBlocking {
23274          id: blocking.job,
23275          run: Box::new(move || {
23276            completed.fetch_add(1, Ordering::SeqCst);
23277          }),
23278        });
23279
23280      let _owner = BlockingOwnerGuard::enter(Some(owner));
23281      assert!(executor.try_owned_blocking_over_cap(&dependency));
23282    }
23283
23284    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23285    assert_eq!(
23286      executor.owner_lending_attempts.load(Ordering::Relaxed),
23287      DEPENDENCY_COUNT,
23288      "targeted lending must perform one O(1) claim attempt per exact dependency"
23289    );
23290    let queue = executor.blocking_queue.lock().unwrap();
23291    assert!(queue.is_empty());
23292    assert_eq!(queue.head, None);
23293    assert_eq!(queue.tail, None);
23294  }
23295
23296  #[cfg(napi_runtime_os_threads)]
23297  #[test]
23298  fn one_dependency_interspersed_owner_publications_stays_linear() {
23299    const PUBLICATION_COUNT: usize = 1024;
23300
23301    let executor = multi_thread_executor(2, None, "linear-publication-lending");
23302    let owner = executor.fresh_owner_token();
23303    let wrong_owner = executor.fresh_owner_token();
23304    let dependency = TaskDependency::new(u64::MAX, Some(owner));
23305    let completed = Arc::new(AtomicUsize::new(0));
23306    for index in 0..PUBLICATION_COUNT {
23307      let cancelled_source = TaskDependency::default();
23308      cancelled_source.set_owned(BlockingDependency {
23309        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23310        owner: Some(owner),
23311      });
23312      dependency.set_borrowed(
23313        &cancelled_source
23314          .get()
23315          .expect("the source publication must initially be live"),
23316      );
23317      cancelled_source.clear();
23318
23319      dependency.set_owned(BlockingDependency {
23320        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23321        owner: Some(wrong_owner),
23322      });
23323      let blocking = BlockingDependency {
23324        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23325        owner: Some(owner),
23326      };
23327      dependency.set_owned(blocking);
23328      let completed = Arc::clone(&completed);
23329      executor
23330        .blocking_queue
23331        .lock()
23332        .unwrap()
23333        .push(QueuedBlocking {
23334          id: blocking.job,
23335          run: Box::new(move || {
23336            completed.fetch_add(1, Ordering::SeqCst);
23337          }),
23338        });
23339    }
23340    dependency.reset_entry_visits();
23341
23342    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23343    let _owner = BlockingOwnerGuard::enter(Some(owner));
23344    for _ in 0..PUBLICATION_COUNT {
23345      assert!(
23346        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23347        "the owner-handoff predicate must find the next exact publication"
23348      );
23349      assert!(
23350        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23351        "the park-availability predicate must find the next serviceable publication"
23352      );
23353      assert!(executor.try_owned_blocking_over_cap(&dependency));
23354    }
23355
23356    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23357    assert!(
23358      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23359      "wrong-owner publications must not satisfy an exact handoff"
23360    );
23361    assert!(
23362      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23363      "wrong-owner publications must not make the owner's lane appear serviceable"
23364    );
23365    assert!(
23366      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23367      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23368      dependency.entry_visits()
23369    );
23370    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23371  }
23372
23373  #[cfg(napi_runtime_os_threads)]
23374  #[test]
23375  fn binding_unowned_dependency_updates_exact_owner_predicate() {
23376    let owner = BlockingOwnerToken {
23377      executor_id: u64::MAX - 200,
23378      frame: u64::MAX - 201,
23379    };
23380    let dependency = TaskDependency::default();
23381    let blocking = BlockingDependency {
23382      job: BlockingJobId(u64::MAX - 202),
23383      owner: None,
23384    };
23385    dependency.set_owned(blocking);
23386
23387    assert!(
23388      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23389      "an unowned publication must not satisfy an exact owner handoff"
23390    );
23391    assert!(
23392      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23393      "an unowned publication is available to the ambient owner"
23394    );
23395
23396    let selected = dependency
23397      .select_for_owner(owner)
23398      .expect("the owner must bind the unowned publication");
23399    assert_eq!(selected.publication.dependency.owner, Some(owner));
23400    assert!(
23401      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23402      "binding must immediately publish the entry to exact-owner predicates"
23403    );
23404    assert!(dependency.try_claim(&selected));
23405    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23406  }
23407
23408  #[cfg(napi_runtime_os_threads)]
23409  #[test]
23410  fn owner_selection_preserves_unowned_and_exact_publication_order() {
23411    let owner = BlockingOwnerToken {
23412      executor_id: u64::MAX - 210,
23413      frame: u64::MAX - 211,
23414    };
23415    let dependency = TaskDependency::default();
23416    let first = BlockingDependency {
23417      job: BlockingJobId(u64::MAX - 212),
23418      owner: None,
23419    };
23420    let second = BlockingDependency {
23421      job: BlockingJobId(u64::MAX - 213),
23422      owner: Some(owner),
23423    };
23424    dependency.set_owned(first);
23425    dependency.set_owned(second);
23426
23427    let selected = dependency
23428      .select_for_owner(owner)
23429      .expect("the first publication must be found");
23430    assert_eq!(
23431      selected.publication.dependency,
23432      BlockingDependency {
23433        owner: Some(owner),
23434        ..first
23435      }
23436    );
23437    assert!(dependency.try_claim(&selected));
23438
23439    let selected = dependency
23440      .select_for_owner(owner)
23441      .expect("the second publication must remain eligible");
23442    assert_eq!(selected.publication.dependency, second);
23443  }
23444
23445  #[cfg(napi_runtime_os_threads)]
23446  #[test]
23447  fn blocking_queue_exact_removal_reclaims_fifo_links() {
23448    const JOB_COUNT: u64 = 4096;
23449
23450    let mut queue = BlockingQueue {
23451      closed: false,
23452      head: None,
23453      tail: None,
23454      jobs: FxHashMap::default(),
23455    };
23456    for id in 1..=JOB_COUNT {
23457      queue.push(QueuedBlocking {
23458        id: BlockingJobId(id),
23459        run: Box::new(|| {}),
23460      });
23461    }
23462    for id in (2..=JOB_COUNT).step_by(2) {
23463      drop(
23464        queue
23465          .remove(BlockingJobId(id))
23466          .expect("even job must remain indexed"),
23467      );
23468    }
23469    for id in (1..=JOB_COUNT).step_by(2) {
23470      drop(
23471        queue
23472          .remove(BlockingJobId(id))
23473          .expect("odd job must remain indexed"),
23474      );
23475    }
23476    assert!(queue.jobs.is_empty());
23477    assert_eq!(queue.head, None);
23478    assert_eq!(queue.tail, None);
23479
23480    let order = Arc::new(Mutex::new(Vec::new()));
23481    for id in 1..=3 {
23482      let order = Arc::clone(&order);
23483      queue.push(QueuedBlocking {
23484        id: BlockingJobId(id),
23485        run: Box::new(move || order.lock().unwrap().push(id)),
23486      });
23487    }
23488    drop(
23489      queue
23490        .remove(BlockingJobId(2))
23491        .expect("middle job must be removable"),
23492    );
23493    queue.pop_front().expect("FIFO head must remain linked")();
23494    queue.pop_front().expect("FIFO tail must remain linked")();
23495    assert!(queue.pop_front().is_none());
23496    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23497    assert_eq!(queue.head, None);
23498    assert_eq!(queue.tail, None);
23499  }
23500
23501  #[cfg(not(target_family = "wasm"))]
23502  #[test]
23503  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23504    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23505
23506    if std::env::var_os(CHILD_ENV).is_some() {
23507      configure(RuntimeOptions {
23508        flavor: RuntimeFlavor::MultiThread,
23509        worker_threads: 2,
23510        max_blocking_tasks: 1,
23511        thread_name_prefix: "dependency-lending-metrics".to_string(),
23512        park_deadline: None,
23513        drain_linger: DEFAULT_DRAIN_LINGER,
23514      })
23515      .unwrap();
23516
23517      let outer = spawn_blocking(|| {
23518        let inner = spawn_blocking(|| 42usize);
23519        block_on(inner).unwrap()
23520      });
23521      assert_eq!(block_on(outer).unwrap(), 42);
23522
23523      // Result delivery wakes the joiner from inside the blocking closure,
23524      // immediately before the scheduler-owned metrics guard retires. Use
23525      // shutdown quiescence before sampling completed/live counters so this
23526      // test does not race that final bookkeeping.
23527      shutdown().unwrap();
23528      let snapshot = metrics();
23529      assert_eq!(snapshot.blocking_tasks_started, 2);
23530      assert_eq!(snapshot.blocking_tasks_completed, 2);
23531      assert_eq!(snapshot.active_blocking_tasks, 0);
23532      assert_eq!(
23533        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23534        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23535      );
23536      return;
23537    }
23538
23539    let output = std::process::Command::new(std::env::current_exe().unwrap())
23540      .arg("--exact")
23541      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23542      .arg("--nocapture")
23543      .env(CHILD_ENV, "1")
23544      .output()
23545      .expect("the dependency-lending metrics subprocess must start");
23546    assert!(
23547      output.status.success(),
23548      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23549      output.status.code(),
23550      String::from_utf8_lossy(&output.stdout),
23551      String::from_utf8_lossy(&output.stderr)
23552    );
23553  }
23554
23555  #[cfg(napi_runtime_os_threads)]
23556  #[test]
23557  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23558    use std::sync::mpsc;
23559
23560    let (done_tx, done_rx) = mpsc::channel();
23561    let runner = std::thread::spawn(move || {
23562      let options = RuntimeOptions {
23563        flavor: RuntimeFlavor::MultiThread,
23564        worker_threads: 2,
23565        max_blocking_tasks: 1,
23566        thread_name_prefix: "exact-blocking-job".to_string(),
23567        park_deadline: None,
23568        drain_linger: DEFAULT_DRAIN_LINGER,
23569      };
23570      let executor =
23571        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23572      let outer_exec = Arc::clone(&executor);
23573      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23574      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23575      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23576      let outer = executor.schedule_blocking(move || {
23577        let sibling = outer_exec.schedule_blocking(move || {
23578          sibling_started_tx.send(()).unwrap();
23579          release_sibling_rx.recv().unwrap();
23580        });
23581        let awaited = outer_exec.schedule_blocking(move || {
23582          awaited_ran_tx.send(()).unwrap();
23583          17usize
23584        });
23585        let mut value = None;
23586        {
23587          let mut future = std::pin::pin!(async {
23588            value = Some(awaited.await.unwrap());
23589          });
23590          outer_exec.block_on(future.as_mut());
23591        }
23592        (sibling, value.unwrap())
23593      });
23594
23595      awaited_ran_rx
23596        .recv_timeout(Duration::from_secs(2))
23597        .expect("the exact awaited job must bypass the earlier detached sibling");
23598      let (sibling, value) = futures::executor::block_on(outer).unwrap();
23599      assert_eq!(value, 17);
23600      sibling_started_rx
23601        .recv_timeout(Duration::from_secs(2))
23602        .expect("the sibling must run after the owner releases its counted slot");
23603      release_sibling_tx.send(()).unwrap();
23604      futures::executor::block_on(sibling).unwrap();
23605      done_tx.send(()).unwrap();
23606    });
23607    done_rx
23608      .recv_timeout(Duration::from_secs(5))
23609      .expect("same-owner sibling selection deadlocked the exact awaited job");
23610    runner.join().unwrap();
23611  }
23612
23613  #[cfg(napi_runtime_os_threads)]
23614  #[test]
23615  fn blocking_dependency_propagates_through_async_join_lineage() {
23616    use std::sync::mpsc;
23617
23618    let (done_tx, done_rx) = mpsc::channel();
23619    let runner = std::thread::spawn(move || {
23620      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23621      let owner_controller = Arc::clone(&controller);
23622      let (child_tx, child_rx) = mpsc::channel();
23623      let outer = controller
23624        .try_spawn_blocking(move || {
23625          let async_controller = Arc::clone(&owner_controller);
23626          let async_child = owner_controller
23627            .try_spawn(async move {
23628              async_controller
23629                .try_spawn_blocking(move || {
23630                  child_tx.send(()).unwrap();
23631                  23usize
23632                })
23633                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23634                .await
23635                .unwrap()
23636            })
23637            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23638          let mut value = None;
23639          {
23640            let mut future = std::pin::pin!(async {
23641              value = Some(async_child.await.unwrap());
23642            });
23643            owner_controller.block_on(future.as_mut());
23644          }
23645          value.unwrap()
23646        })
23647        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23648
23649      child_rx
23650        .recv_timeout(Duration::from_secs(2))
23651        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23652      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23653      controller.shutdown().unwrap();
23654      done_tx.send(()).unwrap();
23655    });
23656    done_rx
23657      .recv_timeout(Duration::from_secs(5))
23658      .expect("blocking dependency lineage deadlocked");
23659    runner.join().unwrap();
23660  }
23661
23662  #[cfg(napi_runtime_os_threads)]
23663  #[test]
23664  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23665    use std::sync::mpsc;
23666
23667    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23668    let outer_controller = Arc::clone(&controller);
23669    let (child_queued_tx, child_queued_rx) = mpsc::channel();
23670    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23671    let (release_outer_tx, release_outer_rx) = oneshot::channel();
23672    let outer = controller
23673      .try_spawn_blocking(move || {
23674        let child_controller = Arc::clone(&outer_controller);
23675        let child = outer_controller
23676          .try_spawn(async move {
23677            let blocking = child_controller
23678              .try_spawn_blocking(move || {
23679                unrelated_ran_tx.send(()).unwrap();
23680                31usize
23681              })
23682              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23683            child_queued_tx.send(()).unwrap();
23684            blocking.await.unwrap()
23685          })
23686          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23687        let mut released = false;
23688        {
23689          let mut future = std::pin::pin!(async {
23690            release_outer_rx.await.unwrap();
23691            released = true;
23692          });
23693          outer_controller.block_on(future.as_mut());
23694        }
23695        assert!(released);
23696        child
23697      })
23698      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23699
23700    child_queued_rx
23701      .recv_timeout(Duration::from_secs(2))
23702      .expect("the cooperative driver must service the unrelated async runnable");
23703    assert!(
23704      unrelated_ran_rx
23705        .recv_timeout(Duration::from_millis(200))
23706        .is_err(),
23707      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23708    );
23709
23710    release_outer_tx.send(()).unwrap();
23711    let child = futures::executor::block_on(outer).unwrap();
23712    unrelated_ran_rx
23713      .recv_timeout(Duration::from_secs(2))
23714      .expect("the unrelated blocking job must run after the owner releases its counted slot");
23715    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23716    controller.shutdown().unwrap();
23717  }
23718
23719  #[cfg(napi_runtime_os_threads)]
23720  #[test]
23721  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23722    let parent = Arc::new(TaskDependency::default());
23723    let _context = TaskDependencyGuard::enter(&parent);
23724    let dependency = BlockingDependency {
23725      job: BlockingJobId(u64::MAX),
23726      owner: None,
23727    };
23728    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23729    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23730      receiver,
23731      dependency,
23732      published: PublishedBlockingDependencies::default(),
23733    });
23734    let waker = futures::task::noop_waker();
23735    let mut cx = Context::from_waker(&waker);
23736
23737    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23738    assert_eq!(parent.get_dependency(), Some(dependency));
23739    drop(handle);
23740    assert_eq!(
23741      parent.get_dependency(),
23742      None,
23743      "an abandoned JoinHandle must clear stale dependency lineage"
23744    );
23745    drop(sender);
23746  }
23747
23748  #[cfg(napi_runtime_os_threads)]
23749  #[test]
23750  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23751    let owner = BlockingOwnerToken {
23752      executor_id: u64::MAX - 40,
23753      frame: u64::MAX - 41,
23754    };
23755    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23756    let _context = TaskDependencyGuard::enter(&parent);
23757    let dependency = BlockingDependency {
23758      job: BlockingJobId(u64::MAX - 42),
23759      owner: None,
23760    };
23761    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23762    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23763      receiver,
23764      dependency,
23765      published: PublishedBlockingDependencies::default(),
23766    });
23767    let waker = futures::task::noop_waker();
23768    let mut cx = Context::from_waker(&waker);
23769
23770    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23771    assert_eq!(
23772      parent.get_dependency(),
23773      Some(BlockingDependency {
23774        owner: Some(owner),
23775        ..dependency
23776      }),
23777      "the ambient task owner must enrich an untagged direct blocking dependency"
23778    );
23779    drop(handle);
23780    assert_eq!(
23781      parent.get_dependency(),
23782      None,
23783      "detach cleanup must match the stable job id after owner enrichment"
23784    );
23785    drop(sender);
23786  }
23787
23788  #[cfg(napi_runtime_os_threads)]
23789  #[test]
23790  fn one_poll_preserves_every_observed_blocking_dependency() {
23791    let parent = Arc::new(TaskDependency::default());
23792    let _context = TaskDependencyGuard::enter(&parent);
23793    let first = BlockingDependency {
23794      job: BlockingJobId(u64::MAX - 60),
23795      owner: None,
23796    };
23797    let second = BlockingDependency {
23798      job: BlockingJobId(u64::MAX - 61),
23799      owner: None,
23800    };
23801    let (first_sender, first_receiver) =
23802      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23803    let (second_sender, second_receiver) =
23804      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23805    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23806      receiver: first_receiver,
23807      dependency: first,
23808      published: PublishedBlockingDependencies::default(),
23809    });
23810    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23811      receiver: second_receiver,
23812      dependency: second,
23813      published: PublishedBlockingDependencies::default(),
23814    });
23815    let waker = futures::task::noop_waker();
23816    let mut cx = Context::from_waker(&waker);
23817
23818    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23819    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23820    assert_eq!(
23821      parent.get_dependencies(),
23822      vec![first, second],
23823      "publishing a later dependency in one user poll must not replace an earlier dependency"
23824    );
23825
23826    drop(first_handle);
23827    drop(second_handle);
23828    drop(first_sender);
23829    drop(second_sender);
23830  }
23831
23832  #[cfg(napi_runtime_os_threads)]
23833  #[test]
23834  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23835    let owner = BlockingOwnerToken {
23836      executor_id: u64::MAX - 900,
23837      frame: u64::MAX - 901,
23838    };
23839    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23840    let _context = TaskDependencyGuard::enter(&parent);
23841    let waker = futures::task::noop_waker();
23842    let mut cx = Context::from_waker(&waker);
23843
23844    let j1 = BlockingDependency {
23845      job: BlockingJobId(u64::MAX - 910),
23846      owner: Some(owner),
23847    };
23848    let j2 = BlockingDependency {
23849      job: BlockingJobId(u64::MAX - 911),
23850      owner: Some(owner),
23851    };
23852
23853    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23854    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23855
23856    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23857      receiver: r1,
23858      dependency: j1,
23859      published: PublishedBlockingDependencies::default(),
23860    });
23861    // TWO Pending polls in one pass. Each publishes, so without coalescing the
23862    // handle leaves two live entries for J1 and can retire only the newer one.
23863    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23864    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23865    let entries_after_double_poll = parent
23866      .state
23867      .lock()
23868      .unwrap_or_else(std::sync::PoisonError::into_inner)
23869      .current
23870      .len();
23871    drop(h1);
23872
23873    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23874      receiver: r2,
23875      dependency: j2,
23876      published: PublishedBlockingDependencies::default(),
23877    });
23878    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23879
23880    assert_eq!(
23881      entries_after_double_poll, 1,
23882      "a repeat Pending poll must supersede the handle's own previous publication"
23883    );
23884    let selected = parent
23885      .select_for_owner(owner)
23886      .expect("a live dependency must be selectable");
23887    assert_eq!(
23888      selected.publication.dependency.job, j2.job,
23889      "a DETACHED job must not be selected for owner lending ahead of the live one"
23890    );
23891    drop((s1, s2));
23892  }
23893
23894  #[cfg(napi_runtime_os_threads)]
23895  #[test]
23896  fn handle_polled_under_a_nested_frame_retires_both_publications() {
23897    let owner = BlockingOwnerToken {
23898      executor_id: u64::MAX - 920,
23899      frame: u64::MAX - 921,
23900    };
23901    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23902    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23903    let waker = futures::task::noop_waker();
23904    let mut cx = Context::from_waker(&waker);
23905
23906    let j1 = BlockingDependency {
23907      job: BlockingJobId(u64::MAX - 930),
23908      owner: Some(owner),
23909    };
23910    let j2 = BlockingDependency {
23911      job: BlockingJobId(u64::MAX - 931),
23912      owner: Some(owner),
23913    };
23914    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23915    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23916    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23917      receiver: r1,
23918      dependency: j1,
23919      published: Default::default(),
23920    });
23921
23922    {
23923      let _outer_ctx = TaskDependencyGuard::enter(&outer);
23924      // A handle can publish into more than one frame: once here, and again
23925      // under a nested re-entrant frame. Detaching must retire BOTH, or the
23926      // forgotten entry keeps a detached job selectable for owner lending.
23927      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23928      {
23929        let _inner_ctx = TaskDependencyGuard::enter(&inner);
23930        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23931      }
23932      drop(h1);
23933      assert!(
23934        outer.get_dependencies().is_empty(),
23935        "detaching must retire the publication made in the outer frame too"
23936      );
23937      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23938        receiver: r2,
23939        dependency: j2,
23940        published: Default::default(),
23941      });
23942      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23943      let selected = outer.select_for_owner(owner).expect("selectable");
23944      assert_eq!(
23945        selected.publication.dependency.job, j2.job,
23946        "a detached job published in an outer frame must not outrank a live one"
23947      );
23948      drop(h2);
23949    }
23950    drop((s1, s2));
23951  }
23952
23953  #[cfg(napi_runtime_os_threads)]
23954  #[test]
23955  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23956    const FRAME_COUNT: usize = 64;
23957    let waker = futures::task::noop_waker();
23958    let mut cx = Context::from_waker(&waker);
23959    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23960    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23961      receiver,
23962      dependency: BlockingDependency {
23963        job: BlockingJobId(u64::MAX - 940),
23964        owner: None,
23965      },
23966      published: PublishedBlockingDependencies::default(),
23967    });
23968    let mut frames = Vec::new();
23969    for _ in 0..FRAME_COUNT {
23970      let frame = Arc::new(TaskDependency::default());
23971      frames.push(Arc::downgrade(&frame));
23972      let _context = TaskDependencyGuard::enter(&frame);
23973      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23974    }
23975    // A handle carried through a sequence of completed frames must not keep
23976    // any of them alive: it remembers each publication by `Weak`, and prunes
23977    // the expired tokens on its next publication, so the history stays at the
23978    // live frame count instead of growing with every frame ever seen.
23979    let retained = frames
23980      .iter()
23981      .filter(|frame| frame.strong_count() > 0)
23982      .count();
23983    assert_eq!(
23984      retained, 0,
23985      "a completed frame must not be kept alive by a pending handle's publication history"
23986    );
23987    drop(handle);
23988    drop(sender);
23989  }
23990
23991  #[cfg(napi_runtime_os_threads)]
23992  #[test]
23993  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23994    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
23995    // sequence, so H1 must keep the position its FIRST poll gave it: if
23996    // re-polling appended a replacement at the tail, H2 would be lent first,
23997    // and an H2 that waits on H1 would deadlock the owner's single lane.
23998    let owner = BlockingOwnerToken {
23999      executor_id: u64::MAX - 950,
24000      frame: u64::MAX - 951,
24001    };
24002    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24003    let _context = TaskDependencyGuard::enter(&parent);
24004    let waker = futures::task::noop_waker();
24005    let mut cx = Context::from_waker(&waker);
24006
24007    let j1 = BlockingDependency {
24008      job: BlockingJobId(u64::MAX - 960),
24009      owner: Some(owner),
24010    };
24011    let j2 = BlockingDependency {
24012      job: BlockingJobId(u64::MAX - 961),
24013      owner: Some(owner),
24014    };
24015    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24016    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24017    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
24018      receiver: r1,
24019      dependency: j1,
24020      published: PublishedBlockingDependencies::default(),
24021    });
24022    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
24023      receiver: r2,
24024      dependency: j2,
24025      published: PublishedBlockingDependencies::default(),
24026    });
24027
24028    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24029    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
24030    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
24031
24032    let selected = parent
24033      .select_for_owner(owner)
24034      .expect("a live dependency must be selectable");
24035    assert_eq!(
24036      selected.publication.dependency.job, j1.job,
24037      "re-polling a handle must not push its dependency behind one published later"
24038    );
24039    drop((h1, h2, s1, s2));
24040  }
24041
24042  #[cfg(napi_runtime_os_threads)]
24043  #[test]
24044  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
24045    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
24046    // `TaskDependency`) but each drains `current` at the start of its poll,
24047    // which is what a real task poll does. A drained frame no longer holds the
24048    // entry, so its token stops standing and is dropped on the next
24049    // publication: the history tracks frames that still hold an entry, not
24050    // every frame the handle has ever been polled under.
24051    const FRAME_COUNT: usize = 256;
24052    let waker = futures::task::noop_waker();
24053    let mut cx = Context::from_waker(&waker);
24054    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24055    let mut handle = JoinHandle(JoinHandleInner::Blocking {
24056      receiver,
24057      dependency: BlockingDependency {
24058        job: BlockingJobId(u64::MAX - 970),
24059        owner: None,
24060      },
24061      published: PublishedBlockingDependencies::default(),
24062    });
24063    let mut retained = Vec::new();
24064    for _ in 0..FRAME_COUNT {
24065      let frame = Arc::new(TaskDependency::default());
24066      retained.push(Arc::clone(&frame));
24067      let _context = TaskDependencyGuard::enter(&frame);
24068      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24069      // The frame's next poll drains what the previous one published.
24070      frame.begin_poll();
24071    }
24072    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
24073      panic!("the handle must still be a blocking handle");
24074    };
24075    let remembered = usize::from(published.first.is_some()) + published.rest.len();
24076    assert!(
24077      remembered <= 2,
24078      "a drained frame must not stay in the publication history, remembered={remembered}"
24079    );
24080    drop(handle);
24081    drop(sender);
24082  }
24083
24084  #[cfg(napi_runtime_os_threads)]
24085  #[test]
24086  fn mass_detach_of_pending_blocking_handles_stays_linear() {
24087    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
24088    // poll pass -- so `begin_poll` never runs to drain `current` and all N
24089    // entries are simultaneously registered and still pending -- and are then
24090    // dropped together. Retiring a handle's own entry is O(1) and
24091    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
24092    //
24093    // This is the shape that made an earlier attempt at this optimisation
24094    // quadratic: it replaced the job index with a scan of `current` on every
24095    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
24096    // the dependency mutex.
24097    const HANDLE_COUNT: usize = 512;
24098
24099    let parent = Arc::new(TaskDependency::default());
24100    let _context = TaskDependencyGuard::enter(&parent);
24101    let waker = futures::task::noop_waker();
24102    let mut cx = Context::from_waker(&waker);
24103
24104    let mut senders = Vec::with_capacity(HANDLE_COUNT);
24105    let mut handles = Vec::with_capacity(HANDLE_COUNT);
24106    for index in 0..HANDLE_COUNT {
24107      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
24108      senders.push(sender);
24109      handles.push(JoinHandle(JoinHandleInner::Blocking {
24110        receiver,
24111        dependency: BlockingDependency {
24112          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
24113          owner: None,
24114        },
24115        published: PublishedBlockingDependencies::default(),
24116      }));
24117    }
24118    for handle in &mut handles {
24119      assert!(Pin::new(handle).poll(&mut cx).is_pending());
24120    }
24121    assert_eq!(
24122      parent
24123        .state
24124        .lock()
24125        .unwrap_or_else(std::sync::PoisonError::into_inner)
24126        .current
24127        .len(),
24128      HANDLE_COUNT,
24129      "every handle must be registered and still pending before the detach"
24130    );
24131
24132    parent.reset_entry_visits();
24133    drop(handles);
24134
24135    assert!(
24136      parent.entry_visits() <= HANDLE_COUNT * 4,
24137      "mass-detach must visit O(n) entries across n handles, visits={}",
24138      parent.entry_visits()
24139    );
24140    assert_eq!(
24141      parent.get_dependency(),
24142      None,
24143      "every detached handle must have retired its own publication"
24144    );
24145    drop(senders);
24146  }
24147
24148  #[cfg(napi_runtime_os_threads)]
24149  #[test]
24150  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
24151    use std::sync::mpsc;
24152
24153    struct ReenterDependencyContext {
24154      dependency: Arc<TaskDependency>,
24155      woke: mpsc::Sender<()>,
24156    }
24157
24158    impl std::task::Wake for ReenterDependencyContext {
24159      fn wake(self: Arc<Self>) {
24160        let _context = TaskDependencyGuard::enter(&self.dependency);
24161        self.woke.send(()).unwrap();
24162      }
24163    }
24164
24165    let parent = Arc::new(TaskDependency::default());
24166    let nested = Arc::new(TaskDependency::default());
24167    let (woke_tx, woke_rx) = mpsc::channel();
24168    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
24169      dependency: Arc::clone(&nested),
24170      woke: woke_tx,
24171    }));
24172    parent.register_waiter(&waiter);
24173    let blocking = BlockingDependency {
24174      job: BlockingJobId(u64::MAX - 62),
24175      owner: None,
24176    };
24177
24178    let _context = TaskDependencyGuard::enter(&parent);
24179    let mut published = PublishedBlockingDependencies::default();
24180    record_blocking_dependency(&mut published, blocking);
24181    woke_rx
24182      .recv_timeout(Duration::from_secs(1))
24183      .expect("dependency notification must permit reentrant TLS context access");
24184
24185    parent.register_waiter(&waiter);
24186    clear_blocking_dependency(published);
24187    woke_rx
24188      .recv_timeout(Duration::from_secs(1))
24189      .expect("dependency clearing must permit reentrant TLS context access");
24190  }
24191
24192  #[cfg(napi_runtime_os_threads)]
24193  #[test]
24194  fn dependency_free_begin_poll_does_not_lock_state() {
24195    use std::sync::mpsc;
24196
24197    let dependency = Arc::new(TaskDependency::default());
24198    let state = dependency.state.lock().unwrap();
24199    let (completed_tx, completed_rx) = mpsc::channel();
24200    let polling_dependency = Arc::clone(&dependency);
24201    let polling = std::thread::spawn(move || {
24202      polling_dependency.begin_poll();
24203      completed_tx.send(()).unwrap();
24204    });
24205
24206    let completed_without_lock = completed_rx
24207      .recv_timeout(Duration::from_millis(200))
24208      .is_ok();
24209    drop(state);
24210    polling.join().unwrap();
24211    assert!(
24212      completed_without_lock,
24213      "a task with no published dependency must skip the dependency-state mutex"
24214    );
24215  }
24216
24217  #[cfg(not(target_family = "wasm"))]
24218  #[test]
24219  fn deep_dependency_liveness_chain_is_iterative() {
24220    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24221
24222    if std::env::var_os(CHILD_ENV).is_some() {
24223      const DEPTH: usize = 100_000;
24224
24225      let mut links = Vec::with_capacity(DEPTH + 1);
24226      links.push(DependencyLink::root());
24227      for _ in 0..DEPTH {
24228        links.push(DependencyLink::derived(links.last().unwrap()));
24229      }
24230      assert!(links.last().unwrap().is_live());
24231      links[0].cancel();
24232      assert!(!links.last().unwrap().is_live());
24233      return;
24234    }
24235
24236    let output = std::process::Command::new(std::env::current_exe().unwrap())
24237      .arg("--exact")
24238      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24239      .arg("--nocapture")
24240      .env(CHILD_ENV, "1")
24241      .output()
24242      .expect("the subprocess test must start");
24243    assert!(
24244      output.status.success(),
24245      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24246      output.status.code(),
24247      String::from_utf8_lossy(&output.stdout),
24248      String::from_utf8_lossy(&output.stderr)
24249    );
24250  }
24251
24252  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24253  #[test]
24254  fn task_dependency_set_contains_panicking_waiter() {
24255    struct PanicWake;
24256    impl std::task::Wake for PanicWake {
24257      fn wake(self: Arc<Self>) {
24258        panic!("intentional TaskDependency::set waiter panic");
24259      }
24260    }
24261
24262    let dependency = TaskDependency::default();
24263    let blocking = BlockingDependency {
24264      job: BlockingJobId(u64::MAX),
24265      owner: None,
24266    };
24267    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24268
24269    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24270    assert!(
24271      result.is_ok(),
24272      "set notification must not unwind its caller"
24273    );
24274    assert_eq!(
24275      dependency.get_dependency(),
24276      Some(blocking),
24277      "set must commit before notifying its waiter"
24278    );
24279  }
24280
24281  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24282  #[test]
24283  fn task_dependency_clear_contains_panicking_waiter() {
24284    struct PanicWake;
24285    impl std::task::Wake for PanicWake {
24286      fn wake(self: Arc<Self>) {
24287        panic!("intentional TaskDependency::clear waiter panic");
24288      }
24289    }
24290
24291    let dependency = TaskDependency::default();
24292    dependency.set_owned(BlockingDependency {
24293      job: BlockingJobId(u64::MAX),
24294      owner: None,
24295    });
24296    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24297
24298    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24299    assert!(
24300      result.is_ok(),
24301      "clear notification must not unwind its caller"
24302    );
24303    assert_eq!(
24304      dependency.get_dependency(),
24305      None,
24306      "clear must commit before notifying its waiter"
24307    );
24308  }
24309
24310  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24311  #[test]
24312  fn task_dependency_clear_if_contains_panicking_waiter() {
24313    struct PanicWake;
24314    impl std::task::Wake for PanicWake {
24315      fn wake(self: Arc<Self>) {
24316        panic!("intentional TaskDependency::clear_if waiter panic");
24317      }
24318    }
24319
24320    let dependency = TaskDependency::default();
24321    let blocking = BlockingDependency {
24322      job: BlockingJobId(u64::MAX),
24323      owner: None,
24324    };
24325    let sequence = dependency.set_owned(blocking);
24326    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24327
24328    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24329    assert!(
24330      result.is_ok(),
24331      "retire notification must not unwind its caller"
24332    );
24333    assert_eq!(
24334      dependency.get_dependency(),
24335      None,
24336      "retire must commit before notifying its waiter"
24337    );
24338  }
24339
24340  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24341  #[test]
24342  fn task_dependency_waiter_replacement_contains_panicking_drop() {
24343    use std::sync::mpsc;
24344
24345    const GENERATION: u64 = u64::MAX - 20;
24346
24347    struct PanicOnDropWake {
24348      dropped: mpsc::Sender<Option<u64>>,
24349    }
24350
24351    #[expect(
24352      clippy::manual_noop_waker,
24353      reason = "the test observes the waker payload's Drop"
24354    )]
24355    impl std::task::Wake for PanicOnDropWake {
24356      fn wake(self: Arc<Self>) {}
24357    }
24358
24359    impl Drop for PanicOnDropWake {
24360      fn drop(&mut self) {
24361        self
24362          .dropped
24363          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24364          .unwrap();
24365        panic!("intentional replaced dependency waiter destructor panic");
24366      }
24367    }
24368
24369    let dependency = TaskDependency::new(GENERATION, None);
24370    let (dropped_tx, dropped_rx) = mpsc::channel();
24371    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24372      dropped: dropped_tx,
24373    }));
24374    dependency.register_waiter(&waiter);
24375    drop(waiter);
24376
24377    let replacement = futures::task::noop_waker();
24378    let result = catch_unwind(AssertUnwindSafe(|| {
24379      dependency.register_waiter(&replacement)
24380    }));
24381    assert!(
24382      result.is_ok(),
24383      "replacing a hostile waiter must not unwind registration"
24384    );
24385    assert_eq!(
24386      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24387      Some(GENERATION),
24388      "the replaced waiter must be destroyed under its runtime generation"
24389    );
24390  }
24391
24392  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24393  #[test]
24394  fn task_dependency_retirement_contains_stored_waiter_drop() {
24395    use std::sync::mpsc;
24396
24397    const GENERATION: u64 = u64::MAX - 21;
24398
24399    struct PanicOnDropWake {
24400      dropped: mpsc::Sender<Option<u64>>,
24401    }
24402
24403    #[expect(
24404      clippy::manual_noop_waker,
24405      reason = "the test observes the waker payload's Drop"
24406    )]
24407    impl std::task::Wake for PanicOnDropWake {
24408      fn wake(self: Arc<Self>) {}
24409    }
24410
24411    impl Drop for PanicOnDropWake {
24412      fn drop(&mut self) {
24413        self
24414          .dropped
24415          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24416          .unwrap();
24417        panic!("intentional stored dependency waiter destructor panic");
24418      }
24419    }
24420
24421    let dependency = TaskDependency::new(GENERATION, None);
24422    let (dropped_tx, dropped_rx) = mpsc::channel();
24423    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24424      dropped: dropped_tx,
24425    }));
24426    dependency.register_waiter(&waiter);
24427    drop(waiter);
24428
24429    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24430    assert!(
24431      result.is_ok(),
24432      "retiring a hostile stored waiter must not unwind"
24433    );
24434    assert_eq!(
24435      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24436      Some(GENERATION),
24437      "the retained waiter must be destroyed under its runtime generation"
24438    );
24439  }
24440
24441  #[cfg(napi_runtime_os_threads)]
24442  #[test]
24443  fn detaching_task_clears_dependency_waiter_parent_retention() {
24444    struct RetainingWake {
24445      retained_parent: Arc<()>,
24446    }
24447
24448    impl std::task::Wake for RetainingWake {
24449      fn wake(self: Arc<Self>) {
24450        let _ = &self.retained_parent;
24451      }
24452    }
24453
24454    let dependency = Arc::new(TaskDependency::default());
24455    let retained_parent = Arc::new(());
24456    let retained_parent_weak = Arc::downgrade(&retained_parent);
24457    let waiter = Waker::from(Arc::new(RetainingWake {
24458      retained_parent: Arc::clone(&retained_parent),
24459    }));
24460    dependency.register_waiter(&waiter);
24461    drop(waiter);
24462    drop(retained_parent);
24463    assert!(
24464      retained_parent_weak.upgrade().is_some(),
24465      "the stored dependency waiter must retain its parent before detachment"
24466    );
24467
24468    let (runnable, task) = async_task::spawn(
24469      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24470      |_| {},
24471    );
24472    drop(JoinHandle(JoinHandleInner::Task {
24473      task: task.fallible(),
24474      awaiter: None,
24475      dependency: Arc::clone(&dependency),
24476    }));
24477    assert!(
24478      retained_parent_weak.upgrade().is_none(),
24479      "detachment must clear the dependency waiter instead of retaining its parent task"
24480    );
24481    drop(runnable);
24482  }
24483
24484  #[cfg(napi_runtime_os_threads)]
24485  #[test]
24486  fn detaching_task_clears_many_parent_publications_linearly() {
24487    const PUBLICATION_COUNT: usize = 1024;
24488
24489    struct CountWake(Arc<AtomicUsize>);
24490
24491    impl std::task::Wake for CountWake {
24492      fn wake(self: Arc<Self>) {
24493        self.0.fetch_add(1, Ordering::SeqCst);
24494      }
24495    }
24496
24497    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24498      dependency
24499        .state
24500        .lock()
24501        .unwrap_or_else(std::sync::PoisonError::into_inner)
24502        .current
24503        .back()
24504        .expect("the sentinel publication must be appended")
24505        .publication
24506        .clone()
24507    }
24508
24509    let owner = BlockingOwnerToken {
24510      executor_id: u64::MAX - 300,
24511      frame: u64::MAX - 301,
24512    };
24513    let other_owner = BlockingOwnerToken {
24514      executor_id: u64::MAX - 302,
24515      frame: u64::MAX - 303,
24516    };
24517    let child = Arc::new(TaskDependency::default());
24518    for index in 0..PUBLICATION_COUNT {
24519      child.set_owned(BlockingDependency {
24520        job: BlockingJobId(1_000_000 + index as u64),
24521        owner: (index % 2 == 0).then_some(owner),
24522      });
24523    }
24524    let child_publications = child.snapshot_all_live();
24525    let source = child_publications[0].clone();
24526
24527    let parent = Arc::new(TaskDependency::default());
24528    let _context = TaskDependencyGuard::enter(&parent);
24529    let (runnable, task) = async_task::spawn(
24530      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24531      |_| {},
24532    );
24533    let mut handle = JoinHandle(JoinHandleInner::Task {
24534      task: task.fallible(),
24535      awaiter: None,
24536      dependency: Arc::clone(&child),
24537    });
24538    let waker = futures::task::noop_waker();
24539    let mut cx = Context::from_waker(&waker);
24540    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24541    assert_eq!(
24542      parent.get_dependencies().len(),
24543      PUBLICATION_COUNT,
24544      "polling the child handle must propagate every current publication"
24545    );
24546
24547    let mut wrong_claim_source = source.clone();
24548    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24549    parent.set_borrowed(&wrong_claim_source);
24550    let wrong_claim_publication = latest_publication(&parent);
24551
24552    let mut wrong_job_source = source.clone();
24553    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24554    parent.set_borrowed(&wrong_job_source);
24555    let wrong_job_publication = latest_publication(&parent);
24556
24557    let wrong_immediate_source = source.derived(None);
24558    parent.set_borrowed(&wrong_immediate_source);
24559    let wrong_immediate_publication = latest_publication(&parent);
24560
24561    let wakes = Arc::new(AtomicUsize::new(0));
24562    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24563    parent.reset_entry_visits();
24564    drop(handle);
24565
24566    assert_eq!(
24567      wakes.load(Ordering::SeqCst),
24568      1,
24569      "one batched parent cleanup must emit one waiter notification"
24570    );
24571    assert!(
24572      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24573      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24574      parent.entry_visits()
24575    );
24576    assert!(
24577      wrong_claim_publication.is_live(),
24578      "claim identity must remain part of the match"
24579    );
24580    assert!(
24581      wrong_job_publication.is_live(),
24582      "job identity must remain part of the match"
24583    );
24584    assert!(
24585      wrong_immediate_publication.is_live(),
24586      "only an exact immediate-parent link may be retired"
24587    );
24588
24589    {
24590      let state = parent
24591        .state
24592        .lock()
24593        .unwrap_or_else(std::sync::PoisonError::into_inner);
24594      assert_eq!(state.current.len(), 3);
24595      // The `job_sequences` index is gone -- a blocking handle retires its own
24596      // entry by sequence -- so "the index retains no stale sequence" is
24597      // structurally impossible rather than something to assert. The
24598      // substantive property that assertion protected, that only the three
24599      // NON-matching sentinels survived the clear, is checked directly on the
24600      // authoritative structure.
24601      assert!(
24602        state
24603          .current
24604          .iter()
24605          .all(|entry| entry.publication.is_live()),
24606        "only the three non-matching sentinels may remain, all still live"
24607      );
24608      assert!(state.unowned_sequences.is_empty());
24609      assert_eq!(state.owner_sequences.len(), 1);
24610      assert_eq!(
24611        state.owner_sequences.get(&owner).map(VecDeque::len),
24612        Some(3)
24613      );
24614    }
24615    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24616    assert!(
24617      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24618      "cancelled unowned publications must not survive in availability predicates"
24619    );
24620    drop(runnable);
24621  }
24622
24623  #[cfg(not(target_family = "wasm"))]
24624  #[test]
24625  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24626    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24627
24628    if std::env::var_os(CHILD_ENV).is_some() {
24629      struct PanicWake;
24630      impl std::task::Wake for PanicWake {
24631        fn wake(self: Arc<Self>) {
24632          panic!("intentional dependency waker panic");
24633        }
24634      }
24635
24636      struct PanicOnDrop;
24637      impl Drop for PanicOnDrop {
24638        fn drop(&mut self) {
24639          panic!("intentional buffered blocking result destructor panic");
24640        }
24641      }
24642
24643      let parent = Arc::new(TaskDependency::default());
24644      let dependency = BlockingDependency {
24645        job: BlockingJobId(u64::MAX - 1),
24646        owner: None,
24647      };
24648      // The handle is built by hand rather than by polling, so hand it the
24649      // publication token a Pending poll would have recorded; detach retires
24650      // the entry by sequence.
24651      let sequence = parent.set_owned(dependency);
24652      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24653      let _context = TaskDependencyGuard::enter(&parent);
24654      let (sender, receiver) = oneshot::channel();
24655      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24656        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24657      ));
24658      drop(JoinHandle(JoinHandleInner::Blocking {
24659        receiver,
24660        dependency,
24661        published: PublishedBlockingDependencies {
24662          first: Some(PublishedBlockingDependency {
24663            state: Arc::downgrade(&parent),
24664            sequence,
24665          }),
24666          rest: Vec::new(),
24667        },
24668      }));
24669      assert_eq!(parent.get_dependency(), None);
24670      return;
24671    }
24672
24673    let output = std::process::Command::new(std::env::current_exe().unwrap())
24674      .arg("--exact")
24675      .arg(
24676        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24677      )
24678      .arg("--nocapture")
24679      .env(CHILD_ENV, "1")
24680      .output()
24681      .expect("the subprocess test must start");
24682    assert!(
24683      output.status.success(),
24684      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24685      output.status.code(),
24686      String::from_utf8_lossy(&output.stdout),
24687      String::from_utf8_lossy(&output.stderr)
24688    );
24689  }
24690
24691  #[cfg(napi_runtime_os_threads)]
24692  #[test]
24693  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24694    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
24695    // that does NOT own a counted blocking slot and re-enters `block_on`
24696    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
24697    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
24698    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
24699    // driver could use the over-cap fallback, the inner jobs would run while
24700    // both holders still occupy slots and push peak active blocking above 2.
24701    // The cap must be honored AND everything must still complete.
24702    use std::sync::{Barrier, mpsc};
24703    use std::time::Duration;
24704
24705    let (done_tx, done_rx) = mpsc::channel();
24706    let peak_out = Arc::new(Mutex::new(None));
24707    let peak_slot = Arc::clone(&peak_out);
24708    let runner = std::thread::spawn(move || {
24709      let metrics = Arc::new(RuntimeMetrics::default());
24710      let options = RuntimeOptions {
24711        flavor: RuntimeFlavor::MultiThread,
24712        worker_threads: 4,
24713        max_blocking_tasks: 2,
24714        thread_name_prefix: "rd1-cap".to_string(),
24715        park_deadline: None,
24716        drain_linger: DEFAULT_DRAIN_LINGER,
24717      };
24718      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24719
24720      let holder_count = 2usize; // saturates max_blocking_tasks
24721      let driver_count = 2usize;
24722
24723      // Holders signal once they hold a counted slot, then wait to be released.
24724      let (held_tx, held_rx) = mpsc::channel();
24725      let release = Arc::new(Barrier::new(holder_count + 1));
24726
24727      let mut holder_handles = Vec::new();
24728      for _ in 0..holder_count {
24729        let held_tx = held_tx.clone();
24730        let release = Arc::clone(&release);
24731        let handle = executor.schedule_blocking(move || {
24732          held_tx.send(()).unwrap();
24733          release.wait();
24734          0usize
24735        });
24736        holder_handles.push(handle);
24737      }
24738      // Wait until both holders hold their slots (cap fully saturated).
24739      for _ in 0..holder_count {
24740        held_rx.recv().unwrap();
24741      }
24742
24743      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
24744      // an inner spawn_blocking.
24745      let mut driver_tasks = Vec::new();
24746      for _ in 0..driver_count {
24747        let exec = Arc::clone(&executor);
24748        let body = async move {
24749          let inner_exec = Arc::clone(&exec);
24750          let mut inner = std::pin::pin!(async move {
24751            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24752            assert_eq!(v, 5);
24753          });
24754          exec.block_on(inner.as_mut());
24755        };
24756        let scheduler = Arc::clone(&executor);
24757        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24758        executor.schedule(runnable);
24759        driver_tasks.push(task);
24760      }
24761
24762      // Let the driver tasks reach block_on. They must park until a holder
24763      // frees a slot instead of running their inner jobs over the cap.
24764      std::thread::sleep(Duration::from_millis(300));
24765
24766      // Release the holders so the inner jobs can run within the cap.
24767      release.wait();
24768
24769      for task in driver_tasks {
24770        futures::executor::block_on(task);
24771      }
24772      for handle in holder_handles {
24773        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24774      }
24775
24776      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24777      let _ = done_tx.send(());
24778    });
24779
24780    match done_rx.recv_timeout(Duration::from_secs(10)) {
24781      Ok(()) => runner.join().unwrap(),
24782      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24783    }
24784    let peak = peak_out.lock().unwrap().expect("peak captured");
24785    assert!(
24786      peak <= 2,
24787      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24788    );
24789  }
24790
24791  #[cfg(napi_runtime_os_threads)]
24792  #[test]
24793  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24794    // Blocking-owner isolation invariant: a counted blocking OWNER that
24795    // re-enters cooperative `block_on` can drive a plain queued runnable via
24796    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
24797    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
24798    // the cap is saturated, the inner job must wait for a slot instead of
24799    // borrowing the owner's token.
24800    //
24801    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
24802    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
24803    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
24804    // drainers, no replacement drainer can be spawned, so O itself runs R via
24805    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
24806    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
24807    // runs within the cap and the peak remains 2. Everything must still
24808    // complete.
24809    use std::sync::{Barrier, mpsc};
24810    use std::time::Duration;
24811
24812    let (done_tx, done_rx) = mpsc::channel();
24813    let peak_out = Arc::new(Mutex::new(None));
24814    let peak_slot = Arc::clone(&peak_out);
24815    let runner = std::thread::spawn(move || {
24816      let metrics = Arc::new(RuntimeMetrics::default());
24817      let options = RuntimeOptions {
24818        flavor: RuntimeFlavor::MultiThread,
24819        worker_threads: 2,
24820        max_blocking_tasks: 2,
24821        thread_name_prefix: "rd1-owner-runnable".to_string(),
24822        park_deadline: None,
24823        drain_linger: DEFAULT_DRAIN_LINGER,
24824      };
24825      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24826
24827      let saturated = Arc::new(Barrier::new(2));
24828      let release = Arc::new(Barrier::new(2));
24829
24830      // Holder H2: holds the second counted slot until released. Not the driver.
24831      let h2_saturated = Arc::clone(&saturated);
24832      let h2_release = Arc::clone(&release);
24833      let h2 = executor.schedule_blocking(move || {
24834        h2_saturated.wait();
24835        h2_release.wait();
24836        0usize
24837      });
24838
24839      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
24840      // spawns plain runnable R and drives it cooperatively via `block_on`.
24841      let o_exec = Arc::clone(&executor);
24842      let o_saturated = Arc::clone(&saturated);
24843      let o = executor.schedule_blocking(move || {
24844        o_saturated.wait(); // both slots held -> cap saturated
24845
24846        let r_exec = Arc::clone(&o_exec);
24847        let body = async move {
24848          let inner_exec = Arc::clone(&r_exec);
24849          let mut inner = std::pin::pin!(async move {
24850            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24851            assert_eq!(v, 5);
24852          });
24853          r_exec.block_on(inner.as_mut());
24854        };
24855        let sched = Arc::clone(&o_exec);
24856        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24857        o_exec.schedule(runnable);
24858
24859        let mut wait_r = std::pin::pin!(async move {
24860          task.await;
24861        });
24862        o_exec.block_on(wait_r.as_mut());
24863        1usize
24864      });
24865
24866      // Give O time to drive R into its inner `block_on`; R must be parked
24867      // waiting for a slot.
24868      std::thread::sleep(Duration::from_millis(300));
24869      // Release H2 so the inner job can run within the cap.
24870      release.wait();
24871
24872      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24873      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24874
24875      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24876      let _ = done_tx.send(());
24877    });
24878
24879    match done_rx.recv_timeout(Duration::from_secs(10)) {
24880      Ok(()) => runner.join().unwrap(),
24881      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24882    }
24883    let peak = peak_out.lock().unwrap().expect("peak captured");
24884    assert!(
24885      peak <= 2,
24886      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24887    );
24888  }
24889
24890  #[cfg(napi_runtime_os_threads)]
24891  #[test]
24892  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
24893    // The over-cap fallback must select the exact pending JoinHandle job, not
24894    // the front of the shared blocking queue.
24895    //
24896    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
24897    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
24898    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
24899    // The exact dependency must skip U and run D. Because U can only run once
24900    // a real slot frees after O completes, D must STRICTLY precede U.
24901    use std::sync::{Barrier, mpsc};
24902
24903    let (done_tx, done_rx) = mpsc::channel();
24904    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
24905    let order_read = Arc::clone(&order_out);
24906    let runner = std::thread::spawn(move || {
24907      let metrics = Arc::new(RuntimeMetrics::default());
24908      let options = RuntimeOptions {
24909        flavor: RuntimeFlavor::MultiThread,
24910        worker_threads: 2,
24911        max_blocking_tasks: 2,
24912        thread_name_prefix: "rd1-unrelated".to_string(),
24913        park_deadline: None,
24914        drain_linger: DEFAULT_DRAIN_LINGER,
24915      };
24916      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24917
24918      // O and H signal once they each hold a counted slot (cap saturated), then
24919      // wait so U is queued before O nests. H stays held until the very end.
24920      let (held_tx, held_rx) = mpsc::channel();
24921      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
24922      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
24923
24924      // Holder H: holds slot 2 for the whole test.
24925      let h_held = held_tx.clone();
24926      let h_rel = Arc::clone(&h_release);
24927      let h = executor.schedule_blocking(move || {
24928        h_held.send(()).unwrap();
24929        h_rel.wait();
24930        0usize
24931      });
24932
24933      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
24934      // its OWN descendant D.
24935      let o_exec = Arc::clone(&executor);
24936      let o_ready = Arc::clone(&u_ready);
24937      let o_order = Arc::clone(&order_out);
24938      let o = executor.schedule_blocking(move || {
24939        held_tx.send(()).unwrap();
24940        o_ready.wait(); // wait until U is queued ahead of D
24941        let inner_exec = Arc::clone(&o_exec);
24942        let d_order = Arc::clone(&o_order);
24943        let mut inner = std::pin::pin!(async move {
24944          let v = inner_exec
24945            .schedule_blocking(move || {
24946              d_order.lock().unwrap().push("D");
24947              5usize
24948            })
24949            .await
24950            .unwrap();
24951          assert_eq!(v, 5);
24952        });
24953        o_exec.block_on(inner.as_mut());
24954        1usize
24955      });
24956
24957      // Wait until BOTH O and H hold their slots -> cap saturated.
24958      held_rx.recv().unwrap();
24959      held_rx.recv().unwrap();
24960
24961      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
24962      let u_order = Arc::clone(&order_out);
24963      let u = executor.schedule_blocking(move || {
24964        u_order.lock().unwrap().push("U");
24965        2usize
24966      });
24967
24968      // Release O to nest now that U is queued ahead of D.
24969      u_ready.wait();
24970
24971      // O makes progress by running D over cap; U only runs after O frees a slot.
24972      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24973      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24974
24975      // Release H and finish.
24976      h_release.wait();
24977      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24978
24979      let _ = done_tx.send(());
24980    });
24981
24982    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24983      Ok(()) => runner.join().unwrap(),
24984      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24985    }
24986    let order = order_read.lock().unwrap().clone();
24987    assert_eq!(
24988      order,
24989      vec!["D", "U"],
24990      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24991    );
24992  }
24993
24994  #[cfg(napi_runtime_os_threads)]
24995  #[test]
24996  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24997    use std::sync::{Barrier, mpsc};
24998
24999    let opts = RuntimeOptions {
25000      flavor: RuntimeFlavor::MultiThread,
25001      worker_threads: 2,
25002      max_blocking_tasks: 2,
25003      thread_name_prefix: "rd1-scope".to_string(),
25004      park_deadline: None,
25005      drain_linger: DEFAULT_DRAIN_LINGER,
25006    };
25007    let exec1 =
25008      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25009    let exec2 =
25010      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25011    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25012
25013    let holder_count = opts.max_blocking_tasks;
25014    let (held_tx, held_rx) = mpsc::channel();
25015    let release = Arc::new(Barrier::new(holder_count + 1));
25016    let mut holder_handles = Vec::new();
25017    for _ in 0..holder_count {
25018      let held_tx = held_tx.clone();
25019      let release = Arc::clone(&release);
25020      holder_handles.push(exec2.schedule_blocking(move || {
25021        held_tx.send(()).unwrap();
25022        release.wait();
25023        0usize
25024      }));
25025    }
25026    for _ in 0..holder_count {
25027      held_rx.recv().unwrap();
25028    }
25029    assert_eq!(
25030      exec2.active_blocking.load(Ordering::Acquire),
25031      opts.max_blocking_tasks
25032    );
25033
25034    let exact = BlockingJobId(u64::MAX);
25035    let ran = Arc::new(AtomicBool::new(false));
25036    let ran_job = Arc::clone(&ran);
25037    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
25038      id: exact,
25039      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25040    });
25041    let dependency = TaskDependency::default();
25042
25043    let token1 = exec1.fresh_owner_token();
25044    {
25045      let _owner = BlockingOwnerGuard::enter(Some(token1));
25046      dependency.set_owned(BlockingDependency {
25047        job: exact,
25048        owner: Some(token1),
25049      });
25050      assert!(
25051        !exec2.try_owned_blocking_over_cap(&dependency),
25052        "a foreign-executor ambient token must fail the executor gate"
25053      );
25054    }
25055
25056    let token2 = exec2.fresh_owner_token();
25057    {
25058      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
25059      let _owner = BlockingOwnerGuard::enter(Some(token2));
25060      dependency.set_owned(BlockingDependency {
25061        job: BlockingJobId(u64::MAX - 1),
25062        owner: Some(token2),
25063      });
25064      assert!(
25065        !exec2.try_owned_blocking_over_cap(&dependency),
25066        "a foreign or unrelated dependency id must fail the exact-job gate"
25067      );
25068      dependency.set_owned(BlockingDependency {
25069        job: BlockingJobId(exact.0 - 2),
25070        owner: Some(token2),
25071      });
25072      assert!(
25073        !exec2.try_owned_blocking_over_cap(&dependency),
25074        "a different job id must not consume the queued dependency"
25075      );
25076      dependency.set_owned(BlockingDependency {
25077        job: exact,
25078        owner: Some(token2),
25079      });
25080      assert!(
25081        exec2.try_owned_blocking_over_cap(&dependency),
25082        "the exact awaited job must run over cap"
25083      );
25084    }
25085    assert!(
25086      ran.load(Ordering::SeqCst),
25087      "the exact dependency must have run"
25088    );
25089
25090    release.wait();
25091    for handle in holder_handles {
25092      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
25093    }
25094  }
25095
25096  #[cfg(napi_runtime_os_threads)]
25097  #[test]
25098  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
25099    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
25100    let first_owner = executor.fresh_owner_token();
25101    let second_owner = executor.fresh_owner_token();
25102    let _first_owner =
25103      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
25104    let _second_owner =
25105      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
25106    let exact = BlockingJobId(u64::MAX - 30);
25107    let dependency = TaskDependency::default();
25108    dependency.set_owned(BlockingDependency {
25109      job: exact,
25110      owner: None,
25111    });
25112    let ran = Arc::new(AtomicBool::new(false));
25113    let ran_job = Arc::clone(&ran);
25114    executor
25115      .blocking_queue
25116      .lock()
25117      .unwrap()
25118      .push(QueuedBlocking {
25119        id: exact,
25120        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25121      });
25122
25123    assert!(
25124      !executor.try_owned_blocking_over_cap(&dependency),
25125      "an untagged driver must not choose between multiple active owner frames"
25126    );
25127    assert!(!ran.load(Ordering::SeqCst));
25128    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
25129    assert!(
25130      queued.is_some(),
25131      "ambiguous ownership must leave the exact job queued"
25132    );
25133  }
25134
25135  #[cfg(napi_runtime_os_threads)]
25136  #[test]
25137  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
25138    let executor = multi_thread_executor(2, None, "live-parked-dependency");
25139    let owner = executor.fresh_owner_token();
25140    let dependency = TaskDependency::new(u64::MAX, Some(owner));
25141    let stale = BlockingJobId(u64::MAX - 10);
25142    let live = BlockingJobId(u64::MAX - 9);
25143    dependency.set_owned(BlockingDependency {
25144      job: stale,
25145      owner: Some(owner),
25146    });
25147    dependency.begin_poll();
25148    dependency.set_owned(BlockingDependency {
25149      job: live,
25150      owner: Some(owner),
25151    });
25152
25153    let stale_ran = Arc::new(AtomicBool::new(false));
25154    let stale_job_ran = Arc::clone(&stale_ran);
25155    let live_ran = Arc::new(AtomicBool::new(false));
25156    let live_job_ran = Arc::clone(&live_ran);
25157    {
25158      let mut queue = executor
25159        .blocking_queue
25160        .lock()
25161        .unwrap_or_else(std::sync::PoisonError::into_inner);
25162      queue.push(QueuedBlocking {
25163        id: stale,
25164        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
25165      });
25166      queue.push(QueuedBlocking {
25167        id: live,
25168        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
25169      });
25170    }
25171
25172    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25173    let _owner = BlockingOwnerGuard::enter(Some(owner));
25174    assert!(
25175      executor.try_owned_blocking_over_cap(&dependency),
25176      "the owner lane must claim the currently published dependency"
25177    );
25178    assert!(
25179      live_ran.load(Ordering::SeqCst),
25180      "the live dependency must run"
25181    );
25182    assert!(
25183      !stale_ran.load(Ordering::SeqCst),
25184      "the stale registered id must remain queued"
25185    );
25186    let remaining = executor
25187      .blocking_queue
25188      .lock()
25189      .unwrap_or_else(std::sync::PoisonError::into_inner)
25190      .jobs
25191      .keys()
25192      .copied()
25193      .collect::<Vec<_>>();
25194    assert_eq!(remaining, vec![stale]);
25195  }
25196
25197  #[cfg(napi_runtime_os_threads)]
25198  #[test]
25199  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
25200    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
25201    let owner = executor.fresh_owner_token();
25202    let child = TaskDependency::new_task(u64::MAX);
25203    let parent = TaskDependency::new(u64::MAX, Some(owner));
25204    let exact = BlockingDependency {
25205      job: BlockingJobId(u64::MAX - 31),
25206      owner: Some(owner),
25207    };
25208
25209    child.set_owned(exact);
25210    let stale = child
25211      .get()
25212      .expect("the child must publish its direct dependency");
25213    child.begin_poll();
25214    parent.set_borrowed(&stale);
25215
25216    let ran = Arc::new(AtomicBool::new(false));
25217    let ran_job = Arc::clone(&ran);
25218    executor
25219      .blocking_queue
25220      .lock()
25221      .unwrap()
25222      .push(QueuedBlocking {
25223        id: exact.job,
25224        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25225      });
25226    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25227    let _owner = BlockingOwnerGuard::enter(Some(owner));
25228    assert!(
25229      !executor.try_owned_blocking_over_cap(&parent),
25230      "a publication copied before child cancellation must remain cancelled after parent install"
25231    );
25232    assert!(!ran.load(Ordering::SeqCst));
25233
25234    child.set_owned(exact);
25235    parent.set_borrowed(
25236      &child
25237        .get()
25238        .expect("the replacement publication must be live"),
25239    );
25240    assert!(
25241      executor.try_owned_blocking_over_cap(&parent),
25242      "a fresh child publication must still authorize its exact queued job"
25243    );
25244    assert!(ran.load(Ordering::SeqCst));
25245  }
25246
25247  #[cfg(napi_runtime_os_threads)]
25248  #[test]
25249  fn intermediate_repoll_does_not_cancel_child_source_claim() {
25250    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25251    let owner = executor.fresh_owner_token();
25252    let child = TaskDependency::new_task(u64::MAX);
25253    let intermediate = TaskDependency::new_task(u64::MAX);
25254    let parent = TaskDependency::new(u64::MAX, Some(owner));
25255    let exact = BlockingDependency {
25256      job: BlockingJobId(u64::MAX - 43),
25257      owner: Some(owner),
25258    };
25259
25260    child.set_owned(exact);
25261    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25262    parent.set_borrowed(
25263      &intermediate
25264        .get()
25265        .expect("the intermediate publication must be live"),
25266    );
25267
25268    intermediate.begin_poll();
25269    assert!(
25270      child.get().is_some(),
25271      "withdrawing an intermediate hop must not cancel the child's source claim"
25272    );
25273    assert!(
25274      parent.get().is_none(),
25275      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25276    );
25277
25278    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25279    parent.set_borrowed(
25280      &intermediate
25281        .get()
25282        .expect("the intermediate must republish the still-live source"),
25283    );
25284    let ran = Arc::new(AtomicBool::new(false));
25285    let ran_job = Arc::clone(&ran);
25286    executor
25287      .blocking_queue
25288      .lock()
25289      .unwrap()
25290      .push(QueuedBlocking {
25291        id: exact.job,
25292        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25293      });
25294
25295    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25296    let _owner = BlockingOwnerGuard::enter(Some(owner));
25297    assert!(
25298      executor.try_owned_blocking_over_cap(&parent),
25299      "a fresh chain through the repolled intermediate must retain the exact source claim"
25300    );
25301    assert!(ran.load(Ordering::SeqCst));
25302  }
25303
25304  #[cfg(napi_runtime_os_threads)]
25305  #[test]
25306  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25307    use std::sync::mpsc;
25308
25309    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25310    let owner = executor.fresh_owner_token();
25311    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25312    let child = Arc::new(TaskDependency::new_task(u64::MAX));
25313    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25314    let exact = BlockingDependency {
25315      job: BlockingJobId(u64::MAX - 44),
25316      owner: Some(owner),
25317    };
25318    child.set_owned(exact);
25319    parent.set_borrowed(&child.get().expect("the child source must be live"));
25320
25321    let ran = Arc::new(AtomicBool::new(false));
25322    let ran_job = Arc::clone(&ran);
25323    executor
25324      .blocking_queue
25325      .lock()
25326      .unwrap()
25327      .push(QueuedBlocking {
25328        id: exact.job,
25329        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25330      });
25331
25332    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25333    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25334    let claimant_executor = Arc::clone(&executor);
25335    let claimant_parent = Arc::clone(&parent);
25336    let claimant = std::thread::spawn(move || {
25337      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25338        *slot.borrow_mut() = Some(Box::new(move || {
25339          claim_entered_tx.send(()).unwrap();
25340          release_claim_rx.recv().unwrap();
25341        }));
25342      });
25343      let _owner = BlockingOwnerGuard::enter(Some(owner));
25344      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25345    });
25346    claim_entered_rx
25347      .recv_timeout(Duration::from_secs(2))
25348      .expect("the exact claimant must hold the shared transition lock");
25349
25350    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25351    let withdrawing_child = Arc::clone(&child);
25352    let withdrawal = std::thread::spawn(move || {
25353      withdrawing_child.begin_poll();
25354      withdrawn_tx.send(()).unwrap();
25355    });
25356    let withdrew_before_claim = withdrawn_rx
25357      .recv_timeout(Duration::from_millis(200))
25358      .is_ok();
25359    release_claim_tx.send(()).unwrap();
25360
25361    assert!(
25362      claimant.join().unwrap(),
25363      "the claim that linearized first must execute the exact job"
25364    );
25365    withdrawal.join().unwrap();
25366    assert!(
25367      !withdrew_before_claim,
25368      "source withdrawal must not interleave after claim validation but before claim commit"
25369    );
25370    assert!(ran.load(Ordering::SeqCst));
25371  }
25372
25373  #[cfg(napi_runtime_os_threads)]
25374  #[test]
25375  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25376    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
25377    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
25378    // executor whose worker set the marker. A worker of executor A that re-enters
25379    // `block_on` on executor B must take the PARKING branch instead -- it owns
25380    // none of B's accounting, so driving B's queue would make it an unaccounted
25381    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
25382    //
25383    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
25384    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
25385    // `block_on` on each executor with the SAME probe shape and observe whether
25386    // the cooperative `run_one` driver actually ran a queued runnable.
25387    //
25388    // The probe future returns `Pending` once (self-waking so the parking branch
25389    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
25390    // `run_one` at least once before the future completes. A single runnable is
25391    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
25392    // no background drainer is ever spawned to steal it). After `block_on`
25393    // returns: the cooperative branch will have driven that runnable
25394    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
25395    // (`runnable_polls == 0`, still queued).
25396    struct PendThenReady {
25397      polls: usize,
25398    }
25399    impl Future for PendThenReady {
25400      type Output = ();
25401      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25402        self.polls += 1;
25403        if self.polls >= 2 {
25404          Poll::Ready(())
25405        } else {
25406          // Self-wake so the parking branch (`futures::executor::block_on`)
25407          // re-polls to completion without any external waker; the cooperative
25408          // branch re-polls unconditionally each loop iteration regardless.
25409          cx.waker().wake_by_ref();
25410          Poll::Pending
25411        }
25412      }
25413    }
25414
25415    // Push one already-resolvable runnable directly onto `target`'s queue (no
25416    // `schedule`, so no drainer is spawned) and detach its task.
25417    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25418      let scheduler = Arc::clone(target);
25419      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25420      target.push_fifo_raw(runnable);
25421      task.detach();
25422    }
25423
25424    let opts = RuntimeOptions {
25425      flavor: RuntimeFlavor::MultiThread,
25426      worker_threads: 2,
25427      max_blocking_tasks: 2,
25428      thread_name_prefix: "rd-onpool-scope".to_string(),
25429      park_deadline: None,
25430      drain_linger: DEFAULT_DRAIN_LINGER,
25431    };
25432    let exec1 =
25433      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25434    let exec2 =
25435      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25436    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25437
25438    // Simulate being on exec1's pool worker for the whole scenario.
25439    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25440
25441    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
25442    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
25443    // touch its queue.
25444    enqueue_runnable(&exec2);
25445    {
25446      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25447      exec2.block_on(probe.as_mut());
25448    }
25449    assert_eq!(
25450      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25451      0,
25452      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25453    );
25454    assert_eq!(
25455      exec2.injector.len(),
25456      1,
25457      "the foreign call must leave exec2's runnable untouched on its queue"
25458    );
25459
25460    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
25461    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
25462    // runnable via `run_one`.
25463    enqueue_runnable(&exec1);
25464    {
25465      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25466      exec1.block_on(probe.as_mut());
25467    }
25468    assert_eq!(
25469      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25470      1,
25471      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25472    );
25473    assert!(
25474      exec1.injector.is_empty(),
25475      "the cooperative branch must have drained exec1's queued runnable"
25476    );
25477
25478    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
25479    // (steal-drain replaces the former `queue.lock().clear()`).
25480    while exec2.steal_one().is_some() {}
25481  }
25482
25483  #[cfg(napi_runtime_os_threads)]
25484  #[test]
25485  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25486    // A long-lived blocking consumer may consume the entire configured
25487    // blocking allowance, but it must not consume the reserve lane required
25488    // by the async producers that feed it.
25489    use std::sync::mpsc;
25490    use std::time::Duration;
25491
25492    const PRODUCER_COUNT: usize = 8;
25493
25494    fn schedule_pool_workload(
25495      executor: &Arc<MultiThreadExecutor>,
25496      tx: &mpsc::Sender<Option<usize>>,
25497      count: usize,
25498    ) {
25499      for i in 0..count {
25500        let tx = tx.clone();
25501        let scheduler = Arc::clone(executor);
25502        let (runnable, task) =
25503          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25504            scheduler.schedule(r);
25505          });
25506        executor.schedule(runnable);
25507        task.detach();
25508      }
25509      let tx = tx.clone();
25510      let scheduler = Arc::clone(executor);
25511      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25512        scheduler.schedule(r)
25513      });
25514      executor.schedule(runnable);
25515      task.detach();
25516    }
25517
25518    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25519    let (done_tx, done_rx) = mpsc::sync_channel(1);
25520    let runner = std::thread::spawn(move || {
25521      let options = RuntimeOptions {
25522        flavor: RuntimeFlavor::MultiThread,
25523        worker_threads: 1,
25524        max_blocking_tasks: 1,
25525        thread_name_prefix: "rd2-pre-fix".to_string(),
25526        park_deadline: None,
25527        drain_linger: DEFAULT_DRAIN_LINGER,
25528      }
25529      .validate()
25530      .expect("production MultiThread options must provide a reserve lane");
25531      let executor =
25532        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25533
25534      let (tx, rx) = mpsc::channel::<Option<usize>>();
25535      let (started_tx, started_rx) = mpsc::sync_channel(1);
25536      let handle = executor.schedule_blocking(move || {
25537        started_tx.send(()).unwrap();
25538        let mut sum = 0usize;
25539        while let Ok(msg) = rx.recv() {
25540          match msg {
25541            Some(value) => sum += value,
25542            None => break,
25543          }
25544        }
25545        sum
25546      });
25547      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25548      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25549      let sum = futures::executor::block_on(handle).unwrap();
25550      done_tx.send(sum).unwrap();
25551    });
25552
25553    match done_rx.recv_timeout(Duration::from_secs(10)) {
25554      Ok(sum) => {
25555        runner.join().unwrap();
25556        assert_eq!(sum, expected_sum);
25557      }
25558      Err(error) => {
25559        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25560      }
25561    }
25562  }
25563
25564  #[test]
25565  fn current_thread_blocking_work_completes_inline() {
25566    let metrics = Arc::new(RuntimeMetrics::default());
25567    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25568      CurrentThreadExecutor::new(Arc::clone(&metrics)),
25569    )));
25570    let registration = backend.work.try_register_work().unwrap();
25571
25572    let value =
25573      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25574        .expect("blocking task should complete");
25575
25576    assert_eq!(value, 7);
25577    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25578    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25579  }
25580
25581  #[cfg(napi_runtime_os_threads)]
25582  #[test]
25583  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25584    use std::sync::mpsc;
25585
25586    let controller = Arc::new(current_thread_controller(
25587      "current-thread-blocking-admission",
25588    ));
25589    let backend = controller.backend();
25590    let (first_started_tx, first_started_rx) = mpsc::channel();
25591    let (release_first_tx, release_first_rx) = mpsc::channel();
25592    let first_controller = Arc::clone(&controller);
25593    let first = std::thread::spawn(move || {
25594      let handle = first_controller
25595        .try_spawn_blocking(move || {
25596          first_started_tx.send(()).unwrap();
25597          release_first_rx.recv().unwrap();
25598          1usize
25599        })
25600        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25601      futures::executor::block_on(handle).unwrap()
25602    });
25603    first_started_rx
25604      .recv_timeout(Duration::from_secs(1))
25605      .unwrap();
25606
25607    let (second_started_tx, second_started_rx) = mpsc::channel();
25608    let second_controller = Arc::clone(&controller);
25609    let second = std::thread::spawn(move || {
25610      let handle = second_controller
25611        .try_spawn_blocking(move || {
25612          second_started_tx.send(()).unwrap();
25613          2usize
25614        })
25615        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25616      futures::executor::block_on(handle).unwrap()
25617    });
25618
25619    wait_until("both CurrentThread blocking calls to be accepted", || {
25620      backend
25621        .work
25622        .state
25623        .lock()
25624        .unwrap_or_else(std::sync::PoisonError::into_inner)
25625        .active
25626        == 2
25627    });
25628    assert!(
25629      second_started_rx
25630        .recv_timeout(Duration::from_millis(100))
25631        .is_err(),
25632      "CurrentThread must not execute two blocking closures concurrently"
25633    );
25634
25635    release_first_tx.send(()).unwrap();
25636    second_started_rx
25637      .recv_timeout(Duration::from_secs(1))
25638      .expect("the waiting blocking call must acquire the released lane");
25639    assert_eq!(first.join().unwrap(), 1);
25640    assert_eq!(second.join().unwrap(), 2);
25641    assert_eq!(
25642      controller
25643        .metrics
25644        .blocking_tasks_started
25645        .load(Ordering::Relaxed),
25646      2
25647    );
25648    assert_eq!(
25649      controller
25650        .metrics
25651        .blocking_tasks_completed
25652        .load(Ordering::Relaxed),
25653      2
25654    );
25655    assert_eq!(
25656      controller
25657        .metrics
25658        .active_blocking_tasks
25659        .load(Ordering::Relaxed),
25660      0
25661    );
25662    assert_eq!(
25663      controller
25664        .metrics
25665        .max_active_blocking_tasks
25666        .load(Ordering::Relaxed),
25667      1,
25668      "the public active-lane metric must remain within CurrentThread's reported capacity"
25669    );
25670    controller.shutdown().unwrap();
25671  }
25672
25673  #[cfg(napi_runtime_os_threads)]
25674  #[test]
25675  fn current_thread_nested_blocking_reuses_the_active_lane() {
25676    use std::sync::mpsc;
25677
25678    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25679    let (done_tx, done_rx) = mpsc::channel();
25680    let runner_controller = Arc::clone(&controller);
25681    let runner = std::thread::spawn(move || {
25682      let nested_controller = Arc::clone(&runner_controller);
25683      let outer = runner_controller
25684        .try_spawn_blocking(move || {
25685          let inner = nested_controller
25686            .try_spawn_blocking(|| 41usize)
25687            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25688          futures::executor::block_on(inner).unwrap() + 1
25689        })
25690        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25691      done_tx
25692        .send(futures::executor::block_on(outer).unwrap())
25693        .unwrap();
25694    });
25695
25696    assert_eq!(
25697      done_rx
25698        .recv_timeout(Duration::from_secs(2))
25699        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25700      42
25701    );
25702    runner.join().unwrap();
25703    assert_eq!(
25704      controller
25705        .metrics
25706        .blocking_tasks_started
25707        .load(Ordering::Relaxed),
25708      2
25709    );
25710    assert_eq!(
25711      controller
25712        .metrics
25713        .blocking_tasks_completed
25714        .load(Ordering::Relaxed),
25715      2
25716    );
25717    assert_eq!(
25718      controller
25719        .metrics
25720        .active_blocking_tasks
25721        .load(Ordering::Relaxed),
25722      0
25723    );
25724    assert_eq!(
25725      controller
25726        .metrics
25727        .max_active_blocking_tasks
25728        .load(Ordering::Relaxed),
25729      1,
25730      "nested work borrows the outer lane instead of reporting a second physical lane"
25731    );
25732    controller.shutdown().unwrap();
25733  }
25734
25735  #[cfg(napi_runtime_os_threads)]
25736  #[test]
25737  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25738    use std::sync::mpsc;
25739
25740    struct HotFuture {
25741      stop: Arc<AtomicBool>,
25742    }
25743
25744    impl Future for HotFuture {
25745      type Output = ();
25746
25747      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25748        if self.stop.load(Ordering::SeqCst) {
25749          Poll::Ready(())
25750        } else {
25751          cx.waker().wake_by_ref();
25752          Poll::Pending
25753        }
25754      }
25755    }
25756
25757    let stop = Arc::new(AtomicBool::new(false));
25758    let (ready_tx, ready_rx) = mpsc::channel();
25759    let (exact_tx, exact_rx) = mpsc::channel();
25760    let (done_tx, done_rx) = mpsc::channel();
25761    let runner_stop = Arc::clone(&stop);
25762    let runner = std::thread::spawn(move || {
25763      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25764        RuntimeMetrics::default(),
25765      )));
25766      let owner = executor.fresh_owner_token();
25767      let job = BlockingJobId(u64::MAX - 70);
25768      let (result_tx, result_rx) = oneshot::channel();
25769      {
25770        let mut state = executor
25771          .blocking_admission
25772          .state
25773          .lock()
25774          .unwrap_or_else(std::sync::PoisonError::into_inner);
25775        state.owner = Some(owner);
25776        let exact_stop = Arc::clone(&runner_stop);
25777        state.queue.push(QueuedBlocking {
25778          id: job,
25779          run: Box::new(move || {
25780            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25781            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25782          }),
25783        });
25784      }
25785      let exact = JoinHandle(JoinHandleInner::Blocking {
25786        receiver: result_rx,
25787        dependency: BlockingDependency {
25788          job,
25789          owner: Some(owner),
25790        },
25791        published: PublishedBlockingDependencies::default(),
25792      });
25793      let _owner = executor.enter_blocking_owner(owner, false);
25794
25795      let scheduler = Arc::clone(&executor);
25796      let hot_stop = Arc::clone(&runner_stop);
25797      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25798        scheduler.schedule(runnable);
25799      });
25800      executor.schedule(runnable);
25801      ready_tx.send(()).unwrap();
25802
25803      let mut value = None;
25804      {
25805        let mut future = std::pin::pin!(async {
25806          value = Some(exact.await.unwrap());
25807          hot_task.await;
25808        });
25809        assert_eq!(
25810          executor.block_on(future.as_mut()),
25811          BlockOnOutcome::Completed
25812        );
25813      }
25814      executor
25815        .blocking_admission
25816        .state
25817        .lock()
25818        .unwrap_or_else(std::sync::PoisonError::into_inner)
25819        .owner = None;
25820      done_tx.send(value.unwrap()).unwrap();
25821    });
25822
25823    ready_rx
25824      .recv_timeout(Duration::from_secs(2))
25825      .expect("the fairness driver must start");
25826    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25827    stop.store(true, Ordering::SeqCst);
25828    assert_eq!(
25829      done_rx
25830        .recv_timeout(Duration::from_secs(2))
25831        .expect("the CurrentThread driver must retire"),
25832      41
25833    );
25834    runner.join().unwrap();
25835    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25836    assert_eq!(
25837      exact_before_stop,
25838      Some(true),
25839      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25840    );
25841  }
25842
25843  #[cfg(napi_runtime_os_threads)]
25844  #[test]
25845  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25846    use std::sync::mpsc;
25847
25848    let controller = Arc::new(current_thread_controller(
25849      "current-thread-cross-driver-blocking",
25850    ));
25851    let backend = controller.backend();
25852    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25853      panic!("configured CurrentThread backend must create a CurrentThread executor");
25854    };
25855    let executor = Arc::clone(executor);
25856    drop(backend);
25857
25858    let (release_driver_tx, release_driver_rx) = oneshot::channel();
25859    let (driver_started_tx, driver_started_rx) = mpsc::channel();
25860    let driver_controller = Arc::clone(&controller);
25861    let driver = std::thread::spawn(move || {
25862      driver_started_tx.send(std::thread::current().id()).unwrap();
25863      block_on_with_controller(&driver_controller, async {
25864        release_driver_rx
25865          .await
25866          .expect("driver release sender must remain alive");
25867      });
25868    });
25869    let driver_thread = driver_started_rx
25870      .recv_timeout(Duration::from_secs(1))
25871      .unwrap();
25872    wait_until("the secondary CurrentThread driver to park", || {
25873      executor
25874        .stop
25875        .parkers
25876        .lock()
25877        .unwrap_or_else(std::sync::PoisonError::into_inner)
25878        .iter()
25879        .filter_map(Weak::upgrade)
25880        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25881    });
25882
25883    let (child_polled_tx, child_polled_rx) = mpsc::channel();
25884    let (release_child_tx, release_child_rx) = mpsc::channel();
25885    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25886    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
25887    let (owner_done_tx, owner_done_rx) = mpsc::channel();
25888    let owner_controller = Arc::clone(&controller);
25889    let owner = std::thread::spawn(move || {
25890      let owner_thread = std::thread::current().id();
25891      let await_controller = Arc::clone(&owner_controller);
25892      let child_controller = Arc::clone(&owner_controller);
25893      let outer = owner_controller
25894        .try_spawn_blocking(move || {
25895          let spawn_controller = Arc::clone(&child_controller);
25896          let child = spawn_controller
25897            .try_spawn(async move {
25898              child_polled_tx.send(std::thread::current().id()).unwrap();
25899              release_child_rx.recv().unwrap();
25900              child_controller
25901                .try_spawn_blocking(move || {
25902                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
25903                  41usize
25904                })
25905                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
25906                .await
25907                .unwrap()
25908                + 1
25909            })
25910            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25911
25912          assert_eq!(
25913            child_polled_rx
25914              .recv_timeout(Duration::from_secs(1))
25915              .unwrap(),
25916            driver_thread,
25917            "the secondary driver must own the child poll that reaches blocking admission"
25918          );
25919          owner_awaiting_tx.send(owner_thread).unwrap();
25920          block_on_with_controller(&await_controller, child).unwrap()
25921        })
25922        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25923      owner_done_tx
25924        .send(futures::executor::block_on(outer))
25925        .unwrap();
25926    });
25927
25928    let owner_thread = owner_awaiting_rx
25929      .recv_timeout(Duration::from_secs(1))
25930      .unwrap();
25931    wait_until(
25932      "the blocking owner to park while awaiting the cross-driver child",
25933      || {
25934        executor
25935          .stop
25936          .parkers
25937          .lock()
25938          .unwrap_or_else(std::sync::PoisonError::into_inner)
25939          .iter()
25940          .filter_map(Weak::upgrade)
25941          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25942      },
25943    );
25944    release_child_tx.send(()).unwrap();
25945
25946    assert_eq!(
25947      blocking_ran_rx
25948        .recv_timeout(Duration::from_secs(2))
25949        .unwrap(),
25950      owner_thread,
25951      "the exact descendant must be serviced by the lane-owning driver"
25952    );
25953    assert_eq!(
25954      owner_done_rx
25955        .recv_timeout(Duration::from_secs(2))
25956        .expect("cross-driver blocking dependency must not deadlock")
25957        .unwrap(),
25958      42
25959    );
25960    release_driver_tx.send(()).unwrap();
25961    owner.join().unwrap();
25962    driver.join().unwrap();
25963    controller.shutdown().unwrap();
25964    assert_eq!(
25965      controller
25966        .metrics
25967        .blocking_tasks_started
25968        .load(Ordering::Relaxed),
25969      2
25970    );
25971    assert_eq!(
25972      controller
25973        .metrics
25974        .blocking_tasks_completed
25975        .load(Ordering::Relaxed),
25976      2
25977    );
25978    assert_eq!(
25979      controller
25980        .metrics
25981        .active_blocking_tasks
25982        .load(Ordering::Relaxed),
25983      0
25984    );
25985    assert_eq!(
25986      controller
25987        .metrics
25988        .max_active_blocking_tasks
25989        .load(Ordering::Relaxed),
25990      1,
25991      "cross-driver dependency service must reuse the owner's physical lane"
25992    );
25993  }
25994
25995  #[cfg(napi_runtime_os_threads)]
25996  #[test]
25997  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25998    use std::sync::mpsc;
25999
26000    let controller = Arc::new(current_thread_controller(
26001      "current-thread-blocking-stop-race",
26002    ));
26003    let backend = controller.backend();
26004    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26005      panic!("configured CurrentThread backend must create a CurrentThread executor");
26006    };
26007    let executor = Arc::clone(executor);
26008    drop(backend);
26009
26010    let ran = Arc::new(AtomicBool::new(false));
26011    let blocking_ran = Arc::clone(&ran);
26012    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
26013    let (release_admission_tx, release_admission_rx) = mpsc::channel();
26014    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
26015    let submit_controller = Arc::clone(&controller);
26016    let submitter = std::thread::spawn(move || {
26017      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26018        *slot.borrow_mut() = Some(Box::new(move || {
26019          admission_locked_tx.send(()).unwrap();
26020          release_admission_rx.recv().unwrap();
26021        }));
26022      });
26023      let handle = submit_controller
26024        .try_spawn_blocking(move || {
26025          blocking_ran.store(true, Ordering::SeqCst);
26026        })
26027        .unwrap_or_else(|_| {
26028          panic!("the submission linearized before shutdown and must be accepted")
26029        });
26030      blocking_result_tx
26031        .send(futures::executor::block_on(handle))
26032        .unwrap();
26033    });
26034    admission_locked_rx
26035      .recv_timeout(Duration::from_secs(2))
26036      .expect("the blocking submitter must pause while holding the admission lock");
26037
26038    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
26039    let shutdown_controller = Arc::clone(&controller);
26040    let shutdown = std::thread::spawn(move || {
26041      shutdown_result_tx
26042        .send(shutdown_controller.shutdown())
26043        .unwrap();
26044    });
26045    wait_until("CurrentThread shutdown to publish generation stop", || {
26046      executor.stop.is_stopping()
26047    });
26048    release_admission_tx.send(()).unwrap();
26049
26050    let blocking_error = blocking_result_rx
26051      .recv_timeout(Duration::from_secs(2))
26052      .expect("the admitted blocking handle must resolve")
26053      .expect_err("stop publication must reject the blocking lane claim");
26054    assert_eq!(
26055      blocking_error.to_string(),
26056      "the async runtime stopped before the blocking task could start"
26057    );
26058    assert!(
26059      !ran.load(Ordering::SeqCst),
26060      "blocking work must not start after stop publication"
26061    );
26062    shutdown_result_rx
26063      .recv_timeout(Duration::from_secs(2))
26064      .expect("shutdown must finish after the rejected admission retires")
26065      .unwrap();
26066    submitter.join().unwrap();
26067    shutdown.join().unwrap();
26068  }
26069
26070  #[cfg(napi_runtime_os_threads)]
26071  #[test]
26072  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
26073    use std::sync::mpsc;
26074
26075    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26076      RuntimeMetrics::default(),
26077    )));
26078    let ran = Arc::new(AtomicBool::new(false));
26079    let job_ran = Arc::clone(&ran);
26080    {
26081      let mut state = executor
26082        .blocking_admission
26083        .state
26084        .lock()
26085        .unwrap_or_else(std::sync::PoisonError::into_inner);
26086      state.queue.push(QueuedBlocking {
26087        id: BlockingJobId(u64::MAX - 71),
26088        run: Box::new(move || {
26089          job_ran.store(true, Ordering::SeqCst);
26090        }),
26091      });
26092    }
26093
26094    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26095    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26096    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26097    let claim_executor = Arc::clone(&executor);
26098    let claimant = std::thread::spawn(move || {
26099      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26100        *slot.borrow_mut() = Some(Box::new(move || {
26101          claim_locked_tx.send(()).unwrap();
26102          release_claim_rx.recv().unwrap();
26103        }));
26104      });
26105      claim_result_tx
26106        .send(claim_executor.run_one_blocking())
26107        .unwrap();
26108    });
26109    claim_locked_rx
26110      .recv_timeout(Duration::from_secs(2))
26111      .expect("the FIFO claimant must pause while holding the admission lock");
26112
26113    executor.stop.begin_shutdown();
26114    release_claim_tx.send(()).unwrap();
26115    assert!(
26116      !claim_result_rx
26117        .recv_timeout(Duration::from_secs(2))
26118        .expect("the stopped FIFO claim must finish"),
26119      "stop publication must reject the queued FIFO claim"
26120    );
26121    claimant.join().unwrap();
26122    assert!(
26123      !ran.load(Ordering::SeqCst),
26124      "queued FIFO work must not start after stop publication"
26125    );
26126    assert!(
26127      !executor
26128        .blocking_admission
26129        .state
26130        .lock()
26131        .unwrap_or_else(std::sync::PoisonError::into_inner)
26132        .queue
26133        .is_empty(),
26134      "the rejected FIFO job must remain queued for shutdown cancellation"
26135    );
26136
26137    executor.close_blocking_admission();
26138    assert!(
26139      executor
26140        .blocking_admission
26141        .state
26142        .lock()
26143        .unwrap_or_else(std::sync::PoisonError::into_inner)
26144        .queue
26145        .is_empty()
26146    );
26147  }
26148
26149  #[cfg(napi_runtime_os_threads)]
26150  #[test]
26151  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26152    use std::sync::mpsc;
26153
26154    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26155      RuntimeMetrics::default(),
26156    )));
26157    let ran = Arc::new(AtomicBool::new(false));
26158    let job_ran = Arc::clone(&ran);
26159    {
26160      let mut state = executor
26161        .blocking_admission
26162        .state
26163        .lock()
26164        .unwrap_or_else(std::sync::PoisonError::into_inner);
26165      state.queue.push(QueuedBlocking {
26166        id: BlockingJobId(u64::MAX - 73),
26167        run: Box::new(move || {
26168          job_ran.store(true, Ordering::SeqCst);
26169        }),
26170      });
26171    }
26172
26173    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26174    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26175    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26176    let claim_executor = Arc::clone(&executor);
26177    let claimant = std::thread::spawn(move || {
26178      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26179        *slot.borrow_mut() = Some(Box::new(move || {
26180          claim_locked_tx.send(()).unwrap();
26181          release_claim_rx.recv().unwrap();
26182        }));
26183      });
26184      claim_result_tx
26185        .send(claim_executor.run_one_blocking())
26186        .unwrap();
26187    });
26188    claim_locked_rx
26189      .recv_timeout(Duration::from_secs(2))
26190      .expect("the FIFO claimant must pause while holding the admission lock");
26191
26192    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26193    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26194    let cancelling_executor = Arc::clone(&executor);
26195    let cancellation = std::thread::spawn(move || {
26196      let cancelling = cancelling_executor
26197        .begin_failed_dispatch_cancellation()
26198        .expect("the test must publish terminal dispatch cancellation");
26199      cancellation_published_tx.send(()).unwrap();
26200      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26201      cancellation_done_tx.send(()).unwrap();
26202    });
26203    cancellation_published_rx
26204      .recv_timeout(Duration::from_secs(2))
26205      .expect("terminal dispatch cancellation must publish before queue cancellation");
26206    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26207    assert!(
26208      matches!(
26209        cancellation_done_rx.try_recv(),
26210        Err(mpsc::TryRecvError::Empty)
26211      ),
26212      "terminal cancellation must wait for the blocking-admission lock"
26213    );
26214
26215    release_claim_tx.send(()).unwrap();
26216    assert!(
26217      !claim_result_rx
26218        .recv_timeout(Duration::from_secs(2))
26219        .expect("the cancelled FIFO claim must finish"),
26220      "terminal cancellation publication must reject the queued FIFO claim"
26221    );
26222    cancellation_done_rx
26223      .recv_timeout(Duration::from_secs(2))
26224      .expect("terminal cancellation must drain the rejected FIFO job");
26225    claimant.join().unwrap();
26226    cancellation.join().unwrap();
26227
26228    assert!(
26229      !ran.load(Ordering::SeqCst),
26230      "terminally cancelled FIFO work must not start"
26231    );
26232    assert!(
26233      executor
26234        .blocking_admission
26235        .state
26236        .lock()
26237        .unwrap_or_else(std::sync::PoisonError::into_inner)
26238        .queue
26239        .is_empty(),
26240      "terminal cancellation must drain the rejected FIFO job"
26241    );
26242    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26243  }
26244
26245  #[cfg(napi_runtime_os_threads)]
26246  #[test]
26247  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26248    use std::sync::mpsc;
26249
26250    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26251      RuntimeMetrics::default(),
26252    )));
26253    let owner = executor.fresh_owner_token();
26254    let job = BlockingJobId(u64::MAX - 72);
26255    let ran = Arc::new(AtomicBool::new(false));
26256    let job_ran = Arc::clone(&ran);
26257    {
26258      let mut state = executor
26259        .blocking_admission
26260        .state
26261        .lock()
26262        .unwrap_or_else(std::sync::PoisonError::into_inner);
26263      state.owner = Some(owner);
26264      state.queue.push(QueuedBlocking {
26265        id: job,
26266        run: Box::new(move || {
26267          job_ran.store(true, Ordering::SeqCst);
26268        }),
26269      });
26270    }
26271    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26272    dependency.set_owned(BlockingDependency {
26273      job,
26274      owner: Some(owner),
26275    });
26276
26277    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26278    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26279    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26280    let claim_executor = Arc::clone(&executor);
26281    let claim_dependency = Arc::clone(&dependency);
26282    let claimant = std::thread::spawn(move || {
26283      let _owner = claim_executor.enter_blocking_owner(owner, false);
26284      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26285        *slot.borrow_mut() = Some(Box::new(move || {
26286          claim_locked_tx.send(()).unwrap();
26287          release_claim_rx.recv().unwrap();
26288        }));
26289      });
26290      claim_result_tx
26291        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26292        .unwrap();
26293    });
26294    claim_locked_rx
26295      .recv_timeout(Duration::from_secs(2))
26296      .expect("the owner claimant must pause while holding the admission lock");
26297
26298    executor.stop.begin_shutdown();
26299    release_claim_tx.send(()).unwrap();
26300    assert!(
26301      !claim_result_rx
26302        .recv_timeout(Duration::from_secs(2))
26303        .expect("the stopped owner claim must finish"),
26304      "stop publication must reject the exact-owner claim"
26305    );
26306    claimant.join().unwrap();
26307    assert!(
26308      !ran.load(Ordering::SeqCst),
26309      "queued exact-owner work must not start after stop publication"
26310    );
26311    assert!(
26312      !executor
26313        .blocking_admission
26314        .state
26315        .lock()
26316        .unwrap_or_else(std::sync::PoisonError::into_inner)
26317        .queue
26318        .is_empty(),
26319      "the rejected exact-owner job must remain queued for shutdown cancellation"
26320    );
26321
26322    executor
26323      .blocking_admission
26324      .state
26325      .lock()
26326      .unwrap_or_else(std::sync::PoisonError::into_inner)
26327      .owner = None;
26328    executor.close_blocking_admission();
26329    assert!(
26330      executor
26331        .blocking_admission
26332        .state
26333        .lock()
26334        .unwrap_or_else(std::sync::PoisonError::into_inner)
26335        .queue
26336        .is_empty()
26337    );
26338  }
26339
26340  #[cfg(napi_runtime_os_threads)]
26341  #[test]
26342  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26343    use std::sync::mpsc;
26344
26345    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26346      RuntimeMetrics::default(),
26347    )));
26348    let owner = executor.fresh_owner_token();
26349    let job = BlockingJobId(u64::MAX - 74);
26350    let ran = Arc::new(AtomicBool::new(false));
26351    let job_ran = Arc::clone(&ran);
26352    {
26353      let mut state = executor
26354        .blocking_admission
26355        .state
26356        .lock()
26357        .unwrap_or_else(std::sync::PoisonError::into_inner);
26358      state.owner = Some(owner);
26359      state.queue.push(QueuedBlocking {
26360        id: job,
26361        run: Box::new(move || {
26362          job_ran.store(true, Ordering::SeqCst);
26363        }),
26364      });
26365    }
26366    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26367    dependency.set_owned(BlockingDependency {
26368      job,
26369      owner: Some(owner),
26370    });
26371
26372    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26373    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26374    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26375    let claim_executor = Arc::clone(&executor);
26376    let claim_dependency = Arc::clone(&dependency);
26377    let claimant = std::thread::spawn(move || {
26378      let _owner = claim_executor.enter_blocking_owner(owner, false);
26379      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26380        *slot.borrow_mut() = Some(Box::new(move || {
26381          claim_locked_tx.send(()).unwrap();
26382          release_claim_rx.recv().unwrap();
26383        }));
26384      });
26385      claim_result_tx
26386        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26387        .unwrap();
26388    });
26389    claim_locked_rx
26390      .recv_timeout(Duration::from_secs(2))
26391      .expect("the owner claimant must pause while holding the admission lock");
26392
26393    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26394    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26395    let cancelling_executor = Arc::clone(&executor);
26396    let cancellation = std::thread::spawn(move || {
26397      let cancelling = cancelling_executor
26398        .begin_failed_dispatch_cancellation()
26399        .expect("the test must publish terminal dispatch cancellation");
26400      cancellation_published_tx.send(()).unwrap();
26401      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26402      cancellation_done_tx.send(()).unwrap();
26403    });
26404    cancellation_published_rx
26405      .recv_timeout(Duration::from_secs(2))
26406      .expect("terminal dispatch cancellation must publish before queue cancellation");
26407    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26408    assert!(
26409      matches!(
26410        cancellation_done_rx.try_recv(),
26411        Err(mpsc::TryRecvError::Empty)
26412      ),
26413      "terminal cancellation must wait for the blocking-admission lock"
26414    );
26415
26416    release_claim_tx.send(()).unwrap();
26417    assert!(
26418      !claim_result_rx
26419        .recv_timeout(Duration::from_secs(2))
26420        .expect("the cancelled owner claim must finish"),
26421      "terminal cancellation publication must reject the exact-owner claim"
26422    );
26423    cancellation_done_rx
26424      .recv_timeout(Duration::from_secs(2))
26425      .expect("terminal cancellation must drain the rejected exact-owner job");
26426    claimant.join().unwrap();
26427    cancellation.join().unwrap();
26428
26429    assert!(
26430      !ran.load(Ordering::SeqCst),
26431      "terminally cancelled exact-owner work must not start"
26432    );
26433    assert!(
26434      executor
26435        .blocking_admission
26436        .state
26437        .lock()
26438        .unwrap_or_else(std::sync::PoisonError::into_inner)
26439        .queue
26440        .is_empty(),
26441      "terminal cancellation must drain the rejected exact-owner job"
26442    );
26443    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26444  }
26445
26446  #[cfg(napi_runtime_os_threads)]
26447  #[test]
26448  fn current_thread_shutdown_cancels_waiting_blocking_work() {
26449    use std::sync::mpsc;
26450
26451    struct DropSignal(mpsc::Sender<()>);
26452
26453    impl Drop for DropSignal {
26454      fn drop(&mut self) {
26455        let _ = self.0.send(());
26456      }
26457    }
26458
26459    let controller = Arc::new(current_thread_controller(
26460      "current-thread-blocking-shutdown",
26461    ));
26462    let backend = controller.backend();
26463    let (first_started_tx, first_started_rx) = mpsc::channel();
26464    let (release_first_tx, release_first_rx) = mpsc::channel();
26465    let first_controller = Arc::clone(&controller);
26466    let first = std::thread::spawn(move || {
26467      let handle = first_controller
26468        .try_spawn_blocking(move || {
26469          first_started_tx.send(()).unwrap();
26470          release_first_rx.recv().unwrap();
26471        })
26472        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26473      futures::executor::block_on(handle)
26474    });
26475    first_started_rx
26476      .recv_timeout(Duration::from_secs(1))
26477      .unwrap();
26478
26479    let waiting_ran = Arc::new(AtomicBool::new(false));
26480    let waiting_ran_task = Arc::clone(&waiting_ran);
26481    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26482    let waiting_drop = DropSignal(waiting_dropped_tx);
26483    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26484    let waiting_controller = Arc::clone(&controller);
26485    let waiting = std::thread::spawn(move || {
26486      let handle = waiting_controller
26487        .try_spawn_blocking(move || {
26488          let _waiting_drop = waiting_drop;
26489          waiting_ran_task.store(true, Ordering::SeqCst);
26490        })
26491        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26492      waiting_result_tx
26493        .send(futures::executor::block_on(handle))
26494        .unwrap();
26495    });
26496    wait_until("both CurrentThread blocking calls to be accepted", || {
26497      backend
26498        .work
26499        .state
26500        .lock()
26501        .unwrap_or_else(std::sync::PoisonError::into_inner)
26502        .active
26503        == 2
26504    });
26505
26506    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26507    let shutdown_controller = Arc::clone(&controller);
26508    let shutdown = std::thread::spawn(move || {
26509      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26510    });
26511    waiting_dropped_rx
26512      .recv_timeout(Duration::from_secs(1))
26513      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26514    assert!(
26515      !waiting_ran.load(Ordering::SeqCst),
26516      "waiting blocking work must not start"
26517    );
26518    assert!(
26519      waiting_result_rx
26520        .recv_timeout(Duration::from_secs(1))
26521        .unwrap()
26522        .is_err(),
26523      "the waiting blocking handle must resolve as cancelled"
26524    );
26525    assert!(
26526      shutdown_rx
26527        .recv_timeout(Duration::from_millis(100))
26528        .is_err(),
26529      "shutdown must still wait for the blocking closure that already owns the lane"
26530    );
26531
26532    release_first_tx.send(()).unwrap();
26533    first.join().unwrap().unwrap();
26534    waiting.join().unwrap();
26535    shutdown_rx
26536      .recv_timeout(Duration::from_secs(1))
26537      .unwrap()
26538      .unwrap();
26539    shutdown.join().unwrap();
26540    assert_eq!(
26541      controller
26542        .metrics
26543        .blocking_tasks_started
26544        .load(Ordering::Relaxed),
26545      1
26546    );
26547    assert_eq!(
26548      controller
26549        .metrics
26550        .blocking_tasks_completed
26551        .load(Ordering::Relaxed),
26552      1
26553    );
26554    assert_eq!(
26555      controller
26556        .metrics
26557        .active_blocking_tasks
26558        .load(Ordering::Relaxed),
26559      0
26560    );
26561  }
26562
26563  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26564  #[test]
26565  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26566    use std::{panic::panic_any, sync::mpsc};
26567
26568    struct ReentrantPayload {
26569      controller: Arc<RuntimeController>,
26570      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26571    }
26572
26573    impl Drop for ReentrantPayload {
26574      fn drop(&mut self) {
26575        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26576        let shutdown = self.controller.shutdown();
26577        self.result.send((generation, shutdown)).unwrap();
26578      }
26579    }
26580
26581    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26582
26583    impl Drop for PanicPayloadOnDrop {
26584      fn drop(&mut self) {
26585        panic_any(self.0.take().expect("the result must panic only once"));
26586      }
26587    }
26588
26589    let controller = Arc::new(current_thread_controller(
26590      "current-thread-panic-payload-generation",
26591    ));
26592    let backend = controller.backend();
26593    let generation = backend.generation();
26594    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26595      panic!("configured CurrentThread backend must create a CurrentThread executor");
26596    };
26597    let executor = Arc::clone(executor);
26598    drop(backend);
26599
26600    // Hold the physical lane long enough to force scheduler-owned delivery.
26601    // Dropping the receiver then makes the queued sender destroy the result.
26602    let owner = executor.fresh_owner_token();
26603    executor
26604      .blocking_admission
26605      .state
26606      .lock()
26607      .unwrap_or_else(std::sync::PoisonError::into_inner)
26608      .owner = Some(owner);
26609    let (result_tx, result_rx) = mpsc::channel();
26610    let payload_controller = Arc::clone(&controller);
26611    let handle = controller
26612      .try_spawn_blocking(move || {
26613        PanicPayloadOnDrop(Some(ReentrantPayload {
26614          controller: payload_controller,
26615          result: result_tx,
26616        }))
26617      })
26618      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26619    drop(handle);
26620    executor
26621      .blocking_admission
26622      .state
26623      .lock()
26624      .unwrap_or_else(std::sync::PoisonError::into_inner)
26625      .owner = None;
26626    let dispatch = current_thread_dispatch(&executor);
26627    controller.drive_current_thread_dispatch(dispatch, false);
26628
26629    let (observed_generation, shutdown) = result_rx
26630      .recv_timeout(Duration::from_secs(1))
26631      .expect("the caught result panic payload must be destroyed in the scheduler turn");
26632    assert_eq!(observed_generation, Some(generation));
26633    let error = shutdown.expect_err("payload destruction is still work in the active generation");
26634    assert!(error.to_string().contains("work running on that runtime"));
26635    controller.shutdown().unwrap();
26636  }
26637
26638  #[cfg(panic = "unwind")]
26639  #[test]
26640  fn current_thread_detached_blocking_result_drop_is_contained() {
26641    use std::sync::mpsc;
26642
26643    struct PanicOnDrop(mpsc::Sender<()>);
26644
26645    impl Drop for PanicOnDrop {
26646      fn drop(&mut self) {
26647        self.0.send(()).unwrap();
26648        panic!("intentional CurrentThread blocking result destructor panic");
26649      }
26650    }
26651
26652    let controller = current_thread_controller("current-thread-blocking-result-drop");
26653    let (dropped_tx, dropped_rx) = mpsc::channel();
26654    let handle = controller
26655      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26656      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26657
26658    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26659    dropped_rx
26660      .recv_timeout(Duration::from_secs(1))
26661      .expect("dropping the detached handle must reclaim its blocking result");
26662    assert!(
26663      result.is_ok(),
26664      "the blocking result destructor panic must be contained"
26665    );
26666    controller.shutdown().unwrap();
26667  }
26668
26669  // ---- Runtime option validation and configuration immutability -------------
26670  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
26671  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
26672
26673  #[test]
26674  fn validate_rejects_zero_worker_threads() {
26675    let error = RuntimeOptions {
26676      flavor: RuntimeFlavor::MultiThread,
26677      worker_threads: 0,
26678      max_blocking_tasks: 1,
26679      thread_name_prefix: "rd8".to_string(),
26680      park_deadline: None,
26681      drain_linger: DEFAULT_DRAIN_LINGER,
26682    }
26683    .validate()
26684    .expect_err("worker_threads == 0 must be rejected");
26685    assert_eq!(
26686      error.to_string(),
26687      "worker_threads must be greater than zero"
26688    );
26689  }
26690
26691  #[test]
26692  fn validate_rejects_zero_max_blocking_tasks() {
26693    let error = RuntimeOptions {
26694      flavor: RuntimeFlavor::MultiThread,
26695      worker_threads: 2,
26696      max_blocking_tasks: 0,
26697      thread_name_prefix: "rd8".to_string(),
26698      park_deadline: None,
26699      drain_linger: DEFAULT_DRAIN_LINGER,
26700    }
26701    .validate()
26702    .expect_err("max_blocking_tasks == 0 must be rejected");
26703    assert_eq!(
26704      error.to_string(),
26705      "max_blocking_tasks must be greater than zero"
26706    );
26707  }
26708
26709  #[test]
26710  fn validate_current_thread_coerces_worker_threads_to_one() {
26711    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
26712    // clamped to `.min(worker_threads)` == 1.
26713    let validated = RuntimeOptions {
26714      flavor: RuntimeFlavor::CurrentThread,
26715      worker_threads: 8,
26716      max_blocking_tasks: 8,
26717      thread_name_prefix: "rd8".to_string(),
26718      park_deadline: None,
26719      drain_linger: DEFAULT_DRAIN_LINGER,
26720    }
26721    .validate()
26722    .expect("CurrentThread options must validate");
26723    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26724    assert_eq!(validated.worker_threads, 1);
26725    assert_eq!(validated.max_blocking_tasks, 1);
26726  }
26727
26728  // `validate()` rejects `MultiThread` on builds that cannot create OS threads
26729  // (after applying the clamp), so this success assertion is valid only where
26730  // `napi_runtime_os_threads` holds: native and `wasm32-wasip1-threads`.
26731  #[cfg(napi_runtime_os_threads)]
26732  #[test]
26733  fn validate_reserves_one_worker_from_blocking_admission() {
26734    let validated = RuntimeOptions {
26735      flavor: RuntimeFlavor::MultiThread,
26736      worker_threads: 2,
26737      max_blocking_tasks: 8,
26738      thread_name_prefix: "rd8".to_string(),
26739      park_deadline: None,
26740      drain_linger: DEFAULT_DRAIN_LINGER,
26741    }
26742    .validate()
26743    .expect("MultiThread options must validate");
26744    assert_eq!(validated.worker_threads, 2);
26745    assert_eq!(validated.max_blocking_tasks, 1);
26746  }
26747
26748  #[cfg(napi_runtime_os_threads)]
26749  #[test]
26750  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26751    let validated = RuntimeOptions {
26752      flavor: RuntimeFlavor::MultiThread,
26753      worker_threads: 1,
26754      max_blocking_tasks: 8,
26755      thread_name_prefix: "rd8-minimum".to_string(),
26756      park_deadline: None,
26757      drain_linger: DEFAULT_DRAIN_LINGER,
26758    }
26759    .validate()
26760    .expect("MultiThread options must validate");
26761    assert_eq!(validated.worker_threads, 2);
26762    assert_eq!(validated.max_blocking_tasks, 1);
26763  }
26764
26765  #[cfg(napi_runtime_os_threads)]
26766  #[test]
26767  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26768    let simulated_32_bit = RuntimeOptions {
26769      flavor: RuntimeFlavor::MultiThread,
26770      worker_threads: 256,
26771      max_blocking_tasks: 256,
26772      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26773      park_deadline: None,
26774      drain_linger: DEFAULT_DRAIN_LINGER,
26775    }
26776    .validate_with_rayon_max_threads(Some(255))
26777    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26778    assert_eq!(simulated_32_bit.worker_threads, 255);
26779    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26780
26781    let rayon_max_threads = max_num_threads();
26782    let requested_threads = rayon_max_threads
26783      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26784      .checked_add(1)
26785      .expect("supported worker counts must leave room for an oversized request");
26786    let controller = RuntimeController::new();
26787    controller
26788      .configure(RuntimeOptions {
26789        flavor: RuntimeFlavor::MultiThread,
26790        worker_threads: requested_threads,
26791        max_blocking_tasks: requested_threads,
26792        thread_name_prefix: "rd8-rayon-cap".to_string(),
26793        park_deadline: None,
26794        drain_linger: DEFAULT_DRAIN_LINGER,
26795      })
26796      .expect("an oversized request must clamp to the production worker ceiling");
26797
26798    let reported = controller.options();
26799    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26800    assert_eq!(reported.worker_threads, expected_workers);
26801    assert_eq!(
26802      reported.max_blocking_tasks,
26803      expected_workers - 1,
26804      "the blocking cap must reserve a lane from the physically realizable worker count"
26805    );
26806  }
26807
26808  #[cfg(napi_runtime_os_threads)]
26809  #[test]
26810  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26811    // u64::MAX microseconds (the exact value an unvalidated
26812    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
26813    // via the removed OnceLock) must clamp to the ceiling everywhere a
26814    // budget enters the system: full options validation, a partial patch
26815    // through the controller, and the executor's defensive constructor
26816    // clamp for direct unvalidated test construction.
26817    let extreme = Duration::from_micros(u64::MAX);
26818    let validated = RuntimeOptions {
26819      flavor: RuntimeFlavor::MultiThread,
26820      worker_threads: 2,
26821      max_blocking_tasks: 1,
26822      thread_name_prefix: "linger-clamp".to_string(),
26823      park_deadline: None,
26824      drain_linger: extreme,
26825    }
26826    .validate_with_rayon_max_threads(Some(2))
26827    .expect("an extreme linger budget must clamp, not fail validation");
26828    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26829
26830    let controller = RuntimeController::new();
26831    controller
26832      .configure_partial(RuntimeOptionsPatch {
26833        drain_linger: Some(extreme),
26834        ..RuntimeOptionsPatch::default()
26835      })
26836      .expect("an extreme linger patch must clamp, not fail validation");
26837    assert_eq!(
26838      controller.options().drain_linger,
26839      MAX_DRAIN_LINGER,
26840      "introspection must report the clamped budget, not the requested one"
26841    );
26842    controller
26843      .configure_partial(RuntimeOptionsPatch {
26844        drain_linger: Some(Duration::ZERO),
26845        ..RuntimeOptionsPatch::default()
26846      })
26847      .expect("ZERO (lingering disabled) must remain a valid budget");
26848    assert_eq!(controller.options().drain_linger, Duration::ZERO);
26849
26850    let metrics = Arc::new(RuntimeMetrics::default());
26851    let unvalidated = RuntimeOptions {
26852      flavor: RuntimeFlavor::MultiThread,
26853      worker_threads: 2,
26854      max_blocking_tasks: 2,
26855      thread_name_prefix: "linger-clamp-exec".to_string(),
26856      park_deadline: None,
26857      drain_linger: extreme,
26858    };
26859    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26860      .expect("the executor must construct with an extreme unvalidated budget");
26861    assert_eq!(
26862      executor.drain_linger,
26863      Some(MAX_DRAIN_LINGER),
26864      "the constructor's defensive clamp must bound unvalidated budgets"
26865    );
26866
26867    let metrics = Arc::new(RuntimeMetrics::default());
26868    let disabled = RuntimeOptions {
26869      flavor: RuntimeFlavor::MultiThread,
26870      worker_threads: 2,
26871      max_blocking_tasks: 2,
26872      thread_name_prefix: "linger-clamp-off".to_string(),
26873      park_deadline: None,
26874      drain_linger: Duration::ZERO,
26875    };
26876    let executor = MultiThreadExecutor::new(&disabled, metrics)
26877      .expect("the executor must construct with lingering disabled");
26878    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26879  }
26880
26881  #[cfg(not(target_family = "wasm"))]
26882  #[test]
26883  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26884    let controller = RuntimeController::new();
26885    let options = controller.options();
26886    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
26887    assert_eq!(
26888      options.max_blocking_tasks,
26889      options.worker_threads.saturating_sub(1).max(1),
26890      "the unconfigured Rust API must use the same validated reserve as the binding"
26891    );
26892  }
26893
26894  #[cfg(napi_runtime_os_threads)]
26895  #[test]
26896  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
26897    use std::{sync::mpsc, time::Duration};
26898
26899    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
26900    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
26901    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
26902    let first_controller = Arc::clone(&controller);
26903    let first = std::thread::spawn(move || {
26904      first_controller
26905        .configure_partial_inner(
26906          RuntimeOptionsPatch {
26907            max_blocking_tasks: Some(3),
26908            ..RuntimeOptionsPatch::default()
26909          },
26910          |candidate| {
26911            assert_eq!(
26912              (candidate.worker_threads, candidate.max_blocking_tasks),
26913              (8, 3)
26914            );
26915            first_merged_tx
26916              .send(())
26917              .expect("the race coordinator must still be listening");
26918            release_first_rx
26919              .recv_timeout(Duration::from_secs(5))
26920              .expect("the first partial configuration must be released");
26921          },
26922        )
26923        .expect("the first partial configuration must commit");
26924    });
26925    first_merged_rx
26926      .recv_timeout(Duration::from_secs(5))
26927      .expect("the first partial configuration must hold the controller lock after merging");
26928
26929    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26930    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26931    let second_controller = Arc::clone(&controller);
26932    let second = std::thread::spawn(move || {
26933      second_started_tx
26934        .send(())
26935        .expect("the race coordinator must still be listening");
26936      second_controller
26937        .configure_partial_inner(
26938          RuntimeOptionsPatch {
26939            worker_threads: Some(6),
26940            ..RuntimeOptionsPatch::default()
26941          },
26942          |candidate| {
26943            second_candidate_tx
26944              .send((candidate.worker_threads, candidate.max_blocking_tasks))
26945              .expect("the race coordinator must still be listening");
26946          },
26947        )
26948        .expect("the second partial configuration must commit");
26949    });
26950    second_started_rx
26951      .recv_timeout(Duration::from_secs(5))
26952      .expect("the second partial configuration thread must start");
26953    release_first_tx
26954      .send(())
26955      .expect("the first partial configuration must still be waiting");
26956
26957    first
26958      .join()
26959      .expect("the first configuration thread must not panic");
26960    second
26961      .join()
26962      .expect("the second configuration thread must not panic");
26963    assert_eq!(
26964      second_candidate_rx
26965        .recv_timeout(Duration::from_secs(5))
26966        .expect("the second partial configuration must expose its merged candidate"),
26967      (6, 3),
26968      "the second caller must merge against the first caller's committed options"
26969    );
26970
26971    let options = controller.options();
26972    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26973    assert_eq!(options.thread_name_prefix, "partial-config-race");
26974  }
26975
26976  #[cfg(napi_runtime_os_threads)]
26977  #[test]
26978  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26979    use std::{sync::mpsc, time::Duration};
26980
26981    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26982    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26983    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26984    let invalid_controller = Arc::clone(&controller);
26985    let invalid = std::thread::spawn(move || {
26986      invalid_controller
26987        .configure_partial_inner(
26988          RuntimeOptionsPatch {
26989            worker_threads: Some(0),
26990            ..RuntimeOptionsPatch::default()
26991          },
26992          |candidate| {
26993            assert_eq!(
26994              (candidate.worker_threads, candidate.max_blocking_tasks),
26995              (0, 2)
26996            );
26997            invalid_merged_tx
26998              .send(())
26999              .expect("the race coordinator must still be listening");
27000            release_invalid_rx
27001              .recv_timeout(Duration::from_secs(5))
27002              .expect("the invalid partial configuration must be released");
27003          },
27004        )
27005        .expect_err("the invalid partial configuration must be rejected")
27006        .to_string()
27007    });
27008    invalid_merged_rx
27009      .recv_timeout(Duration::from_secs(5))
27010      .expect("the invalid partial configuration must hold the lock after merging");
27011
27012    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
27013    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
27014    let valid_controller = Arc::clone(&controller);
27015    let valid = std::thread::spawn(move || {
27016      valid_started_tx
27017        .send(())
27018        .expect("the race coordinator must still be listening");
27019      valid_controller
27020        .configure_partial_inner(
27021          RuntimeOptionsPatch {
27022            max_blocking_tasks: Some(4),
27023            ..RuntimeOptionsPatch::default()
27024          },
27025          |candidate| {
27026            valid_candidate_tx
27027              .send((candidate.worker_threads, candidate.max_blocking_tasks))
27028              .expect("the race coordinator must still be listening");
27029          },
27030        )
27031        .expect("the valid partial configuration must commit");
27032    });
27033    valid_started_rx
27034      .recv_timeout(Duration::from_secs(5))
27035      .expect("the valid partial configuration thread must start");
27036    release_invalid_tx
27037      .send(())
27038      .expect("the invalid partial configuration must still be waiting");
27039
27040    assert_eq!(
27041      invalid
27042        .join()
27043        .expect("the invalid configuration thread must not panic"),
27044      "worker_threads must be greater than zero"
27045    );
27046    valid
27047      .join()
27048      .expect("the valid configuration thread must not panic");
27049    assert_eq!(
27050      valid_candidate_rx
27051        .recv_timeout(Duration::from_secs(5))
27052        .expect("the valid partial configuration must expose its merged candidate"),
27053      (6, 4),
27054      "the valid caller must merge against the last committed options, not the rejected candidate"
27055    );
27056
27057    let options = controller.options();
27058    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
27059    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
27060  }
27061
27062  #[test]
27063  fn configure_after_backend_started_is_rejected() {
27064    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
27065    // so starting it is cheap and spawns no OS threads.
27066    let controller = RuntimeController::new();
27067    controller
27068      .configure(RuntimeOptions {
27069        flavor: RuntimeFlavor::CurrentThread,
27070        worker_threads: 1,
27071        max_blocking_tasks: 1,
27072        thread_name_prefix: "rd8".to_string(),
27073        park_deadline: None,
27074        drain_linger: DEFAULT_DRAIN_LINGER,
27075      })
27076      .expect("first configure before the backend exists must succeed");
27077
27078    // Materialize the backend; further configuration must now be refused.
27079    let _backend = controller.backend();
27080
27081    let error = controller
27082      .configure(RuntimeOptions::default())
27083      .expect_err("configure after the backend started must be rejected");
27084    assert_eq!(
27085      error.to_string(),
27086      "the async runtime configuration is frozen; configure it before the first async call"
27087    );
27088  }
27089
27090  #[test]
27091  fn partial_configuration_after_backend_started_is_rejected() {
27092    let controller = current_thread_controller("partial-config-frozen");
27093    let _backend = controller.backend();
27094
27095    let error = controller
27096      .configure_partial(RuntimeOptionsPatch {
27097        worker_threads: Some(2),
27098        ..RuntimeOptionsPatch::default()
27099      })
27100      .expect_err("partial configuration after the backend started must be rejected");
27101    assert_eq!(
27102      error.to_string(),
27103      "the async runtime configuration is frozen; configure it before the first async call"
27104    );
27105  }
27106
27107  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
27108    let controller = RuntimeController::new();
27109    controller
27110      .configure(RuntimeOptions {
27111        flavor: RuntimeFlavor::CurrentThread,
27112        worker_threads: 1,
27113        max_blocking_tasks: 1,
27114        thread_name_prefix: thread_name_prefix.to_string(),
27115        park_deadline: None,
27116        drain_linger: DEFAULT_DRAIN_LINGER,
27117      })
27118      .expect("CurrentThread test configuration must be accepted");
27119    controller
27120  }
27121
27122  #[cfg(napi_runtime_os_threads)]
27123  fn multi_thread_controller(
27124    thread_name_prefix: &str,
27125    worker_threads: usize,
27126    max_blocking_tasks: usize,
27127  ) -> RuntimeController {
27128    let controller = RuntimeController::new();
27129    controller
27130      .configure(RuntimeOptions {
27131        flavor: RuntimeFlavor::MultiThread,
27132        worker_threads,
27133        max_blocking_tasks,
27134        thread_name_prefix: thread_name_prefix.to_string(),
27135        park_deadline: None,
27136        drain_linger: DEFAULT_DRAIN_LINGER,
27137      })
27138      .expect("MultiThread test configuration must be accepted");
27139    controller
27140  }
27141
27142  #[test]
27143  fn blocking_backend_creation_failure_preserves_diagnostic() {
27144    let controller = current_thread_controller("blocking-backend-creation-failure");
27145    let ran = Arc::new(AtomicBool::new(false));
27146    let work_ran = Arc::clone(&ran);
27147
27148    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27149    let Err((error, work)) = controller.try_spawn_blocking(move || {
27150      work_ran.store(true, Ordering::SeqCst);
27151      17usize
27152    }) else {
27153      panic!("the injected backend creation failure must reject blocking work");
27154    };
27155    assert_eq!(
27156      error.to_string(),
27157      "injected async runtime backend creation failure"
27158    );
27159    assert!(
27160      !ran.load(Ordering::SeqCst),
27161      "rejected blocking work must not run"
27162    );
27163    assert_eq!(
27164      work(),
27165      17,
27166      "the rejected blocking closure must remain usable"
27167    );
27168    assert!(ran.load(Ordering::SeqCst));
27169
27170    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27171    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
27172      .expect_err("infallible blocking submission must return a failed join handle");
27173    assert_eq!(
27174      error.to_string(),
27175      "injected async runtime backend creation failure"
27176    );
27177
27178    let handle = controller
27179      .try_spawn_blocking(|| 31usize)
27180      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
27181    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27182    controller.shutdown().unwrap();
27183  }
27184
27185  #[cfg(not(target_family = "wasm"))]
27186  #[test]
27187  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
27188    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
27189
27190    if std::env::var_os(CHILD_ENV).is_some() {
27191      struct PollProbe {
27192        polls: Arc<AtomicUsize>,
27193      }
27194
27195      impl Future for PollProbe {
27196        type Output = ();
27197
27198        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27199          self.polls.fetch_add(1, Ordering::SeqCst);
27200          Poll::Ready(())
27201        }
27202      }
27203
27204      shutdown().expect("the isolated runtime must stop before block_on admission");
27205      let polls = Arc::new(AtomicUsize::new(0));
27206      let mut future = std::pin::pin!(PollProbe {
27207        polls: Arc::clone(&polls)
27208      });
27209      let error = try_block_on_dyn(future.as_mut())
27210        .expect_err("the stopped runtime must reject the borrowed future");
27211      assert_eq!(
27212        error.to_string(),
27213        "the async runtime is stopped; call start before submitting work"
27214      );
27215      assert_eq!(
27216        polls.load(Ordering::SeqCst),
27217        0,
27218        "rejected block_on must not poll its future"
27219      );
27220
27221      start().expect("the isolated runtime must restart");
27222      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27223      assert_eq!(polls.load(Ordering::SeqCst), 1);
27224      shutdown().expect("the restarted isolated runtime must stop");
27225      return;
27226    }
27227
27228    let output = std::process::Command::new(std::env::current_exe().unwrap())
27229      .arg("--exact")
27230      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27231      .arg("--nocapture")
27232      .env(CHILD_ENV, "1")
27233      .output()
27234      .expect("the stopped block_on subprocess must start");
27235    assert!(
27236      output.status.success(),
27237      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27238      output.status.code(),
27239      String::from_utf8_lossy(&output.stdout),
27240      String::from_utf8_lossy(&output.stderr)
27241    );
27242  }
27243
27244  #[cfg(not(target_family = "wasm"))]
27245  #[test]
27246  fn try_block_on_dyn_reports_concurrent_shutdown() {
27247    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27248
27249    if std::env::var_os(CHILD_ENV).is_some() {
27250      use std::sync::mpsc;
27251
27252      struct PendingProbe {
27253        entered: Option<mpsc::Sender<()>>,
27254        complete: Arc<AtomicBool>,
27255      }
27256
27257      impl Future for PendingProbe {
27258        type Output = ();
27259
27260        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27261          if let Some(entered) = self.entered.take() {
27262            entered.send(()).unwrap();
27263          }
27264          if self.complete.load(Ordering::SeqCst) {
27265            Poll::Ready(())
27266          } else {
27267            Poll::Pending
27268          }
27269        }
27270      }
27271
27272      configure(RuntimeOptions {
27273        flavor: RuntimeFlavor::CurrentThread,
27274        worker_threads: 1,
27275        max_blocking_tasks: 1,
27276        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27277        park_deadline: None,
27278        drain_linger: DEFAULT_DRAIN_LINGER,
27279      })
27280      .expect("the isolated runtime must accept CurrentThread configuration");
27281      start().expect("the isolated runtime must start");
27282
27283      let (entered_tx, entered_rx) = mpsc::channel();
27284      let (error_tx, error_rx) = mpsc::channel();
27285      let (retry_tx, retry_rx) = mpsc::channel();
27286      let complete = Arc::new(AtomicBool::new(false));
27287      let future_complete = Arc::clone(&complete);
27288      let runner = std::thread::spawn(move || {
27289        let mut future = std::pin::pin!(PendingProbe {
27290          entered: Some(entered_tx),
27291          complete: future_complete
27292        });
27293        let error = try_block_on_dyn(future.as_mut())
27294          .expect_err("concurrent shutdown must interrupt the borrowed future")
27295          .to_string();
27296        error_tx.send(error).unwrap();
27297        retry_rx.recv().unwrap();
27298        try_block_on_dyn(future.as_mut())
27299          .expect("restart must accept the same interrupted borrowed future");
27300      });
27301      entered_rx
27302        .recv_timeout(Duration::from_secs(2))
27303        .expect("the borrowed future must be polled before shutdown");
27304      shutdown().expect("shutdown must wait for the active block_on scope to retire");
27305      assert_eq!(
27306        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27307        "the async runtime stopped before block_on completed its future"
27308      );
27309      start().expect("the isolated runtime must restart after the interrupted drive");
27310      complete.store(true, Ordering::SeqCst);
27311      retry_tx.send(()).unwrap();
27312      runner.join().unwrap();
27313      shutdown().expect("the retried isolated runtime must stop");
27314      return;
27315    }
27316
27317    let output = std::process::Command::new(std::env::current_exe().unwrap())
27318      .arg("--exact")
27319      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27320      .arg("--nocapture")
27321      .env(CHILD_ENV, "1")
27322      .output()
27323      .expect("the concurrent block_on shutdown subprocess must start");
27324    assert!(
27325      output.status.success(),
27326      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27327      output.status.code(),
27328      String::from_utf8_lossy(&output.stdout),
27329      String::from_utf8_lossy(&output.stderr)
27330    );
27331  }
27332
27333  #[test]
27334  fn initial_lifecycle_start_preserves_lazy_configuration() {
27335    let controller = RuntimeController::new();
27336    controller
27337      .start()
27338      .expect("initial lifecycle start must succeed");
27339    controller
27340      .configure(RuntimeOptions {
27341        flavor: RuntimeFlavor::CurrentThread,
27342        worker_threads: 1,
27343        max_blocking_tasks: 1,
27344        thread_name_prefix: "lifecycle-initial-start".to_string(),
27345        park_deadline: None,
27346        drain_linger: DEFAULT_DRAIN_LINGER,
27347      })
27348      .expect("module-registration start must not consume the configuration window");
27349
27350    let state = controller
27351      .state
27352      .lock()
27353      .unwrap_or_else(std::sync::PoisonError::into_inner);
27354    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27355  }
27356
27357  #[test]
27358  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27359    let controller = RuntimeController::new();
27360
27361    for _ in 0..3 {
27362      controller
27363        .start()
27364        .expect("zero-work lifecycle start must succeed");
27365      assert!(matches!(
27366        &controller
27367          .state
27368          .lock()
27369          .unwrap_or_else(std::sync::PoisonError::into_inner)
27370          .lifecycle,
27371        RuntimeLifecycle::Initial
27372      ));
27373
27374      controller
27375        .shutdown()
27376        .expect("zero-work lifecycle shutdown must succeed");
27377      assert!(matches!(
27378        &controller
27379          .state
27380          .lock()
27381          .unwrap_or_else(std::sync::PoisonError::into_inner)
27382          .lifecycle,
27383        RuntimeLifecycle::StoppedBeforeFirstUse
27384      ));
27385    }
27386
27387    controller
27388      .start()
27389      .expect("a later zero-work restart must remain lazy");
27390    controller
27391      .configure(RuntimeOptions {
27392        flavor: RuntimeFlavor::CurrentThread,
27393        worker_threads: 1,
27394        max_blocking_tasks: 1,
27395        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27396        park_deadline: None,
27397        drain_linger: DEFAULT_DRAIN_LINGER,
27398      })
27399      .expect("zero-work lifecycle cycles must not consume the configuration window");
27400
27401    let state = controller
27402      .state
27403      .lock()
27404      .unwrap_or_else(std::sync::PoisonError::into_inner);
27405    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27406  }
27407
27408  #[cfg(napi_runtime_os_threads)]
27409  #[test]
27410  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27411    use std::sync::mpsc;
27412
27413    struct BlockingDropFuture {
27414      entered: mpsc::Sender<()>,
27415      release: mpsc::Receiver<()>,
27416    }
27417
27418    impl Future for BlockingDropFuture {
27419      type Output = ();
27420
27421      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27422        Poll::Pending
27423      }
27424    }
27425
27426    impl Drop for BlockingDropFuture {
27427      fn drop(&mut self) {
27428        self.entered.send(()).unwrap();
27429        self.release.recv().unwrap();
27430      }
27431    }
27432
27433    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27434    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27435    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27436    let submit_controller = Arc::clone(&controller);
27437    let submitter = std::thread::spawn(move || {
27438      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27439      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27440        entered: drop_entered_tx,
27441        release: release_drop_rx,
27442      }));
27443      assert!(
27444        result.is_err(),
27445        "the injected backend creation failure must reject the submission"
27446      );
27447    });
27448    drop_entered_rx
27449      .recv_timeout(Duration::from_secs(2))
27450      .expect("the rejected future destructor must start");
27451
27452    let retry_ran = Arc::new(AtomicBool::new(false));
27453    let retry_ran_task = Arc::clone(&retry_ran);
27454    let Err(retry) = controller.try_spawn_detached(async move {
27455      retry_ran_task.store(true, Ordering::SeqCst);
27456    }) else {
27457      panic!("backend creation must remain closed during rejected destruction");
27458    };
27459    drop(retry);
27460    assert!(!retry_ran.load(Ordering::SeqCst));
27461
27462    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27463    let shutdown_controller = Arc::clone(&controller);
27464    let shutdown = std::thread::spawn(move || {
27465      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27466    });
27467    wait_until("initial shutdown to close runtime admission", || {
27468      matches!(
27469        controller
27470          .state
27471          .lock()
27472          .unwrap_or_else(std::sync::PoisonError::into_inner)
27473          .lifecycle,
27474        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27475      )
27476    });
27477    assert!(
27478      shutdown_rx
27479        .recv_timeout(Duration::from_millis(200))
27480        .is_err(),
27481      "initial shutdown must wait for rejected user state destruction"
27482    );
27483    assert!(
27484      controller.try_spawn_detached(async {}).is_err(),
27485      "shutdown must keep first-generation admission closed while it waits"
27486    );
27487
27488    let (start_tx, start_rx) = mpsc::channel();
27489    let start_controller = Arc::clone(&controller);
27490    let starter = std::thread::spawn(move || {
27491      start_tx.send(start_controller.start()).unwrap();
27492    });
27493    assert!(
27494      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27495      "start must wait for the zero-backend shutdown transition to finish"
27496    );
27497
27498    release_drop_tx.send(()).unwrap();
27499    submitter.join().unwrap();
27500    shutdown_rx
27501      .recv_timeout(Duration::from_secs(2))
27502      .unwrap()
27503      .unwrap();
27504    shutdown.join().unwrap();
27505    start_rx
27506      .recv_timeout(Duration::from_secs(2))
27507      .unwrap()
27508      .unwrap();
27509    starter.join().unwrap();
27510
27511    let ran = Arc::new(AtomicBool::new(false));
27512    let ran_task = Arc::clone(&ran);
27513    controller
27514      .try_spawn_detached(async move {
27515        ran_task.store(true, Ordering::SeqCst);
27516      })
27517      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27518    let dispatch = controller_current_thread_dispatch(&controller);
27519    controller.drive_current_thread_dispatch(dispatch, false);
27520    assert!(ran.load(Ordering::SeqCst));
27521    controller.shutdown().unwrap();
27522  }
27523
27524  #[cfg(napi_runtime_os_threads)]
27525  #[test]
27526  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27527    use std::sync::mpsc;
27528
27529    struct BlockingDropFuture {
27530      entered: mpsc::Sender<()>,
27531      release: mpsc::Receiver<()>,
27532    }
27533
27534    impl Future for BlockingDropFuture {
27535      type Output = ();
27536
27537      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27538        Poll::Pending
27539      }
27540    }
27541
27542    impl Drop for BlockingDropFuture {
27543      fn drop(&mut self) {
27544        self.entered.send(()).unwrap();
27545        self.release.recv().unwrap();
27546      }
27547    }
27548
27549    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27550    let old_generation = controller.backend().generation();
27551    controller.shutdown().unwrap();
27552
27553    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27554    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27555    let submit_controller = Arc::clone(&controller);
27556    let submitter = std::thread::spawn(move || {
27557      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27558        entered: drop_entered_tx,
27559        release: release_drop_rx,
27560      }));
27561      assert!(
27562        result.is_err(),
27563        "the stopped runtime must reject the submission"
27564      );
27565    });
27566    drop_entered_rx
27567      .recv_timeout(Duration::from_secs(2))
27568      .unwrap();
27569
27570    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27571    let shutdown_controller = Arc::clone(&controller);
27572    let shutdown = std::thread::spawn(move || {
27573      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27574    });
27575    wait_until(
27576      "stopped shutdown to enter its zero-backend transition",
27577      || {
27578        matches!(
27579          controller
27580            .state
27581            .lock()
27582            .unwrap_or_else(std::sync::PoisonError::into_inner)
27583            .lifecycle,
27584          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27585        )
27586      },
27587    );
27588
27589    let (start_tx, start_rx) = mpsc::channel();
27590    let start_controller = Arc::clone(&controller);
27591    let starter = std::thread::spawn(move || {
27592      start_tx.send(start_controller.start()).unwrap();
27593    });
27594    assert!(
27595      shutdown_rx
27596        .recv_timeout(Duration::from_millis(200))
27597        .is_err(),
27598      "stopped shutdown must wait for rejected destruction"
27599    );
27600    assert!(
27601      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27602      "restart must wait for the stopped zero-backend transition"
27603    );
27604
27605    release_drop_tx.send(()).unwrap();
27606    submitter.join().unwrap();
27607    shutdown_rx
27608      .recv_timeout(Duration::from_secs(2))
27609      .unwrap()
27610      .unwrap();
27611    shutdown.join().unwrap();
27612    start_rx
27613      .recv_timeout(Duration::from_secs(2))
27614      .unwrap()
27615      .unwrap();
27616    starter.join().unwrap();
27617
27618    let new_generation = controller.backend().generation();
27619    assert_ne!(new_generation, old_generation);
27620    controller.shutdown().unwrap();
27621  }
27622
27623  #[test]
27624  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27625    use std::sync::mpsc;
27626
27627    struct ShutdownOnDrop {
27628      controller: Arc<RuntimeController>,
27629      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27630    }
27631
27632    impl Future for ShutdownOnDrop {
27633      type Output = ();
27634
27635      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27636        Poll::Pending
27637      }
27638    }
27639
27640    impl Drop for ShutdownOnDrop {
27641      fn drop(&mut self) {
27642        self.result.send(self.controller.shutdown()).unwrap();
27643      }
27644    }
27645
27646    let controller = Arc::new(current_thread_controller(
27647      "failed-initial-reentrant-shutdown",
27648    ));
27649    let (result_tx, result_rx) = mpsc::channel();
27650    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27651    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27652      controller: Arc::clone(&controller),
27653      result: result_tx,
27654    }));
27655    assert!(rejected.is_err());
27656
27657    let error = result_rx
27658      .recv_timeout(Duration::from_secs(1))
27659      .expect("the rejected destructor must not self-deadlock")
27660      .expect_err("rejected destruction cannot wait for its own shutdown");
27661    assert!(error.to_string().contains("rejected submission"));
27662    {
27663      let state = controller
27664        .state
27665        .lock()
27666        .unwrap_or_else(std::sync::PoisonError::into_inner);
27667      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27668      assert_eq!(state.rejected_drops, 0);
27669    }
27670
27671    let handle = controller
27672      .try_spawn(async { 23usize })
27673      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27674    let dispatch = controller_current_thread_dispatch(&controller);
27675    controller.drive_current_thread_dispatch(dispatch, false);
27676    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27677    controller.shutdown().unwrap();
27678  }
27679
27680  #[cfg(napi_runtime_os_threads)]
27681  #[test]
27682  fn failed_initial_submission_destructor_cannot_reenter_start() {
27683    use std::sync::mpsc;
27684
27685    struct StartOnDrop {
27686      controller: Arc<RuntimeController>,
27687      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27688      release: mpsc::Receiver<()>,
27689    }
27690
27691    impl Future for StartOnDrop {
27692      type Output = ();
27693
27694      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27695        Poll::Pending
27696      }
27697    }
27698
27699    impl Drop for StartOnDrop {
27700      fn drop(&mut self) {
27701        self.result.send(self.controller.start()).unwrap();
27702        self.release.recv().unwrap();
27703      }
27704    }
27705
27706    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27707    let (result_tx, result_rx) = mpsc::channel();
27708    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27709    let (submission_tx, submission_rx) = mpsc::channel();
27710    let submit_controller = Arc::clone(&controller);
27711    let submitter = std::thread::spawn(move || {
27712      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27713      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27714        controller: Arc::clone(&submit_controller),
27715        result: result_tx,
27716        release: release_drop_rx,
27717      }));
27718      submission_tx.send(rejected).unwrap();
27719    });
27720
27721    let error = result_rx
27722      .recv_timeout(Duration::from_secs(1))
27723      .expect("the rejected destructor must not self-deadlock")
27724      .expect_err("rejected destruction must not report a successful runtime start");
27725    assert!(error.to_string().contains("rejected submission"));
27726    {
27727      let state = controller
27728        .state
27729        .lock()
27730        .unwrap_or_else(std::sync::PoisonError::into_inner);
27731      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27732      assert_eq!(state.rejected_drops, 1);
27733    }
27734
27735    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27736    let (external_start_tx, external_start_rx) = mpsc::channel();
27737    let start_controller = Arc::clone(&controller);
27738    let starter = std::thread::spawn(move || {
27739      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27740        *slot.borrow_mut() = Some(Box::new(move || {
27741          wait_entered_tx.send(()).unwrap();
27742        }));
27743      });
27744      external_start_tx.send(start_controller.start()).unwrap();
27745    });
27746    wait_entered_rx
27747      .recv_timeout(Duration::from_secs(1))
27748      .expect("external start must reach the Initial rejected-destruction wait");
27749    assert!(
27750      external_start_rx.try_recv().is_err(),
27751      "external start must not return while rejected destruction remains active"
27752    );
27753
27754    release_drop_tx.send(()).unwrap();
27755    assert!(
27756      submission_rx
27757        .recv_timeout(Duration::from_secs(2))
27758        .unwrap()
27759        .is_err()
27760    );
27761    submitter.join().unwrap();
27762    external_start_rx
27763      .recv_timeout(Duration::from_secs(2))
27764      .unwrap()
27765      .unwrap();
27766    starter.join().unwrap();
27767
27768    {
27769      let state = controller
27770        .state
27771        .lock()
27772        .unwrap_or_else(std::sync::PoisonError::into_inner);
27773      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27774      assert_eq!(state.rejected_drops, 0);
27775    }
27776
27777    let handle = controller
27778      .try_spawn(async { 31usize })
27779      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27780    let dispatch = controller_current_thread_dispatch(&controller);
27781    controller.drive_current_thread_dispatch(dispatch, false);
27782    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27783    controller.shutdown().unwrap();
27784  }
27785
27786  #[cfg(panic = "unwind")]
27787  #[test]
27788  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
27789    use std::sync::mpsc;
27790
27791    struct BlockingDropFuture {
27792      controller: Arc<RuntimeController>,
27793      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27794      release: mpsc::Receiver<()>,
27795    }
27796
27797    impl Future for BlockingDropFuture {
27798      type Output = ();
27799
27800      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27801        Poll::Pending
27802      }
27803    }
27804
27805    impl Drop for BlockingDropFuture {
27806      fn drop(&mut self) {
27807        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
27808        self
27809          .entered
27810          .send((generation, self.controller.shutdown()))
27811          .unwrap();
27812        self.release.recv().unwrap();
27813      }
27814    }
27815
27816    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
27817    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27818    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27819    let (runner_done_tx, runner_done_rx) = mpsc::channel();
27820    let runner_controller = Arc::clone(&controller);
27821    let runner = std::thread::spawn(move || {
27822      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27823      let result = catch_unwind(AssertUnwindSafe(|| {
27824        block_on_with_controller(
27825          &runner_controller,
27826          BlockingDropFuture {
27827            controller: Arc::clone(&runner_controller),
27828            entered: drop_entered_tx,
27829            release: release_drop_rx,
27830          },
27831        );
27832      }));
27833      runner_done_tx.send(result.is_err()).unwrap();
27834    });
27835
27836    let (generation, reentrant_shutdown) = drop_entered_rx
27837      .recv_timeout(Duration::from_secs(2))
27838      .expect("failed block_on acquisition must destroy its owned future");
27839    assert_eq!(generation, None, "no runtime generation was created");
27840    let error = reentrant_shutdown
27841      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
27842    assert!(error.to_string().contains("rejected submission"));
27843
27844    let Err(retry_error) = controller.try_backend() else {
27845      panic!("backend retry must remain closed during rejected block_on destruction");
27846    };
27847    assert!(retry_error.to_string().contains("rejected submission"));
27848
27849    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27850    let shutdown_controller = Arc::clone(&controller);
27851    let shutdown = std::thread::spawn(move || {
27852      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27853    });
27854    assert!(
27855      shutdown_rx
27856        .recv_timeout(Duration::from_millis(200))
27857        .is_err(),
27858      "initial shutdown must wait for rejected block_on destruction"
27859    );
27860
27861    release_drop_tx.send(()).unwrap();
27862    assert!(
27863      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27864      "block_on must preserve its infallible panic contract after contained cleanup"
27865    );
27866    runner.join().unwrap();
27867    shutdown_rx
27868      .recv_timeout(Duration::from_secs(2))
27869      .unwrap()
27870      .unwrap();
27871    shutdown.join().unwrap();
27872
27873    controller
27874      .start()
27875      .expect("zero-generation shutdown must remain restartable");
27876    let ran = Arc::new(AtomicBool::new(false));
27877    let ran_task = Arc::clone(&ran);
27878    controller
27879      .try_spawn_detached(async move {
27880        ran_task.store(true, Ordering::SeqCst);
27881      })
27882      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
27883    let dispatch = controller_current_thread_dispatch(&controller);
27884    controller.drive_current_thread_dispatch(dispatch, false);
27885    assert!(ran.load(Ordering::SeqCst));
27886    controller.shutdown().unwrap();
27887  }
27888
27889  #[cfg(not(target_family = "wasm"))]
27890  #[test]
27891  fn public_block_on_contains_poll_and_drop_double_panic() {
27892    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
27893
27894    if std::env::var_os(CHILD_ENV).is_some() {
27895      struct PanicInPollAndDrop;
27896
27897      impl Future for PanicInPollAndDrop {
27898        type Output = ();
27899
27900        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27901          panic!("intentional block_on poll panic");
27902        }
27903      }
27904
27905      impl Drop for PanicInPollAndDrop {
27906        fn drop(&mut self) {
27907          panic!("intentional block_on future destructor panic");
27908        }
27909      }
27910
27911      let controller = current_thread_controller("block-on-double-panic");
27912      assert!(
27913        catch_unwind(AssertUnwindSafe(|| {
27914          block_on_with_controller(&controller, PanicInPollAndDrop);
27915        }))
27916        .is_err(),
27917        "the original poll panic must still reach the caller"
27918      );
27919      controller
27920        .shutdown()
27921        .expect("contained future destruction must not strand the runtime");
27922      return;
27923    }
27924
27925    let output = std::process::Command::new(std::env::current_exe().unwrap())
27926      .arg("--exact")
27927      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27928      .arg("--nocapture")
27929      .env(CHILD_ENV, "1")
27930      .output()
27931      .expect("the subprocess test must start");
27932    assert!(
27933      output.status.success(),
27934      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27935      output.status.code(),
27936      String::from_utf8_lossy(&output.stdout),
27937      String::from_utf8_lossy(&output.stderr)
27938    );
27939  }
27940
27941  #[test]
27942  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27943    let controller = RuntimeController::new();
27944    controller
27945      .start()
27946      .expect("initial lifecycle start must succeed");
27947    controller
27948      .shutdown()
27949      .expect("zero-work lifecycle shutdown must succeed");
27950    controller
27951      .start()
27952      .expect("zero-work lifecycle restart must remain lazy");
27953    controller
27954      .configure(RuntimeOptions {
27955        flavor: RuntimeFlavor::CurrentThread,
27956        worker_threads: 1,
27957        max_blocking_tasks: 1,
27958        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27959        park_deadline: None,
27960        drain_linger: DEFAULT_DRAIN_LINGER,
27961      })
27962      .expect("configuration before first real async use must succeed");
27963
27964    let first_generation = controller.backend().generation();
27965    controller
27966      .shutdown()
27967      .expect("the first real generation must stop");
27968    assert!(matches!(
27969      &controller
27970        .state
27971        .lock()
27972        .unwrap_or_else(std::sync::PoisonError::into_inner)
27973        .lifecycle,
27974      RuntimeLifecycle::Stopped
27975    ));
27976    let error = controller
27977      .configure(RuntimeOptions::default())
27978      .expect_err("a real generation must permanently freeze configuration");
27979    assert_eq!(
27980      error.to_string(),
27981      "the async runtime configuration is frozen; configure it before the first async call"
27982    );
27983
27984    controller
27985      .start()
27986      .expect("the stopped real generation must restart");
27987    let restarted_generation = {
27988      let state = controller
27989        .state
27990        .lock()
27991        .unwrap_or_else(std::sync::PoisonError::into_inner);
27992      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27993        panic!("a real generation restart must eagerly create its replacement backend");
27994      };
27995      backend.generation()
27996    };
27997    assert_ne!(
27998      restarted_generation, first_generation,
27999      "a real generation restart must not reuse the retired backend"
28000    );
28001    controller
28002      .shutdown()
28003      .expect("the replacement generation must stop");
28004  }
28005
28006  #[test]
28007  fn runtime_controller_rejects_submissions_after_shutdown() {
28008    let controller = current_thread_controller("lifecycle-reject");
28009    controller.start().expect("runtime must start");
28010    drop(controller.backend());
28011    controller.shutdown().expect("runtime must stop");
28012
28013    let async_ran = Arc::new(AtomicBool::new(false));
28014    let async_ran_task = Arc::clone(&async_ran);
28015    let Err(rejected_future) = controller.try_spawn_detached(async move {
28016      async_ran_task.store(true, Ordering::SeqCst);
28017    }) else {
28018      panic!("shutdown must reject async work");
28019    };
28020    assert!(
28021      !async_ran.load(Ordering::SeqCst),
28022      "a rejected future must not be polled"
28023    );
28024    futures::executor::block_on(rejected_future);
28025    assert!(
28026      async_ran.load(Ordering::SeqCst),
28027      "the original future must remain usable"
28028    );
28029
28030    let blocking_ran = Arc::new(AtomicBool::new(false));
28031    let blocking_ran_task = Arc::clone(&blocking_ran);
28032    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
28033      blocking_ran_task.store(true, Ordering::SeqCst);
28034      7
28035    }) else {
28036      panic!("shutdown must reject blocking work");
28037    };
28038    assert_eq!(
28039      blocking_error.to_string(),
28040      "the async runtime is stopped; call start before submitting work"
28041    );
28042    assert!(
28043      !blocking_ran.load(Ordering::SeqCst),
28044      "rejected blocking work must not run"
28045    );
28046    assert_eq!(
28047      rejected_work(),
28048      7,
28049      "the original closure must remain usable"
28050    );
28051
28052    let config_error = controller
28053      .configure(RuntimeOptions::default())
28054      .expect_err("shutdown must not make runtime configuration mutable again");
28055    assert_eq!(
28056      config_error.to_string(),
28057      "the async runtime configuration is frozen; configure it before the first async call"
28058    );
28059
28060    let state = controller
28061      .state
28062      .lock()
28063      .unwrap_or_else(std::sync::PoisonError::into_inner);
28064    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
28065  }
28066
28067  #[test]
28068  fn rejected_future_can_be_resubmitted_after_restart() {
28069    let controller = current_thread_controller("lifecycle-retry-future");
28070    controller.start().expect("runtime must start");
28071    drop(controller.backend());
28072    controller.shutdown().expect("runtime must stop");
28073
28074    let runs = Arc::new(AtomicUsize::new(0));
28075    let runs_task = Arc::clone(&runs);
28076    let future = async move {
28077      runs_task.fetch_add(1, Ordering::SeqCst);
28078      23
28079    };
28080    let Err((error, future)) = controller.try_spawn(future) else {
28081      panic!("stopped runtime must reject the future");
28082    };
28083    assert!(error.to_string().contains("runtime is stopped"));
28084    assert_eq!(runs.load(Ordering::SeqCst), 0);
28085
28086    controller.start().expect("runtime must restart");
28087    let handle = controller
28088      .try_spawn(future)
28089      .unwrap_or_else(|_| panic!("restart must accept retained future"));
28090    let dispatch = controller_current_thread_dispatch(&controller);
28091    controller.drive_current_thread_dispatch(dispatch, false);
28092    assert_eq!(
28093      futures::executor::block_on(handle).expect("retained future must complete"),
28094      23
28095    );
28096    assert_eq!(runs.load(Ordering::SeqCst), 1);
28097    controller
28098      .shutdown()
28099      .expect("replacement runtime must stop");
28100  }
28101
28102  #[test]
28103  fn runtime_controller_start_after_shutdown_accepts_work() {
28104    let controller = current_thread_controller("lifecycle-restart");
28105    controller.start().expect("runtime must start");
28106    drop(controller.backend());
28107    controller.shutdown().expect("runtime must stop");
28108    controller.start().expect("runtime must restart");
28109
28110    let async_ran = Arc::new(AtomicBool::new(false));
28111    let async_ran_task = Arc::clone(&async_ran);
28112    controller
28113      .try_spawn_detached(async move {
28114        async_ran_task.store(true, Ordering::SeqCst);
28115      })
28116      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
28117    assert!(
28118      !async_ran.load(Ordering::SeqCst),
28119      "hostless CurrentThread submission must remain enqueue-only"
28120    );
28121    let dispatch = controller_current_thread_dispatch(&controller);
28122    controller.drive_current_thread_dispatch(dispatch, false);
28123    assert!(
28124      async_ran.load(Ordering::SeqCst),
28125      "restarted runtime must poll submitted work"
28126    );
28127
28128    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
28129      panic!("restarted runtime must accept blocking work");
28130    };
28131    assert_eq!(
28132      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
28133      11
28134    );
28135  }
28136
28137  #[test]
28138  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
28139    let controller = current_thread_controller("stale-host-generation");
28140    let old_generation = controller.backend().generation();
28141    let old_dispatch = controller_current_thread_dispatch(&controller);
28142    controller.shutdown().unwrap();
28143    controller.start().unwrap();
28144    let new_generation = controller.backend().generation();
28145    assert_ne!(new_generation, old_generation);
28146
28147    let ran = Arc::new(AtomicBool::new(false));
28148    let ran_task = Arc::clone(&ran);
28149    controller
28150      .try_spawn_detached(async move {
28151        ran_task.store(true, Ordering::SeqCst);
28152      })
28153      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
28154    let new_dispatch = controller_current_thread_dispatch(&controller);
28155    assert_ne!(new_dispatch, old_dispatch);
28156
28157    controller.drive_current_thread_dispatch(old_dispatch, false);
28158    assert!(
28159      !ran.load(Ordering::SeqCst),
28160      "a callback from the retired generation must not touch the restarted executor"
28161    );
28162    controller.drive_current_thread_dispatch(new_dispatch, false);
28163    assert!(ran.load(Ordering::SeqCst));
28164    controller.shutdown().unwrap();
28165  }
28166
28167  #[test]
28168  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
28169    let controller = current_thread_controller("stale-host-cancellation");
28170    let old_generation = controller.backend().generation();
28171    let old_dispatch = controller_current_thread_dispatch(&controller);
28172    controller.shutdown().unwrap();
28173    controller.start().unwrap();
28174    let backend = controller.backend();
28175    let new_generation = backend.generation();
28176    assert_ne!(new_generation, old_generation);
28177    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28178      panic!("configured CurrentThread backend must create a CurrentThread executor");
28179    };
28180    let new_dispatch = current_thread_dispatch(executor);
28181    assert_ne!(new_dispatch, old_dispatch);
28182
28183    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
28184    assert_eq!(
28185      executor.dispatch_pending.load(Ordering::Acquire),
28186      new_dispatch,
28187      "a delayed failure from the retired generation must not release the replacement latch"
28188    );
28189
28190    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
28191    assert_eq!(
28192      executor.dispatch_pending.load(Ordering::Acquire),
28193      0,
28194      "the live generation must be able to cancel its accepted dispatch"
28195    );
28196    drop(backend);
28197    controller.shutdown().unwrap();
28198  }
28199
28200  #[cfg(napi_runtime_os_threads)]
28201  #[test]
28202  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
28203    use std::sync::mpsc;
28204
28205    type ProbeResult = (
28206      &'static str,
28207      Option<u64>,
28208      Result<(), String>,
28209      Result<(), String>,
28210      Result<(), String>,
28211    );
28212
28213    struct StaleGenerationProbe {
28214      label: &'static str,
28215      controller: Arc<RuntimeController>,
28216      result: mpsc::Sender<ProbeResult>,
28217    }
28218
28219    impl Drop for StaleGenerationProbe {
28220      fn drop(&mut self) {
28221        let generation = active_runtime_generation();
28222        let submission = match self.controller.try_spawn(async {}) {
28223          Ok(handle) => {
28224            drop(handle);
28225            Ok(())
28226          }
28227          Err((error, future)) => {
28228            drop(future);
28229            Err(error.to_string())
28230          }
28231        };
28232        let start = self.controller.start().map_err(|error| error.to_string());
28233        let shutdown = self
28234          .controller
28235          .shutdown()
28236          .map_err(|error| error.to_string());
28237        self
28238          .result
28239          .send((self.label, generation, submission, start, shutdown))
28240          .unwrap();
28241      }
28242    }
28243
28244    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28245    let old_generation = controller.backend().generation();
28246    let (result_tx, result_rx) = mpsc::channel();
28247
28248    let async_controller = Arc::clone(&controller);
28249    let async_result = result_tx.clone();
28250    let async_handle = controller
28251      .try_spawn(async move {
28252        StaleGenerationProbe {
28253          label: "async",
28254          controller: async_controller,
28255          result: async_result,
28256        }
28257      })
28258      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28259    let old_dispatch = controller_current_thread_dispatch(&controller);
28260    controller.drive_current_thread_dispatch(old_dispatch, false);
28261
28262    let ready_controller = Arc::clone(&controller);
28263    let ready_handle = controller
28264      .try_spawn_blocking(move || StaleGenerationProbe {
28265        label: "ready",
28266        controller: ready_controller,
28267        result: result_tx,
28268      })
28269      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28270
28271    controller.shutdown().unwrap();
28272    controller.start().unwrap();
28273    let new_generation = controller.backend().generation();
28274    assert_ne!(new_generation, old_generation);
28275
28276    drop(async_handle);
28277    let async_result = result_rx
28278      .recv_timeout(Duration::from_secs(2))
28279      .expect("the retained async output must be destroyed without waiting on the replacement");
28280    drop(ready_handle);
28281    let ready_result = result_rx
28282      .recv_timeout(Duration::from_secs(2))
28283      .expect("the retained immediate output must be destroyed without touching the replacement");
28284
28285    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28286      let (label, generation, submission, start, shutdown) = result;
28287      assert_eq!(label, expected_label);
28288      assert_eq!(generation, Some(old_generation));
28289      for error in [submission, start, shutdown] {
28290        assert!(
28291          error
28292            .expect_err("retired-generation output destruction must be rejected")
28293            .contains("retired generation")
28294        );
28295      }
28296    }
28297
28298    let survivor = controller
28299      .try_spawn(async { 29usize })
28300      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28301    let new_dispatch = controller_current_thread_dispatch(&controller);
28302    controller.drive_current_thread_dispatch(new_dispatch, false);
28303    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28304    controller.shutdown().unwrap();
28305  }
28306
28307  #[cfg(napi_runtime_os_threads)]
28308  #[test]
28309  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28310    use std::sync::mpsc;
28311
28312    struct ShutdownOnDrop {
28313      controller: Arc<RuntimeController>,
28314      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28315    }
28316
28317    impl Drop for ShutdownOnDrop {
28318      fn drop(&mut self) {
28319        self
28320          .result
28321          .send((active_runtime_generation(), self.controller.shutdown()))
28322          .unwrap();
28323      }
28324    }
28325
28326    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28327    let old_generation = controller.backend().generation();
28328    let (result_tx, result_rx) = mpsc::channel();
28329    let old_handle = controller
28330      .try_spawn_blocking({
28331        let controller = Arc::clone(&controller);
28332        move || ShutdownOnDrop {
28333          controller,
28334          result: result_tx,
28335        }
28336      })
28337      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28338    wait_until(
28339      "the first-generation blocking result to be buffered",
28340      || {
28341        controller
28342          .metrics
28343          .blocking_tasks_completed
28344          .load(Ordering::Acquire)
28345          == 1
28346      },
28347    );
28348    controller.shutdown().unwrap();
28349    controller.start().unwrap();
28350    let new_generation = controller.backend().generation();
28351    assert_ne!(new_generation, old_generation);
28352
28353    let (holder_started_tx, holder_started_rx) = mpsc::channel();
28354    let (release_holder_tx, release_holder_rx) = mpsc::channel();
28355    let holder = controller
28356      .try_spawn_blocking(move || {
28357        holder_started_tx.send(()).unwrap();
28358        release_holder_rx.recv().unwrap();
28359      })
28360      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28361    holder_started_rx
28362      .recv_timeout(Duration::from_secs(2))
28363      .unwrap();
28364
28365    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28366    let shutdown_controller = Arc::clone(&controller);
28367    let shutdown = std::thread::spawn(move || {
28368      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28369    });
28370    wait_until("the replacement generation to enter Stopping", || {
28371      matches!(
28372        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28373        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28374      )
28375    });
28376
28377    let dropper = std::thread::spawn(move || drop(old_handle));
28378    let (observed_generation, reentrant_shutdown) =
28379      match result_rx.recv_timeout(Duration::from_secs(2)) {
28380        Ok(result) => result,
28381        Err(error) => {
28382          release_holder_tx.send(()).unwrap();
28383          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28384          let _ = dropper.join();
28385          let _ = shutdown.join();
28386          panic!("retained first-generation output waited on replacement shutdown: {error}");
28387        }
28388      };
28389    assert_eq!(observed_generation, Some(old_generation));
28390    let error = reentrant_shutdown
28391      .expect_err("a retained first-generation output must not control replacement shutdown");
28392    assert!(error.to_string().contains("retired generation"));
28393
28394    release_holder_tx.send(()).unwrap();
28395    shutdown_rx
28396      .recv_timeout(Duration::from_secs(2))
28397      .unwrap()
28398      .unwrap();
28399    dropper.join().unwrap();
28400    shutdown.join().unwrap();
28401    futures::executor::block_on(holder).unwrap();
28402  }
28403
28404  #[cfg(napi_runtime_os_threads)]
28405  #[test]
28406  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28407    use std::sync::mpsc;
28408
28409    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28410    let generation = controller.backend().generation();
28411    let dispatch = controller_current_thread_dispatch(&controller);
28412    let (admitted_tx, admitted_rx) = mpsc::channel();
28413    let (release_tx, release_rx) = mpsc::channel();
28414    let driver_controller = Arc::clone(&controller);
28415    let driver = std::thread::spawn(move || {
28416      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28417        *slot.borrow_mut() = Some(Box::new(move || {
28418          admitted_tx.send(()).unwrap();
28419          release_rx.recv().unwrap();
28420        }));
28421      });
28422      driver_controller.drive_current_thread_dispatch(dispatch, false);
28423    });
28424    admitted_rx
28425      .recv_timeout(Duration::from_secs(2))
28426      .expect("the host turn must claim its scheduler role before execution");
28427
28428    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28429    let shutdown_controller = Arc::clone(&controller);
28430    let shutdown = std::thread::spawn(move || {
28431      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28432    });
28433    wait_until(
28434      "shutdown to close the admitted CurrentThread generation",
28435      || {
28436        let state = controller
28437          .state
28438          .lock()
28439          .unwrap_or_else(std::sync::PoisonError::into_inner);
28440        matches!(
28441          &state.lifecycle,
28442          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28443        )
28444      },
28445    );
28446    assert!(
28447      shutdown_rx
28448        .recv_timeout(Duration::from_millis(200))
28449        .is_err(),
28450      "shutdown must wait for an admitted host callback even before it starts draining"
28451    );
28452
28453    release_tx.send(()).unwrap();
28454    driver.join().unwrap();
28455    shutdown_rx
28456      .recv_timeout(Duration::from_secs(2))
28457      .unwrap()
28458      .unwrap();
28459    shutdown.join().unwrap();
28460  }
28461
28462  #[cfg(napi_runtime_os_threads)]
28463  #[test]
28464  fn spawn_racing_shutdown_cannot_recreate_backend() {
28465    use std::sync::mpsc;
28466
28467    let controller = Arc::new(current_thread_controller("lifecycle-race"));
28468    controller.start().expect("runtime must start");
28469    drop(controller.backend());
28470
28471    let (at_hook_tx, at_hook_rx) = mpsc::channel();
28472    let (release_tx, release_rx) = mpsc::channel();
28473    let ran = Arc::new(AtomicBool::new(false));
28474    let ran_task = Arc::clone(&ran);
28475    let worker_controller = Arc::clone(&controller);
28476    let worker = std::thread::spawn(move || {
28477      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28478        *slot.borrow_mut() = Some(Box::new(move || {
28479          at_hook_tx
28480            .send(())
28481            .expect("test coordinator must still be listening");
28482          release_rx
28483            .recv()
28484            .expect("test coordinator must release the submission");
28485        }));
28486      });
28487
28488      let future = async move {
28489        ran_task.store(true, Ordering::SeqCst);
28490      };
28491      match worker_controller.try_spawn_detached(future) {
28492        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28493        Err(future) => drop(future),
28494      }
28495    });
28496
28497    at_hook_rx
28498      .recv_timeout(Duration::from_secs(5))
28499      .expect("submitting thread must stop before taking the controller lock");
28500    controller.shutdown().expect("runtime must stop");
28501    release_tx
28502      .send(())
28503      .expect("submitting thread must still be waiting");
28504    worker.join().expect("submitting thread must not panic");
28505
28506    assert!(
28507      !ran.load(Ordering::SeqCst),
28508      "the rejected task must never run"
28509    );
28510    let state = controller
28511      .state
28512      .lock()
28513      .unwrap_or_else(std::sync::PoisonError::into_inner);
28514    assert!(
28515      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28516      "the rejected submission must not lazily recreate a backend"
28517    );
28518  }
28519
28520  #[cfg(napi_runtime_os_threads)]
28521  #[test]
28522  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28523    use std::sync::mpsc;
28524
28525    struct BlockingDropFuture {
28526      work: Arc<GenerationWork>,
28527      entered: mpsc::Sender<(Option<u64>, usize)>,
28528      release: mpsc::Receiver<()>,
28529    }
28530
28531    impl Future for BlockingDropFuture {
28532      type Output = ();
28533
28534      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28535        Poll::Pending
28536      }
28537    }
28538
28539    impl Drop for BlockingDropFuture {
28540      fn drop(&mut self) {
28541        let active = self
28542          .work
28543          .state
28544          .lock()
28545          .unwrap_or_else(std::sync::PoisonError::into_inner)
28546          .active;
28547        self
28548          .entered
28549          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28550          .unwrap();
28551        self.release.recv().unwrap();
28552      }
28553    }
28554
28555    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28556    let backend = controller.backend();
28557    let old_generation = backend.generation();
28558    let work = Arc::clone(&backend.work);
28559    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28560      panic!("configured CurrentThread backend must create a CurrentThread executor");
28561    };
28562    let executor = Arc::clone(executor);
28563    drop(backend);
28564
28565    let (registered_tx, registered_rx) = mpsc::channel();
28566    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28567    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28568    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28569    let submit_controller = Arc::clone(&controller);
28570    let submit_work = Arc::clone(&work);
28571    let submitter = std::thread::spawn(move || {
28572      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28573        *slot.borrow_mut() = Some(Box::new(move || {
28574          registered_tx.send(()).unwrap();
28575          release_submission_rx.recv().unwrap();
28576        }));
28577      });
28578      submit_controller
28579        .try_spawn_detached(BlockingDropFuture {
28580          work: submit_work,
28581          entered: drop_entered_tx,
28582          release: release_drop_rx,
28583        })
28584        .unwrap_or_else(|_| {
28585          panic!("the submission linearized before shutdown and must be accepted")
28586        });
28587    });
28588    registered_rx
28589      .recv_timeout(Duration::from_secs(2))
28590      .expect("the submitting thread must register generation work before shutdown");
28591
28592    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28593    let shutdown_controller = Arc::clone(&controller);
28594    let shutdown = std::thread::spawn(move || {
28595      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28596    });
28597    wait_until("CurrentThread shutdown to close the runnable queue", || {
28598      executor
28599        .queue
28600        .lock()
28601        .unwrap_or_else(std::sync::PoisonError::into_inner)
28602        .closed
28603    });
28604    release_submission_tx.send(()).unwrap();
28605
28606    let (drop_generation, active_during_drop) = drop_entered_rx
28607      .recv_timeout(Duration::from_secs(2))
28608      .expect("the closed queue must destroy the accepted unpolled future");
28609    assert_eq!(drop_generation, Some(old_generation));
28610    assert_eq!(
28611      active_during_drop, 1,
28612      "accepted generation work must remain registered through user future destruction"
28613    );
28614
28615    let (start_tx, start_rx) = mpsc::channel();
28616    let start_controller = Arc::clone(&controller);
28617    let starter = std::thread::spawn(move || {
28618      start_tx.send(start_controller.start()).unwrap();
28619    });
28620    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28621    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28622    let shutdown_overlapped = early_shutdown.is_some();
28623    let restart_overlapped = early_start.is_some();
28624
28625    release_drop_tx.send(()).unwrap();
28626    submitter.join().unwrap();
28627    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28628      shutdown_rx
28629        .recv_timeout(Duration::from_secs(2))
28630        .expect("shutdown must finish after drop")
28631    });
28632    shutdown_result.unwrap();
28633    shutdown.join().unwrap();
28634    let start_result = early_start.unwrap_or_else(|| {
28635      start_rx
28636        .recv_timeout(Duration::from_secs(2))
28637        .expect("restart must follow shutdown")
28638    });
28639    start_result.unwrap();
28640    starter.join().unwrap();
28641
28642    assert!(
28643      !shutdown_overlapped,
28644      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28645    );
28646    assert!(
28647      !restart_overlapped,
28648      "restart must not create a new generation while an accepted old-generation future is being destroyed"
28649    );
28650    assert_ne!(controller.backend().generation(), old_generation);
28651    controller.shutdown().unwrap();
28652  }
28653
28654  #[cfg(napi_runtime_os_threads)]
28655  #[test]
28656  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28657    use std::sync::mpsc;
28658
28659    struct BlockingDrop {
28660      work: Arc<GenerationWork>,
28661      entered: mpsc::Sender<(Option<u64>, usize)>,
28662      release: mpsc::Receiver<()>,
28663    }
28664
28665    impl Drop for BlockingDrop {
28666      fn drop(&mut self) {
28667        let active = self
28668          .work
28669          .state
28670          .lock()
28671          .unwrap_or_else(std::sync::PoisonError::into_inner)
28672          .active;
28673        self
28674          .entered
28675          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28676          .unwrap();
28677        self.release.recv().unwrap();
28678      }
28679    }
28680
28681    let controller = Arc::new(multi_thread_controller(
28682      "accepted-blocking-submission-drop",
28683      2,
28684      1,
28685    ));
28686    let backend = controller.backend();
28687    let old_generation = backend.generation();
28688    let work = Arc::clone(&backend.work);
28689    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28690      panic!("configured MultiThread backend must create a MultiThread executor");
28691    };
28692    let executor = Arc::downgrade(executor);
28693    drop(backend);
28694
28695    let (registered_tx, registered_rx) = mpsc::channel();
28696    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28697    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28698    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28699    let submit_controller = Arc::clone(&controller);
28700    let submitter = std::thread::spawn(move || {
28701      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28702        *slot.borrow_mut() = Some(Box::new(move || {
28703          registered_tx.send(()).unwrap();
28704          release_submission_rx.recv().unwrap();
28705        }));
28706      });
28707      let drop_signal = BlockingDrop {
28708        work,
28709        entered: drop_entered_tx,
28710        release: release_drop_rx,
28711      };
28712      let handle = submit_controller
28713        .try_spawn_blocking(move || {
28714          let _drop_signal = drop_signal;
28715        })
28716        .unwrap_or_else(|_| {
28717          panic!("the submission linearized before shutdown and must be accepted")
28718        });
28719      assert!(
28720        futures::executor::block_on(handle).is_err(),
28721        "the executor must cancel work that reaches its queue after shutdown closed it"
28722      );
28723    });
28724    registered_rx
28725      .recv_timeout(Duration::from_secs(2))
28726      .expect("the submitting thread must register generation work before shutdown");
28727
28728    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28729    let shutdown_controller = Arc::clone(&controller);
28730    let shutdown = std::thread::spawn(move || {
28731      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28732    });
28733    wait_until("MultiThread shutdown to close the blocking queue", || {
28734      executor
28735        .upgrade()
28736        .expect("the retiring executor must remain alive until shutdown completes")
28737        .blocking_queue
28738        .lock()
28739        .unwrap_or_else(std::sync::PoisonError::into_inner)
28740        .closed
28741    });
28742    release_submission_tx.send(()).unwrap();
28743
28744    let (drop_generation, active_during_drop) = drop_entered_rx
28745      .recv_timeout(Duration::from_secs(2))
28746      .expect("the closed queue must destroy the accepted blocking closure");
28747    assert_eq!(drop_generation, Some(old_generation));
28748    assert_eq!(
28749      active_during_drop, 1,
28750      "accepted generation work must remain registered through blocking closure destruction"
28751    );
28752
28753    let (start_tx, start_rx) = mpsc::channel();
28754    let start_controller = Arc::clone(&controller);
28755    let starter = std::thread::spawn(move || {
28756      start_tx.send(start_controller.start()).unwrap();
28757    });
28758    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28759    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28760    let shutdown_overlapped = early_shutdown.is_some();
28761    let restart_overlapped = early_start.is_some();
28762
28763    release_drop_tx.send(()).unwrap();
28764    submitter.join().unwrap();
28765    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28766      shutdown_rx
28767        .recv_timeout(Duration::from_secs(2))
28768        .expect("shutdown must finish after drop")
28769    });
28770    shutdown_result.unwrap();
28771    shutdown.join().unwrap();
28772    let start_result = early_start.unwrap_or_else(|| {
28773      start_rx
28774        .recv_timeout(Duration::from_secs(2))
28775        .expect("restart must follow shutdown")
28776    });
28777    start_result.unwrap();
28778    starter.join().unwrap();
28779
28780    assert!(
28781      !shutdown_overlapped,
28782      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
28783    );
28784    assert!(
28785      !restart_overlapped,
28786      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
28787    );
28788    assert_ne!(controller.backend().generation(), old_generation);
28789    controller.shutdown().unwrap();
28790  }
28791
28792  #[cfg(napi_runtime_os_threads)]
28793  #[test]
28794  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
28795    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
28796    let options = controller.options();
28797    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
28798
28799    let backend = controller.backend();
28800    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28801      panic!("configured MultiThread backend must create a Rayon executor");
28802    };
28803    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
28804    let lifecycle = Arc::clone(&executor.worker_lifecycle);
28805    drop(backend);
28806
28807    controller.shutdown().expect("runtime must stop");
28808    assert_eq!(
28809      lifecycle.remaining(),
28810      0,
28811      "shutdown must observe every physical worker exit"
28812    );
28813  }
28814
28815  #[cfg(napi_runtime_os_threads)]
28816  #[test]
28817  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
28818    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
28819    // includes worker TLS-destructor completion -- `wait_for_all_workers`
28820    // joins the OS threads, and an OS thread only becomes joinable after its
28821    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
28822    // worker's destructor and sampling the counter on the shutdown thread at
28823    // the very instant `shutdown()` returns: a barrier that excluded the
28824    // destructors would race that sample and fail loudly.
28825    //
28826    // The destructor must never do more than record. The previous shape
28827    // parked it on a condvar until the harness released it -- on Windows TLS
28828    // destructors run under the process loader lock, so the first parked
28829    // worker blocked the second from even ENTERING its destructor, the
28830    // release condition (both entered) became unsatisfiable, and the parked
28831    // destructor froze every thread spawn/exit in the process: the
28832    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
28833    // `WorkerTlsDropAction`).
28834    use std::sync::mpsc;
28835
28836    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
28837    let backend = controller.backend();
28838    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28839      panic!("configured MultiThread backend must create a Rayon executor");
28840    };
28841    let worker_count = executor.pool.current_num_threads();
28842    let completed = Arc::new(AtomicUsize::new(0));
28843    let probe_completed = Arc::clone(&completed);
28844    executor.pool.broadcast(move |_| {
28845      WORKER_TLS_DROP_PROBE.with(|slot| {
28846        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28847          action: Some(WorkerTlsDropAction::Record {
28848            completed: Arc::clone(&probe_completed),
28849          }),
28850        });
28851        assert!(
28852          previous.is_none(),
28853          "each worker must install exactly one TLS drop probe"
28854        );
28855      });
28856    });
28857    drop(backend);
28858
28859    assert_eq!(
28860      completed.load(Ordering::SeqCst),
28861      0,
28862      "no worker TLS destructor may run before shutdown retires the workers"
28863    );
28864
28865    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28866    let shutdown_controller = Arc::clone(&controller);
28867    let sample_completed = Arc::clone(&completed);
28868    let shutdown = std::thread::spawn(move || {
28869      let result = shutdown_controller.shutdown();
28870      // Sample on the SAME thread, before any further synchronization could
28871      // mask a barrier gap: at this instant every destructor must already
28872      // have recorded itself.
28873      let seen_at_return = sample_completed.load(Ordering::SeqCst);
28874      shutdown_tx.send((result, seen_at_return)).unwrap();
28875    });
28876    let (shutdown_result, seen_at_return) = shutdown_rx
28877      .recv_timeout(Duration::from_secs(10))
28878      .expect("shutdown must finish once worker TLS destructors have run");
28879    shutdown_result.unwrap();
28880    assert_eq!(
28881      seen_at_return, worker_count,
28882      "every worker TLS destructor must have completed before shutdown() returned: \
28883       the worker-join barrier must include TLS-destructor completion"
28884    );
28885    join_within(
28886      "the shutdown thread exits after teardown",
28887      shutdown,
28888      Duration::from_secs(30),
28889    );
28890  }
28891
28892  #[cfg(not(target_family = "wasm"))]
28893  #[test]
28894  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
28895    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
28896
28897    if std::env::var_os(CHILD_ENV).is_some() {
28898      use std::sync::mpsc;
28899
28900      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
28901      let backend = controller.backend();
28902      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28903        panic!("configured MultiThread backend must create a Rayon executor");
28904      };
28905      let installed = Arc::new(AtomicBool::new(false));
28906      let (completed_tx, completed_rx) = mpsc::channel();
28907      let probe_controller = Arc::clone(&controller);
28908      let probe_installed = Arc::clone(&installed);
28909      executor.pool.broadcast(move |_| {
28910        if probe_installed.swap(true, Ordering::SeqCst) {
28911          return;
28912        }
28913        WORKER_TLS_DROP_PROBE.with(|slot| {
28914          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28915            action: Some(WorkerTlsDropAction::ReenterLifecycle {
28916              controller: Arc::clone(&probe_controller),
28917              completed: completed_tx.clone(),
28918            }),
28919          });
28920          assert!(
28921            previous.is_none(),
28922            "the selected worker must have no prior TLS probe"
28923          );
28924        });
28925      });
28926      assert!(
28927        installed.load(Ordering::SeqCst),
28928        "one worker must install the lifecycle probe"
28929      );
28930      drop(backend);
28931
28932      controller
28933        .shutdown()
28934        .expect("external shutdown must join the retiring workers");
28935      let (start, shutdown) = completed_rx
28936        .recv_timeout(Duration::from_secs(2))
28937        .expect("the TLS destructor must complete both lifecycle reentry attempts");
28938      assert_eq!(
28939        start.unwrap_err(),
28940        "cannot restart the async runtime from work in the generation being stopped"
28941      );
28942      assert_eq!(
28943        shutdown.unwrap_err(),
28944        "cannot wait for async runtime shutdown from work in the generation being stopped"
28945      );
28946      return;
28947    }
28948
28949    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28950      .arg("--exact")
28951      .arg(
28952        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28953      )
28954      .arg("--nocapture")
28955      .env(CHILD_ENV, "1")
28956      .stdout(std::process::Stdio::piped())
28957      .stderr(std::process::Stdio::piped())
28958      .spawn()
28959      .expect("the TLS lifecycle-reentry subprocess must start");
28960    let deadline = Instant::now() + Duration::from_secs(5);
28961    let status = loop {
28962      if let Some(status) = child
28963        .try_wait()
28964        .expect("the subprocess status must be readable")
28965      {
28966        break status;
28967      }
28968      if Instant::now() >= deadline {
28969        let _ = child.kill();
28970        let output = child
28971          .wait_with_output()
28972          .expect("the timed-out subprocess must be reaped");
28973        panic!(
28974          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28975          String::from_utf8_lossy(&output.stdout),
28976          String::from_utf8_lossy(&output.stderr)
28977        );
28978      }
28979      std::thread::sleep(Duration::from_millis(10));
28980    };
28981    let output = child
28982      .wait_with_output()
28983      .expect("the completed subprocess output must be read");
28984    assert!(
28985      status.success(),
28986      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28987      status.code(),
28988      String::from_utf8_lossy(&output.stdout),
28989      String::from_utf8_lossy(&output.stderr)
28990    );
28991  }
28992
28993  #[cfg(napi_runtime_os_threads)]
28994  #[test]
28995  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28996    use std::sync::mpsc;
28997
28998    struct DropSignal(mpsc::Sender<()>);
28999    impl Drop for DropSignal {
29000      fn drop(&mut self) {
29001        let _ = self.0.send(());
29002      }
29003    }
29004
29005    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
29006    let (polled_tx, polled_rx) = mpsc::channel();
29007    let (dropped_tx, dropped_rx) = mpsc::channel();
29008    controller
29009      .try_spawn_detached(async move {
29010        let _drop_signal = DropSignal(dropped_tx);
29011        polled_tx.send(()).unwrap();
29012        std::future::pending::<()>().await;
29013      })
29014      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
29015    polled_rx
29016      .recv_timeout(Duration::from_secs(5))
29017      .expect("the pending task must be polled before shutdown");
29018
29019    controller
29020      .shutdown()
29021      .expect("shutdown must cancel accepted async work");
29022    dropped_rx
29023      .recv_timeout(Duration::from_secs(5))
29024      .expect("shutdown must drop the accepted task future before returning");
29025  }
29026
29027  #[cfg(napi_runtime_os_threads)]
29028  #[test]
29029  fn current_thread_shutdown_outranks_perpetual_self_wake() {
29030    use std::sync::mpsc;
29031
29032    struct SelfWakingPending {
29033      entered: Option<mpsc::Sender<()>>,
29034    }
29035
29036    impl Future for SelfWakingPending {
29037      type Output = ();
29038
29039      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29040        if let Some(entered) = self.entered.take() {
29041          entered.send(()).unwrap();
29042        }
29043        cx.waker().wake_by_ref();
29044        Poll::Pending
29045      }
29046    }
29047
29048    let controller = Arc::new(current_thread_controller(
29049      "current-thread-self-wake-shutdown",
29050    ));
29051    let (entered_tx, entered_rx) = mpsc::channel();
29052    let (outcome_tx, outcome_rx) = mpsc::channel();
29053    let block_on_controller = Arc::clone(&controller);
29054    let runner = std::thread::spawn(move || {
29055      let mut future = std::pin::pin!(SelfWakingPending {
29056        entered: Some(entered_tx)
29057      });
29058      outcome_tx
29059        .send(block_on_controller.block_on(future.as_mut()))
29060        .unwrap();
29061    });
29062    entered_rx
29063      .recv_timeout(Duration::from_secs(2))
29064      .expect("the self-waking future must be polled before shutdown");
29065
29066    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29067    let shutdown_controller = Arc::clone(&controller);
29068    let shutdown = std::thread::spawn(move || {
29069      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29070    });
29071    shutdown_rx
29072      .recv_timeout(Duration::from_secs(2))
29073      .expect("shutdown must outrank a stored self-wake permit")
29074      .unwrap();
29075    assert_eq!(
29076      outcome_rx
29077        .recv_timeout(Duration::from_secs(2))
29078        .expect("the raw block_on driver must report shutdown"),
29079      BlockOnOutcome::Stopped
29080    );
29081    runner.join().unwrap();
29082    shutdown.join().unwrap();
29083  }
29084
29085  #[cfg(napi_runtime_os_threads)]
29086  #[test]
29087  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
29088    use std::sync::mpsc;
29089
29090    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
29091    let backend = controller.backend();
29092    let executor = match &backend.executor {
29093      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29094      RuntimeExecutor::CurrentThread(_) => unreachable!(),
29095    };
29096    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
29097    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
29098    for _ in 0..2 {
29099      let worker_ready_tx = worker_ready_tx.clone();
29100      let release_workers = Arc::clone(&release_workers);
29101      executor.pool.spawn(move || {
29102        worker_ready_tx.send(()).unwrap();
29103        let (released, changed) = &*release_workers;
29104        let mut released = released
29105          .lock()
29106          .unwrap_or_else(std::sync::PoisonError::into_inner);
29107        while !*released {
29108          released = changed
29109            .wait(released)
29110            .unwrap_or_else(std::sync::PoisonError::into_inner);
29111        }
29112      });
29113    }
29114    drop(worker_ready_tx);
29115    for _ in 0..2 {
29116      worker_ready_rx
29117        .recv_timeout(Duration::from_secs(5))
29118        .expect("both workers must be occupied before the FIFO runnable is submitted");
29119    }
29120    drop(executor);
29121    drop(backend);
29122
29123    let polls = Arc::new(AtomicUsize::new(0));
29124    let handle = controller.spawn(StopPollProbe {
29125      polls: Arc::clone(&polls),
29126      first_poll: None,
29127    });
29128    wait_until("the FIFO runnable to be queued", || {
29129      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
29130    });
29131
29132    let observed_queue = Arc::new(Mutex::new(None));
29133    let hook_observed_queue = Arc::clone(&observed_queue);
29134    let hook_metrics = Arc::clone(&controller.metrics);
29135    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29136      *slot.borrow_mut() = Some(Box::new(move || {
29137        let (released, changed) = &*release_workers;
29138        *released
29139          .lock()
29140          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29141        changed.notify_all();
29142        wait_until("the stopped FIFO runnable claim", || {
29143          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
29144        });
29145        *hook_observed_queue
29146          .lock()
29147          .unwrap_or_else(std::sync::PoisonError::into_inner) =
29148          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
29149      }));
29150    });
29151
29152    controller
29153      .shutdown()
29154      .expect("shutdown must cancel the stopped FIFO claim");
29155    assert_eq!(polls.load(Ordering::SeqCst), 0);
29156    assert_eq!(
29157      *observed_queue
29158        .lock()
29159        .unwrap_or_else(std::sync::PoisonError::into_inner),
29160      Some(0)
29161    );
29162    assert_eq!(
29163      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29164      0
29165    );
29166    drop(handle);
29167  }
29168
29169  #[cfg(napi_runtime_os_threads)]
29170  #[test]
29171  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
29172    use std::sync::mpsc;
29173
29174    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
29175    let backend = controller.backend();
29176    let executor = match &backend.executor {
29177      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
29178      RuntimeExecutor::CurrentThread(_) => unreachable!(),
29179    };
29180    let polls = Arc::new(AtomicUsize::new(0));
29181    let scheduler = Arc::downgrade(&executor);
29182    let (runnable, task) = async_task::spawn(
29183      StopPollProbe {
29184        polls: Arc::clone(&polls),
29185        first_poll: None,
29186      },
29187      move |runnable| {
29188        if let Some(executor) = scheduler.upgrade() {
29189          executor.schedule(runnable);
29190        }
29191      },
29192    );
29193    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
29194    let (release_slot_tx, release_slot_rx) = mpsc::channel();
29195    let (claim_done_tx, claim_done_rx) = mpsc::channel();
29196    let worker_executor = Arc::clone(&executor);
29197    let worker_metrics = Arc::clone(&controller.metrics);
29198    executor.pool.spawn(move || {
29199      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
29200      worker_executor.schedule(runnable);
29201      slot_ready_tx.send(()).unwrap();
29202      release_slot_rx.recv().unwrap();
29203      let claimed = worker_executor.run_one_runnable();
29204      claim_done_tx
29205        .send((
29206          claimed,
29207          worker_metrics.queued_runnables.load(Ordering::SeqCst),
29208        ))
29209        .unwrap();
29210    });
29211    slot_ready_rx
29212      .recv_timeout(Duration::from_secs(5))
29213      .expect("the worker must publish the LIFO runnable before shutdown");
29214    assert_eq!(
29215      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29216      1
29217    );
29218    drop(executor);
29219    drop(backend);
29220
29221    let observed_claim = Arc::new(Mutex::new(None));
29222    let hook_observed_claim = Arc::clone(&observed_claim);
29223    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29224      *slot.borrow_mut() = Some(Box::new(move || {
29225        release_slot_tx.send(()).unwrap();
29226        let result = claim_done_rx
29227          .recv_timeout(Duration::from_secs(5))
29228          .expect("the stopped LIFO runnable claim must complete before abort");
29229        *hook_observed_claim
29230          .lock()
29231          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29232      }));
29233    });
29234
29235    controller
29236      .shutdown()
29237      .expect("shutdown must cancel the stopped LIFO claim");
29238    assert_eq!(polls.load(Ordering::SeqCst), 0);
29239    assert_eq!(
29240      *observed_claim
29241        .lock()
29242        .unwrap_or_else(std::sync::PoisonError::into_inner),
29243      Some((true, 0))
29244    );
29245    assert_eq!(
29246      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29247      0
29248    );
29249    drop(task);
29250  }
29251
29252  #[cfg(napi_runtime_os_threads)]
29253  #[test]
29254  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29255    use std::sync::mpsc;
29256
29257    struct BlockingDrop {
29258      entered: mpsc::Sender<()>,
29259      release: mpsc::Receiver<()>,
29260    }
29261
29262    impl Drop for BlockingDrop {
29263      fn drop(&mut self) {
29264        self.entered.send(()).unwrap();
29265        self.release.recv().unwrap();
29266      }
29267    }
29268
29269    struct TwoPollProbe {
29270      first_poll: Option<mpsc::Sender<()>>,
29271      release_first_poll: mpsc::Receiver<()>,
29272      second_poll: mpsc::Sender<()>,
29273    }
29274
29275    impl Future for TwoPollProbe {
29276      type Output = ();
29277
29278      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29279        if let Some(first_poll) = self.first_poll.take() {
29280          first_poll.send(()).unwrap();
29281          self.release_first_poll.recv().unwrap();
29282        } else {
29283          self.second_poll.send(()).unwrap();
29284        }
29285        cx.waker().wake_by_ref();
29286        Poll::Pending
29287      }
29288    }
29289
29290    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29291    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29292    let (release_drop_tx, release_drop_rx) = mpsc::channel();
29293    let blocking_drop = BlockingDrop {
29294      entered: drop_entered_tx,
29295      release: release_drop_rx,
29296    };
29297    controller
29298      .try_spawn_detached(async move {
29299        let _drop = blocking_drop;
29300        std::future::pending::<()>().await;
29301      })
29302      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29303
29304    let (first_poll_tx, first_poll_rx) = mpsc::channel();
29305    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29306    let (second_poll_tx, second_poll_rx) = mpsc::channel();
29307    let (outcome_tx, outcome_rx) = mpsc::channel();
29308    let runner_controller = Arc::clone(&controller);
29309    let runner = std::thread::spawn(move || {
29310      let mut future = std::pin::pin!(TwoPollProbe {
29311        first_poll: Some(first_poll_tx),
29312        release_first_poll: release_first_poll_rx,
29313        second_poll: second_poll_tx,
29314      });
29315      outcome_tx
29316        .send(runner_controller.block_on(future.as_mut()))
29317        .unwrap();
29318    });
29319    first_poll_rx
29320      .recv_timeout(Duration::from_secs(2))
29321      .expect("the active block_on poll must enter before shutdown");
29322
29323    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29324    let shutdown_controller = Arc::clone(&controller);
29325    let shutdown = std::thread::spawn(move || {
29326      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29327    });
29328    drop_entered_rx
29329      .recv_timeout(Duration::from_secs(2))
29330      .expect("shutdown must begin destroying queued work");
29331
29332    release_first_poll_tx.send(()).unwrap();
29333    let polled_again = second_poll_rx
29334      .recv_timeout(Duration::from_millis(200))
29335      .is_ok();
29336    release_drop_tx.send(()).unwrap();
29337
29338    assert!(
29339      !polled_again,
29340      "an active block_on driver must observe generation stop before queued user cleanup blocks"
29341    );
29342    assert_eq!(
29343      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29344      BlockOnOutcome::Stopped
29345    );
29346    shutdown_rx
29347      .recv_timeout(Duration::from_secs(2))
29348      .unwrap()
29349      .unwrap();
29350    runner.join().unwrap();
29351    shutdown.join().unwrap();
29352  }
29353
29354  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29355  #[test]
29356  fn shutdown_cancels_nested_block_on_within_deadline() {
29357    use std::sync::mpsc;
29358
29359    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29360    let task_controller = Arc::clone(&controller);
29361    let (entered_tx, entered_rx) = mpsc::channel();
29362    let task = controller
29363      .try_spawn(async move {
29364        entered_tx.send(()).unwrap();
29365        block_on_with_controller(&task_controller, std::future::pending::<()>());
29366      })
29367      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29368    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29369
29370    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29371    let shutdown_controller = Arc::clone(&controller);
29372    let shutdown = std::thread::spawn(move || {
29373      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29374    });
29375    shutdown_rx
29376      .recv_timeout(Duration::from_secs(2))
29377      .expect("shutdown must wake and cancel an active nested block_on")
29378      .unwrap();
29379    shutdown.join().unwrap();
29380    assert!(
29381      futures::executor::block_on(task).is_err(),
29382      "the cancelled nested block_on task must resolve as stopped"
29383    );
29384  }
29385
29386  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29387  #[test]
29388  fn stopped_public_block_on_drops_future_before_generation_retirement() {
29389    use std::sync::mpsc;
29390
29391    struct PendingDrop {
29392      entered: Option<mpsc::Sender<()>>,
29393      dropped: mpsc::Sender<Option<u64>>,
29394      release: mpsc::Receiver<()>,
29395    }
29396
29397    impl Future for PendingDrop {
29398      type Output = ();
29399
29400      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29401        if let Some(entered) = self.entered.take() {
29402          entered.send(()).unwrap();
29403        }
29404        Poll::Pending
29405      }
29406    }
29407
29408    impl Drop for PendingDrop {
29409      fn drop(&mut self) {
29410        self
29411          .dropped
29412          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29413          .unwrap();
29414        self.release.recv().unwrap();
29415      }
29416    }
29417
29418    let controller = Arc::new(current_thread_controller(
29419      "public-block-on-generation-destruction",
29420    ));
29421    let generation = controller.backend().generation();
29422    let (entered_tx, entered_rx) = mpsc::channel();
29423    let (dropped_tx, dropped_rx) = mpsc::channel();
29424    let (release_tx, release_rx) = mpsc::channel();
29425    let (runner_done_tx, runner_done_rx) = mpsc::channel();
29426    let block_controller = Arc::clone(&controller);
29427    let runner = std::thread::spawn(move || {
29428      let result = catch_unwind(AssertUnwindSafe(|| {
29429        block_on_with_controller(
29430          &block_controller,
29431          PendingDrop {
29432            entered: Some(entered_tx),
29433            dropped: dropped_tx,
29434            release: release_rx,
29435          },
29436        );
29437      }));
29438      runner_done_tx.send(result.is_err()).unwrap();
29439    });
29440    entered_rx
29441      .recv_timeout(Duration::from_secs(2))
29442      .expect("the public block_on future must be polled before shutdown");
29443
29444    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29445    let shutdown_controller = Arc::clone(&controller);
29446    let shutdown = std::thread::spawn(move || {
29447      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29448    });
29449
29450    assert_eq!(
29451      dropped_rx
29452        .recv_timeout(Duration::from_secs(2))
29453        .expect("shutdown must wake block_on and begin destroying its future"),
29454      Some(generation),
29455      "the stopped future must be destroyed under its retiring generation identity"
29456    );
29457    assert!(
29458      shutdown_rx
29459        .recv_timeout(Duration::from_millis(200))
29460        .is_err(),
29461      "shutdown must not publish Stopped while the public block_on future destructor is running"
29462    );
29463
29464    release_tx.send(()).unwrap();
29465    assert!(
29466      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29467      "the public helper must retain its existing early-stop panic behavior"
29468    );
29469    shutdown_rx
29470      .recv_timeout(Duration::from_secs(2))
29471      .unwrap()
29472      .unwrap();
29473    runner.join().unwrap();
29474    shutdown.join().unwrap();
29475  }
29476
29477  #[cfg(not(target_family = "wasm"))]
29478  #[test]
29479  fn shutdown_contains_panicking_nested_block_on_future_drop() {
29480    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29481
29482    if std::env::var_os(CHILD_ENV).is_some() {
29483      use std::sync::mpsc;
29484
29485      struct PendingPanicOnDrop {
29486        entered: Option<mpsc::Sender<()>>,
29487      }
29488
29489      impl Future for PendingPanicOnDrop {
29490        type Output = ();
29491
29492        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29493          if let Some(entered) = self.entered.take() {
29494            entered.send(()).unwrap();
29495          }
29496          Poll::Pending
29497        }
29498      }
29499
29500      impl Drop for PendingPanicOnDrop {
29501        fn drop(&mut self) {
29502          panic!("intentional nested block_on future destructor panic");
29503        }
29504      }
29505
29506      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29507      let task_controller = Arc::clone(&controller);
29508      let (entered_tx, entered_rx) = mpsc::channel();
29509      let task = controller
29510        .try_spawn(async move {
29511          block_on_with_controller(
29512            &task_controller,
29513            PendingPanicOnDrop {
29514              entered: Some(entered_tx),
29515            },
29516          );
29517        })
29518        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29519      entered_rx
29520        .recv_timeout(Duration::from_secs(2))
29521        .expect("the nested block_on future must be polled before shutdown");
29522
29523      controller
29524        .shutdown()
29525        .expect("shutdown must contain nested future destruction");
29526      assert!(
29527        futures::executor::block_on(task).is_err(),
29528        "shutdown cancellation must still surface as a task error"
29529      );
29530      return;
29531    }
29532
29533    let output = std::process::Command::new(std::env::current_exe().unwrap())
29534      .arg("--exact")
29535      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29536      .arg("--nocapture")
29537      .env(CHILD_ENV, "1")
29538      .output()
29539      .expect("the subprocess test must start");
29540    assert!(
29541      output.status.success(),
29542      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29543      output.status.code(),
29544      String::from_utf8_lossy(&output.stdout),
29545      String::from_utf8_lossy(&output.stderr)
29546    );
29547  }
29548
29549  #[cfg(not(target_family = "wasm"))]
29550  #[test]
29551  fn panicking_future_drop_is_contained_before_async_task_drop() {
29552    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29553
29554    if std::env::var_os(CHILD_ENV).is_some() {
29555      struct PanicOnDrop;
29556      impl Drop for PanicOnDrop {
29557        fn drop(&mut self) {
29558          panic!("intentional future destructor panic");
29559        }
29560      }
29561
29562      let controller = current_thread_controller("future-drop-subprocess");
29563      let bomb = PanicOnDrop;
29564      controller
29565        .try_spawn_detached(async move {
29566          let _bomb = bomb;
29567          std::future::pending::<()>().await;
29568        })
29569        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29570      controller
29571        .shutdown()
29572        .expect("shutdown must contain the future destructor panic");
29573      return;
29574    }
29575
29576    let output = std::process::Command::new(std::env::current_exe().unwrap())
29577      .arg("--exact")
29578      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29579      .arg("--nocapture")
29580      .env(CHILD_ENV, "1")
29581      .output()
29582      .expect("the subprocess test must start");
29583    assert!(
29584      output.status.success(),
29585      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29586      output.status.code(),
29587      String::from_utf8_lossy(&output.stdout),
29588      String::from_utf8_lossy(&output.stderr)
29589    );
29590  }
29591
29592  #[cfg(not(target_family = "wasm"))]
29593  #[test]
29594  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29595    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29596
29597    if std::env::var_os(CHILD_ENV).is_some() {
29598      struct PanicOnDrop;
29599      impl Drop for PanicOnDrop {
29600        fn drop(&mut self) {
29601          panic!("intentional detached output destructor panic");
29602        }
29603      }
29604
29605      let controller = current_thread_controller("output-drop-subprocess");
29606      let handle = controller
29607        .try_spawn(async { PanicOnDrop })
29608        .unwrap_or_else(|_| panic!("runtime must accept the task"));
29609      drop(handle);
29610      let dispatch = controller_current_thread_dispatch(&controller);
29611      controller.drive_current_thread_dispatch(dispatch, false);
29612      controller.shutdown().unwrap();
29613      return;
29614    }
29615
29616    let output = std::process::Command::new(std::env::current_exe().unwrap())
29617      .arg("--exact")
29618      .arg(
29619        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29620      )
29621      .arg("--nocapture")
29622      .env(CHILD_ENV, "1")
29623      .output()
29624      .expect("the subprocess test must start");
29625    assert!(
29626      output.status.success(),
29627      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29628      output.status.code(),
29629      String::from_utf8_lossy(&output.stdout),
29630      String::from_utf8_lossy(&output.stderr)
29631    );
29632  }
29633
29634  #[cfg(napi_runtime_os_threads)]
29635  #[test]
29636  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29637    use std::sync::mpsc;
29638
29639    struct BlockingDrop {
29640      controller: Arc<RuntimeController>,
29641      entered: mpsc::Sender<Option<u64>>,
29642      release: mpsc::Receiver<()>,
29643      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29644    }
29645
29646    impl Drop for BlockingDrop {
29647      fn drop(&mut self) {
29648        self
29649          .entered
29650          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29651          .unwrap();
29652        self.release.recv().unwrap();
29653        self
29654          .reentrant_shutdown
29655          .send(self.controller.shutdown())
29656          .unwrap();
29657      }
29658    }
29659
29660    let controller = Arc::new(current_thread_controller(
29661      "current-thread-host-drain-output-retirement",
29662    ));
29663    let old_generation = controller.backend().generation();
29664    let (entered_tx, entered_rx) = mpsc::channel();
29665    let (release_tx, release_rx) = mpsc::channel();
29666    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29667    let output_controller = Arc::clone(&controller);
29668    let handle = controller
29669      .try_spawn(async move {
29670        BlockingDrop {
29671          controller: output_controller,
29672          entered: entered_tx,
29673          release: release_rx,
29674          reentrant_shutdown: reentrant_tx,
29675        }
29676      })
29677      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29678    drop(handle);
29679    let dispatch = controller_current_thread_dispatch(&controller);
29680
29681    let driver_controller = Arc::clone(&controller);
29682    let driver =
29683      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29684    assert_eq!(
29685      entered_rx
29686        .recv_timeout(Duration::from_secs(2))
29687        .expect("the host turn must begin destroying the detached output"),
29688      Some(old_generation),
29689      "detached output destruction must retain the generation that produced it"
29690    );
29691
29692    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29693    let shutdown_controller = Arc::clone(&controller);
29694    let shutdown = std::thread::spawn(move || {
29695      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29696    });
29697    wait_until(
29698      "CurrentThread shutdown to enter the old generation's stopping state",
29699      || {
29700        let state = controller
29701          .state
29702          .lock()
29703          .unwrap_or_else(std::sync::PoisonError::into_inner);
29704        matches!(
29705          &state.lifecycle,
29706          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29707        )
29708      },
29709    );
29710
29711    let (restart_tx, restart_rx) = mpsc::channel();
29712    let restart_controller = Arc::clone(&controller);
29713    let restart = std::thread::spawn(move || {
29714      restart_controller.start().unwrap();
29715      restart_tx
29716        .send(restart_controller.backend().generation())
29717        .unwrap();
29718    });
29719
29720    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29721    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29722    let shutdown_overlapped = early_shutdown.is_some();
29723    let restart_overlapped = early_restart.is_some();
29724    release_tx.send(()).unwrap();
29725
29726    let reentrant_error = reentrant_rx
29727      .recv_timeout(Duration::from_secs(2))
29728      .expect("the retiring output destructor must finish its lifecycle check")
29729      .expect_err("old-generation output destruction must not shut down a replacement runtime");
29730    assert!(
29731      reentrant_error
29732        .to_string()
29733        .contains("generation being stopped"),
29734      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29735    );
29736
29737    let shutdown_result = early_shutdown.unwrap_or_else(|| {
29738      shutdown_rx
29739        .recv_timeout(Duration::from_secs(2))
29740        .expect("shutdown must finish after detached output destruction retires")
29741    });
29742    shutdown_result.unwrap();
29743    let new_generation = early_restart.unwrap_or_else(|| {
29744      restart_rx
29745        .recv_timeout(Duration::from_secs(2))
29746        .expect("restart must finish after the old host turn retires")
29747    });
29748
29749    driver.join().unwrap();
29750    shutdown.join().unwrap();
29751    restart.join().unwrap();
29752    assert!(
29753      !shutdown_overlapped,
29754      "shutdown must not publish Stopped while a host turn is destroying detached output"
29755    );
29756    assert!(
29757      !restart_overlapped,
29758      "restart must not overlap a host turn from the retiring generation"
29759    );
29760    assert_ne!(new_generation, old_generation);
29761    controller.shutdown().unwrap();
29762  }
29763
29764  #[cfg(napi_runtime_os_threads)]
29765  #[test]
29766  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
29767    use std::sync::mpsc;
29768
29769    struct ShutdownOnDrop {
29770      controller: Arc<RuntimeController>,
29771      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29772    }
29773
29774    impl Drop for ShutdownOnDrop {
29775      fn drop(&mut self) {
29776        let _ = self.result.send(self.controller.shutdown());
29777      }
29778    }
29779
29780    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
29781    let (running_tx, running_rx) = mpsc::channel();
29782    let (release_tx, release_rx) = mpsc::channel();
29783    let running = controller
29784      .try_spawn_blocking(move || {
29785        running_tx.send(()).unwrap();
29786        release_rx.recv().unwrap();
29787      })
29788      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29789    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29790
29791    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29792    let bomb = ShutdownOnDrop {
29793      controller: Arc::clone(&controller),
29794      result: reentrant_tx,
29795    };
29796    let queued = controller
29797      .try_spawn_blocking(move || {
29798        let _bomb = &bomb;
29799      })
29800      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29801
29802    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29803    let shutdown_controller = Arc::clone(&controller);
29804    let shutdown = std::thread::spawn(move || {
29805      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29806    });
29807    let error = reentrant_rx
29808      .recv_timeout(Duration::from_secs(2))
29809      .expect("queued destruction must not self-deadlock")
29810      .expect_err("the retiring generation must reject reentrant shutdown");
29811    assert!(error.to_string().contains("generation being stopped"));
29812    release_tx.send(()).unwrap();
29813    shutdown_rx
29814      .recv_timeout(Duration::from_secs(2))
29815      .unwrap()
29816      .unwrap();
29817    shutdown.join().unwrap();
29818    futures::executor::block_on(running).unwrap();
29819    assert!(futures::executor::block_on(queued).is_err());
29820  }
29821
29822  #[cfg(napi_runtime_os_threads)]
29823  #[test]
29824  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
29825    use std::sync::mpsc;
29826
29827    struct ShutdownAfterRelease {
29828      controller: Arc<RuntimeController>,
29829      entered: mpsc::Sender<()>,
29830      release: mpsc::Receiver<()>,
29831      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29832    }
29833
29834    impl Drop for ShutdownAfterRelease {
29835      fn drop(&mut self) {
29836        self.entered.send(()).unwrap();
29837        self.release.recv().unwrap();
29838        self.result.send(self.controller.shutdown()).unwrap();
29839      }
29840    }
29841
29842    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
29843    let (holder_started_tx, holder_started_rx) = mpsc::channel();
29844    let (release_holder_tx, release_holder_rx) = mpsc::channel();
29845    let holder = controller
29846      .try_spawn_blocking(move || {
29847        holder_started_tx.send(()).unwrap();
29848        release_holder_rx.recv().unwrap();
29849      })
29850      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29851    holder_started_rx
29852      .recv_timeout(Duration::from_secs(2))
29853      .unwrap();
29854
29855    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29856    let shutdown_controller = Arc::clone(&controller);
29857    let shutdown = std::thread::spawn(move || {
29858      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29859    });
29860    wait_until("the runtime entered Stopping", || {
29861      matches!(
29862        controller
29863          .state
29864          .lock()
29865          .unwrap_or_else(std::sync::PoisonError::into_inner)
29866          .lifecycle,
29867        RuntimeLifecycle::Stopping(_)
29868      )
29869    });
29870
29871    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29872    let (release_drop_tx, release_drop_rx) = mpsc::channel();
29873    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29874    let rejected_controller = Arc::clone(&controller);
29875    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
29876    let rejected = std::thread::spawn(move || {
29877      let bomb = ShutdownAfterRelease {
29878        controller: Arc::clone(&rejected_controller),
29879        entered: drop_entered_tx,
29880        release: release_drop_rx,
29881        result: reentrant_tx,
29882      };
29883      let handle = rejected_controller.spawn(async move {
29884        let _bomb = bomb;
29885        std::future::pending::<()>().await;
29886      });
29887      rejected_done_tx
29888        .send(futures::executor::block_on(handle))
29889        .unwrap();
29890    });
29891    drop_entered_rx
29892      .recv_timeout(Duration::from_secs(2))
29893      .unwrap();
29894
29895    release_holder_tx.send(()).unwrap();
29896    assert!(
29897      shutdown_rx
29898        .recv_timeout(Duration::from_millis(200))
29899        .is_err(),
29900      "shutdown crossed into Stopped before rejected destruction finished"
29901    );
29902
29903    let (start_tx, start_rx) = mpsc::channel();
29904    let start_controller = Arc::clone(&controller);
29905    let starter = std::thread::spawn(move || {
29906      start_tx.send(start_controller.start()).unwrap();
29907    });
29908    assert!(
29909      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
29910      "restart crossed into a new generation before rejected destruction finished"
29911    );
29912
29913    release_drop_tx.send(()).unwrap();
29914    let error = reentrant_rx
29915      .recv_timeout(Duration::from_secs(2))
29916      .expect("the rejected destructor must complete")
29917      .expect_err("the retiring generation must reject the destructor's shutdown");
29918    assert!(error.to_string().contains("generation being stopped"));
29919    assert!(
29920      rejected_done_rx
29921        .recv_timeout(Duration::from_secs(2))
29922        .unwrap()
29923        .is_err()
29924    );
29925    rejected.join().unwrap();
29926
29927    shutdown_rx
29928      .recv_timeout(Duration::from_secs(2))
29929      .unwrap()
29930      .unwrap();
29931    shutdown.join().unwrap();
29932    start_rx
29933      .recv_timeout(Duration::from_secs(2))
29934      .unwrap()
29935      .unwrap();
29936    starter.join().unwrap();
29937    futures::executor::block_on(holder).unwrap();
29938    controller.shutdown().unwrap();
29939  }
29940
29941  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29942  #[test]
29943  fn shutdown_contains_hostile_caught_panic_payload() {
29944    use std::{panic::panic_any, sync::mpsc};
29945
29946    struct PanicAgain;
29947    impl Drop for PanicAgain {
29948      fn drop(&mut self) {
29949        panic!("caught panic payload destructor escaped");
29950      }
29951    }
29952
29953    struct PanicPayloadOnDrop(mpsc::Sender<()>);
29954    impl Drop for PanicPayloadOnDrop {
29955      fn drop(&mut self) {
29956        let _ = self.0.send(());
29957        panic_any(PanicAgain);
29958      }
29959    }
29960
29961    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29962    let (running_tx, running_rx) = mpsc::channel();
29963    let (release_tx, release_rx) = mpsc::channel();
29964    let running = controller
29965      .try_spawn_blocking(move || {
29966        running_tx.send(()).unwrap();
29967        release_rx.recv().unwrap();
29968      })
29969      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29970    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29971
29972    let (drop_tx, drop_rx) = mpsc::channel();
29973    let payload = PanicPayloadOnDrop(drop_tx);
29974    let queued = controller
29975      .try_spawn_blocking(move || {
29976        let _payload = &payload;
29977      })
29978      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29979
29980    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29981    let shutdown_controller = Arc::clone(&controller);
29982    let shutdown = std::thread::spawn(move || {
29983      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29984    });
29985    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29986    release_tx.send(()).unwrap();
29987    shutdown_rx
29988      .recv_timeout(Duration::from_secs(2))
29989      .expect("hostile panic payload destruction must remain contained")
29990      .unwrap();
29991    shutdown
29992      .join()
29993      .expect("the caught payload must not panic during destruction");
29994    futures::executor::block_on(running).unwrap();
29995    assert!(futures::executor::block_on(queued).is_err());
29996  }
29997
29998  #[cfg(napi_runtime_os_threads)]
29999  #[test]
30000  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
30001    use std::sync::mpsc;
30002
30003    struct DropSignal(mpsc::Sender<()>);
30004    impl Drop for DropSignal {
30005      fn drop(&mut self) {
30006        let _ = self.0.send(());
30007      }
30008    }
30009
30010    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
30011    let (running_tx, running_rx) = mpsc::channel();
30012    let (release_tx, release_rx) = mpsc::channel();
30013    let running = controller
30014      .try_spawn_blocking(move || {
30015        running_tx.send(()).unwrap();
30016        release_rx.recv().unwrap();
30017        7usize
30018      })
30019      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30020    running_rx
30021      .recv_timeout(Duration::from_secs(5))
30022      .expect("the first blocking job must hold the only blocking slot");
30023
30024    let queued_ran = Arc::new(AtomicBool::new(false));
30025    let queued_ran_work = Arc::clone(&queued_ran);
30026    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
30027    let queued_drop_signal = DropSignal(queued_dropped_tx);
30028    let queued = controller
30029      .try_spawn_blocking(move || {
30030        let _drop_signal = queued_drop_signal;
30031        queued_ran_work.store(true, Ordering::SeqCst);
30032        11usize
30033      })
30034      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30035
30036    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30037    let shutdown_controller = Arc::clone(&controller);
30038    let shutdown = std::thread::spawn(move || {
30039      shutdown_controller.shutdown().unwrap();
30040      shutdown_done_tx.send(()).unwrap();
30041    });
30042
30043    queued_dropped_rx
30044      .recv_timeout(Duration::from_secs(5))
30045      .expect("shutdown must drop queued blocking work");
30046    assert!(
30047      !queued_ran.load(Ordering::SeqCst),
30048      "queued blocking work must not execute"
30049    );
30050    assert!(
30051      shutdown_done_rx.try_recv().is_err(),
30052      "shutdown must wait while an accepted blocking closure is still running"
30053    );
30054
30055    release_tx.send(()).unwrap();
30056    shutdown_done_rx
30057      .recv_timeout(Duration::from_secs(5))
30058      .expect("shutdown must finish after running blocking work retires");
30059    shutdown.join().unwrap();
30060    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
30061    assert!(
30062      futures::executor::block_on(queued).is_err(),
30063      "the queued blocking handle must resolve as cancelled"
30064    );
30065  }
30066
30067  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30068  #[test]
30069  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
30070    use std::sync::mpsc;
30071
30072    struct PanicOnDrop(mpsc::Sender<()>);
30073    impl Drop for PanicOnDrop {
30074      fn drop(&mut self) {
30075        let _ = self.0.send(());
30076        panic!("intentional queued blocking destructor panic");
30077      }
30078    }
30079
30080    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
30081    let (running_tx, running_rx) = mpsc::channel();
30082    let (release_tx, release_rx) = mpsc::channel();
30083    let running = controller
30084      .try_spawn_blocking(move || {
30085        running_tx.send(()).unwrap();
30086        release_rx.recv().unwrap();
30087      })
30088      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30089    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30090
30091    let (drop_tx, drop_rx) = mpsc::channel();
30092    let panic_on_drop = PanicOnDrop(drop_tx);
30093    let queued = controller
30094      .try_spawn_blocking(move || {
30095        let _keep_captured_until_run = &panic_on_drop;
30096      })
30097      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
30098
30099    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30100    let shutdown_controller = Arc::clone(&controller);
30101    let shutdown = std::thread::spawn(move || {
30102      shutdown_controller.shutdown().unwrap();
30103      shutdown_done_tx.send(()).unwrap();
30104    });
30105
30106    drop_rx
30107      .recv_timeout(Duration::from_secs(5))
30108      .expect("shutdown must attempt to drop the queued closure");
30109    release_tx.send(()).unwrap();
30110    shutdown_done_rx
30111      .recv_timeout(Duration::from_secs(5))
30112      .expect("a panicking queued destructor must not strand shutdown");
30113    shutdown
30114      .join()
30115      .expect("shutdown must contain the destructor panic");
30116    futures::executor::block_on(running).unwrap();
30117    assert!(futures::executor::block_on(queued).is_err());
30118
30119    controller
30120      .start()
30121      .expect("the stopped controller must restart");
30122    let restarted = controller
30123      .try_spawn_blocking(|| 17usize)
30124      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30125    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
30126    controller.shutdown().unwrap();
30127  }
30128
30129  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30130  #[test]
30131  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
30132    use std::{panic::panic_any, sync::mpsc};
30133
30134    struct ReentrantPayload {
30135      controller: Arc<RuntimeController>,
30136      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
30137    }
30138
30139    impl Drop for ReentrantPayload {
30140      fn drop(&mut self) {
30141        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
30142        let shutdown = self.controller.shutdown();
30143        self.result.send((generation, shutdown)).unwrap();
30144      }
30145    }
30146
30147    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
30148
30149    impl Drop for PanicPayloadOnDrop {
30150      fn drop(&mut self) {
30151        panic_any(self.0.take().expect("the result must panic only once"));
30152      }
30153    }
30154
30155    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
30156    let generation = controller.backend().generation();
30157    let (started_tx, started_rx) = mpsc::channel();
30158    let (release_tx, release_rx) = mpsc::channel();
30159    let (result_tx, result_rx) = mpsc::channel();
30160    let payload_controller = Arc::clone(&controller);
30161    let handle = controller
30162      .try_spawn_blocking(move || {
30163        started_tx.send(()).unwrap();
30164        release_rx.recv().unwrap();
30165        PanicPayloadOnDrop(Some(ReentrantPayload {
30166          controller: payload_controller,
30167          result: result_tx,
30168        }))
30169      })
30170      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30171    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30172
30173    // Detach the receiver before the result is delivered. Sending then drops
30174    // the result on the worker, whose destructor intentionally panics.
30175    drop(handle);
30176    release_tx.send(()).unwrap();
30177    let (observed_generation, reentrant_shutdown) = result_rx
30178      .recv_timeout(Duration::from_secs(5))
30179      .expect("the dropped result panic payload must not self-deadlock");
30180    assert_eq!(observed_generation, Some(generation));
30181    let error =
30182      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
30183    assert!(error.to_string().contains("work running on that runtime"));
30184
30185    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30186    let shutdown_controller = Arc::clone(&controller);
30187    let shutdown = std::thread::spawn(move || {
30188      shutdown_controller.shutdown().unwrap();
30189      shutdown_done_tx.send(()).unwrap();
30190    });
30191    shutdown_done_rx
30192      .recv_timeout(Duration::from_secs(5))
30193      .expect("a dropped-result destructor panic must not strand shutdown");
30194    shutdown
30195      .join()
30196      .expect("shutdown must contain the destructor panic");
30197
30198    controller
30199      .start()
30200      .expect("the stopped controller must restart");
30201    let restarted = controller
30202      .try_spawn_blocking(|| 19usize)
30203      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30204    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
30205    controller.shutdown().unwrap();
30206  }
30207
30208  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30209  #[test]
30210  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
30211    use std::sync::mpsc;
30212
30213    struct PanicOnDrop(mpsc::Sender<()>);
30214
30215    impl Drop for PanicOnDrop {
30216      fn drop(&mut self) {
30217        self.0.send(()).unwrap();
30218        panic!("intentional buffered blocking result destructor panic");
30219      }
30220    }
30221
30222    let options = RuntimeOptions {
30223      flavor: RuntimeFlavor::MultiThread,
30224      worker_threads: 2,
30225      max_blocking_tasks: 1,
30226      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30227      park_deadline: None,
30228      drain_linger: DEFAULT_DRAIN_LINGER,
30229    };
30230    let executor =
30231      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30232    let (started_tx, started_rx) = mpsc::channel();
30233    let (dropped_tx, dropped_rx) = mpsc::channel();
30234    let handle = executor.schedule_blocking(move || {
30235      started_tx.send(()).unwrap();
30236      PanicOnDrop(dropped_tx)
30237    });
30238    started_rx
30239      .recv_timeout(Duration::from_secs(2))
30240      .expect("the blocking closure must run");
30241    wait_until("the completed blocking result to be buffered", || {
30242      executor.active_blocking.load(Ordering::Acquire) == 0
30243    });
30244    assert!(
30245      dropped_rx.try_recv().is_err(),
30246      "the buffered result must remain owned by the live receiver"
30247    );
30248
30249    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30250    assert!(
30251      dropped.is_ok(),
30252      "dropping the buffered result must contain its destructor panic"
30253    );
30254    dropped_rx
30255      .recv_timeout(Duration::from_secs(2))
30256      .expect("dropping the receiver must reclaim the buffered result");
30257  }
30258
30259  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30260  #[test]
30261  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30262    use std::sync::mpsc;
30263
30264    struct PanicOnDrop(mpsc::Sender<()>);
30265    impl Drop for PanicOnDrop {
30266      fn drop(&mut self) {
30267        let _ = self.0.send(());
30268        panic!("intentional late blocking destructor panic");
30269      }
30270    }
30271
30272    let executor = multi_thread_executor(2, None, "closed-queue-drop");
30273    executor.begin_shutdown();
30274
30275    let (drop_tx, drop_rx) = mpsc::channel();
30276    let panic_on_drop = PanicOnDrop(drop_tx);
30277    let handle = executor.schedule_blocking(move || {
30278      let _keep_captured_until_run = &panic_on_drop;
30279    });
30280
30281    drop_rx
30282      .recv_timeout(Duration::from_secs(5))
30283      .expect("the closed queue must retire the rejected closure");
30284    assert!(
30285      futures::executor::block_on(handle).is_err(),
30286      "the rejected blocking handle must resolve as cancelled"
30287    );
30288  }
30289
30290  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30291  #[test]
30292  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30293    struct PanicWake;
30294    impl std::task::Wake for PanicWake {
30295      fn wake(self: Arc<Self>) {
30296        panic!("intentional timer waker panic");
30297      }
30298    }
30299
30300    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30301    let backend = controller.backend();
30302    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30303      panic!("configured MultiThread backend must create a Rayon executor");
30304    };
30305    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30306    let waker = Waker::from(Arc::new(PanicWake));
30307    let mut cx = Context::from_waker(&waker);
30308    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30309    drop(backend);
30310
30311    controller
30312      .shutdown()
30313      .expect("shutdown must contain the timer waker panic");
30314    controller
30315      .start()
30316      .expect("the stopped controller must restart");
30317    let restarted = controller
30318      .try_spawn_blocking(|| 23usize)
30319      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30320    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30321    controller.shutdown().unwrap();
30322  }
30323
30324  #[cfg(napi_runtime_os_threads)]
30325  #[test]
30326  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30327    use std::sync::mpsc;
30328
30329    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30330    let backend = controller.backend();
30331    let old_generation = backend.generation();
30332    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30333      panic!("configured MultiThread backend must create a Rayon executor");
30334    };
30335    let old_workers = Arc::clone(&executor.worker_lifecycle);
30336    drop(backend);
30337
30338    let (running_tx, running_rx) = mpsc::channel();
30339    let (release_tx, release_rx) = mpsc::channel();
30340    let running = controller
30341      .try_spawn_blocking(move || {
30342        running_tx.send(()).unwrap();
30343        release_rx.recv().unwrap();
30344      })
30345      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30346    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30347
30348    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30349    let shutdown_controller = Arc::clone(&controller);
30350    let shutdown = std::thread::spawn(move || {
30351      shutdown_controller.shutdown().unwrap();
30352      shutdown_done_tx.send(()).unwrap();
30353    });
30354    wait_until("controller entered Stopping", || {
30355      let state = controller
30356        .state
30357        .lock()
30358        .unwrap_or_else(std::sync::PoisonError::into_inner);
30359      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30360    });
30361
30362    let (start_done_tx, start_done_rx) = mpsc::channel();
30363    let start_controller = Arc::clone(&controller);
30364    let starter = std::thread::spawn(move || {
30365      start_controller.start().unwrap();
30366      start_done_tx.send(()).unwrap();
30367    });
30368    assert!(
30369      start_done_rx.try_recv().is_err(),
30370      "start must wait while shutdown is quiescing"
30371    );
30372    assert!(
30373      old_workers.remaining() > 0,
30374      "the held old generation must still own workers"
30375    );
30376
30377    release_tx.send(()).unwrap();
30378    futures::executor::block_on(running).unwrap();
30379    shutdown_done_rx
30380      .recv_timeout(Duration::from_secs(5))
30381      .unwrap();
30382    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30383    shutdown.join().unwrap();
30384    starter.join().unwrap();
30385    assert_eq!(
30386      old_workers.remaining(),
30387      0,
30388      "restart may begin only after every old worker exits"
30389    );
30390
30391    let backend = controller.backend();
30392    assert_ne!(backend.generation(), old_generation);
30393    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30394      panic!("restarted backend must remain MultiThread");
30395    };
30396    assert_eq!(
30397      executor.pool.current_num_threads(),
30398      controller.options().worker_threads
30399    );
30400    drop(backend);
30401    controller.shutdown().unwrap();
30402  }
30403
30404  #[cfg(napi_runtime_os_threads)]
30405  #[test]
30406  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30407    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30408    let task_controller = Arc::clone(&controller);
30409    let handle = controller
30410      .try_spawn(async move {
30411        task_controller
30412          .shutdown()
30413          .expect_err("runtime work must not wait for its own generation")
30414          .to_string()
30415      })
30416      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30417    let message = futures::executor::block_on(handle).unwrap();
30418    assert!(message.contains("cannot shut down"));
30419    controller.shutdown().unwrap();
30420  }
30421
30422  // ---- two-phase shutdown handshake ----------------------------------------
30423  // `shutdown` is `begin_shutdown` + `finish_shutdown`, so every existing
30424  // shutdown test above also covers the composition. These cover the window
30425  // the split opens: phase 1 must return while work is still running, the
30426  // poll must eventually answer `false`, a re-entered phase 1 must never
30427  // wait, and every OPENED phase 1 must pair with exactly one
30428  // `finish_shutdown` -- no more (the extra one is the orphan error) and no
30429  // fewer (a racing `start` in the window must not turn a completed shutdown
30430  // into an error).
30431
30432  #[cfg(napi_runtime_os_threads)]
30433  #[test]
30434  fn two_phase_shutdown_matches_single_call() {
30435    // The threaded WASI artifact enters shutdown from the JS thread, so phase
30436    // 1 has to come back while a blocking closure is still running; phase 2
30437    // then does exactly what the single call does once the closure retires.
30438    let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30439    let entered = Arc::new(AtomicBool::new(false));
30440    let release = Arc::new(AtomicBool::new(false));
30441    let job_entered = Arc::clone(&entered);
30442    let job_release = Arc::clone(&release);
30443    let handle = controller
30444      .try_spawn_blocking(move || {
30445        job_entered.store(true, Ordering::SeqCst);
30446        while !job_release.load(Ordering::SeqCst) {
30447          std::hint::spin_loop();
30448        }
30449        7usize
30450      })
30451      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30452    while !entered.load(Ordering::SeqCst) {
30453      std::thread::yield_now();
30454    }
30455
30456    assert!(
30457      controller
30458        .begin_shutdown()
30459        .expect("phase 1 on a running generation must be accepted"),
30460      "phase 1 must report the still-running blocking closure instead of waiting for it"
30461    );
30462    assert!(
30463      controller.runtime_work_pending(),
30464      "the parked closure holds its generation guard"
30465    );
30466    // Admission is closed by `backend_locked` the moment `Stopping` is
30467    // published; phase 1 adds no admission code of its own.
30468    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30469      panic!("a submission between the phases must be rejected");
30470    };
30471    assert_eq!(
30472      error.to_string(),
30473      "the async runtime is stopped; call start before submitting work"
30474    );
30475
30476    release.store(true, Ordering::SeqCst);
30477    let deadline = Instant::now() + Duration::from_secs(5);
30478    while controller.runtime_work_pending() {
30479      assert!(
30480        Instant::now() < deadline,
30481        "the released closure must retire and the poll must answer false"
30482      );
30483      std::thread::yield_now();
30484    }
30485    assert_eq!(
30486      futures::executor::block_on(handle).unwrap(),
30487      7usize,
30488      "the closure that phase 1 refused to wait for still delivers its result"
30489    );
30490
30491    controller
30492      .finish_shutdown()
30493      .expect("phase 2 must complete the outstanding phase 1");
30494    {
30495      let state = controller
30496        .state
30497        .lock()
30498        .unwrap_or_else(std::sync::PoisonError::into_inner);
30499      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30500      assert!(
30501        state.draining.is_none(),
30502        "phase 2 must consume the phase-1 handoff"
30503      );
30504    }
30505
30506    // The split leaves the restart path exactly as the single call does.
30507    controller.start().expect("a stopped runtime must restart");
30508    drop(controller.backend());
30509    controller
30510      .shutdown()
30511      .expect("the single call must still shut the restarted runtime down");
30512  }
30513
30514  #[cfg(napi_runtime_os_threads)]
30515  #[test]
30516  fn concurrent_shutdown_during_two_phase_still_reports_success() {
30517    // A `shutdown` from a second thread must still answer `Ok(())` while a
30518    // handshake is in flight, and neither thread may wedge the other. THIS
30519    // test pins the interleaving where the phase-1 OWNER drains: the
30520    // concurrent call is started from the owner's own
30521    // `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e. after the handoff is out
30522    // of the state and before the owner waits, so the second thread provably
30523    // arrives at an already-taken handshake and must report the owner's
30524    // publication. The mirror image (the concurrent call drains, the owner
30525    // finishes afterwards) is
30526    // `phase_one_owner_finishes_after_a_concurrent_shutdown_drains`.
30527    let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30528    let entered = Arc::new(AtomicBool::new(false));
30529    let committed = Arc::new(AtomicBool::new(false));
30530    let job_entered = Arc::clone(&entered);
30531    let job_committed = Arc::clone(&committed);
30532    // The closure retires only once the concurrent call has committed, so the
30533    // owner's drain cannot run to completion before the second thread is in
30534    // flight: both are always overlapped, as before.
30535    let handle = controller
30536      .try_spawn_blocking(move || {
30537        job_entered.store(true, Ordering::SeqCst);
30538        while !job_committed.load(Ordering::SeqCst) {
30539          std::hint::spin_loop();
30540        }
30541        3usize
30542      })
30543      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30544    wait_until("the blocking closure to park", || {
30545      entered.load(Ordering::SeqCst)
30546    });
30547    assert!(
30548      controller
30549        .begin_shutdown()
30550        .expect("phase 1 must be accepted")
30551    );
30552
30553    let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30554    let hook_controller = Arc::clone(&controller);
30555    let hook_committed = Arc::clone(&committed);
30556    let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30557    let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30558    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30559      *slot.borrow_mut() = Some(Box::new(move || {
30560        hook_took_the_drain.store(true, Ordering::SeqCst);
30561        let concurrent = std::thread::spawn(move || {
30562          hook_committed.store(true, Ordering::SeqCst);
30563          hook_controller.shutdown()
30564        });
30565        concurrent_tx
30566          .send(concurrent)
30567          .expect("the test thread must still be waiting for the concurrent handle");
30568      }));
30569    });
30570
30571    controller
30572      .finish_shutdown()
30573      .expect("phase 2 must complete the handshake");
30574    assert!(
30575      owner_took_the_drain.load(Ordering::SeqCst),
30576      "the phase-1 owner must be the thread that took the handoff"
30577    );
30578    concurrent_rx
30579      .recv()
30580      .expect("the hook must have started the concurrent shutdown")
30581      .join()
30582      .expect("the concurrent shutdown thread must not panic")
30583      .expect("a concurrent single-call shutdown must still succeed");
30584
30585    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30586    let state = controller
30587      .state
30588      .lock()
30589      .unwrap_or_else(std::sync::PoisonError::into_inner);
30590    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30591    assert!(state.draining.is_none());
30592  }
30593
30594  #[cfg(napi_runtime_os_threads)]
30595  #[test]
30596  fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30597    // The handoff slot must never go empty while the join is still running.
30598    // `finish_shutdown` takes the backend out of the state and only then
30599    // waits; for the whole of that window the lifecycle still reads
30600    // `Stopping`. If the slot were left `None` there, a re-entering phase 1
30601    // would fall through to the lifecycle loop and PARK on `lifecycle_changed`
30602    // until the join it is racing publishes `Stopped` -- breaking the
30603    // documented "a `begin_shutdown` while one is outstanding never waits"
30604    // contract -- and `runtime_work_pending` would answer `false` about work
30605    // that is demonstrably still parked. On a host that drives both calls from
30606    // the same thread pool that the drained work needs, that park is a
30607    // deadlock.
30608    //
30609    // The probe is started from `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e.
30610    // provably after the handoff has left the slot and before the finisher
30611    // waits, and the closure it must see is released only once the probe has
30612    // answered (or a bounded watchdog gives up), so a parking probe FAILS the
30613    // test instead of hanging it.
30614    let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30615    let entered = Arc::new(AtomicBool::new(false));
30616    let release = Arc::new(AtomicBool::new(false));
30617    let job_entered = Arc::clone(&entered);
30618    let job_release = Arc::clone(&release);
30619    let handle = controller
30620      .try_spawn_blocking(move || {
30621        job_entered.store(true, Ordering::SeqCst);
30622        while !job_release.load(Ordering::SeqCst) {
30623          std::hint::spin_loop();
30624        }
30625        11usize
30626      })
30627      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30628    wait_until("the blocking closure to park", || {
30629      entered.load(Ordering::SeqCst)
30630    });
30631    assert!(
30632      controller
30633        .begin_shutdown()
30634        .expect("phase 1 must be accepted"),
30635      "the parked closure must be reported as live"
30636    );
30637
30638    let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30639    let hook_controller = Arc::clone(&controller);
30640    let hook_release = Arc::clone(&release);
30641    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30642      *slot.borrow_mut() = Some(Box::new(move || {
30643        let probe_controller = Arc::clone(&hook_controller);
30644        let probe = std::thread::spawn(move || {
30645          let pending = probe_controller.runtime_work_pending();
30646          (pending, probe_controller.begin_shutdown())
30647        });
30648        let watchdog = std::thread::spawn(move || {
30649          let deadline = Instant::now() + Duration::from_secs(5);
30650          while !probe.is_finished() && Instant::now() < deadline {
30651            std::thread::yield_now();
30652          }
30653          // Whether the probe answered BEFORE the drained closure retired is
30654          // the whole verdict: releasing afterwards would let a parked probe
30655          // be woken by the `Stopped` publication and look healthy.
30656          let answered_during_the_drain = probe.is_finished();
30657          hook_release.store(true, Ordering::SeqCst);
30658          (answered_during_the_drain, probe.join())
30659        });
30660        probe_tx
30661          .send(watchdog)
30662          .expect("the test thread must still be waiting for the probe handle");
30663      }));
30664    });
30665
30666    controller
30667      .finish_shutdown()
30668      .expect("phase 2 must complete the outstanding phase 1");
30669    let (answered_during_the_drain, probe) = probe_rx
30670      .recv()
30671      .expect("the hook must have started the probe")
30672      .join()
30673      .expect("the watchdog thread must not panic");
30674    let (pending, verdict) = probe.expect("the probe thread must not panic");
30675    assert!(
30676      answered_during_the_drain,
30677      "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30678    );
30679    assert!(
30680      pending,
30681      "the poll must still see the parked closure while the drain that is waiting for it runs"
30682    );
30683    assert!(
30684      verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30685      "the re-entering phase 1 must report the parked closure as live"
30686    );
30687
30688    assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30689    {
30690      let state = controller
30691        .state
30692        .lock()
30693        .unwrap_or_else(std::sync::PoisonError::into_inner);
30694      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30695      assert!(
30696        state.draining.is_none(),
30697        "publishing `Stopped` must retire the in-flight drain marker too"
30698      );
30699    }
30700    assert!(
30701      !controller.runtime_work_pending(),
30702      "a stopped runtime has no pending work"
30703    );
30704
30705    controller.start().expect("a stopped runtime must restart");
30706    drop(controller.backend());
30707    controller
30708      .shutdown()
30709      .expect("the single call must still shut the restarted runtime down");
30710  }
30711
30712  #[cfg(napi_runtime_os_threads)]
30713  #[test]
30714  fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30715    // The other half of the race above, hand-sequenced by a join: a
30716    // concurrent single-call `shutdown` takes the outstanding handoff AND
30717    // completes the whole shutdown, and only then does the thread that opened
30718    // phase 1 call its own phase 2. The runtime is already `Stopped`, so the
30719    // join that caller is owed has happened -- phase 2 owes it `Ok(())`, not
30720    // "shutdown was not started", whoever performed that join.
30721    let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30722    let entered = Arc::new(AtomicBool::new(false));
30723    let release = Arc::new(AtomicBool::new(false));
30724    let job_entered = Arc::clone(&entered);
30725    let job_release = Arc::clone(&release);
30726    let handle = controller
30727      .try_spawn_blocking(move || {
30728        job_entered.store(true, Ordering::SeqCst);
30729        while !job_release.load(Ordering::SeqCst) {
30730          std::hint::spin_loop();
30731        }
30732        3usize
30733      })
30734      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30735    wait_until("the blocking closure to park", || {
30736      entered.load(Ordering::SeqCst)
30737    });
30738    assert!(
30739      controller
30740        .begin_shutdown()
30741        .expect("phase 1 must be accepted"),
30742      "the parked closure must be reported as live"
30743    );
30744
30745    let single_call = Arc::clone(&controller);
30746    let concurrent = std::thread::spawn(move || single_call.shutdown());
30747    release.store(true, Ordering::SeqCst);
30748    concurrent
30749      .join()
30750      .expect("the concurrent shutdown thread must not panic")
30751      .expect("a concurrent single-call shutdown must still succeed");
30752    {
30753      let state = controller
30754        .state
30755        .lock()
30756        .unwrap_or_else(std::sync::PoisonError::into_inner);
30757      assert!(
30758        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30759        "the concurrent call must have published the stop"
30760      );
30761      assert!(
30762        state.draining.is_none(),
30763        "the concurrent call must have consumed the phase-1 handoff"
30764      );
30765    }
30766
30767    controller
30768      .finish_shutdown()
30769      .expect("the thread that began phase 1 must still be told its shutdown completed");
30770    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30771
30772    // Flipped with the idempotent model (it used to expect the orphan error
30773    // here): a phase 2 after a restart finds nothing outstanding, so it is a
30774    // no-op `Ok(())` -- and it must NOT adopt the generation the restart
30775    // published.
30776    controller.start().expect("a stopped runtime must restart");
30777    drop(controller.backend());
30778    controller
30779      .finish_shutdown()
30780      .expect("phase 2 after a restart is a no-op, not an error");
30781    {
30782      let state = controller
30783        .state
30784        .lock()
30785        .unwrap_or_else(std::sync::PoisonError::into_inner);
30786      assert!(
30787        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30788        "the no-op phase 2 must not stop the restarted generation"
30789      );
30790    }
30791    controller
30792      .shutdown()
30793      .expect("the single call must still shut the restarted runtime down");
30794  }
30795
30796  #[test]
30797  fn begin_shutdown_twice_does_not_block() {
30798    // The host thread may re-enter the handshake from a turn it took between
30799    // the phases. A second phase 1 must answer from the outstanding one --
30800    // taking the `Stopping` arm instead would park this thread on the condvar
30801    // forever, so a regression hangs this test rather than failing it. It
30802    // also OPENS nothing -- and, flipped with the idempotent model, the second
30803    // phase 2 is no longer an orphan error: the stop it was asked to complete
30804    // has been completed, so it answers `Ok(())`.
30805    let controller = current_thread_controller("two-phase-reentrant-begin");
30806    drop(controller.backend());
30807
30808    assert!(
30809      !controller
30810        .begin_shutdown()
30811        .expect("phase 1 on an idle generation must be accepted"),
30812      "an idle generation has nothing for the host to wait for"
30813    );
30814    {
30815      let state = controller
30816        .state
30817        .lock()
30818        .unwrap_or_else(std::sync::PoisonError::into_inner);
30819      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
30820    }
30821    assert!(
30822      !controller
30823        .begin_shutdown()
30824        .expect("a second phase 1 must answer immediately"),
30825      "the second phase 1 must report the same verdict"
30826    );
30827
30828    controller
30829      .finish_shutdown()
30830      .expect("one phase 2 completes the single outstanding phase 1");
30831    {
30832      let state = controller
30833        .state
30834        .lock()
30835        .unwrap_or_else(std::sync::PoisonError::into_inner);
30836      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30837      assert!(state.draining.is_none());
30838    }
30839
30840    controller
30841      .finish_shutdown()
30842      .expect("a second phase 2 repeats the completed stop instead of erroring");
30843    {
30844      let state = controller
30845        .state
30846        .lock()
30847        .unwrap_or_else(std::sync::PoisonError::into_inner);
30848      assert!(
30849        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30850        "the repeated phase 2 must change nothing"
30851      );
30852    }
30853  }
30854
30855  #[test]
30856  fn current_thread_two_phase_is_idle_immediately() {
30857    // No cfg gate: this is the threadless `wasm32-wasip1` shape, where the
30858    // handshake must degrade to two cheap calls.
30859    let controller = current_thread_controller("two-phase-current-thread");
30860    drop(controller.backend());
30861    assert!(
30862      !controller.runtime_work_pending(),
30863      "an idle running generation has no pending work"
30864    );
30865
30866    assert!(
30867      !controller
30868        .begin_shutdown()
30869        .expect("phase 1 must be accepted"),
30870      "phase 1 on an idle generation must report nothing to wait for"
30871    );
30872    assert!(
30873      !controller.runtime_work_pending(),
30874      "the poll must stay false across the window"
30875    );
30876    controller.finish_shutdown().expect("phase 2 must complete");
30877    {
30878      let state = controller
30879        .state
30880        .lock()
30881        .unwrap_or_else(std::sync::PoisonError::into_inner);
30882      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30883    }
30884
30885    controller.start().expect("a stopped runtime must restart");
30886    drop(controller.backend());
30887    controller
30888      .shutdown()
30889      .expect("the single call must still work");
30890  }
30891
30892  #[test]
30893  fn finish_shutdown_without_begin_is_an_error() {
30894    let controller = current_thread_controller("two-phase-orphan-finish");
30895    drop(controller.backend());
30896    let error = controller
30897      .finish_shutdown()
30898      .expect_err("phase 2 without an outstanding phase 1 must be rejected");
30899    assert_eq!(
30900      error.to_string(),
30901      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
30902    );
30903    controller
30904      .shutdown()
30905      .expect("the rejected phase 2 must leave the runtime shuttable");
30906  }
30907
30908  #[test]
30909  fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
30910    // A phase 1 on an already stopped runtime settles with no backend. The
30911    // `start` that follows publishes a FRESH generation, which owes nothing
30912    // to a handshake opened against the previous one. If the settled handoff
30913    // survived that `start`, the next `shutdown` would short-circuit on it --
30914    // phase 1 answering from the stale handoff, phase 2 consuming it -- and
30915    // report success while this generation was still `Running`, still holding
30916    // its backend and still accepting work.
30917    let controller = current_thread_controller("two-phase-stale-settled");
30918    drop(controller.backend());
30919    controller
30920      .shutdown()
30921      .expect("the first shutdown must complete");
30922
30923    assert!(
30924      !controller
30925        .begin_shutdown()
30926        .expect("phase 1 on a stopped runtime must be accepted"),
30927      "a stopped runtime has no work for the host to wait for"
30928    );
30929    controller.start().expect("a stopped runtime must restart");
30930    {
30931      let state = controller
30932        .state
30933        .lock()
30934        .unwrap_or_else(std::sync::PoisonError::into_inner);
30935      assert!(
30936        state.draining.is_none(),
30937        "a fresh generation must not inherit the settled handoff"
30938      );
30939    }
30940    drop(controller.backend());
30941
30942    controller
30943      .shutdown()
30944      .expect("the second shutdown must stop the restarted generation");
30945    {
30946      let state = controller
30947        .state
30948        .lock()
30949        .unwrap_or_else(std::sync::PoisonError::into_inner);
30950      assert!(
30951        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30952        "the restarted generation must really be stopped, not reported stopped"
30953      );
30954      assert!(state.draining.is_none());
30955    }
30956    assert!(
30957      controller.current_backend.load().is_none(),
30958      "the stopped generation's backend must be gone"
30959    );
30960    assert!(
30961      !controller.runtime_work_pending(),
30962      "a stopped runtime has no pending work"
30963    );
30964    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30965      panic!("a submission after the second shutdown must be rejected");
30966    };
30967    assert_eq!(
30968      error.to_string(),
30969      "the async runtime is stopped; call start before submitting work"
30970    );
30971  }
30972
30973  #[test]
30974  fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
30975    // Renamed and flipped again with the idempotent model (it counted phases
30976    // before, and errored before that): a `start` between the phases DROPS the
30977    // settled handoff -- so it can never answer for the new generation -- but
30978    // the stop that was announced still happened, so EVERY phase 2 that
30979    // follows reports it, and none of them touches the restarted generation.
30980    // Without this, a single-call `shutdown` racing a `start` reports `Err`
30981    // for a shutdown it performed.
30982    let controller = current_thread_controller("two-phase-settled-idempotent");
30983    drop(controller.backend());
30984    controller
30985      .shutdown()
30986      .expect("the first shutdown must complete");
30987    assert!(
30988      !controller
30989        .begin_shutdown()
30990        .expect("phase 1 on a stopped runtime must be accepted")
30991    );
30992    controller.start().expect("a stopped runtime must restart");
30993    {
30994      let state = controller
30995        .state
30996        .lock()
30997        .unwrap_or_else(std::sync::PoisonError::into_inner);
30998      assert!(
30999        state.draining.is_none(),
31000        "the restart must drop the settled handoff"
31001      );
31002      assert!(
31003        state.phase_ever_opened,
31004        "the restart must not erase the fact that a stop was announced"
31005      );
31006    }
31007
31008    controller
31009      .finish_shutdown()
31010      .expect("the announced stop must be reported to the caller that opened it");
31011    controller
31012      .finish_shutdown()
31013      .expect("and to every phase 2 after it -- the call is idempotent");
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::Running(_)),
31021        "neither phase 2 may stop the generation the restart published"
31022      );
31023    }
31024    drop(controller.backend());
31025    controller
31026      .shutdown()
31027      .expect("the no-op phase 2s must leave the runtime shuttable");
31028  }
31029
31030  #[test]
31031  fn shutdown_racing_a_start_still_reports_ok() {
31032    // The defect this whole rule exists for. The single call takes
31033    // the lifecycle lock twice; on an already stopped runtime phase 1 settles
31034    // with no backend and publishes no `Stopping` for a `start` to wait in,
31035    // so a `start` can land in the window and drop the parked handoff. Phase 2
31036    // then finds an empty slot and a `Running` lifecycle. `phase_ever_opened`
31037    // is what lets it answer `Ok(())` for the shutdown this call really did
31038    // perform, instead of "shutdown was not started".
31039    //
31040    // Deterministic, not stressed: the hook runs the `start` inside the window
31041    // itself. In the wild the same interleaving shows up as a flaky
31042    // `NO_PHASE_ERROR` from a `shutdown` racing a restart on another thread.
31043    let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
31044    drop(controller.backend());
31045    controller
31046      .shutdown()
31047      .expect("the first shutdown must complete");
31048
31049    let start_controller = Arc::clone(&controller);
31050    let started = Arc::new(AtomicBool::new(false));
31051    let hook_started = Arc::clone(&started);
31052    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31053      *slot.borrow_mut() = Some(Box::new(move || {
31054        start_controller
31055          .start()
31056          .expect("a stopped runtime must restart");
31057        hook_started.store(true, Ordering::SeqCst);
31058      }));
31059    });
31060    controller
31061      .shutdown()
31062      .expect("a shutdown must report the phase it opened, even across a racing start");
31063    assert!(
31064      started.load(Ordering::SeqCst),
31065      "the hook must have run the start inside the window"
31066    );
31067
31068    {
31069      let state = controller
31070        .state
31071        .lock()
31072        .unwrap_or_else(std::sync::PoisonError::into_inner);
31073      assert!(
31074        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31075        "the racing start wins the window: its generation is live, not stopped"
31076      );
31077      assert!(
31078        state.draining.is_none(),
31079        "the start must have dropped the settled handoff"
31080      );
31081    }
31082
31083    // The generation the start published is a real one: it accepts work, and
31084    // the next shutdown really stops it.
31085    let ran = Arc::new(AtomicBool::new(false));
31086    let ran_task = Arc::clone(&ran);
31087    controller
31088      .try_spawn_detached(async move {
31089        ran_task.store(true, Ordering::SeqCst);
31090      })
31091      .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
31092    let dispatch = controller_current_thread_dispatch(&controller);
31093    controller.drive_current_thread_dispatch(dispatch, false);
31094    assert!(ran.load(Ordering::SeqCst));
31095    controller
31096      .shutdown()
31097      .expect("the restarted generation must still shut down");
31098    let state = controller
31099      .state
31100      .lock()
31101      .unwrap_or_else(std::sync::PoisonError::into_inner);
31102    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31103    assert!(state.draining.is_none());
31104  }
31105
31106  // The runtime FLAVOR under test is CurrentThread -- that is where the race
31107  // this pins was 8.2% -- but the probe itself needs a second OS thread, so it
31108  // cannot run on the threadless `wasm32-wasip1` lane.
31109  #[cfg(napi_runtime_os_threads)]
31110  #[test]
31111  fn concurrent_split_finish_racing_a_single_call_shutdown() {
31112    // The interleaving the counting model got wrong (8.2% on CurrentThread in
31113    // a 100k-iteration probe, and the reason phases are no longer counted): a
31114    // single call opens the phase and a SECOND thread, using the documented
31115    // split pair, completes it. With a count, the split thread's finish spent
31116    // the opener's credit and the single call was then told "shutdown was not
31117    // started" -- for a stop it had already published.
31118    //
31119    // Deterministic, not stressed: the hook parks the single call between its
31120    // halves and runs the split pair to completion there.
31121    let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
31122    drop(controller.backend());
31123
31124    let split_controller = Arc::clone(&controller);
31125    let (tx, rx) = std::sync::mpsc::channel();
31126    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
31127      *slot.borrow_mut() = Some(Box::new(move || {
31128        let split = std::thread::spawn(move || {
31129          let pending = split_controller
31130            .begin_shutdown()
31131            .expect("a second phase 1 must re-report the outstanding handoff");
31132          split_controller
31133            .finish_shutdown()
31134            .expect("an in-protocol phase 2 must complete the outstanding stop");
31135          pending
31136        });
31137        tx.send(
31138          split
31139            .join()
31140            .expect("the split shutdown thread must not panic"),
31141        )
31142        .expect("the test thread must still be waiting for the split result");
31143      }));
31144    });
31145
31146    controller
31147      .shutdown()
31148      .expect("the single call must report the stop it announced, whoever joined it");
31149    assert!(
31150      !rx
31151        .try_recv()
31152        .expect("the hook must have run the split pair inside the window"),
31153      "an idle generation has nothing for the host to wait for"
31154    );
31155
31156    let state = controller
31157      .state
31158      .lock()
31159      .unwrap_or_else(std::sync::PoisonError::into_inner);
31160    assert!(
31161      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
31162      "the generation must really be stopped, not reported stopped"
31163    );
31164    assert!(state.draining.is_none());
31165  }
31166
31167  #[test]
31168  fn repeated_begin_start_pairs_leave_no_residue() {
31169    // The other half of what the count got wrong: `k x (begin, start)` parked
31170    // `k` credits that nothing ever drained, so the next `k` stray phase 2s
31171    // reported success off a leaked count. A sticky bool cannot accumulate --
31172    // the behaviour is the same at every `k`.
31173    const PAIRS: usize = 50;
31174    let controller = current_thread_controller("begin-start-residue");
31175    drop(controller.backend());
31176    for _ in 0..PAIRS {
31177      controller
31178        .shutdown()
31179        .expect("each shutdown must stop the live generation");
31180      assert!(
31181        !controller
31182          .begin_shutdown()
31183          .expect("phase 1 on a stopped runtime must be accepted"),
31184        "a stopped runtime has no work for the host to wait for"
31185      );
31186      controller
31187        .start()
31188        .expect("a zero-backend window must let a restart through");
31189      let state = controller
31190        .state
31191        .lock()
31192        .unwrap_or_else(std::sync::PoisonError::into_inner);
31193      assert!(
31194        state.draining.is_none(),
31195        "the restart must drop the settled handoff"
31196      );
31197      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
31198    }
31199
31200    controller
31201      .finish_shutdown()
31202      .expect("a phase 2 with nothing outstanding is a no-op Ok");
31203    {
31204      let state = controller
31205        .state
31206        .lock()
31207        .unwrap_or_else(std::sync::PoisonError::into_inner);
31208      assert!(
31209        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
31210        "the no-op phase 2 must not stop the generation the last restart published"
31211      );
31212    }
31213
31214    controller
31215      .shutdown()
31216      .expect("a full shutdown after the begin/start pairs must still work");
31217    let state = controller
31218      .state
31219      .lock()
31220      .unwrap_or_else(std::sync::PoisonError::into_inner);
31221    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31222    assert!(state.draining.is_none());
31223  }
31224
31225  #[cfg(napi_runtime_os_threads)]
31226  #[test]
31227  fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31228    // The MultiThread half of the same rule: the worker join lives in phase
31229    // 2, so a stale settled handoff would let `shutdown` return while this
31230    // generation's workers were still alive.
31231    let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31232    drop(controller.backend());
31233    controller
31234      .shutdown()
31235      .expect("the first shutdown must complete");
31236    assert!(
31237      !controller
31238        .begin_shutdown()
31239        .expect("phase 1 on a stopped runtime must be accepted")
31240    );
31241    controller.start().expect("a stopped runtime must restart");
31242
31243    let backend = controller.backend();
31244    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31245      panic!("configured MultiThread backend must create a Rayon executor");
31246    };
31247    let workers = Arc::clone(&executor.worker_lifecycle);
31248    drop(backend);
31249    let handle = controller
31250      .try_spawn_blocking(|| 5usize)
31251      .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31252    assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31253
31254    controller
31255      .shutdown()
31256      .expect("the second shutdown must stop the restarted generation");
31257    assert_eq!(
31258      workers.remaining(),
31259      0,
31260      "phase 2 must join the restarted generation's workers"
31261    );
31262    {
31263      let state = controller
31264        .state
31265        .lock()
31266        .unwrap_or_else(std::sync::PoisonError::into_inner);
31267      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31268      assert!(state.draining.is_none());
31269    }
31270    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31271      panic!("a submission after the second shutdown must be rejected");
31272    };
31273    assert_eq!(
31274      error.to_string(),
31275      "the async runtime is stopped; call start before submitting work"
31276    );
31277  }
31278
31279  /// A rejected submission whose destructor is parked until `release` is sent.
31280  /// The counted window (`rejected_drops`) stays open for the whole of that
31281  /// `Drop`, which is the state phase 1 must refuse to wait on.
31282  #[cfg(napi_runtime_os_threads)]
31283  struct ParkedRejectedDropFuture {
31284    entered: std::sync::mpsc::Sender<()>,
31285    release: std::sync::mpsc::Receiver<()>,
31286  }
31287
31288  #[cfg(napi_runtime_os_threads)]
31289  impl Future for ParkedRejectedDropFuture {
31290    type Output = ();
31291
31292    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31293      Poll::Pending
31294    }
31295  }
31296
31297  #[cfg(napi_runtime_os_threads)]
31298  impl Drop for ParkedRejectedDropFuture {
31299    fn drop(&mut self) {
31300      self.entered.send(()).unwrap();
31301      self.release.recv().unwrap();
31302    }
31303  }
31304
31305  /// Reject one submission on a spawned thread and return once its destructor
31306  /// has been entered, so the caller runs with `rejected_drops != 0` held open
31307  /// by a thread that is not it.
31308  #[cfg(napi_runtime_os_threads)]
31309  fn park_a_rejected_destructor(
31310    controller: &Arc<RuntimeController>,
31311    fail_backend_creation: bool,
31312  ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31313    use std::sync::mpsc;
31314
31315    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31316    let (release_drop_tx, release_drop_rx) = mpsc::channel();
31317    let submit_controller = Arc::clone(controller);
31318    let submitter = std::thread::spawn(move || {
31319      if fail_backend_creation {
31320        FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31321      }
31322      let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31323        entered: drop_entered_tx,
31324        release: release_drop_rx,
31325      }));
31326      assert!(
31327        result.is_err(),
31328        "the submission must be rejected, not accepted"
31329      );
31330    });
31331    drop_entered_rx
31332      .recv_timeout(Duration::from_secs(2))
31333      .expect("the rejected future destructor must start");
31334    (submitter, release_drop_tx)
31335  }
31336
31337  /// Run phase 1 on a spawned thread and answer within `timeout`.
31338  ///
31339  /// A regression HANGS inside `begin_shutdown` instead of returning a wrong
31340  /// value, so the call cannot be made inline: the `recv_timeout` is what
31341  /// turns the hang into a failure.
31342  #[cfg(napi_runtime_os_threads)]
31343  fn begin_shutdown_within(
31344    controller: &Arc<RuntimeController>,
31345    what: &str,
31346    timeout: Duration,
31347  ) -> Result<bool, RuntimeConfigError> {
31348    use std::sync::mpsc;
31349
31350    let (tx, rx) = mpsc::channel();
31351    let begin_controller = Arc::clone(controller);
31352    let beginner = std::thread::spawn(move || {
31353      tx.send(begin_controller.begin_shutdown()).unwrap();
31354    });
31355    let answer = rx
31356      .recv_timeout(timeout)
31357      .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31358    join_within(what, beginner, Duration::from_secs(2));
31359    answer
31360  }
31361
31362  #[cfg(napi_runtime_os_threads)]
31363  #[test]
31364  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31365    // Phase 1 never waits -- including for a rejected submission whose
31366    // destructor is running on ANOTHER thread. That destructor is arbitrary
31367    // user `Drop` code; on the threaded WASI artifact the thread running it
31368    // may be waiting for the very host turn the phase-1 caller can only give
31369    // by returning, so waiting here is the deadlock the split API exists to
31370    // avoid. The wait belongs to phase 2, and the window must report itself
31371    // as pending work so the host keeps turning its loop.
31372    use std::sync::mpsc;
31373
31374    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31375    drop(controller.backend());
31376    controller
31377      .shutdown()
31378      .expect("the first shutdown must complete");
31379
31380    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31381
31382    assert!(
31383      begin_shutdown_within(
31384        &controller,
31385        "phase 1 on a stopped runtime with a rejected destructor outstanding",
31386        Duration::from_secs(2),
31387      )
31388      .expect("phase 1 on a stopped runtime must be accepted"),
31389      "the outstanding rejected destruction is a wait phase 2 still owes"
31390    );
31391    assert!(
31392      controller.runtime_work_pending(),
31393      "the poll must report the destruction, or the host stops turning the loop that frees it"
31394    );
31395    {
31396      let state = controller
31397        .state
31398        .lock()
31399        .unwrap_or_else(std::sync::PoisonError::into_inner);
31400      assert!(matches!(
31401        &state.lifecycle,
31402        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31403      ));
31404      assert!(matches!(
31405        &state.draining,
31406        Some(ShutdownDrain::ZeroBackend(
31407          ZeroBackendShutdownTarget::Stopped
31408        ))
31409      ));
31410    }
31411
31412    assert!(
31413      begin_shutdown_within(
31414        &controller,
31415        "a second phase 1 inside the zero-backend window",
31416        Duration::from_secs(2),
31417      )
31418      .expect("a second phase 1 must answer from the outstanding handoff"),
31419      "the second phase 1 must repeat the same verdict"
31420    );
31421
31422    let (finish_tx, finish_rx) = mpsc::channel();
31423    let finish_controller = Arc::clone(&controller);
31424    let finisher = std::thread::spawn(move || {
31425      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31426    });
31427    assert!(
31428      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31429      "phase 2 owns the wait phase 1 refused"
31430    );
31431
31432    release_drop_tx.send(()).unwrap();
31433    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31434    finish_rx
31435      .recv_timeout(Duration::from_secs(2))
31436      .expect("phase 2 must return once the destructor retires")
31437      .expect("phase 2 must complete the announced stop");
31438    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31439
31440    {
31441      let state = controller
31442        .state
31443        .lock()
31444        .unwrap_or_else(std::sync::PoisonError::into_inner);
31445      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31446      assert!(state.draining.is_none());
31447    }
31448  }
31449
31450  #[cfg(napi_runtime_os_threads)]
31451  #[test]
31452  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31453    // The `Initial` twin of the test above: a backend creation that fails
31454    // leaves the lifecycle `Initial` with the destructor of the rejected
31455    // submission still running, and phase 1 must publish and return there
31456    // too. The target it parks is `StoppedBeforeFirstUse`, and phase 2 is
31457    // what publishes it.
31458    use std::sync::mpsc;
31459
31460    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31461    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31462
31463    assert!(
31464      begin_shutdown_within(
31465        &controller,
31466        "phase 1 on an initial runtime with a rejected destructor outstanding",
31467        Duration::from_secs(2),
31468      )
31469      .expect("phase 1 on an initial runtime must be accepted"),
31470      "the outstanding rejected destruction is a wait phase 2 still owes"
31471    );
31472    assert!(controller.runtime_work_pending());
31473    {
31474      let state = controller
31475        .state
31476        .lock()
31477        .unwrap_or_else(std::sync::PoisonError::into_inner);
31478      assert!(matches!(
31479        &state.lifecycle,
31480        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31481      ));
31482      assert!(matches!(
31483        &state.draining,
31484        Some(ShutdownDrain::ZeroBackend(
31485          ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31486        ))
31487      ));
31488    }
31489    assert!(
31490      controller.try_spawn_detached(async {}).is_err(),
31491      "the announced stop must keep first-generation admission closed"
31492    );
31493
31494    let (finish_tx, finish_rx) = mpsc::channel();
31495    let finish_controller = Arc::clone(&controller);
31496    let finisher = std::thread::spawn(move || {
31497      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31498    });
31499    assert!(
31500      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31501      "phase 2 owns the wait phase 1 refused"
31502    );
31503
31504    release_drop_tx.send(()).unwrap();
31505    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31506    finish_rx
31507      .recv_timeout(Duration::from_secs(2))
31508      .expect("phase 2 must return once the destructor retires")
31509      .expect("phase 2 must complete the announced stop");
31510    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31511
31512    {
31513      let state = controller
31514        .state
31515        .lock()
31516        .unwrap_or_else(std::sync::PoisonError::into_inner);
31517      assert!(
31518        matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31519        "a generation that never had a backend must stop before first use"
31520      );
31521      assert!(state.draining.is_none());
31522    }
31523  }
31524
31525  #[cfg(napi_runtime_os_threads)]
31526  #[test]
31527  fn start_waits_out_a_zero_backend_phase_one() {
31528    // Phase 1 no longer waits, so the `StoppingWithoutBackend` window now
31529    // lasts until phase 2 publishes instead of only for the length of phase
31530    // 1's internal wait. `start` must wait that whole window out: the
31531    // destructor still runs under the RETIRED generation's guard, so a
31532    // restart admitted before phase 2 would publish a fresh backend on top of
31533    // it. The existing coverage only drives the single-call `shutdown`; this
31534    // pins the invariant on the split API.
31535    use std::sync::mpsc;
31536
31537    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31538    let old_generation = controller.backend().generation();
31539    controller
31540      .shutdown()
31541      .expect("the first shutdown must complete");
31542
31543    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31544    assert!(
31545      begin_shutdown_within(
31546        &controller,
31547        "phase 1 before the restart",
31548        Duration::from_secs(2),
31549      )
31550      .expect("phase 1 on a stopped runtime must be accepted")
31551    );
31552
31553    let (start_tx, start_rx) = mpsc::channel();
31554    let start_controller = Arc::clone(&controller);
31555    let starter = std::thread::spawn(move || {
31556      start_tx.send(start_controller.start()).unwrap();
31557    });
31558    assert!(
31559      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31560      "a restart must wait for the announced zero-backend stop to be published"
31561    );
31562
31563    let (finish_tx, finish_rx) = mpsc::channel();
31564    let finish_controller = Arc::clone(&controller);
31565    let finisher = std::thread::spawn(move || {
31566      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31567    });
31568    assert!(
31569      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31570      "phase 2 must still be waiting on the destructor"
31571    );
31572
31573    release_drop_tx.send(()).unwrap();
31574    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31575    finish_rx
31576      .recv_timeout(Duration::from_secs(2))
31577      .expect("phase 2 must return once the destructor retires")
31578      .expect("phase 2 must complete the announced stop");
31579    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31580    start_rx
31581      .recv_timeout(Duration::from_secs(2))
31582      .expect("the restart must be admitted once phase 2 has published")
31583      .expect("a stopped runtime must restart after phase 2");
31584    join_within("the restart thread", starter, Duration::from_secs(2));
31585
31586    let new_generation = controller.backend().generation();
31587    assert_ne!(
31588      new_generation, old_generation,
31589      "the restart must publish a fresh generation"
31590    );
31591    controller
31592      .shutdown()
31593      .expect("the restarted generation must still shut down");
31594  }
31595
31596  #[cfg(napi_runtime_os_threads)]
31597  #[test]
31598  fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31599    // The order the production WASI loader actually runs, which the three
31600    // tests above never do: `begin` -> turn the host loop, polling
31601    // `runtime_work_pending` until it answers `false` -> only THEN call
31602    // `finish`. They park phase 2 on a thread BEFORE releasing the
31603    // destructor, so they prove the wait; this proves the way out of it. The
31604    // poll must stop reporting work once the rejected destructor retires, and
31605    // the `finish` entered after that must not block -- a loader that polled
31606    // to zero and then found phase 2 parked would wedge the disposal on the
31607    // JavaScript thread, which is the whole failure the split API avoids.
31608    use std::sync::mpsc;
31609
31610    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31611    drop(controller.backend());
31612    controller
31613      .shutdown()
31614      .expect("the first shutdown must complete");
31615
31616    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31617    assert!(
31618      begin_shutdown_within(
31619        &controller,
31620        "phase 1 before the loader's poll loop",
31621        Duration::from_secs(2),
31622      )
31623      .expect("phase 1 on a stopped runtime must be accepted"),
31624      "the outstanding rejected destruction is a wait phase 2 still owes"
31625    );
31626    assert!(
31627      controller.runtime_work_pending(),
31628      "the loader gates its poll loop on this, so it must report the destruction first"
31629    );
31630
31631    release_drop_tx.send(()).unwrap();
31632    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31633
31634    // The host's turns, bounded: a handoff that never clears fails here
31635    // instead of spinning for the length of the CI step.
31636    let deadline = std::time::Instant::now() + Duration::from_secs(5);
31637    while controller.runtime_work_pending() {
31638      assert!(
31639        std::time::Instant::now() < deadline,
31640        "the poll must stop reporting work once the rejected destructor retires"
31641      );
31642      std::thread::sleep(Duration::from_millis(1));
31643    }
31644
31645    let (finish_tx, finish_rx) = mpsc::channel();
31646    let finish_controller = Arc::clone(&controller);
31647    let finisher = std::thread::spawn(move || {
31648      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31649    });
31650    finish_rx
31651      .recv_timeout(Duration::from_millis(200))
31652      .expect("phase 2 entered after the poll cleared must not block")
31653      .expect("phase 2 must complete the announced stop");
31654    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31655
31656    {
31657      let state = controller
31658        .state
31659        .lock()
31660        .unwrap_or_else(std::sync::PoisonError::into_inner);
31661      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31662      assert!(state.draining.is_none());
31663    }
31664  }
31665
31666  /// Poll `runtime_work_pending` between the phases of an idle generation's
31667  /// shutdown while `hold` keeps a lock the poll reads under held for good,
31668  /// and require the answer "pending" -- an exact read would say idle.
31669  ///
31670  /// `hold` forgets the guard: that is the pool thread that trapped while
31671  /// holding the lock on `wasm32-wasip1-threads`, where a trap unwinds
31672  /// nothing, so the lock never comes back. The loader polls from the
31673  /// JavaScript thread, so the poll runs on another thread here too, and a
31674  /// regression HANGS in it: the `recv_timeout` turns that into a failure.
31675  /// Nothing can take the lock again, so the runtime is leaked, not shut down.
31676  #[cfg(napi_runtime_os_threads)]
31677  fn assert_poll_reports_pending_while_held(
31678    controller: RuntimeController,
31679    what: &str,
31680    hold: impl FnOnce(&RuntimeController, &RuntimeBackend),
31681  ) {
31682    use std::sync::mpsc;
31683
31684    let controller = Arc::new(controller);
31685    let backend = controller.backend();
31686    assert!(
31687      !controller
31688        .begin_shutdown()
31689        .expect("phase 1 on an idle generation must be accepted"),
31690      "an idle generation has nothing for the host to wait for"
31691    );
31692    assert!(
31693      !controller.runtime_work_pending(),
31694      "with every lock free the poll must read the idle generation"
31695    );
31696
31697    hold(&controller, &backend);
31698    let (pending_tx, pending_rx) = mpsc::channel();
31699    let poll_controller = Arc::clone(&controller);
31700    let poller = std::thread::spawn(move || {
31701      pending_tx
31702        .send(poll_controller.runtime_work_pending())
31703        .unwrap();
31704    });
31705    let pending = pending_rx
31706      .recv_timeout(Duration::from_secs(5))
31707      .unwrap_or_else(|_| panic!("the work-pending poll must not wait for {what}"));
31708    join_within(what, poller, Duration::from_secs(2));
31709    assert!(
31710      pending,
31711      "a poll that cannot read {what} must answer pending, not idle"
31712    );
31713
31714    std::mem::forget(backend);
31715    std::mem::forget(controller);
31716  }
31717
31718  #[cfg(napi_runtime_os_threads)]
31719  fn current_thread_executor(backend: &RuntimeBackend) -> &CurrentThreadExecutor {
31720    match &backend.executor {
31721      RuntimeExecutor::CurrentThread(executor) => executor,
31722      RuntimeExecutor::MultiThread(_) => panic!("expected a CurrentThread backend"),
31723    }
31724  }
31725
31726  #[cfg(napi_runtime_os_threads)]
31727  #[test]
31728  fn work_pending_poll_does_not_wait_for_a_held_controller_lock() {
31729    for controller in [
31730      current_thread_controller("poll-held-controller-lock-ct"),
31731      multi_thread_controller("poll-held-controller-lock-mt", 2, 1),
31732    ] {
31733      assert_poll_reports_pending_while_held(
31734        controller,
31735        "the controller state lock",
31736        |controller, _| {
31737          std::mem::forget(controller.state.lock().unwrap());
31738        },
31739      );
31740    }
31741  }
31742
31743  #[cfg(napi_runtime_os_threads)]
31744  #[test]
31745  fn work_pending_poll_does_not_wait_for_a_held_generation_lock() {
31746    for controller in [
31747      current_thread_controller("poll-held-generation-lock-ct"),
31748      multi_thread_controller("poll-held-generation-lock-mt", 2, 1),
31749    ] {
31750      assert_poll_reports_pending_while_held(
31751        controller,
31752        "the generation state lock",
31753        |_, backend| {
31754          std::mem::forget(backend.work.state.lock().unwrap());
31755        },
31756      );
31757    }
31758  }
31759
31760  #[cfg(napi_runtime_os_threads)]
31761  #[test]
31762  fn current_thread_work_pending_poll_does_not_wait_for_a_held_queue_lock() {
31763    assert_poll_reports_pending_while_held(
31764      current_thread_controller("poll-held-queue-lock"),
31765      "the CurrentThread queue lock",
31766      |_, backend| {
31767        std::mem::forget(current_thread_executor(backend).queue.lock().unwrap());
31768      },
31769    );
31770  }
31771
31772  #[cfg(napi_runtime_os_threads)]
31773  #[test]
31774  fn current_thread_work_pending_poll_does_not_wait_for_a_held_scheduler_lock() {
31775    assert_poll_reports_pending_while_held(
31776      current_thread_controller("poll-held-scheduler-lock"),
31777      "the CurrentThread scheduler lock",
31778      |_, backend| {
31779        std::mem::forget(
31780          current_thread_executor(backend)
31781            .scheduler_idle_lock
31782            .lock()
31783            .unwrap(),
31784        );
31785      },
31786    );
31787  }
31788
31789  /// Run a shutdown phase on a thread standing in for the host's JavaScript
31790  /// thread, after the caller left the runtime the way a pool thread that
31791  /// trapped on `wasm32-wasip1-threads` leaves it: a lock held for good (a
31792  /// forgotten guard), or work that never retires. The phase must keep
31793  /// waiting while no thread has crashed, and must give up once the host's
31794  /// crash flag is up -- a trap on wasm, `GaveUpAfterThreadCrash` here. A
31795  /// regression HANGS in the phase: the `recv_timeout` turns that into a
31796  /// failure. Nothing can release what the caller left behind, so the runtime
31797  /// is leaked, not shut down.
31798  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31799  fn assert_shutdown_phase_gives_up_after_a_thread_crash(
31800    what: &str,
31801    controller: Arc<RuntimeController>,
31802    phase: impl FnOnce(&RuntimeController) + Send + 'static,
31803  ) {
31804    use std::sync::mpsc;
31805
31806    let crashed = Arc::new(AtomicBool::new(false));
31807    let (outcome_tx, outcome_rx) = mpsc::channel();
31808    let js_thread = {
31809      let controller = Arc::clone(&controller);
31810      let crashed = Arc::clone(&crashed);
31811      std::thread::spawn(move || {
31812        crate::sync::watch_crash_flag_for_test(crashed);
31813        let outcome = match catch_unwind(AssertUnwindSafe(|| phase(&controller))) {
31814          Ok(()) => "returned",
31815          Err(payload) if payload.is::<crate::sync::GaveUpAfterThreadCrash>() => "gave up",
31816          Err(_) => "panicked",
31817        };
31818        outcome_tx.send(outcome).unwrap();
31819      })
31820    };
31821    assert!(
31822      outcome_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31823      "{what}: with no thread crashed, the phase must keep waiting"
31824    );
31825    crashed.store(true, Ordering::SeqCst);
31826    let outcome = outcome_rx
31827      .recv_timeout(Duration::from_secs(5))
31828      .unwrap_or_else(|_| panic!("{what}: the phase must stop waiting once a thread crashed"));
31829    join_within(what, js_thread, Duration::from_secs(2));
31830    assert_eq!(outcome, "gave up", "{what}");
31831    std::mem::forget(controller);
31832  }
31833
31834  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31835  #[test]
31836  fn begin_shutdown_gives_up_on_a_held_lock_after_a_thread_crash() {
31837    type Hold = fn(&RuntimeController, &RuntimeBackend);
31838    let holds: [(&str, Hold); 2] = [
31839      ("the controller state lock", |controller, _| {
31840        std::mem::forget(controller.state.lock().unwrap());
31841      }),
31842      // `close_and_abort` takes it first thing.
31843      ("the generation state lock", |_, backend| {
31844        std::mem::forget(backend.work.state.lock().unwrap());
31845      }),
31846    ];
31847    for (what, hold) in holds {
31848      for controller in [
31849        current_thread_controller("crash-begin-ct"),
31850        multi_thread_controller("crash-begin-mt", 2, 1),
31851      ] {
31852        let controller = Arc::new(controller);
31853        let backend = controller.backend();
31854        hold(&controller, &backend);
31855        assert_shutdown_phase_gives_up_after_a_thread_crash(
31856          &format!("begin_shutdown on {what}"),
31857          controller,
31858          |controller| {
31859            let _ = controller.begin_shutdown();
31860          },
31861        );
31862        std::mem::forget(backend);
31863      }
31864    }
31865  }
31866
31867  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31868  #[test]
31869  fn finish_shutdown_gives_up_after_a_thread_crash() {
31870    for controller in [
31871      current_thread_controller("crash-finish-work-ct"),
31872      multi_thread_controller("crash-finish-work-mt", 2, 1),
31873    ] {
31874      // A task on the thread that trapped: its guard never drops, so the
31875      // generation never goes idle and `wait_until_idle` never wakes.
31876      let controller = Arc::new(controller);
31877      let backend = controller.backend();
31878      let work = backend
31879        .work
31880        .try_register_work()
31881        .expect("an open generation must accept work");
31882      assert!(
31883        controller
31884          .begin_shutdown()
31885          .expect("phase 1 must be accepted"),
31886        "phase 1 must report the registered work"
31887      );
31888      std::mem::forget(work);
31889      assert_shutdown_phase_gives_up_after_a_thread_crash(
31890        "finish_shutdown on work that never retires",
31891        controller,
31892        |controller| {
31893          let _ = controller.finish_shutdown();
31894        },
31895      );
31896      std::mem::forget(backend);
31897    }
31898    for controller in [
31899      current_thread_controller("crash-finish-lock-ct"),
31900      multi_thread_controller("crash-finish-lock-mt", 2, 1),
31901    ] {
31902      let controller = Arc::new(controller);
31903      let backend = controller.backend();
31904      assert!(
31905        !controller
31906          .begin_shutdown()
31907          .expect("phase 1 on an idle generation must be accepted"),
31908        "an idle generation has nothing for the host to wait for"
31909      );
31910      std::mem::forget(backend.work.state.lock().unwrap());
31911      assert_shutdown_phase_gives_up_after_a_thread_crash(
31912        "finish_shutdown on the generation state lock",
31913        controller,
31914        |controller| {
31915          let _ = controller.finish_shutdown();
31916        },
31917      );
31918      std::mem::forget(backend);
31919    }
31920  }
31921
31922  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
31923  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
31924  // global `RUNTIME`) and run the workload on a child thread guarded by a
31925  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
31926  // wedging the whole suite.
31927
31928  #[cfg(napi_runtime_os_threads)]
31929  #[test]
31930  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
31931    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
31932    // blocking job while `active_blocking >= max_blocking`. The guard is
31933    // exercised deterministically (no sleeps, no incidental worker overlap):
31934    //
31935    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
31936    //      occupies a counted slot (`take_blocking` increments `active_blocking`
31937    //      BEFORE running the closure), signals `held`, then parks on a release
31938    //      barrier -- so all slots are provably occupied before we proceed.
31939    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
31940    //      With the cap intact `take_blocking` returns `None` for them, so the
31941    //      idle workers cannot pop them: no extra may signal `extra_started`.
31942    //   3. We release the holders, then assert EVERY job (holders + extras)
31943    //      completes with the right result and the recorded peak never exceeded
31944    //      the cap.
31945    //
31946    // Decisive property: if the `active >= self.max_blocking` guard were removed,
31947    // the two idle workers would pop the extras while both holders are still
31948    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
31949    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
31950    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
31951    // can never self-deadlock on the over-cap extras.
31952    use std::sync::{Barrier, mpsc};
31953    use std::time::Duration;
31954
31955    let (done_tx, done_rx) = mpsc::channel();
31956    let peak_out = Arc::new(Mutex::new(None));
31957    let peak_slot = Arc::clone(&peak_out);
31958    let runner = std::thread::spawn(move || {
31959      let metrics = Arc::new(RuntimeMetrics::default());
31960      let options = RuntimeOptions {
31961        flavor: RuntimeFlavor::MultiThread,
31962        worker_threads: 4,
31963        max_blocking_tasks: 2,
31964        thread_name_prefix: "rd9-blk-cap".to_string(),
31965        park_deadline: None,
31966        drain_linger: DEFAULT_DRAIN_LINGER,
31967      };
31968      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31969
31970      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
31971      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
31972
31973      // Holders: signal once they hold a counted slot, then park until released.
31974      let (held_tx, held_rx) = mpsc::channel();
31975      let release = Arc::new(Barrier::new(holder_count + 1));
31976      let mut holder_handles = Vec::new();
31977      for _ in 0..holder_count {
31978        let held_tx = held_tx.clone();
31979        let release = Arc::clone(&release);
31980        holder_handles.push(executor.schedule_blocking(move || {
31981          held_tx.send(()).unwrap();
31982          release.wait();
31983          0usize
31984        }));
31985      }
31986      // Wait until every slot is provably occupied (cap fully saturated).
31987      for _ in 0..holder_count {
31988        held_rx.recv().unwrap();
31989      }
31990
31991      // Queue the beyond-cap extras. Each signals the instant it begins running.
31992      let (extra_started_tx, extra_started_rx) = mpsc::channel();
31993      let mut extra_handles = Vec::new();
31994      for i in 0..extra_count {
31995        let extra_started_tx = extra_started_tx.clone();
31996        extra_handles.push(executor.schedule_blocking(move || {
31997          extra_started_tx.send(i).unwrap();
31998          i * 2
31999        }));
32000      }
32001
32002      // With the cap intact, `take_blocking` returns `None` while both slots are
32003      // held, so no extra can have started running yet.
32004      assert!(
32005        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
32006        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
32007      );
32008
32009      // Release the holders so the queued extras can run within the cap.
32010      release.wait();
32011
32012      for handle in holder_handles {
32013        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
32014      }
32015      // Each handle owns its own oneshot receiver, so awaiting in order returns
32016      // each extra's own result regardless of the order the pool ran them.
32017      for (i, handle) in extra_handles.into_iter().enumerate() {
32018        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
32019      }
32020
32021      // `blocking_tasks_started` is incremented at job entry, so once every result
32022      // is in, all holder + extra starts are recorded.
32023      assert_eq!(
32024        metrics.blocking_tasks_started.load(Ordering::Relaxed),
32025        (holder_count + extra_count) as u64
32026      );
32027
32028      *peak_slot
32029        .lock()
32030        .unwrap_or_else(std::sync::PoisonError::into_inner) =
32031        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
32032      let _ = done_tx.send(());
32033    });
32034
32035    match done_rx.recv_timeout(Duration::from_secs(10)) {
32036      Ok(()) => runner.join().unwrap(),
32037      Err(error) => panic!("blocking-cap test did not complete ({error})"),
32038    }
32039    let peak = peak_out
32040      .lock()
32041      .unwrap_or_else(std::sync::PoisonError::into_inner)
32042      .expect("peak captured");
32043    assert!(
32044      peak >= 1,
32045      "at least one blocking job must have run (peak {peak})"
32046    );
32047    assert!(
32048      peak <= 2,
32049      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
32050    );
32051  }
32052
32053  #[cfg(napi_runtime_os_threads)]
32054  #[test]
32055  fn multi_thread_drain_budget_completes_large_runnable_batch() {
32056    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
32057    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
32058    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
32059    // still complete every task, with the runnable counters settling back to 0.
32060    use std::sync::mpsc;
32061    use std::time::{Duration, Instant};
32062
32063    let (done_tx, done_rx) = mpsc::channel();
32064    let runner = std::thread::spawn(move || {
32065      let metrics = Arc::new(RuntimeMetrics::default());
32066      let options = RuntimeOptions {
32067        flavor: RuntimeFlavor::MultiThread,
32068        worker_threads: 2,
32069        max_blocking_tasks: 2,
32070        thread_name_prefix: "rd9-budget".to_string(),
32071        park_deadline: None,
32072        drain_linger: DEFAULT_DRAIN_LINGER,
32073      };
32074      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32075
32076      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
32077      let completed = Arc::new(AtomicU64::new(0));
32078      let mut tasks = Vec::new();
32079      for _ in 0..task_count {
32080        let completed = Arc::clone(&completed);
32081        let scheduler = Arc::clone(&executor);
32082        let (runnable, task) = async_task::spawn(
32083          async move {
32084            completed.fetch_add(1, Ordering::Relaxed);
32085          },
32086          move |r| scheduler.schedule(r),
32087        );
32088        executor.schedule(runnable);
32089        tasks.push(task);
32090      }
32091      for task in tasks {
32092        futures::executor::block_on(task);
32093      }
32094
32095      // Every immediately-ready future is polled exactly once; `runnable_polls`
32096      // (incremented at poll entry, before completion) is therefore exact here.
32097      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
32098      assert_eq!(
32099        metrics.runnable_polls.load(Ordering::Relaxed),
32100        task_count as u64
32101      );
32102
32103      // The active-runnable guard for the LAST task drops just after its Task
32104      // resolves (i.e. just after `block_on` returns), so give the counters a
32105      // brief, bounded moment to settle to 0 rather than asserting them racily.
32106      let deadline = Instant::now() + Duration::from_secs(5);
32107      loop {
32108        let active = metrics.active_runnables.load(Ordering::Relaxed);
32109        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
32110        if active == 0 && queued == 0 {
32111          break;
32112        }
32113        assert!(
32114          Instant::now() < deadline,
32115          "runnable counters did not settle to 0: active={active} queued={queued}"
32116        );
32117        std::thread::yield_now();
32118      }
32119      let _ = done_tx.send(());
32120    });
32121
32122    match done_rx.recv_timeout(Duration::from_secs(15)) {
32123      Ok(()) => runner.join().unwrap(),
32124      Err(error) => panic!("drain-budget test did not complete ({error})"),
32125    }
32126  }
32127
32128  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32129  #[test]
32130  fn multi_thread_spawned_panic_surfaces_as_join_error() {
32131    // A panicking spawned future must surface as `Err(JoinError)` carrying the
32132    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
32133    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
32134    // drive the resulting `JoinHandle` on this executor -- without touching the
32135    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
32136    // `panic!("x")` message round-trips as `"x"`.
32137    use std::sync::mpsc;
32138    use std::time::Duration;
32139
32140    let (done_tx, done_rx) = mpsc::channel();
32141    let runner = std::thread::spawn(move || {
32142      let metrics = Arc::new(RuntimeMetrics::default());
32143      let options = RuntimeOptions {
32144        flavor: RuntimeFlavor::MultiThread,
32145        worker_threads: 2,
32146        max_blocking_tasks: 2,
32147        thread_name_prefix: "rd9-panic".to_string(),
32148        park_deadline: None,
32149        drain_linger: DEFAULT_DRAIN_LINGER,
32150      };
32151      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
32152
32153      let scheduler = Arc::clone(&executor);
32154      let wrapped = async {
32155        AssertUnwindSafe(async { panic!("x") })
32156          .catch_unwind()
32157          .await
32158          .map(|()| ContainedTaskOutput::new((), u64::MAX))
32159          .map_err(panic_payload_to_join_error)
32160      };
32161      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
32162      executor.schedule(runnable);
32163
32164      let result: Result<(), JoinError> =
32165        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
32166          task: task.fallible(),
32167          awaiter: None,
32168          dependency: Arc::new(TaskDependency::default()),
32169        }));
32170      let _ = done_tx.send(result);
32171    });
32172
32173    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
32174      Ok(result) => {
32175        runner.join().unwrap();
32176        result
32177      }
32178      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
32179    };
32180    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
32181    assert_eq!(error.to_string(), "x");
32182  }
32183
32184  #[cfg(napi_runtime_os_threads)]
32185  #[test]
32186  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
32187    use std::sync::mpsc;
32188
32189    let metrics = Arc::new(RuntimeMetrics::default());
32190    metrics.tasks_spawned.store(1, Ordering::Relaxed);
32191    metrics.tasks_completed.store(2, Ordering::Relaxed);
32192    metrics.tasks_panicked.store(3, Ordering::Relaxed);
32193    metrics.runnable_schedules.store(4, Ordering::Relaxed);
32194    metrics.runnable_polls.store(5, Ordering::Relaxed);
32195    metrics
32196      .blocking_tasks_scheduled
32197      .store(14, Ordering::Relaxed);
32198    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
32199    metrics
32200      .blocking_tasks_completed
32201      .store(11, Ordering::Relaxed);
32202    metrics.runnable_scheduled();
32203    let (guards_live_tx, guards_live_rx) = mpsc::channel();
32204    let (release_tx, release_rx) = mpsc::channel();
32205    let guard_metrics = Arc::clone(&metrics);
32206    let guard_thread = std::thread::spawn(move || {
32207      let runnable_guard = guard_metrics.runnable_started();
32208      let blocking_guard = guard_metrics.blocking_started(true);
32209      guards_live_tx.send(()).unwrap();
32210      release_rx.recv().unwrap();
32211      drop(runnable_guard);
32212      drop(blocking_guard);
32213    });
32214    guards_live_rx.recv().unwrap();
32215    let fingerprint_before_reset = metrics.progress_fingerprint();
32216
32217    metrics.reset();
32218
32219    assert!(
32220      metrics.progress_fingerprint() != fingerprint_before_reset,
32221      "a reset must advance the deadlock detector generation even if counters later repeat"
32222    );
32223    assert_eq!(
32224      metrics.tasks_spawned.load(Ordering::Relaxed),
32225      0,
32226      "tasks_spawned"
32227    );
32228    assert_eq!(
32229      metrics.tasks_completed.load(Ordering::Relaxed),
32230      0,
32231      "tasks_completed"
32232    );
32233    assert_eq!(
32234      metrics.tasks_panicked.load(Ordering::Relaxed),
32235      0,
32236      "tasks_panicked"
32237    );
32238    assert_eq!(
32239      metrics.runnable_schedules.load(Ordering::Relaxed),
32240      0,
32241      "runnable_schedules"
32242    );
32243    assert_eq!(
32244      metrics.runnable_polls.load(Ordering::Relaxed),
32245      0,
32246      "runnable_polls"
32247    );
32248    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
32249    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
32250    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
32251    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
32252    assert_eq!(
32253      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
32254      0,
32255      "blocking_tasks_scheduled"
32256    );
32257    assert_eq!(
32258      metrics.blocking_tasks_started.load(Ordering::Relaxed),
32259      0,
32260      "blocking_tasks_started"
32261    );
32262    assert_eq!(
32263      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
32264      0,
32265      "blocking_tasks_completed"
32266    );
32267    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
32268    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
32269
32270    release_tx.send(()).unwrap();
32271    guard_thread.join().unwrap();
32272    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
32273    assert_eq!(
32274      metrics.active_blocking_tasks.load(Ordering::Relaxed),
32275      0,
32276      "a live blocking guard completed after reset without unsigned underflow"
32277    );
32278    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
32279  }
32280
32281  #[cfg(napi_runtime_os_threads)]
32282  #[test]
32283  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
32284    use std::sync::mpsc;
32285
32286    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
32287      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
32288        *slot.borrow_mut() = Some(Box::new(move || {
32289          entered.send(()).unwrap();
32290          release.recv().unwrap();
32291        }));
32292      });
32293    }
32294
32295    let options = RuntimeOptions {
32296      flavor: RuntimeFlavor::CurrentThread,
32297      worker_threads: 1,
32298      max_blocking_tasks: 1,
32299      thread_name_prefix: "metrics-snapshot".to_string(),
32300      park_deadline: None,
32301      drain_linger: DEFAULT_DRAIN_LINGER,
32302    };
32303
32304    let queued_metrics = Arc::new(RuntimeMetrics::default());
32305    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
32306    let (queued_release_tx, queued_release_rx) = mpsc::channel();
32307    let queued_writer_metrics = Arc::clone(&queued_metrics);
32308    let queued_writer = std::thread::spawn(move || {
32309      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
32310      queued_writer_metrics.runnable_scheduled();
32311    });
32312    queued_entered_rx.recv().unwrap();
32313    assert_eq!(
32314      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
32315      0
32316    );
32317    let snapshot = queued_metrics.snapshot(&options);
32318    assert_eq!(snapshot.queued_runnables, 1);
32319    assert_eq!(snapshot.max_queued_runnables, 1);
32320    queued_release_tx.send(()).unwrap();
32321    queued_writer.join().unwrap();
32322    queued_metrics.runnable_cancelled();
32323
32324    let active_metrics = Arc::new(RuntimeMetrics::default());
32325    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
32326    let (active_entered_tx, active_entered_rx) = mpsc::channel();
32327    let (active_release_tx, active_release_rx) = mpsc::channel();
32328    let active_writer_metrics = Arc::clone(&active_metrics);
32329    let active_writer = std::thread::spawn(move || {
32330      pause_next_high_water_update(active_entered_tx, active_release_rx);
32331      let _active = active_writer_metrics.runnable_started();
32332    });
32333    active_entered_rx.recv().unwrap();
32334    assert_eq!(
32335      active_metrics.max_active_runnables.load(Ordering::Relaxed),
32336      0
32337    );
32338    let snapshot = active_metrics.snapshot(&options);
32339    assert_eq!(snapshot.active_runnables, 1);
32340    assert_eq!(snapshot.max_active_runnables, 1);
32341    active_release_tx.send(()).unwrap();
32342    active_writer.join().unwrap();
32343
32344    let blocking_metrics = Arc::new(RuntimeMetrics::default());
32345    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
32346    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
32347    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
32348    let blocking_writer = std::thread::spawn(move || {
32349      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
32350      let _active = blocking_writer_metrics.blocking_started(true);
32351    });
32352    blocking_entered_rx.recv().unwrap();
32353    assert_eq!(
32354      blocking_metrics
32355        .max_active_blocking_tasks
32356        .load(Ordering::Relaxed),
32357      0
32358    );
32359    let snapshot = blocking_metrics.snapshot(&options);
32360    assert_eq!(snapshot.active_blocking_tasks, 1);
32361    assert_eq!(snapshot.max_active_blocking_tasks, 1);
32362    blocking_release_tx.send(()).unwrap();
32363    blocking_writer.join().unwrap();
32364  }
32365
32366  #[cfg(panic = "unwind")]
32367  #[test]
32368  fn runtime_metrics_reset_fails_before_generation_wraparound() {
32369    let metrics = RuntimeMetrics::default();
32370    metrics
32371      .reset_generation
32372      .store(u64::MAX - 1, Ordering::SeqCst);
32373    metrics.tasks_spawned.store(7, Ordering::Relaxed);
32374
32375    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
32376    assert!(exhausted.is_err());
32377    assert!(!metrics.reset_lock.is_poisoned());
32378    assert_eq!(
32379      metrics.reset_generation.load(Ordering::SeqCst),
32380      u64::MAX - 1
32381    );
32382    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
32383  }
32384
32385  // ---- Self-detecting block_on deadlocks -------------------------------------
32386  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
32387  // park decision with no pending wake panics with the typed diagnostic; (2) a
32388  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
32389  // lives at the PARK DECISION, not at Pending-return; (3) an
32390  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
32391  // progress RESETS the deadline instead of firing; (5) the
32392  // foreign/napi whole-build park is excluded from the deadline entirely.
32393
32394  /// A future that never completes and never wakes anyone: once its driver
32395  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
32396  struct NeverReady;
32397  impl Future for NeverReady {
32398    type Output = ();
32399    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32400      Poll::Pending
32401    }
32402  }
32403
32404  #[cfg(napi_runtime_os_threads)]
32405  struct ReadyAfterOwnerLanePublication {
32406    dependency: BlockingDependency,
32407    ready: Arc<AtomicBool>,
32408  }
32409
32410  #[cfg(napi_runtime_os_threads)]
32411  impl Future for ReadyAfterOwnerLanePublication {
32412    type Output = ();
32413
32414    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32415      if self.ready.load(Ordering::SeqCst) {
32416        Poll::Ready(())
32417      } else {
32418        // This shape publishes on every pending poll and never retires; the
32419        // owner-lane assertions read the state, not the token.
32420        let mut published = PublishedBlockingDependencies::default();
32421        record_blocking_dependency(&mut published, self.dependency);
32422        Poll::Pending
32423      }
32424    }
32425  }
32426
32427  #[cfg(panic = "unwind")]
32428  #[test]
32429  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
32430    // Shape (1): on a threadless build a CT park decision with an empty queue
32431    // and no wake token pending is a PROVABLE deadlock (no other thread exists
32432    // to deliver the wake) -- `block_on` must panic immediately with the typed
32433    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
32434    // runs on a child thread bounded by recv_timeout so a missing detection
32435    // (a hang) fails the test rather than wedging the suite.
32436    use std::sync::mpsc;
32437
32438    let (done_tx, done_rx) = mpsc::channel();
32439    let runner = std::thread::spawn(move || {
32440      let metrics = Arc::new(RuntimeMetrics::default());
32441      // Native stand-in for the threadless wasm build; no deadline because the
32442      // certain case is not timing-based.
32443      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32444
32445      let payload = catch_unwind(AssertUnwindSafe(|| {
32446        let mut future = std::pin::pin!(NeverReady);
32447        executor.block_on(future.as_mut());
32448      }))
32449      .expect_err("a threadless park with an empty queue and no pending wake must panic");
32450      let diagnostic = payload
32451        .downcast_ref::<BlockOnDeadlock>()
32452        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
32453      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
32454      assert_eq!(
32455        diagnostic.park_deadline, None,
32456        "the certain case carries no deadline"
32457      );
32458      let message = diagnostic.to_string();
32459      assert!(
32460        message.contains("block_on") && message.contains("JS"),
32461        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
32462      );
32463      let _ = done_tx.send(());
32464    });
32465
32466    match done_rx.recv_timeout(Duration::from_secs(10)) {
32467      Ok(()) => runner.join().unwrap(),
32468      Err(error) => panic!(
32469        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
32470      ),
32471    }
32472  }
32473
32474  #[cfg(napi_runtime_os_threads)]
32475  #[test]
32476  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32477    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
32478    // `Pending` but wakes itself synchronously during the poll leaves
32479    // a wake token pending, so the loop must consume the token and re-poll --
32480    // NOT panic -- even on a threadless build. "Pending returned" alone is
32481    // never a deadlock.
32482    use std::sync::mpsc;
32483
32484    struct PendThenReady {
32485      polls: usize,
32486    }
32487    impl Future for PendThenReady {
32488      type Output = ();
32489      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32490        self.polls += 1;
32491        if self.polls >= 2 {
32492          Poll::Ready(())
32493        } else {
32494          cx.waker().wake_by_ref();
32495          Poll::Pending
32496        }
32497      }
32498    }
32499
32500    let (done_tx, done_rx) = mpsc::channel();
32501    let runner = std::thread::spawn(move || {
32502      let metrics = Arc::new(RuntimeMetrics::default());
32503      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32504      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32505      executor.block_on(future.as_mut());
32506      assert_eq!(
32507        future.polls, 2,
32508        "the self-woken future must have been re-polled to completion"
32509      );
32510      let _ = done_tx.send(());
32511    });
32512
32513    match done_rx.recv_timeout(Duration::from_secs(10)) {
32514      Ok(()) => runner.join().unwrap(),
32515      Err(error) => panic!(
32516        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32517      ),
32518    }
32519  }
32520
32521  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32522  #[test]
32523  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32524    // Threaded CT is NOT provably dead at the park decision (another OS thread
32525    // could deliver a wake later), so detection there is deadline-based and
32526    // opt-in. A park that outlives the armed deadline with ZERO runtime
32527    // progress must panic with the typed diagnostic.
32528    use std::sync::mpsc;
32529
32530    let (done_tx, done_rx) = mpsc::channel();
32531    let runner = std::thread::spawn(move || {
32532      let metrics = Arc::new(RuntimeMetrics::default());
32533      let executor = Arc::new(CurrentThreadExecutor::with_detection(
32534        metrics,
32535        false,
32536        Some(Duration::from_millis(50)),
32537      ));
32538
32539      let payload = catch_unwind(AssertUnwindSafe(|| {
32540        let mut future = std::pin::pin!(NeverReady);
32541        executor.block_on(future.as_mut());
32542      }))
32543      .expect_err("a dead native CT park must fire the armed deadline");
32544      let diagnostic = payload
32545        .downcast_ref::<BlockOnDeadlock>()
32546        .expect("typed payload");
32547      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32548      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32549      // Teardown inside the bounded window: a wedged executor drop must fail
32550      // the recv below loudly, not hang the final join silently.
32551      drop(executor);
32552      let _ = done_tx.send(());
32553    });
32554
32555    match done_rx.recv_timeout(Duration::from_secs(10)) {
32556      Ok(()) => join_within(
32557        "the CT deadline-fire runner exits after teardown",
32558        runner,
32559        Duration::from_secs(30),
32560      ),
32561      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32562    }
32563  }
32564
32565  #[cfg(napi_runtime_os_threads)]
32566  #[test]
32567  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32568    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
32569    // runtime is alive (e.g. a slow JS plugin with other
32570    // work proceeding), so the deadline must re-arm instead of firing.
32571    // Progress is injected by advancing the very counter the reset reads --
32572    // the exact seam, isolated from wake delivery (production advances it via
32573    // real runnable polls).
32574    //
32575    // Deadline/cadence ratio: the reset is only exercised when at least one
32576    // tick lands inside EVERY armed window, so the window must dwarf the
32577    // injector thread's worst-case scheduling stall. On starved CI hosts
32578    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
32579    // can overshoot past a 60ms window, leaving one window with zero
32580    // progress -- which IS a deadline fire by design. Ticks every ~25ms
32581    // against a 1s window tolerate a ~975ms stall while the park is still
32582    // held across multiple full windows, so the repeated RE-ARM (not merely
32583    // the first arm) is what keeps the park alive.
32584    use std::sync::mpsc;
32585
32586    let deadline = Duration::from_millis(1000);
32587    let metrics = Arc::new(RuntimeMetrics::default());
32588    let executor = Arc::new(CurrentThreadExecutor::with_detection(
32589      Arc::clone(&metrics),
32590      false,
32591      Some(deadline),
32592    ));
32593
32594    let (value_tx, value_rx) = oneshot::channel::<usize>();
32595    let (done_tx, done_rx) = mpsc::channel();
32596    let runner = std::thread::spawn(move || {
32597      let mut output = None;
32598      {
32599        let mut future = std::pin::pin!(async {
32600          output = Some(value_rx.await.unwrap());
32601        });
32602        executor.block_on(future.as_mut());
32603      }
32604      assert_eq!(output, Some(9));
32605      // Teardown inside the bounded window (see `join_within`).
32606      drop(executor);
32607      let _ = done_tx.send(());
32608    });
32609
32610    // Hold the park across several full deadline windows, every one
32611    // containing many injected progress ticks, then deliver the (legitimately
32612    // late) wake. The loop is elapsed-based so an oversleeping iteration
32613    // extends the hold (more windows) instead of thinning the tick supply,
32614    // and the final tick lands at most one iteration before the wake.
32615    let hold_until = Instant::now() + deadline * 5 / 2;
32616    while Instant::now() < hold_until {
32617      std::thread::sleep(Duration::from_millis(25));
32618      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32619    }
32620    value_tx.send(9).unwrap();
32621
32622    match done_rx.recv_timeout(Duration::from_secs(30)) {
32623      Ok(()) => join_within(
32624        "the CT deadline-reset runner exits after teardown",
32625        runner,
32626        Duration::from_secs(30),
32627      ),
32628      Err(error) => panic!(
32629        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32630      ),
32631    }
32632  }
32633
32634  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32635  #[test]
32636  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32637    // Shape (3): the MT deadline applies to the DriverParker inside
32638    // `cooperative_block_on`. Run the cooperative branch directly on this
32639    // thread with the test-only on-pool marker so the panic is observable as
32640    // a typed payload without rayon in between.
32641    use std::sync::mpsc;
32642
32643    let (done_tx, done_rx) = mpsc::channel();
32644    let runner = std::thread::spawn(move || {
32645      let options = RuntimeOptions {
32646        flavor: RuntimeFlavor::MultiThread,
32647        worker_threads: 1,
32648        max_blocking_tasks: 1,
32649        thread_name_prefix: "deadline-fire".to_string(),
32650        park_deadline: Some(Duration::from_millis(50)),
32651        drain_linger: DEFAULT_DRAIN_LINGER,
32652      };
32653      let executor =
32654        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32655
32656      let payload = {
32657        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32658        catch_unwind(AssertUnwindSafe(|| {
32659          let mut future = std::pin::pin!(NeverReady);
32660          executor.block_on(future.as_mut());
32661        }))
32662        .expect_err("a dead cooperative park must fire the armed deadline")
32663      };
32664      let diagnostic = payload
32665        .downcast_ref::<BlockOnDeadlock>()
32666        .expect("typed payload");
32667      assert_eq!(
32668        diagnostic.kind,
32669        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32670      );
32671      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32672      let message = diagnostic.to_string();
32673      assert!(
32674        message.contains("block_on"),
32675        "diagnostic must name the hazard, got: {message}"
32676      );
32677      assert_eq!(
32678        executor.parked_drivers.count.load(Ordering::SeqCst),
32679        0,
32680        "the firing driver must deregister itself before panicking"
32681      );
32682      // Teardown inside the bounded window (see `join_within`).
32683      drop(executor);
32684      let _ = done_tx.send(());
32685    });
32686
32687    match done_rx.recv_timeout(Duration::from_secs(10)) {
32688      Ok(()) => join_within(
32689        "the MT deadline-fire runner exits after executor teardown",
32690        runner,
32691        Duration::from_secs(30),
32692      ),
32693      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32694    }
32695  }
32696
32697  #[cfg(napi_runtime_os_threads)]
32698  #[test]
32699  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32700    // Shape (4), MT side of the progress reset: a legitimately slow wake under
32701    // an armed deadline must NOT fire while the executor's
32702    // metrics keep advancing. Topology: the sole worker is parked as a
32703    // cooperative driver (real pool thread, real DriverParker park); the test
32704    // injects progress ticks across several full deadline windows, then
32705    // delivers the late wake.
32706    //
32707    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
32708    // so the window must dwarf the injector thread's worst-case scheduling
32709    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
32710    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
32711    // and a full window with zero progress IS a deadline fire by design.
32712    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
32713    // still holding the park across multiple full windows, proving the
32714    // repeated RE-ARM rather than a single lucky first window.
32715    use std::sync::mpsc;
32716
32717    let (done_tx, done_rx) = mpsc::channel();
32718    let runner = std::thread::spawn(move || {
32719      let deadline = Duration::from_millis(1000);
32720      let metrics = Arc::new(RuntimeMetrics::default());
32721      let options = RuntimeOptions {
32722        flavor: RuntimeFlavor::MultiThread,
32723        worker_threads: 1,
32724        max_blocking_tasks: 1,
32725        thread_name_prefix: "deadline-reset".to_string(),
32726        park_deadline: Some(deadline),
32727        drain_linger: DEFAULT_DRAIN_LINGER,
32728      };
32729      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32730
32731      // Park the sole worker as a cooperative driver awaiting a gate (same
32732      // topology as the miss-compensation test).
32733      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32734      let td_exec = Arc::clone(&executor);
32735      let completed = Arc::new(AtomicBool::new(false));
32736      let td_flag = Arc::clone(&completed);
32737      let td_body = async move {
32738        let mut inner = std::pin::pin!(async move {
32739          assert_eq!(gate_rx.await.unwrap(), 9);
32740        });
32741        td_exec.block_on(inner.as_mut());
32742        td_flag.store(true, Ordering::SeqCst);
32743      };
32744      let sched = Arc::clone(&executor);
32745      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
32746      executor.schedule(td_runnable);
32747      wait_until("the sole driver parked", || {
32748        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
32749      });
32750
32751      // Outlive the armed deadline several times over, with every window
32752      // containing many injected progress ticks (the counter the reset
32753      // reads). Elapsed-based so an oversleeping iteration extends the hold
32754      // instead of thinning the tick supply, and the final tick lands at
32755      // most one iteration before the late wake below.
32756      let hold_until = Instant::now() + deadline * 5 / 2;
32757      while Instant::now() < hold_until {
32758        std::thread::sleep(Duration::from_millis(25));
32759        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32760      }
32761      assert!(
32762        !completed.load(Ordering::SeqCst),
32763        "the driver must still be parked awaiting its late wake"
32764      );
32765      gate_tx.send(9).unwrap();
32766      futures::executor::block_on(td_task);
32767      assert!(
32768        completed.load(Ordering::SeqCst),
32769        "the late wake must complete the parked driver"
32770      );
32771      // Teardown inside the bounded window (see `join_within`).
32772      drop(executor);
32773      let _ = done_tx.send(());
32774    });
32775
32776    match done_rx.recv_timeout(Duration::from_secs(30)) {
32777      Ok(()) => join_within(
32778        "the MT deadline-reset runner exits after executor teardown",
32779        runner,
32780        Duration::from_secs(30),
32781      ),
32782      Err(error) => panic!(
32783        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
32784      ),
32785    }
32786  }
32787
32788  #[cfg(napi_runtime_os_threads)]
32789  #[test]
32790  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
32791    // Shape (5): the foreign/napi caller thread parks in
32792    // `futures::executor::block_on` for the ENTIRE build -- the normal
32793    // production shape. Even with an aggressively short deadline armed and
32794    // ZERO executor progress for many windows, that park must never fire.
32795    use std::sync::mpsc;
32796
32797    let (done_tx, done_rx) = mpsc::channel();
32798    let runner = std::thread::spawn(move || {
32799      let options = RuntimeOptions {
32800        flavor: RuntimeFlavor::MultiThread,
32801        worker_threads: 2,
32802        max_blocking_tasks: 2,
32803        thread_name_prefix: "foreign-exempt".to_string(),
32804        park_deadline: Some(Duration::from_millis(40)),
32805        drain_linger: DEFAULT_DRAIN_LINGER,
32806      };
32807      let executor =
32808        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32809
32810      let (value_tx, value_rx) = oneshot::channel::<usize>();
32811      let waker_thread = std::thread::spawn(move || {
32812        // Well past several deadline windows, with zero executor progress.
32813        std::thread::sleep(Duration::from_millis(300));
32814        let _ = value_tx.send(7);
32815      });
32816
32817      let started = Instant::now();
32818      let mut output = None;
32819      {
32820        let mut future = std::pin::pin!(async {
32821          output = Some(value_rx.await.unwrap());
32822        });
32823        // This (non-pool) thread takes the parking branch: exempt by design.
32824        executor.block_on(future.as_mut());
32825      }
32826      assert_eq!(output, Some(7));
32827      assert!(
32828        started.elapsed() >= Duration::from_millis(250),
32829        "the park must genuinely have outlived the 40ms deadline many times over"
32830      );
32831      waker_thread.join().unwrap();
32832      // Teardown inside the bounded window: this test was one of the silent
32833      // wedges on the Windows CI runners -- every assertion above had
32834      // passed, so the hang lived in the unbounded residue (executor drop /
32835      // thread teardown). Keep that phase inside the loud bounds.
32836      drop(executor);
32837      let _ = done_tx.send(());
32838    });
32839
32840    match done_rx.recv_timeout(Duration::from_secs(10)) {
32841      Ok(()) => join_within(
32842        "the exempt foreign-park runner exits after executor teardown",
32843        runner,
32844        Duration::from_secs(30),
32845      ),
32846      Err(error) => {
32847        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
32848      }
32849    }
32850  }
32851
32852  #[cfg(napi_runtime_os_threads)]
32853  #[test]
32854  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
32855    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
32856    // hook instead of a wall-clock lottery. Interleaving:
32857    //   (1) the cooperative park's deadline expires with genuinely zero
32858    //       progress (nothing else touches the executor before the park);
32859    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
32860    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
32861    //       pops this parker, stores the permit and reports the wake as
32862    //       DELIVERED (so no drainer is spawned). The wake was counted as
32863    //       delivered TO US, so the post-deregister permit re-check is the
32864    //       first-line catch; queued work and the enqueue counters in the
32865    //       fingerprint are further backstops;
32866    //   (3) the expiry decision runs. It must treat the permit as a wake, run
32867    //       the job (which sends the gate), and complete the awaited future
32868    //       instead of reporting `BlockOnDeadlock`.
32869    use std::sync::mpsc;
32870
32871    let (done_tx, done_rx) = mpsc::channel();
32872    let runner = std::thread::spawn(move || {
32873      let options = RuntimeOptions {
32874        flavor: RuntimeFlavor::MultiThread,
32875        worker_threads: 1,
32876        max_blocking_tasks: 1,
32877        thread_name_prefix: "edge-wake".to_string(),
32878        park_deadline: Some(Duration::from_millis(40)),
32879        drain_linger: DEFAULT_DRAIN_LINGER,
32880      };
32881      let executor =
32882        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32883
32884      // Until the racing job sends this gate, the awaited future is Pending.
32885      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32886      // (registered_before_wake, registered_after_wake) observed by the hook.
32887      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
32888
32889      let hook_exec = Arc::clone(&executor);
32890      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32891        *slot.borrow_mut() = Some(Box::new(move || {
32892          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32893          // The racing schedule: pushes the job, then `wake_one` pops the
32894          // still-registered parker (permit stored, wake counted as
32895          // delivered, no drainer).
32896          let handle = hook_exec.schedule_blocking(move || {
32897            let _ = gate_tx.send(5usize);
32898            0usize
32899          });
32900          drop(handle); // result unobserved; the gate send is the signal
32901          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32902          hook_saw_tx.send((before, after)).unwrap();
32903        }));
32904      });
32905
32906      // The test-only on-pool marker runs the cooperative branch and its
32907      // thread-local hook on THIS thread.
32908      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32909      let mut output = None;
32910      {
32911        let mut future = std::pin::pin!(async {
32912          output = Some(gate_rx.await.unwrap());
32913        });
32914        executor.block_on(future.as_mut());
32915      }
32916      // Clear the hook slot BEFORE any assert can panic: a hook left
32917      // installed captures an executor Arc, and dropping the last executor
32918      // Arc from a TLS destructor at thread exit is loader-lock hostile on
32919      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
32920      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32921      assert_eq!(
32922        output,
32923        Some(5),
32924        "the edge-delivered wake's job must have run and completed the future"
32925      );
32926
32927      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
32928      assert_eq!(
32929        before, 1,
32930        "the hook must observe the parker STILL registered (race window)"
32931      );
32932      assert_eq!(
32933        after, 0,
32934        "the racing wake_one must have popped the parker (wake DELIVERED)"
32935      );
32936      // Teardown inside the bounded window (see `join_within`).
32937      drop(executor);
32938      let _ = done_tx.send(());
32939    });
32940
32941    match done_rx.recv_timeout(Duration::from_secs(10)) {
32942      Ok(()) => join_within(
32943        "the expiry-edge-wake runner exits after executor teardown",
32944        runner,
32945        Duration::from_secs(30),
32946      ),
32947      Err(error) => panic!(
32948        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
32949      ),
32950    }
32951  }
32952
32953  #[cfg(napi_runtime_os_threads)]
32954  #[test]
32955  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
32956    // Enqueue/fingerprint invariant: both runnable and blocking submissions
32957    // must advance the progress fingerprint at ENQUEUE. A blocking submission
32958    // landing after `has_queued_work()` but before the final fingerprint read
32959    // has already pushed deliverable work, while its wake targets an EMPTY
32960    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
32961    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
32962    // fires in this window on the driver thread:
32963    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
32964    //     job on the real worker -- `blocking_tasks_started` provably cannot
32965    //     advance behind our back;
32966    //   * the enqueue counter must therefore save the healthy park.
32967    use std::sync::mpsc;
32968
32969    let (done_tx, done_rx) = mpsc::channel();
32970    let runner = std::thread::spawn(move || {
32971      let options = RuntimeOptions {
32972        flavor: RuntimeFlavor::MultiThread,
32973        worker_threads: 1,
32974        max_blocking_tasks: 1,
32975        thread_name_prefix: "verdict-blocking".to_string(),
32976        park_deadline: Some(Duration::from_millis(40)),
32977        drain_linger: DEFAULT_DRAIN_LINGER,
32978      };
32979      let executor =
32980        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32981      executor
32982        .active_drainers
32983        .store(executor.max_drainers, Ordering::SeqCst);
32984
32985      // Until the racing job sends this gate, the awaited future is Pending.
32986      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32987      // Registry size observed by the hook (must be 0: already deregistered,
32988      // so no permit can possibly be delivered for this enqueue).
32989      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
32990
32991      let hook_exec = Arc::clone(&executor);
32992      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32993        *slot.borrow_mut() = Some(Box::new(move || {
32994          let handle = hook_exec.schedule_blocking(move || {
32995            let _ = gate_tx.send(11usize);
32996            0usize
32997          });
32998          drop(handle); // result unobserved; the gate send is the signal
32999          hook_saw_tx
33000            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
33001            .unwrap();
33002        }));
33003      });
33004
33005      // The test-only on-pool marker runs the cooperative branch and its
33006      // thread-local hook on THIS thread.
33007      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33008      let mut output = None;
33009      {
33010        let mut future = std::pin::pin!(async {
33011          output = Some(gate_rx.await.unwrap());
33012        });
33013        executor.block_on(future.as_mut());
33014      }
33015      assert_eq!(
33016        output,
33017        Some(11),
33018        "the enqueued job must have been run by the surviving driver via run_one"
33019      );
33020
33021      let registered_at_hook = hook_saw_rx
33022        .recv()
33023        .expect("the verdict hook must have fired");
33024      assert_eq!(
33025        registered_at_hook, 0,
33026        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
33027      );
33028      executor.active_drainers.store(0, Ordering::SeqCst);
33029      let _ = done_tx.send(());
33030    });
33031
33032    match done_rx.recv_timeout(Duration::from_secs(10)) {
33033      Ok(()) => runner.join().unwrap(),
33034      Err(error) => panic!(
33035        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
33036      ),
33037    }
33038  }
33039
33040  #[cfg(napi_runtime_os_threads)]
33041  #[test]
33042  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
33043    // Publish only through the deadlock-admission protocol after every legacy
33044    // final check. No runnable, blocking job, permit, timer, or metrics
33045    // fingerprint changes, so only the synchronized publication epoch can
33046    // prevent the false verdict. The future observes a separate test flag on
33047    // its next poll; that flag is deliberately not a scheduler wake source.
33048    use std::sync::mpsc;
33049
33050    struct ReadyAfterPublication(Arc<AtomicBool>);
33051
33052    impl Future for ReadyAfterPublication {
33053      type Output = ();
33054
33055      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
33056        if self.0.load(Ordering::SeqCst) {
33057          Poll::Ready(())
33058        } else {
33059          Poll::Pending
33060        }
33061      }
33062    }
33063
33064    let (done_tx, done_rx) = mpsc::channel();
33065    let runner = std::thread::spawn(move || {
33066      let options = RuntimeOptions {
33067        flavor: RuntimeFlavor::MultiThread,
33068        worker_threads: 1,
33069        max_blocking_tasks: 1,
33070        thread_name_prefix: "final-verdict-publication".to_string(),
33071        park_deadline: Some(Duration::from_millis(40)),
33072        drain_linger: DEFAULT_DRAIN_LINGER,
33073      };
33074      let executor =
33075        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
33076
33077      let ready = Arc::new(AtomicBool::new(false));
33078      let baseline_fingerprint = executor.metrics.progress_fingerprint();
33079      let baseline_publications = executor.deadlock_state.publications();
33080      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
33081      let hook_exec = Arc::clone(&executor);
33082      let hook_ready = Arc::clone(&ready);
33083      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33084        *slot.borrow_mut() = Some(Box::new(move || {
33085          let publication_executor = Arc::clone(&hook_exec);
33086          std::thread::spawn(move || {
33087            let mut admission = publication_executor
33088              .begin_deadlock_admission()
33089              .expect("the deadline detector must enable admission accounting");
33090            hook_ready.store(true, Ordering::SeqCst);
33091            admission.mark_published();
33092          })
33093          .join()
33094          .unwrap();
33095          hook_saw_tx
33096            .send((
33097              hook_exec.deadlock_state.publications(),
33098              !hook_exec.has_queued_runnable(),
33099              hook_exec
33100                .blocking_queue
33101                .lock()
33102                .unwrap_or_else(std::sync::PoisonError::into_inner)
33103                .is_empty(),
33104              hook_exec.next_timer_deadline().is_none(),
33105              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
33106              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
33107            ))
33108            .unwrap();
33109        }));
33110      });
33111
33112      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33113      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
33114      assert_eq!(
33115        executor.block_on(future.as_mut()),
33116        BlockOnOutcome::Completed,
33117        "the publication epoch must veto the first verdict and permit the next poll"
33118      );
33119      let (
33120        publications,
33121        runnable_queue_empty,
33122        blocking_queue_empty,
33123        timer_heap_empty,
33124        fingerprint_unchanged,
33125        parked_drivers,
33126      ) = hook_saw_rx
33127        .recv()
33128        .expect("the final-verdict hook must have fired");
33129      assert_eq!(
33130        publications,
33131        baseline_publications
33132          .checked_add(1)
33133          .expect("test publication epoch must not overflow"),
33134        "the cross-thread admission must publish exactly one epoch"
33135      );
33136      assert!(
33137        runnable_queue_empty,
33138        "the publication test must not queue a runnable"
33139      );
33140      assert!(
33141        blocking_queue_empty,
33142        "the publication test must not queue blocking work"
33143      );
33144      assert!(
33145        timer_heap_empty,
33146        "the publication test must not register a timer"
33147      );
33148      assert!(
33149        fingerprint_unchanged,
33150        "ordinary metrics progress must not veto the publication-only verdict"
33151      );
33152      assert_eq!(
33153        parked_drivers, 0,
33154        "the publication must land after the cooperative parker was deregistered"
33155      );
33156      let _ = done_tx.send(());
33157    });
33158
33159    match done_rx.recv_timeout(Duration::from_secs(10)) {
33160      Ok(()) => runner.join().unwrap(),
33161      Err(error) => panic!(
33162        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
33163      ),
33164    }
33165  }
33166
33167  #[cfg(napi_runtime_os_threads)]
33168  #[test]
33169  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
33170    // The exact owner lane is saturated and reserved while the cooperative
33171    // driver parks. At expiry, release selects that driver and publishes its
33172    // handoff, then pauses before unpark. The driver reaches the gated verdict
33173    // with no queue, timer, metric, or ordinary publication change. The
33174    // exact-owner publication boundary must serialize the final check. The
33175    // hook then consumes the ordinary permit and renews the lane reservation,
33176    // leaving only the pending-handoff predicate to veto the false deadlock.
33177    use std::sync::mpsc;
33178
33179    let executor = multi_thread_executor(
33180      1,
33181      Some(Duration::from_millis(40)),
33182      "deadline-selected-handoff",
33183    );
33184    let owner = executor.fresh_owner_token();
33185    executor.active_blocking_owners.register(owner);
33186    executor
33187      .active_blocking
33188      .store(executor.max_blocking, Ordering::SeqCst);
33189
33190    let ready = Arc::new(AtomicBool::new(false));
33191    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33192    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33193    let (selected_tx, selected_rx) = mpsc::channel();
33194    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
33195    let (released_tx, released_rx) = mpsc::channel();
33196    let (renewed_tx, renewed_rx) = mpsc::channel();
33197    let release_executor = Arc::clone(&executor);
33198    let renewal_executor = Arc::clone(&executor);
33199    let release_ready = Arc::clone(&ready);
33200    let releaser = std::thread::spawn(move || {
33201      let reservation =
33202        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33203          .expect("the synthetic exact-owner lane must start reserved");
33204      reservation_ready_tx.send(()).unwrap();
33205      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
33206        *slot.borrow_mut() = Some(Box::new(move || {
33207          selected_tx.send(()).unwrap();
33208          allow_unpark_rx.recv().unwrap();
33209          let renewed = renewal_executor
33210            .active_blocking_owners
33211            .reserve(owner)
33212            .expect("the released exact-owner lane must be reservable before unpark");
33213          release_ready.store(true, Ordering::SeqCst);
33214          renewed_tx.send(renewed).unwrap();
33215        }));
33216      });
33217      release_reservation_rx.recv().unwrap();
33218      released_tx
33219        .send(release_executor.release_owner_lane_and_wake(reservation))
33220        .unwrap();
33221    });
33222    reservation_ready_rx
33223      .recv_timeout(Duration::from_secs(2))
33224      .expect("the exact-owner reservation must be established before the park");
33225
33226    let baseline_fingerprint = executor.metrics.progress_fingerprint();
33227    let baseline_publications = executor.deadlock_state.publications();
33228    let expiry_executor = Arc::clone(&executor);
33229    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
33230      *slot.borrow_mut() = Some(Box::new(move || {
33231        release_reservation_tx.send(()).unwrap();
33232        selected_rx
33233          .recv_timeout(Duration::from_secs(2))
33234          .expect("reservation release must select the still-registered exact-owner driver");
33235        assert_eq!(
33236          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
33237          0,
33238          "handoff selection must remove the driver before pausing ahead of unpark"
33239        );
33240      }));
33241    });
33242
33243    let verdict_executor = Arc::clone(&executor);
33244    let verdict_ready = Arc::clone(&ready);
33245    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33246      *slot.borrow_mut() = Some(Box::new(move || {
33247        assert!(
33248          verdict_executor.active_blocking_owners.is_available(owner),
33249          "reservation release must publish lane availability before handoff selection"
33250        );
33251        assert!(!verdict_executor.has_queued_runnable());
33252        assert!(
33253          verdict_executor
33254            .blocking_queue
33255            .lock()
33256            .unwrap_or_else(std::sync::PoisonError::into_inner)
33257            .is_empty()
33258        );
33259        assert!(verdict_executor.next_timer_deadline().is_none());
33260        assert!(
33261          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33262          "ordinary metrics progress must not save the selected-handoff verdict"
33263        );
33264        assert_eq!(
33265          verdict_executor.deadlock_state.publications(),
33266          baseline_publications
33267        );
33268        assert!(
33269          !verdict_ready.load(Ordering::SeqCst),
33270          "the selected handoff must still be paused before unpark at the gated verdict"
33271        );
33272        allow_unpark_tx.send(()).unwrap();
33273      }));
33274    });
33275    let exact_owner_executor = Arc::clone(&executor);
33276    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
33277      *slot.borrow_mut() = Some(Box::new(move |parker| {
33278        assert!(
33279          parker.consume_permit(),
33280          "the selected handoff must unpark before exact-owner final predicates run"
33281        );
33282        assert!(
33283          parker.has_owner_handoff(),
33284          "consuming the ordinary permit must leave the exact-owner handoff pending"
33285        );
33286        assert!(
33287          !exact_owner_executor
33288            .active_blocking_owners
33289            .is_available(owner),
33290          "the renewed reservation must prevent lane availability from saving the verdict"
33291        );
33292      }));
33293    });
33294
33295    {
33296      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33297      let _owner = BlockingOwnerGuard::enter(Some(owner));
33298      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33299        dependency: BlockingDependency {
33300          job: BlockingJobId(u64::MAX - 80),
33301          owner: Some(owner)
33302        },
33303        ready: Arc::clone(&ready),
33304      });
33305      assert_eq!(
33306        executor.block_on(future.as_mut()),
33307        BlockOnOutcome::Completed
33308      );
33309    }
33310    assert!(
33311      released_rx
33312        .recv_timeout(Duration::from_secs(2))
33313        .expect("the selected handoff publication must finish"),
33314      "reservation release must have selected the cooperative driver"
33315    );
33316    let renewed = renewed_rx
33317      .recv_timeout(Duration::from_secs(2))
33318      .expect("the selected handoff must renew the owner reservation before unpark");
33319    releaser.join().unwrap();
33320
33321    executor.active_blocking_owners.release(owner, renewed);
33322    executor.active_blocking.store(0, Ordering::SeqCst);
33323    executor.active_blocking_owners.unregister(owner);
33324    executor.begin_shutdown();
33325    executor.wait_until_scheduler_idle();
33326  }
33327
33328  #[cfg(napi_runtime_os_threads)]
33329  #[test]
33330  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
33331    // Release begins only after the deadline path has deregistered its parker
33332    // and completed every legacy final check. No targeted wake or ordinary
33333    // scheduler publication can save the verdict; the synchronized exact-owner
33334    // availability predicate must observe the released lane.
33335    use std::sync::mpsc;
33336
33337    let executor = multi_thread_executor(
33338      1,
33339      Some(Duration::from_millis(40)),
33340      "deadline-owner-no-parker",
33341    );
33342    let owner = executor.fresh_owner_token();
33343    executor.active_blocking_owners.register(owner);
33344    executor
33345      .active_blocking
33346      .store(executor.max_blocking, Ordering::SeqCst);
33347
33348    let ready = Arc::new(AtomicBool::new(false));
33349    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
33350    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
33351    let (released_tx, released_rx) = mpsc::channel();
33352    let release_executor = Arc::clone(&executor);
33353    let release_ready = Arc::clone(&ready);
33354    let releaser = std::thread::spawn(move || {
33355      let reservation =
33356        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
33357          .expect("the synthetic exact-owner lane must start reserved");
33358      reservation_ready_tx.send(()).unwrap();
33359      release_reservation_rx.recv().unwrap();
33360      let woke = release_executor.release_owner_lane_and_wake(reservation);
33361      release_ready.store(true, Ordering::SeqCst);
33362      released_tx.send(woke).unwrap();
33363    });
33364    reservation_ready_rx
33365      .recv_timeout(Duration::from_secs(2))
33366      .expect("the exact-owner reservation must be established before the park");
33367
33368    let baseline_fingerprint = executor.metrics.progress_fingerprint();
33369    let baseline_publications = executor.deadlock_state.publications();
33370    let verdict_executor = Arc::clone(&executor);
33371    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
33372      *slot.borrow_mut() = Some(Box::new(move || {
33373        assert_eq!(
33374          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
33375          0,
33376          "the release must begin only after deadline deregistration"
33377        );
33378        release_reservation_tx.send(()).unwrap();
33379        assert!(
33380          !released_rx
33381            .recv_timeout(Duration::from_secs(2))
33382            .expect("the no-parker owner release must finish"),
33383          "no registered parker may receive the released owner lane"
33384        );
33385        assert!(verdict_executor.active_blocking_owners.is_available(owner));
33386        assert!(!verdict_executor.has_queued_runnable());
33387        assert!(
33388          verdict_executor
33389            .blocking_queue
33390            .lock()
33391            .unwrap_or_else(std::sync::PoisonError::into_inner)
33392            .is_empty()
33393        );
33394        assert!(verdict_executor.next_timer_deadline().is_none());
33395        assert!(
33396          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
33397          "ordinary metrics progress must not save the no-parker release verdict"
33398        );
33399        assert_eq!(
33400          verdict_executor.deadlock_state.publications(),
33401          baseline_publications
33402        );
33403      }));
33404    });
33405
33406    {
33407      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33408      let _owner = BlockingOwnerGuard::enter(Some(owner));
33409      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
33410        dependency: BlockingDependency {
33411          job: BlockingJobId(u64::MAX - 81),
33412          owner: Some(owner)
33413        },
33414        ready: Arc::clone(&ready),
33415      });
33416      assert_eq!(
33417        executor.block_on(future.as_mut()),
33418        BlockOnOutcome::Completed
33419      );
33420    }
33421    releaser.join().unwrap();
33422
33423    executor.active_blocking.store(0, Ordering::SeqCst);
33424    executor.active_blocking_owners.unregister(owner);
33425    executor.begin_shutdown();
33426    executor.wait_until_scheduler_idle();
33427  }
33428
33429  #[cfg(napi_runtime_os_threads)]
33430  #[test]
33431  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
33432    // Pin the shutdown race after the final verdict gate has closed:
33433    //   1. an accepted task is pending, so shutdown abort must wake it;
33434    //   2. a cooperative block_on reaches its deadline and pauses while
33435    //      holding the closed verdict gate;
33436    //   3. shutdown publishes generation stop, transitions
33437    //      Running -> Stopping, then its abort wake blocks entering scheduler
33438    //      admission behind that gate;
33439    //   4. the gated final verdict must observe stop and return Stopped,
33440    //      reopening the gate so the abort wake and shutdown can finish.
33441    //
33442    // Before stop was published by the controller transition, step 3 blocked
33443    // inside abort before executor shutdown could set it. The driver then saw
33444    // empty queues and emitted a false BlockOnDeadlock.
33445    use std::sync::mpsc;
33446
33447    let controller = Arc::new(RuntimeController::new());
33448    controller
33449      .configure(RuntimeOptions {
33450        flavor: RuntimeFlavor::MultiThread,
33451        worker_threads: 2,
33452        max_blocking_tasks: 1,
33453        thread_name_prefix: "shutdown-gated-verdict".to_string(),
33454        park_deadline: Some(Duration::from_millis(40)),
33455        drain_linger: DEFAULT_DRAIN_LINGER,
33456      })
33457      .expect("the shutdown race test configuration must be accepted");
33458
33459    let backend = controller.backend();
33460    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33461      panic!("the configured backend must be MultiThread");
33462    };
33463    let executor_id = executor.id;
33464    let weak_executor = Arc::downgrade(executor);
33465    drop(backend);
33466
33467    let (task_polled_tx, task_polled_rx) = mpsc::channel();
33468    let task = controller
33469      .try_spawn(async move {
33470        task_polled_tx.send(()).unwrap();
33471        std::future::pending::<()>().await;
33472      })
33473      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33474    task_polled_rx
33475      .recv_timeout(Duration::from_secs(2))
33476      .expect("the accepted task must publish an abortable pending waker");
33477
33478    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33479    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33480    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33481    let driver_controller = Arc::clone(&controller);
33482    let driver = std::thread::spawn(move || {
33483      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33484        *slot.borrow_mut() = Some(Box::new(move || {
33485          gate_closed_tx.send(()).unwrap();
33486          release_verdict_rx.recv().unwrap();
33487        }));
33488      });
33489
33490      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33491      let mut pending = std::pin::pin!(NeverReady);
33492      let result = catch_unwind(AssertUnwindSafe(|| {
33493        driver_controller.block_on(pending.as_mut())
33494      }));
33495      let result = match result {
33496        Ok(outcome) => Ok(outcome),
33497        Err(payload) => {
33498          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33499          discard_panic_payload(payload);
33500          Err(was_deadlock)
33501        }
33502      };
33503      driver_result_tx.send(result).unwrap();
33504    });
33505
33506    gate_closed_rx
33507      .recv_timeout(Duration::from_secs(2))
33508      .expect("the cooperative driver must pause with the verdict gate closed");
33509
33510    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33511    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33512    let shutdown_controller = Arc::clone(&controller);
33513    let shutdown = std::thread::spawn(move || {
33514      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33515        *slot.borrow_mut() = Some(Box::new(move || {
33516          abort_blocked_tx.send(()).unwrap();
33517        }));
33518      });
33519      shutdown_result_tx
33520        .send(shutdown_controller.shutdown())
33521        .unwrap();
33522    });
33523
33524    abort_blocked_rx
33525      .recv_timeout(Duration::from_secs(2))
33526      .expect("the accepted task's abort wake must reach the closed verdict gate");
33527    let lifecycle_is_stopping = {
33528      let state = controller
33529        .state
33530        .lock()
33531        .unwrap_or_else(std::sync::PoisonError::into_inner);
33532      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33533    };
33534    let stop_was_published = weak_executor
33535      .upgrade()
33536      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33537
33538    release_verdict_tx.send(()).unwrap();
33539
33540    let driver_result = driver_result_rx
33541      .recv_timeout(Duration::from_secs(2))
33542      .expect("the gated driver must resolve after observing shutdown");
33543    let shutdown_result = shutdown_result_rx
33544      .recv_timeout(Duration::from_secs(5))
33545      .expect("shutdown must finish after the verdict gate reopens");
33546    // Both threads have sent their results; what remains is teardown (and on
33547    // the shutdown thread, the controller's `wait_for_all_workers` already
33548    // completed inside `shutdown()`). This test was one of the silent wedges
33549    // on the Windows CI runners -- bound the residue loudly.
33550    join_within(
33551      "the gated cooperative driver exits after teardown",
33552      driver,
33553      Duration::from_secs(30),
33554    );
33555    join_within(
33556      "the shutdown thread exits after teardown",
33557      shutdown,
33558      Duration::from_secs(30),
33559    );
33560
33561    assert!(
33562      lifecycle_is_stopping,
33563      "the abort wake must occur after Running -> Stopping"
33564    );
33565    assert!(
33566      stop_was_published,
33567      "generation stop must be visible before shutdown invokes accepted-task aborts"
33568    );
33569    assert_eq!(
33570      driver_result,
33571      Ok(BlockOnOutcome::Stopped),
33572      "shutdown must not be reported as a gated BlockOnDeadlock"
33573    );
33574    shutdown_result.unwrap();
33575    assert!(
33576      futures::executor::block_on(task).is_err(),
33577      "the accepted pending task must resolve as cancelled"
33578    );
33579  }
33580
33581  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33582  #[test]
33583  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33584    // Complement the publication-wins race above. The verdict owns both its
33585    // admission gate and the stop-publication mutex before shutdown starts.
33586    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
33587    // Running -> Stopping until the verdict finishes its synchronized checks.
33588    use std::sync::mpsc;
33589
33590    let controller = Arc::new(RuntimeController::new());
33591    controller
33592      .configure(RuntimeOptions {
33593        flavor: RuntimeFlavor::MultiThread,
33594        worker_threads: 2,
33595        max_blocking_tasks: 1,
33596        thread_name_prefix: "verdict-before-shutdown".to_string(),
33597        park_deadline: Some(Duration::from_millis(40)),
33598        drain_linger: DEFAULT_DRAIN_LINGER,
33599      })
33600      .expect("the verdict-first test configuration must be accepted");
33601
33602    let backend = controller.backend();
33603    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33604      panic!("the configured backend must be MultiThread");
33605    };
33606    let executor_id = executor.id;
33607    drop(backend);
33608
33609    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33610    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33611    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33612    let driver_controller = Arc::clone(&controller);
33613    let driver = std::thread::spawn(move || {
33614      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33615        *slot.borrow_mut() = Some(Box::new(move || {
33616          verdict_locked_tx.send(()).unwrap();
33617          release_verdict_rx.recv().unwrap();
33618        }));
33619      });
33620
33621      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33622      let mut pending = std::pin::pin!(NeverReady);
33623      let result = catch_unwind(AssertUnwindSafe(|| {
33624        driver_controller.block_on(pending.as_mut())
33625      }));
33626      let result = match result {
33627        Ok(outcome) => Ok(outcome),
33628        Err(payload) => {
33629          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33630          discard_panic_payload(payload);
33631          Err(was_deadlock)
33632        }
33633      };
33634      driver_result_tx.send(result).unwrap();
33635    });
33636
33637    verdict_locked_rx
33638      .recv_timeout(Duration::from_secs(2))
33639      .expect("the verdict must hold the synchronized stop-publication guard");
33640
33641    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33642    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33643    let shutdown_controller = Arc::clone(&controller);
33644    let shutdown = std::thread::spawn(move || {
33645      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33646        *slot.borrow_mut() = Some(Box::new(move || {
33647          shutdown_waiting_tx.send(()).unwrap();
33648        }));
33649      });
33650      shutdown_result_tx
33651        .send(shutdown_controller.shutdown())
33652        .unwrap();
33653    });
33654
33655    shutdown_waiting_rx
33656      .recv_timeout(Duration::from_secs(2))
33657      .expect("shutdown must reach the lifecycle-to-publication lock edge");
33658    let early_shutdown = shutdown_result_rx
33659      .recv_timeout(Duration::from_millis(100))
33660      .ok();
33661    let shutdown_was_blocked = early_shutdown.is_none();
33662    release_verdict_tx.send(()).unwrap();
33663
33664    let driver_result = driver_result_rx
33665      .recv_timeout(Duration::from_secs(2))
33666      .expect("the verdict-first driver must finish after release");
33667    let shutdown_result = early_shutdown.unwrap_or_else(|| {
33668      shutdown_result_rx
33669        .recv_timeout(Duration::from_secs(5))
33670        .expect("shutdown must finish after the verdict releases publication")
33671    });
33672    driver.join().unwrap();
33673    shutdown.join().unwrap();
33674
33675    assert!(
33676      shutdown_was_blocked,
33677      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33678    );
33679    assert_eq!(
33680      driver_result,
33681      Err(true),
33682      "a verdict that linearizes before shutdown must retain the typed deadlock"
33683    );
33684    shutdown_result.unwrap();
33685  }
33686
33687  #[cfg(not(target_family = "wasm"))]
33688  #[test]
33689  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33690    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33691
33692    if std::env::var_os(CHILD_ENV).is_some() {
33693      let executor =
33694        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33695      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
33696      // hook only needs to prove that scheduler admission returns.
33697      executor
33698        .active_drainers
33699        .store(executor.max_drainers, Ordering::SeqCst);
33700
33701      let hook_returned = Arc::new(AtomicBool::new(false));
33702      let hook_flag = Arc::clone(&hook_returned);
33703      let hook_executor = Arc::clone(&executor);
33704      let previous_hook = std::panic::take_hook();
33705      std::panic::set_hook(Box::new(move |_| {
33706        let handle = hook_executor.schedule_blocking(|| ());
33707        drop(handle);
33708        hook_flag.store(true, Ordering::SeqCst);
33709      }));
33710
33711      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33712      let result = catch_unwind(AssertUnwindSafe(|| {
33713        let mut pending = std::pin::pin!(std::future::pending::<()>());
33714        executor.block_on(pending.as_mut());
33715      }));
33716      std::panic::set_hook(previous_hook);
33717
33718      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33719      assert!(
33720        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33721        "the deadline must retain its structured diagnostic"
33722      );
33723      assert!(
33724        hook_returned.load(Ordering::SeqCst),
33725        "the panic hook's scheduler submission must return before unwind"
33726      );
33727
33728      executor.active_drainers.store(0, Ordering::SeqCst);
33729      executor.begin_shutdown();
33730      executor.wait_until_scheduler_idle();
33731      return;
33732    }
33733
33734    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33735      .arg("--exact")
33736      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33737      .arg("--nocapture")
33738      .env(CHILD_ENV, "1")
33739      .stdout(std::process::Stdio::piped())
33740      .stderr(std::process::Stdio::piped())
33741      .spawn()
33742      .expect("the panic-hook subprocess must start");
33743    let deadline = Instant::now() + Duration::from_secs(5);
33744    let status = loop {
33745      if let Some(status) = child
33746        .try_wait()
33747        .expect("the subprocess status must be readable")
33748      {
33749        break status;
33750      }
33751      if Instant::now() >= deadline {
33752        let _ = child.kill();
33753        let output = child
33754          .wait_with_output()
33755          .expect("the timed-out subprocess must be reaped");
33756        panic!(
33757          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
33758          String::from_utf8_lossy(&output.stdout),
33759          String::from_utf8_lossy(&output.stderr)
33760        );
33761      }
33762      std::thread::sleep(Duration::from_millis(10));
33763    };
33764    let output = child
33765      .wait_with_output()
33766      .expect("the completed subprocess output must be read");
33767    assert!(
33768      status.success(),
33769      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
33770      status.code(),
33771      String::from_utf8_lossy(&output.stdout),
33772      String::from_utf8_lossy(&output.stderr)
33773    );
33774  }
33775
33776  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
33777  // the embedding host's env-resolution pipeline together with the read
33778  // itself; its tests live there.
33779
33780  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
33781  // The tests cover both deadlock-detection interactions. All tests build
33782  // executors LOCALLY (never the global RUNTIME or the process-global host
33783  // driver) and run hang-prone workloads on a child thread bounded by
33784  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
33785
33786  #[cfg(panic = "unwind")]
33787  #[test]
33788  fn contained_waker_separates_wake_and_drop_panics() {
33789    struct PanicWakeAndDrop;
33790
33791    impl std::task::Wake for PanicWakeAndDrop {
33792      fn wake(self: Arc<Self>) {
33793        panic!("intentional consuming wake panic");
33794      }
33795
33796      fn wake_by_ref(self: &Arc<Self>) {
33797        panic!("intentional borrowed wake panic");
33798      }
33799    }
33800
33801    impl Drop for PanicWakeAndDrop {
33802      fn drop(&mut self) {
33803        panic!("intentional RawWaker destructor panic");
33804      }
33805    }
33806
33807    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
33808  }
33809
33810  struct TestHostileRawWakerState {
33811    clones: AtomicUsize,
33812    drop_panics: AtomicUsize,
33813    panic_on_clone: Option<usize>,
33814  }
33815
33816  struct TestHostileRawWakerToken {
33817    state: Arc<TestHostileRawWakerState>,
33818    panic_on_drop: bool,
33819  }
33820
33821  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
33822    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
33823    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
33824    assert_ne!(
33825      token.state.panic_on_clone,
33826      Some(clone),
33827      "intentional RawWaker clone panic"
33828    );
33829    let clone = Box::new(TestHostileRawWakerToken {
33830      state: Arc::clone(&token.state),
33831      panic_on_drop: true,
33832    });
33833    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
33834  }
33835
33836  unsafe fn wake_test_hostile_waker(data: *const ()) {
33837    unsafe { drop_test_hostile_waker(data) };
33838  }
33839
33840  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
33841
33842  unsafe fn drop_test_hostile_waker(data: *const ()) {
33843    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
33844    if token.panic_on_drop {
33845      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
33846      panic!("intentional cloned RawWaker destructor panic");
33847    }
33848  }
33849
33850  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
33851    clone_test_hostile_waker,
33852    wake_test_hostile_waker,
33853    wake_test_hostile_waker_by_ref,
33854    drop_test_hostile_waker,
33855  );
33856
33857  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
33858    let token = Box::new(TestHostileRawWakerToken {
33859      state,
33860      panic_on_drop: false,
33861    });
33862    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
33863    unsafe { Waker::from_raw(raw) }
33864  }
33865
33866  /// Waker that only counts. For polling a raw `Sleep` without an executor.
33867  struct CountingWake(AtomicUsize);
33868  impl std::task::Wake for CountingWake {
33869    fn wake(self: Arc<Self>) {
33870      self.0.fetch_add(1, Ordering::SeqCst);
33871    }
33872  }
33873
33874  /// One armed stub timer: the latest waker plus a cancellation flag.
33875  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
33876
33877  /// Test-local host driver: fires each registered timer from a helper OS
33878  /// thread at its deadline (re-registration under the same id refreshes the
33879  /// waker without arming a second thread, per the `TimerDriver` contract).
33880  struct StubHostTimerDriver {
33881    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
33882  }
33883
33884  impl StubHostTimerDriver {
33885    fn new() -> Self {
33886      Self {
33887        timers: Mutex::new(FxHashMap::default()),
33888      }
33889    }
33890  }
33891
33892  impl TimerDriver for StubHostTimerDriver {
33893    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33894      let mut timers = self.timers.lock().unwrap();
33895      if let Some(cell) = timers.get(&id) {
33896        *cell.0.lock().unwrap() = waker;
33897        return;
33898      }
33899      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
33900      timers.insert(id, Arc::clone(&cell));
33901      std::thread::spawn(move || {
33902        let now = Instant::now();
33903        if deadline > now {
33904          std::thread::sleep(deadline - now);
33905        }
33906        if !cell.1.load(Ordering::SeqCst) {
33907          cell.0.lock().unwrap().wake_by_ref();
33908        }
33909      });
33910    }
33911
33912    fn cancel(&self, id: TimerId) {
33913      let removed = self.timers.lock().unwrap().remove(&id);
33914      if let Some(cell) = removed {
33915        cell.1.store(true, Ordering::SeqCst);
33916      }
33917    }
33918  }
33919
33920  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
33921  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
33922  /// per timer id), never fires on its own, and reports the controllable
33923  /// `live` flag (a killed stub plays a driver whose owning host context --
33924  /// napi env in the binding -- was torn down).
33925  struct ManualStubDriver {
33926    live: AtomicBool,
33927    registers: Mutex<Vec<(TimerId, Instant)>>,
33928    cancels: Mutex<Vec<TimerId>>,
33929    pending: Mutex<FxHashMap<TimerId, Waker>>,
33930    swept: AtomicBool,
33931  }
33932
33933  impl ManualStubDriver {
33934    fn new() -> Arc<Self> {
33935      Arc::new(Self {
33936        live: AtomicBool::new(true),
33937        registers: Mutex::new(Vec::new()),
33938        cancels: Mutex::new(Vec::new()),
33939        pending: Mutex::new(FxHashMap::default()),
33940        swept: AtomicBool::new(false),
33941      })
33942    }
33943
33944    fn kill(&self) {
33945      self.live.store(false, Ordering::SeqCst);
33946    }
33947  }
33948
33949  impl TimerDriver for ManualStubDriver {
33950    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33951      self.registers.lock().unwrap().push((id, deadline));
33952      self.pending.lock().unwrap().insert(id, waker);
33953    }
33954
33955    fn cancel(&self, id: TimerId) {
33956      self.cancels.lock().unwrap().push(id);
33957      self.pending.lock().unwrap().remove(&id);
33958    }
33959
33960    fn is_live(&self) -> bool {
33961      self.live.load(Ordering::SeqCst)
33962    }
33963
33964    fn on_swept(&self) {
33965      // The binding shape: drain the pending map and wake everything armed
33966      // here so those sleeps re-poll onto the next live registrant.
33967      self.swept.store(true, Ordering::SeqCst);
33968      let wakers: Vec<Waker> = self
33969        .pending
33970        .lock()
33971        .unwrap()
33972        .drain()
33973        .map(|(_, w)| w)
33974        .collect();
33975      for waker in wakers {
33976        waker.wake();
33977      }
33978    }
33979  }
33980
33981  struct PanickingTimerDriver {
33982    swept: AtomicBool,
33983  }
33984
33985  impl TimerDriver for PanickingTimerDriver {
33986    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33987
33988    fn cancel(&self, _id: TimerId) {}
33989
33990    fn is_live(&self) -> bool {
33991      panic!("intentional timer-driver liveness panic");
33992    }
33993
33994    fn on_swept(&self) {
33995      self.swept.store(true, Ordering::SeqCst);
33996      panic!("intentional timer-driver sweep panic");
33997    }
33998  }
33999
34000  struct PanickingRegisterTimerDriver {
34001    registers: AtomicUsize,
34002    cancels: Mutex<Vec<TimerId>>,
34003    pending: Mutex<FxHashMap<TimerId, Waker>>,
34004  }
34005
34006  impl PanickingRegisterTimerDriver {
34007    fn new() -> Arc<Self> {
34008      Arc::new(Self {
34009        registers: AtomicUsize::new(0),
34010        cancels: Mutex::new(Vec::new()),
34011        pending: Mutex::new(FxHashMap::default()),
34012      })
34013    }
34014  }
34015
34016  impl TimerDriver for PanickingRegisterTimerDriver {
34017    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
34018      self.registers.fetch_add(1, Ordering::SeqCst);
34019      self.pending.lock().unwrap().insert(id, waker);
34020      panic!("intentional timer-driver register panic after arming");
34021    }
34022
34023    fn cancel(&self, id: TimerId) {
34024      self.cancels.lock().unwrap().push(id);
34025      self.pending.lock().unwrap().remove(&id);
34026    }
34027  }
34028
34029  struct PanicBeforeRetainingTimerDriver {
34030    registers: AtomicUsize,
34031  }
34032
34033  impl TimerDriver for PanicBeforeRetainingTimerDriver {
34034    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34035      self.registers.fetch_add(1, Ordering::SeqCst);
34036      panic!("intentional timer-driver register panic before retaining its waker");
34037    }
34038
34039    fn cancel(&self, _id: TimerId) {}
34040  }
34041
34042  struct PanickingCancelDropTimerDriver {
34043    cancels: Arc<AtomicUsize>,
34044    drops: Arc<AtomicUsize>,
34045  }
34046
34047  impl TimerDriver for PanickingCancelDropTimerDriver {
34048    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34049
34050    fn cancel(&self, _id: TimerId) {
34051      self.cancels.fetch_add(1, Ordering::SeqCst);
34052      panic!("intentional timer-driver cancel panic");
34053    }
34054  }
34055
34056  impl Drop for PanickingCancelDropTimerDriver {
34057    fn drop(&mut self) {
34058      self.drops.fetch_add(1, Ordering::SeqCst);
34059      panic!("intentional timer-driver destructor panic");
34060    }
34061  }
34062
34063  struct UnregisterLaterThenPanicTimerDriver {
34064    registry: Weak<TimerDriverRegistry>,
34065    later: Mutex<Option<TimerDriverId>>,
34066    registers: AtomicUsize,
34067  }
34068
34069  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
34070    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
34071      self.registers.fetch_add(1, Ordering::SeqCst);
34072      let later = *self.later.lock().unwrap();
34073      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
34074        registry.unregister(later);
34075      }
34076      panic!("intentional earlier timer-driver panic after unregistering a later driver");
34077    }
34078
34079    fn cancel(&self, _id: TimerId) {}
34080  }
34081
34082  struct ReentrantLivenessDriver {
34083    registry: std::sync::Weak<TimerDriverRegistry>,
34084    registration: Mutex<Option<TimerDriverId>>,
34085  }
34086
34087  impl TimerDriver for ReentrantLivenessDriver {
34088    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34089
34090    fn cancel(&self, _id: TimerId) {}
34091
34092    fn is_live(&self) -> bool {
34093      let registration = *self.registration.lock().unwrap();
34094      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
34095        registry.unregister(id);
34096      }
34097      false
34098    }
34099  }
34100
34101  struct ReentrantMultiUnregisterTimerDriver {
34102    registry: std::sync::Weak<TimerDriverRegistry>,
34103    registrations: Mutex<Vec<TimerDriverId>>,
34104  }
34105
34106  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
34107    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34108
34109    fn cancel(&self, _id: TimerId) {}
34110
34111    fn is_live(&self) -> bool {
34112      if let Some(registry) = self.registry.upgrade() {
34113        for registration in self.registrations.lock().unwrap().iter().copied() {
34114          registry.unregister(registration);
34115        }
34116      }
34117      false
34118    }
34119  }
34120
34121  struct ReentrantSweepTimerDriver {
34122    registry: std::sync::Weak<TimerDriverRegistry>,
34123    registration: Mutex<Option<TimerDriverId>>,
34124    sweeps: AtomicUsize,
34125    removed_before_sweep: AtomicBool,
34126  }
34127
34128  impl TimerDriver for ReentrantSweepTimerDriver {
34129    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34130
34131    fn cancel(&self, _id: TimerId) {}
34132
34133    fn is_live(&self) -> bool {
34134      false
34135    }
34136
34137    fn on_swept(&self) {
34138      self.sweeps.fetch_add(1, Ordering::SeqCst);
34139      let registration = self.registration.lock().unwrap().take();
34140      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
34141        let state = registry
34142          .state
34143          .try_lock()
34144          .expect("the sweep callback must run outside the timer-driver registry lock");
34145        assert!(
34146          !state.entries.contains_key(&registration),
34147          "the dead driver must be unlinked before its sweep callback runs"
34148        );
34149        self.removed_before_sweep.store(true, Ordering::SeqCst);
34150        drop(state);
34151        // Mirrors JsTimerHostInner::evict: the sweep already removed this
34152        // registration, so explicit host cleanup must remain a cheap no-op.
34153        registry.unregister(registration);
34154      }
34155    }
34156  }
34157
34158  struct ReentrantDropDriver {
34159    registry: std::sync::Weak<TimerDriverRegistry>,
34160    fallback: TimerDriverId,
34161  }
34162
34163  impl TimerDriver for ReentrantDropDriver {
34164    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
34165
34166    fn cancel(&self, _id: TimerId) {}
34167  }
34168
34169  impl Drop for ReentrantDropDriver {
34170    fn drop(&mut self) {
34171      if let Some(registry) = self.registry.upgrade() {
34172        registry.unregister(self.fallback);
34173      }
34174    }
34175  }
34176
34177  /// Waker whose `wake` just latches a flag -- observable evidence that a
34178  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
34179  #[derive(Default)]
34180  struct WakeFlag(AtomicBool);
34181
34182  impl std::task::Wake for WakeFlag {
34183    fn wake(self: Arc<Self>) {
34184      self.0.store(true, Ordering::SeqCst);
34185    }
34186  }
34187
34188  #[cfg(napi_runtime_os_threads)]
34189  fn multi_thread_executor(
34190    workers: usize,
34191    park_deadline: Option<Duration>,
34192    prefix: &str,
34193  ) -> Arc<MultiThreadExecutor> {
34194    let options = RuntimeOptions {
34195      flavor: RuntimeFlavor::MultiThread,
34196      worker_threads: workers,
34197      max_blocking_tasks: workers,
34198      thread_name_prefix: prefix.to_string(),
34199      park_deadline,
34200      drain_linger: DEFAULT_DRAIN_LINGER,
34201    };
34202    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
34203  }
34204
34205  /// Local driver registry holding exactly `driver` (the common single-host
34206  /// shape of the ported pre-registry tests).
34207  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
34208    let registry = Arc::new(TimerDriverRegistry::default());
34209    registry.register(driver);
34210    registry
34211  }
34212
34213  #[cfg(napi_runtime_os_threads)]
34214  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
34215    // The MultiThread arm never touches the driver registry; empty is fine.
34216    make_sleep(
34217      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
34218      &Arc::new(TimerDriverRegistry::default()),
34219      deadline,
34220    )
34221  }
34222
34223  #[cfg(napi_runtime_os_threads)]
34224  #[test]
34225  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
34226    use std::sync::mpsc;
34227
34228    let executor = multi_thread_executor(
34229      1,
34230      Some(Duration::from_secs(1)),
34231      "timer-registration-admission",
34232    );
34233    let (entered_tx, entered_rx) = mpsc::channel();
34234    let (release_tx, release_rx) = mpsc::channel();
34235    let registration_executor = Arc::clone(&executor);
34236    let registration = std::thread::spawn(move || {
34237      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
34238        *slot.borrow_mut() = Some(Box::new(move || {
34239          entered_tx.send(()).unwrap();
34240          release_rx.recv().unwrap();
34241        }));
34242      });
34243      registration_executor.register_timer(
34244        1,
34245        Instant::now() + Duration::from_secs(60),
34246        futures::task::noop_waker(),
34247        &Arc::new(AtomicBool::new(false)),
34248      );
34249    });
34250
34251    entered_rx
34252      .recv_timeout(Duration::from_secs(2))
34253      .expect("timer registration must pause after entering verdict admission");
34254    let verdict = executor.try_begin_deadlock_verdict();
34255    let admission_blocked_verdict = verdict.is_none();
34256    drop(verdict);
34257    release_tx.send(()).unwrap();
34258    registration.join().unwrap();
34259
34260    assert!(
34261      admission_blocked_verdict,
34262      "a timer registration must remain announced until its heap entry is visible"
34263    );
34264    assert!(
34265      executor.next_timer_deadline().is_some(),
34266      "the admitted registration must publish its timer after release"
34267    );
34268    executor.begin_shutdown();
34269    executor.wait_until_scheduler_idle();
34270  }
34271
34272  #[cfg(napi_runtime_os_threads)]
34273  #[derive(Clone, Copy, Debug)]
34274  enum TimerFireAdmissionPath {
34275    Timekeeper,
34276    Cooperative,
34277    Shutdown,
34278  }
34279
34280  #[cfg(napi_runtime_os_threads)]
34281  struct ScheduleAfterTimerWakeRelease {
34282    executor: Arc<MultiThreadExecutor>,
34283    runnable: Mutex<Option<Runnable>>,
34284    entered: std::sync::mpsc::Sender<()>,
34285    release: Mutex<std::sync::mpsc::Receiver<()>>,
34286    scheduled: std::sync::mpsc::Sender<()>,
34287  }
34288
34289  #[cfg(napi_runtime_os_threads)]
34290  impl ScheduleAfterTimerWakeRelease {
34291    fn publish(&self) {
34292      self.entered.send(()).unwrap();
34293      self
34294        .release
34295        .lock()
34296        .unwrap_or_else(std::sync::PoisonError::into_inner)
34297        .recv()
34298        .unwrap();
34299      let runnable = self
34300        .runnable
34301        .lock()
34302        .unwrap_or_else(std::sync::PoisonError::into_inner)
34303        .take();
34304      if let Some(runnable) = runnable {
34305        self.executor.schedule(runnable);
34306      }
34307      self.scheduled.send(()).unwrap();
34308    }
34309  }
34310
34311  #[cfg(napi_runtime_os_threads)]
34312  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
34313    fn wake(self: Arc<Self>) {
34314      self.publish();
34315    }
34316
34317    fn wake_by_ref(self: &Arc<Self>) {
34318      self.publish();
34319    }
34320  }
34321
34322  #[cfg(napi_runtime_os_threads)]
34323  fn insert_timer_for_fire_admission_test(
34324    executor: &MultiThreadExecutor,
34325    id: TimerId,
34326    deadline: Instant,
34327    waker: Waker,
34328    fired: Arc<AtomicBool>,
34329  ) {
34330    let mut inner = executor
34331      .timers
34332      .inner
34333      .lock()
34334      .unwrap_or_else(std::sync::PoisonError::into_inner);
34335    inner.entries.insert(id, HeapTimerEntry { waker, fired });
34336    inner.queue.push(deadline, id);
34337  }
34338
34339  #[cfg(napi_runtime_os_threads)]
34340  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
34341    use std::sync::mpsc;
34342
34343    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
34344    let (runnable, task) = async_task::spawn(async {}, |_| {});
34345    task.detach();
34346    let (entered_tx, entered_rx) = mpsc::channel();
34347    let (release_tx, release_rx) = mpsc::channel();
34348    let (scheduled_tx, scheduled_rx) = mpsc::channel();
34349    let fired = Arc::new(AtomicBool::new(false));
34350    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
34351      executor: Arc::clone(&executor),
34352      runnable: Mutex::new(Some(runnable)),
34353      entered: entered_tx,
34354      release: Mutex::new(release_rx),
34355      scheduled: scheduled_tx,
34356    }));
34357
34358    let fire_thread = match path {
34359      TimerFireAdmissionPath::Timekeeper => {
34360        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
34361        None
34362      }
34363      TimerFireAdmissionPath::Cooperative => {
34364        insert_timer_for_fire_admission_test(
34365          &executor,
34366          1,
34367          Instant::now(),
34368          waker,
34369          Arc::clone(&fired),
34370        );
34371        let fire_executor = Arc::clone(&executor);
34372        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
34373      }
34374      TimerFireAdmissionPath::Shutdown => {
34375        insert_timer_for_fire_admission_test(
34376          &executor,
34377          1,
34378          Instant::now() + Duration::from_secs(60),
34379          waker,
34380          Arc::clone(&fired),
34381        );
34382        let fire_executor = Arc::clone(&executor);
34383        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
34384      }
34385    };
34386
34387    // Hang detector on a guaranteed event that needs a freshly spawned
34388    // thread (timekeeper / fire thread) to get CPU -- generous for starved
34389    // CI runners.
34390    entered_rx
34391      .recv_timeout(Duration::from_secs(10))
34392      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
34393    assert!(
34394      executor.next_timer_deadline().is_none(),
34395      "{path:?} must exercise the remove-before-wake window"
34396    );
34397    assert!(
34398      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
34399      "{path:?} must retain admission while its waker is publishing"
34400    );
34401    let verdict = executor.try_begin_deadlock_verdict();
34402    let admission_blocked_verdict = verdict.is_none();
34403    drop(verdict);
34404
34405    release_tx.send(()).unwrap();
34406    scheduled_rx
34407      .recv_timeout(Duration::from_secs(10))
34408      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
34409    if let Some(fire_thread) = fire_thread {
34410      fire_thread.join().unwrap();
34411    }
34412    assert!(
34413      admission_blocked_verdict,
34414      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
34415    );
34416
34417    executor.begin_shutdown();
34418    executor.wait_until_scheduler_idle();
34419  }
34420
34421  #[cfg(napi_runtime_os_threads)]
34422  #[test]
34423  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
34424    for path in [
34425      TimerFireAdmissionPath::Timekeeper,
34426      TimerFireAdmissionPath::Cooperative,
34427      TimerFireAdmissionPath::Shutdown,
34428    ] {
34429      assert_timer_fire_path_holds_deadlock_admission(path);
34430    }
34431  }
34432
34433  #[cfg(napi_runtime_os_threads)]
34434  #[test]
34435  fn multi_thread_sleep_fires_from_foreign_block_on() {
34436    // The napi-caller shape: a NON-pool thread parks in
34437    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
34438    // the executor, so ONLY the dedicated timekeeper can fire the timer --
34439    // this is the timekeeper's existence test.
34440    use std::sync::mpsc;
34441
34442    let (done_tx, done_rx) = mpsc::channel();
34443    let runner = std::thread::spawn(move || {
34444      let executor = multi_thread_executor(2, None, "timer-foreign");
34445      let started = Instant::now();
34446      let mut future = std::pin::pin!(async {
34447        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
34448      });
34449      executor.block_on(future.as_mut());
34450      assert!(
34451        started.elapsed() >= Duration::from_millis(80),
34452        "the sleep must not resolve before its deadline"
34453      );
34454      let _ = done_tx.send(());
34455    });
34456
34457    match done_rx.recv_timeout(Duration::from_secs(10)) {
34458      Ok(()) => runner.join().unwrap(),
34459      Err(error) => {
34460        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
34461      }
34462    }
34463  }
34464
34465  #[cfg(napi_runtime_os_threads)]
34466  #[test]
34467  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
34468    // A long timer waits on the dedicated timer thread, leaving the sole
34469    // low-level test worker available for ordinary scheduler work.
34470    use std::sync::mpsc;
34471
34472    let (done_tx, done_rx) = mpsc::channel();
34473    let runner = std::thread::spawn(move || {
34474      let executor = multi_thread_executor(1, None, "timer-drainer");
34475
34476      // Arm a long timer through a real spawned task so the sole worker
34477      // returns to rayon afterwards (the watch-idle Debouncing shape).
34478      let sleep_exec = Arc::clone(&executor);
34479      let sleep_done = Arc::new(AtomicBool::new(false));
34480      let sleep_flag = Arc::clone(&sleep_done);
34481      let sched = Arc::clone(&executor);
34482      let (sleep_runnable, sleep_task) = async_task::spawn(
34483        async move {
34484          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34485          sleep_flag.store(true, Ordering::SeqCst);
34486        },
34487        move |r| sched.schedule(r),
34488      );
34489      executor.schedule(sleep_runnable);
34490      wait_until("the timekeeper parked on the 60s deadline", || {
34491        executor
34492          .timers
34493          .inner
34494          .lock()
34495          .unwrap()
34496          .timekeeper_parker
34497          .is_some()
34498      });
34499
34500      // Queued work must run NOW, not in 60s.
34501      let counter = Arc::new(AtomicUsize::new(0));
34502      let mut tasks = Vec::new();
34503      for _ in 0..32 {
34504        let counter = Arc::clone(&counter);
34505        let sched = Arc::clone(&executor);
34506        let (runnable, task) = async_task::spawn(
34507          async move {
34508            counter.fetch_add(1, Ordering::SeqCst);
34509          },
34510          move |r| sched.schedule(r),
34511        );
34512        executor.schedule(runnable);
34513        tasks.push(task);
34514      }
34515      for task in tasks {
34516        futures::executor::block_on(task);
34517      }
34518      assert_eq!(counter.load(Ordering::SeqCst), 32);
34519      assert!(
34520        !sleep_done.load(Ordering::SeqCst),
34521        "the 60s timer must still be pending: the work ran DURING the timer wait"
34522      );
34523      // Cancel the long sleep so the executor tears down promptly.
34524      executor.shutdown_timers();
34525      futures::executor::block_on(sleep_task);
34526      let _ = done_tx.send(());
34527    });
34528
34529    match done_rx.recv_timeout(Duration::from_secs(15)) {
34530      Ok(()) => runner.join().unwrap(),
34531      Err(error) => {
34532        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34533      }
34534    }
34535  }
34536
34537  #[cfg(napi_runtime_os_threads)]
34538  #[test]
34539  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34540    let executor = multi_thread_executor(2, None, "timer-lazy-start");
34541    assert!(
34542      executor.timers.thread.lock().unwrap().is_none(),
34543      "constructing a runtime without timers must not create an extra OS thread"
34544    );
34545
34546    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34547    assert!(
34548      executor.timers.thread.lock().unwrap().is_none(),
34549      "an unpolled sleep has not registered a deadline"
34550    );
34551    let waker = futures::task::noop_waker();
34552    let mut cx = Context::from_waker(&waker);
34553    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34554    assert!(
34555      executor.timers.thread.lock().unwrap().is_some(),
34556      "the first timer registration must start the dedicated service"
34557    );
34558  }
34559
34560  #[cfg(napi_runtime_os_threads)]
34561  #[test]
34562  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34563    use std::sync::mpsc;
34564
34565    let executor = multi_thread_executor(1, None, "timer-closed-start");
34566    executor.shutdown_timers();
34567    let handle_guard = executor.timers.thread.lock().unwrap();
34568    let ensure_executor = Arc::clone(&executor);
34569    let (completed_tx, completed_rx) = mpsc::channel();
34570    let ensure = std::thread::spawn(move || {
34571      ensure_executor.timers.ensure_started().unwrap();
34572      completed_tx.send(()).unwrap();
34573    });
34574
34575    // Hang detector on a freshly spawned thread getting CPU -- generous for
34576    // starved CI runners (a real regression blocks forever on the held lock).
34577    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34578      "ensure_started consulted or joined the timer handle before observing the closed heap",
34579    );
34580    drop(handle_guard);
34581    ensure.join().unwrap();
34582  }
34583
34584  #[cfg(napi_runtime_os_threads)]
34585  #[test]
34586  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34587    const TIMERS: u64 = 4096;
34588
34589    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34590    let base = Instant::now() + Duration::from_secs(3600);
34591    for id in 0..TIMERS {
34592      executor.register_timer(
34593        id,
34594        base + Duration::from_secs(id),
34595        futures::task::noop_waker(),
34596        &Arc::new(AtomicBool::new(false)),
34597      );
34598    }
34599    {
34600      let inner = executor.timers.inner.lock().unwrap();
34601      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34602      assert_eq!(inner.queue.len(), inner.entries.len());
34603    }
34604
34605    // Preserve the earliest node while removing every later node. A lazy
34606    // BinaryHeap would retain all 4095 tombstones until that first deadline.
34607    for id in 1..TIMERS {
34608      executor.cancel_timer(id);
34609    }
34610    {
34611      let inner = executor.timers.inner.lock().unwrap();
34612      assert_eq!(inner.entries.len(), 1);
34613      assert_eq!(inner.queue.len(), 1);
34614      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34615    }
34616
34617    executor.cancel_timer(0);
34618    {
34619      let inner = executor.timers.inner.lock().unwrap();
34620      assert!(inner.entries.is_empty());
34621      assert!(inner.queue.is_empty());
34622    }
34623    executor.shutdown_timers();
34624    executor.timers.join();
34625  }
34626
34627  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34628  #[test]
34629  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34630    let executor = multi_thread_executor(2, None, "timer-start-failure");
34631    executor
34632      .timers
34633      .fail_next_start
34634      .store(true, Ordering::SeqCst);
34635
34636    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34637    let waker = Waker::from(Arc::clone(&wake_count));
34638    let baseline_waker_refs = Arc::strong_count(&wake_count);
34639    let mut cx = Context::from_waker(&waker);
34640    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34641
34642    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34643    assert!(
34644      failure.is_err(),
34645      "the injected timer-thread start failure must panic the poll"
34646    );
34647    {
34648      let inner = executor.timers.inner.lock().unwrap();
34649      assert!(
34650        inner.entries.is_empty(),
34651        "a failed start must not retain a timer entry"
34652      );
34653      assert!(
34654        inner.queue.is_empty(),
34655        "a failed start must not retain a stale heap node"
34656      );
34657      assert!(
34658        inner.timekeeper_parker.is_none(),
34659        "a failed start must not publish a timekeeper parker"
34660      );
34661    }
34662    assert!(
34663      executor.timers.thread.lock().unwrap().is_none(),
34664      "a failed start must not retain a thread handle"
34665    );
34666    assert_eq!(
34667      Arc::strong_count(&wake_count),
34668      baseline_waker_refs,
34669      "a failed start must release the cloned timer waker"
34670    );
34671
34672    assert!(
34673      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34674      "the same Sleep must be safely retryable after a transient start failure"
34675    );
34676    {
34677      let inner = executor.timers.inner.lock().unwrap();
34678      assert_eq!(
34679        inner.entries.len(),
34680        1,
34681        "the retry must publish exactly one timer entry"
34682      );
34683      assert_eq!(
34684        inner.queue.len(),
34685        1,
34686        "the retry must publish exactly one heap node"
34687      );
34688    }
34689    assert!(
34690      executor.timers.thread.lock().unwrap().is_some(),
34691      "the retry must start the dedicated timekeeper"
34692    );
34693
34694    drop(sleep);
34695    assert!(
34696      executor.timers.inner.lock().unwrap().entries.is_empty(),
34697      "dropping the retried Sleep must cancel its registration"
34698    );
34699    executor.shutdown_timers();
34700    executor.wait_until_scheduler_idle();
34701    assert!(
34702      executor.timers.thread.lock().unwrap().is_none(),
34703      "shutdown after a recovered start failure must join the timekeeper"
34704    );
34705  }
34706
34707  #[cfg(napi_runtime_os_threads)]
34708  #[test]
34709  fn pending_timer_preserves_every_configured_rayon_worker() {
34710    use std::sync::mpsc;
34711
34712    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34713    let waker = futures::task::noop_waker();
34714    let mut cx = Context::from_waker(&waker);
34715    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34716    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34717    wait_until("the dedicated timer thread to park", || {
34718      executor
34719        .timers
34720        .inner
34721        .lock()
34722        .unwrap()
34723        .timekeeper_parker
34724        .is_some()
34725    });
34726
34727    let released = Arc::new((Mutex::new(false), Condvar::new()));
34728    let (started_tx, started_rx) = mpsc::channel();
34729    let (completed_tx, completed_rx) = mpsc::channel();
34730    for _ in 0..2 {
34731      let released = Arc::clone(&released);
34732      let started_tx = started_tx.clone();
34733      let completed_tx = completed_tx.clone();
34734      executor.pool.spawn(move || {
34735        started_tx.send(()).unwrap();
34736        let (lock, changed) = &*released;
34737        let mut released = lock
34738          .lock()
34739          .unwrap_or_else(std::sync::PoisonError::into_inner);
34740        while !*released {
34741          released = changed
34742            .wait(released)
34743            .unwrap_or_else(std::sync::PoisonError::into_inner);
34744        }
34745        completed_tx.send(()).unwrap();
34746      });
34747    }
34748    drop(started_tx);
34749    drop(completed_tx);
34750
34751    let first = started_rx.recv_timeout(Duration::from_secs(2));
34752    let second = started_rx.recv_timeout(Duration::from_secs(2));
34753    {
34754      let (lock, changed) = &*released;
34755      *lock
34756        .lock()
34757        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
34758      changed.notify_all();
34759    }
34760    drop(sleep);
34761    for _ in 0..2 {
34762      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34763    }
34764
34765    assert!(
34766      first.is_ok(),
34767      "the first configured Rayon worker did not start"
34768    );
34769    assert!(
34770      second.is_ok(),
34771      "a pending timer consumed one of the two configured Rayon workers"
34772    );
34773  }
34774
34775  #[cfg(napi_runtime_os_threads)]
34776  #[test]
34777  fn timekeeper_never_enters_a_blocking_closure() {
34778    use std::{
34779      sync::{Barrier, mpsc},
34780      time::Duration,
34781    };
34782
34783    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
34784    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34785    let waker = Waker::from(Arc::clone(&wake_count));
34786    let mut cx = Context::from_waker(&waker);
34787    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
34788    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34789    wait_until(
34790      "the timekeeper parked before blocking work was submitted",
34791      || {
34792        executor
34793          .timers
34794          .inner
34795          .lock()
34796          .unwrap()
34797          .timekeeper_parker
34798          .is_some()
34799      },
34800    );
34801
34802    let release = Arc::new(Barrier::new(2));
34803    let blocker_release = Arc::clone(&release);
34804    let (started_tx, started_rx) = mpsc::sync_channel(1);
34805    let blocking = executor.schedule_blocking(move || {
34806      started_tx.send(()).unwrap();
34807      blocker_release.wait();
34808    });
34809    // Hang detector (the closure start needs a worker to get CPU) --
34810    // generous for starved CI runners.
34811    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
34812
34813    wait_until(
34814      "the timer waker to fire while the Rayon worker is blocked",
34815      || wake_count.0.load(Ordering::SeqCst) != 0,
34816    );
34817    release.wait();
34818    futures::executor::block_on(blocking).unwrap();
34819    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34820  }
34821
34822  #[cfg(napi_runtime_os_threads)]
34823  #[test]
34824  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
34825    use std::sync::mpsc;
34826
34827    struct PendingEntered(Option<mpsc::Sender<()>>);
34828
34829    impl Future for PendingEntered {
34830      type Output = ();
34831
34832      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
34833        if let Some(entered) = self.0.take() {
34834          entered.send(()).unwrap();
34835        }
34836        Poll::Pending
34837      }
34838    }
34839
34840    let options = RuntimeOptions {
34841      flavor: RuntimeFlavor::MultiThread,
34842      worker_threads: 2,
34843      max_blocking_tasks: 1,
34844      thread_name_prefix: "nested-timer-wake".to_string(),
34845      park_deadline: None,
34846      drain_linger: DEFAULT_DRAIN_LINGER,
34847    };
34848    let executor =
34849      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
34850
34851    let timer_exec = Arc::clone(&executor);
34852    let (timer_tx, timer_rx) = mpsc::channel();
34853    let timer_scheduler = Arc::clone(&executor);
34854    let (timer_runnable, timer_task) = async_task::spawn(
34855      async move {
34856        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
34857        timer_tx.send(()).unwrap();
34858      },
34859      move |runnable| timer_scheduler.schedule(runnable),
34860    );
34861    executor.schedule(timer_runnable);
34862    wait_until(
34863      "the dedicated timekeeper parked before nested block_on",
34864      || {
34865        executor
34866          .timers
34867          .inner
34868          .lock()
34869          .unwrap()
34870          .timekeeper_parker
34871          .is_some()
34872      },
34873    );
34874
34875    let (entered_tx, entered_rx) = mpsc::channel();
34876    let nested_exec = Arc::clone(&executor);
34877    let nested_scheduler = Arc::clone(&executor);
34878    let (nested_runnable, nested_task) = async_task::spawn(
34879      async move {
34880        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
34881        let outcome = nested_exec.block_on(pending.as_mut());
34882        drop(pending);
34883        assert_eq!(
34884          outcome,
34885          BlockOnOutcome::Stopped,
34886          "shutdown must stop the raw nested block_on driver"
34887        );
34888      },
34889      move |runnable| nested_scheduler.schedule(runnable),
34890    );
34891    executor.schedule(nested_runnable);
34892    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34893
34894    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
34895    let (release_tx, release_rx) = mpsc::channel();
34896    let blocking = executor.schedule_blocking(move || {
34897      blocking_started_tx.send(()).unwrap();
34898      release_rx.recv().unwrap();
34899    });
34900    blocking_started_rx
34901      .recv_timeout(Duration::from_secs(2))
34902      .expect("a general worker must service blocking work");
34903    timer_rx
34904      .recv_timeout(Duration::from_secs(2))
34905      .expect("the timer wake must reach the nested cooperative driver");
34906
34907    release_tx.send(()).unwrap();
34908    futures::executor::block_on(blocking).unwrap();
34909    executor.begin_shutdown();
34910    assert_eq!(
34911      futures::executor::block_on(nested_task.fallible()),
34912      Some(())
34913    );
34914    futures::executor::block_on(timer_task);
34915  }
34916
34917  #[cfg(napi_runtime_os_threads)]
34918  #[test]
34919  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
34920    // Task requirement: a NEW earliest deadline (heap peek changes) must
34921    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
34922    // timekeeper far in the future; the 60ms timer must still fire on time.
34923    use std::sync::mpsc;
34924
34925    let (done_tx, done_rx) = mpsc::channel();
34926    let runner = std::thread::spawn(move || {
34927      let executor = multi_thread_executor(2, None, "timer-rearm");
34928
34929      let long_exec = Arc::clone(&executor);
34930      let long_done = Arc::new(AtomicBool::new(false));
34931      let long_flag = Arc::clone(&long_done);
34932      let sched = Arc::clone(&executor);
34933      let (long_runnable, long_task) = async_task::spawn(
34934        async move {
34935          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
34936          long_flag.store(true, Ordering::SeqCst);
34937        },
34938        move |r| sched.schedule(r),
34939      );
34940      executor.schedule(long_runnable);
34941      wait_until("the timekeeper parked on the 60s sentinel", || {
34942        executor
34943          .timers
34944          .inner
34945          .lock()
34946          .unwrap()
34947          .timekeeper_parker
34948          .is_some()
34949      });
34950
34951      let started = Instant::now();
34952      let mut future = std::pin::pin!(async {
34953        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
34954      });
34955      executor.block_on(future.as_mut());
34956      let elapsed = started.elapsed();
34957      assert!(
34958        elapsed >= Duration::from_millis(60),
34959        "fired before its deadline: {elapsed:?}"
34960      );
34961      assert!(
34962        !long_done.load(Ordering::SeqCst),
34963        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
34964         timekeeper, not by the sentinel expiring"
34965      );
34966      executor.shutdown_timers();
34967      futures::executor::block_on(long_task);
34968      let _ = done_tx.send(());
34969    });
34970
34971    match done_rx.recv_timeout(Duration::from_secs(15)) {
34972      Ok(()) => runner.join().unwrap(),
34973      Err(error) => panic!(
34974        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
34975      ),
34976    }
34977  }
34978
34979  #[cfg(napi_runtime_os_threads)]
34980  #[test]
34981  fn multi_thread_sleep_cancels_registration_on_drop() {
34982    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
34983    // its heap registration (no waker fired later, heap drains empty).
34984    let executor = multi_thread_executor(1, None, "timer-cancel");
34985    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34986    let waker = Waker::from(Arc::clone(&wake_count));
34987    let mut cx = Context::from_waker(&waker);
34988
34989    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
34990    // starved runner (a past-due Sleep would resolve inline instead of
34991    // registering, failing the assert below for the wrong reason).
34992    let deadline = Instant::now() + Duration::from_millis(250);
34993    let mut sleep = heap_sleep(&executor, deadline);
34994    assert!(
34995      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34996      "must register, not resolve"
34997    );
34998    assert!(
34999      executor.next_timer_deadline().is_some(),
35000      "the registration must be in the heap"
35001    );
35002    drop(sleep);
35003    assert!(
35004      executor.next_timer_deadline().is_none(),
35005      "cancel-on-drop must retire the heap registration"
35006    );
35007    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
35008    // cancelled timer must not wake. The negative assert is structural once
35009    // the heap is empty; waiting until the wall clock is genuinely past the
35010    // deadline keeps the ghost-fire probe honest.
35011    while Instant::now() < deadline + Duration::from_millis(25) {
35012      std::thread::sleep(Duration::from_millis(10));
35013    }
35014    executor.fire_due_timers();
35015    assert_eq!(
35016      wake_count.0.load(Ordering::SeqCst),
35017      0,
35018      "a dropped Sleep's waker must never fire"
35019    );
35020  }
35021
35022  #[cfg(napi_runtime_os_threads)]
35023  #[test]
35024  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
35025    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
35026    let waker = futures::task::noop_waker();
35027    let mut cx = Context::from_waker(&waker);
35028    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
35029
35030    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35031    wait_until(
35032      "the timekeeper parked on the timer that will be cancelled",
35033      || {
35034        executor
35035          .timers
35036          .inner
35037          .lock()
35038          .unwrap()
35039          .timekeeper_parker
35040          .is_some()
35041      },
35042    );
35043
35044    drop(sleep);
35045    wait_until(
35046      "the cancelled timer's timekeeper returned to its idle wait",
35047      || {
35048        executor.next_timer_deadline().is_none()
35049          && executor
35050            .timers
35051            .inner
35052            .lock()
35053            .unwrap()
35054            .timekeeper_parker
35055            .is_none()
35056      },
35057    );
35058  }
35059
35060  #[cfg(napi_runtime_os_threads)]
35061  #[test]
35062  fn timer_waker_replacement_drops_outside_heap_lock() {
35063    use std::sync::mpsc;
35064
35065    struct DropOtherTimer(Mutex<Option<Sleep>>);
35066
35067    #[expect(
35068      clippy::manual_noop_waker,
35069      reason = "the no-op wake type has a reentrant Drop used by this regression"
35070    )]
35071    impl std::task::Wake for DropOtherTimer {
35072      fn wake(self: Arc<Self>) {}
35073    }
35074
35075    impl Drop for DropOtherTimer {
35076      fn drop(&mut self) {
35077        drop(self.0.lock().unwrap().take());
35078      }
35079    }
35080
35081    let (done_tx, done_rx) = mpsc::channel();
35082    let runner = std::thread::spawn(move || {
35083      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
35084      let deadline = Instant::now() + Duration::from_secs(120);
35085      let mut other = heap_sleep(&executor, deadline);
35086      let noop = futures::task::noop_waker();
35087      let mut noop_cx = Context::from_waker(&noop);
35088      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
35089
35090      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
35091      let mut primary = heap_sleep(&executor, deadline);
35092      let mut old_cx = Context::from_waker(&old);
35093      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
35094      drop(old);
35095
35096      let replacement = futures::task::noop_waker();
35097      let mut replacement_cx = Context::from_waker(&replacement);
35098      assert!(
35099        Pin::new(&mut primary)
35100          .poll(&mut replacement_cx)
35101          .is_pending()
35102      );
35103      drop(primary);
35104      executor.shutdown_timers();
35105      done_tx.send(()).unwrap();
35106    });
35107    // Hang detector on a freshly spawned runner thread -- generous for
35108    // starved CI runners (a real regression deadlocks forever).
35109    done_rx
35110      .recv_timeout(Duration::from_secs(10))
35111      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
35112    runner.join().unwrap();
35113  }
35114
35115  #[cfg(napi_runtime_os_threads)]
35116  #[test]
35117  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
35118    use std::sync::mpsc;
35119
35120    struct DropFinalExecutorOnWake {
35121      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
35122      completed: mpsc::Sender<()>,
35123    }
35124
35125    impl std::task::Wake for DropFinalExecutorOnWake {
35126      fn wake(self: Arc<Self>) {
35127        let executor = self
35128          .executor
35129          .lock()
35130          .unwrap_or_else(std::sync::PoisonError::into_inner)
35131          .take()
35132          .expect("the timer waker must own the final executor reference");
35133        assert_eq!(
35134          ON_TIMER_THREAD.with(std::cell::Cell::get),
35135          Some(executor.id),
35136          "the dedicated timer thread must deliver the timer wake"
35137        );
35138        drop(executor);
35139        self
35140          .completed
35141          .send(())
35142          .expect("the test receiver must outlive the timer-thread callback");
35143      }
35144    }
35145
35146    let executor = multi_thread_executor(1, None, "timer-final-arc");
35147    let (completed_tx, completed_rx) = mpsc::channel();
35148    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
35149    {
35150      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
35151        executor: Mutex::new(Some(Arc::clone(&executor))),
35152        completed: completed_tx,
35153      }));
35154      let mut cx = Context::from_waker(&waker);
35155      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35156    }
35157    wait_until(
35158      "the timer thread parked before owning the final executor Arc",
35159      || {
35160        executor
35161          .timers
35162          .inner
35163          .lock()
35164          .unwrap()
35165          .timekeeper_parker
35166          .is_some()
35167      },
35168    );
35169
35170    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
35171    // heap waker owns the final direct executor Arc and releases it from the
35172    // timer thread while `TimerHeap::drop` still owns that thread's handle.
35173    drop(executor);
35174    // Hang detector: the fire needs the (already spawned) timer thread to
35175    // reach its 80ms deadline -- generous for starved CI runners.
35176    completed_rx
35177      .recv_timeout(Duration::from_secs(10))
35178      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
35179  }
35180
35181  #[cfg(not(target_family = "wasm"))]
35182  #[test]
35183  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
35184    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
35185    // pending wakers -- a `watcher.close()` awaiting a
35186    // debounce sleep must complete, not hang. Also pins fire-on-register for
35187    // a Sleep polled after shutdown.
35188    use std::sync::mpsc;
35189
35190    let (done_tx, done_rx) = mpsc::channel();
35191    let runner = std::thread::spawn(move || {
35192      let controller = Arc::new(RuntimeController::new());
35193      // Default options: MultiThread on native.
35194      let backend = controller.backend();
35195      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
35196        panic!("default native backend must be MultiThread");
35197      };
35198      let executor = Arc::downgrade(executor);
35199      let mut late = heap_sleep(
35200        &executor
35201          .upgrade()
35202          .expect("the initial executor must be live"),
35203        Instant::now() + Duration::from_secs(120),
35204      );
35205      drop(backend);
35206
35207      let (parked_tx, parked_rx) = mpsc::channel();
35208      let block_controller = Arc::clone(&controller);
35209      let block_exec = Weak::clone(&executor);
35210      let blocker = std::thread::spawn(move || {
35211        let mut future = std::pin::pin!(async {
35212          let executor = block_exec
35213            .upgrade()
35214            .expect("the runtime must remain live while blocking");
35215          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
35216          drop(executor);
35217          parked_tx.send(()).unwrap();
35218          sleep.await;
35219        });
35220        block_controller.block_on(future.as_mut());
35221      });
35222      parked_rx.recv().unwrap();
35223      wait_until("the 120s sleep registered in the heap", || {
35224        executor
35225          .upgrade()
35226          .is_some_and(|executor| executor.next_timer_deadline().is_some())
35227      });
35228
35229      // The acceptance surface: controller shutdown while a sleep is armed.
35230      controller
35231        .shutdown()
35232        .expect("runtime shutdown must succeed");
35233      blocker
35234        .join()
35235        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
35236
35237      // A Sleep polled AFTER shutdown resolves immediately instead of
35238      // registering with a dead heap.
35239      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
35240      let mut cx = Context::from_waker(&waker);
35241      assert!(
35242        Pin::new(&mut late).poll(&mut cx).is_ready(),
35243        "a Sleep polled after shutdown must fire early, never park a closing runtime"
35244      );
35245      let _ = done_tx.send(());
35246    });
35247
35248    match done_rx.recv_timeout(Duration::from_secs(10)) {
35249      Ok(()) => runner.join().unwrap(),
35250      Err(error) => {
35251        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
35252      }
35253    }
35254  }
35255
35256  #[cfg(napi_runtime_os_threads)]
35257  #[test]
35258  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
35259    // Timer-wait/deadline invariant: the park deadline is armed
35260    // SHORTER than the next timer deadline. The cooperative driver awaiting
35261    // the sleep sits with ZERO
35262    // executor progress for several full deadline windows -- a timer wait is
35263    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
35264    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
35265    // also allows the parked driver to race the dedicated timekeeper and fire
35266    // the timer itself, pinning the cooperative timer-bounded park.
35267    use std::sync::mpsc;
35268
35269    let (done_tx, done_rx) = mpsc::channel();
35270    let runner = std::thread::spawn(move || {
35271      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
35272
35273      let task_exec = Arc::clone(&executor);
35274      let body = async move {
35275        let started = Instant::now();
35276        let mut inner = std::pin::pin!(async {
35277          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
35278        });
35279        // Re-entrant block_on from the pool worker: the cooperative park.
35280        task_exec.block_on(inner.as_mut());
35281        assert!(started.elapsed() >= Duration::from_millis(250));
35282      };
35283      let sched = Arc::clone(&executor);
35284      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
35285      executor.schedule(runnable);
35286
35287      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
35288      // by run_runnable's catch_unwind), so the task would never complete and
35289      // the bounded harness below would report the failure.
35290      futures::executor::block_on(task);
35291      // Teardown inside the bounded window: this test was one of the silent
35292      // wedges on the Windows CI runners with every assertion already
35293      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
35294      // (a timer was polled, so the timekeeper thread is live) -- exactly
35295      // the kind of phase that must fail loudly rather than hang.
35296      drop(executor);
35297      let _ = done_tx.send(());
35298    });
35299
35300    match done_rx.recv_timeout(Duration::from_secs(10)) {
35301      Ok(()) => join_within(
35302        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
35303        runner,
35304        Duration::from_secs(30),
35305      ),
35306      Err(error) => {
35307        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
35308      }
35309    }
35310  }
35311
35312  #[cfg(napi_runtime_os_threads)]
35313  #[test]
35314  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
35315    // Timer-registration/verdict race, deterministic via the verdict hook: a
35316    // timer registered AFTER a deadline park was committed bumps no progress
35317    // counter by design, lands after the permit and queued-work re-checks, and
35318    // is invisible to the fingerprint. The heap re-check must recognize that
35319    // guaranteed future wake, veto the panic, and let the driver re-park
35320    // timer-bounded until the fire's task-wake completes the future.
35321    use std::sync::mpsc;
35322
35323    // A raw Sleep the hook registers is woken by this waker, which releases
35324    // the awaited future's gate.
35325    struct GateWake {
35326      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
35327    }
35328    impl std::task::Wake for GateWake {
35329      fn wake(self: Arc<Self>) {
35330        let gate = self.gate.lock().unwrap().take();
35331        if let Some(gate) = gate {
35332          let _ = gate.send(23);
35333        }
35334      }
35335    }
35336
35337    let (done_tx, done_rx) = mpsc::channel();
35338    let runner = std::thread::spawn(move || {
35339      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
35340      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
35341      // replacement drainer behind our back; only the verdict's heap re-check
35342      // can save this park.
35343      executor
35344        .active_drainers
35345        .store(executor.max_drainers, Ordering::SeqCst);
35346
35347      // Completion condition: the timer registered inside the hook fires and
35348      // its waker (the gate) releases the awaited future.
35349      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
35350      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
35351
35352      let hook_exec = Arc::clone(&executor);
35353      let hook_gate = Arc::clone(&gate_tx);
35354      // The Sleep must outlive the hook (drop cancels): park it in a slot the
35355      // runner thread later cleans up.
35356      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
35357      let hook_sleep_slot = Arc::clone(&parked_sleep);
35358      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
35359        *slot.borrow_mut() = Some(Box::new(move || {
35360          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
35361          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
35362          let mut cx = Context::from_waker(&waker);
35363          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35364          *hook_sleep_slot.lock().unwrap() = Some(sleep);
35365        }));
35366      });
35367
35368      // The test-only on-pool marker runs the cooperative branch and its
35369      // thread-local hook on THIS thread.
35370      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
35371      let mut output = None;
35372      {
35373        let mut future = std::pin::pin!(async {
35374          output = Some(gate_rx.await.unwrap());
35375        });
35376        executor.block_on(future.as_mut());
35377      }
35378      // Clear the hook slot BEFORE any assert can panic: this hook captures
35379      // an executor Arc and THIS executor's timekeeper is live (a timer was
35380      // polled), so a leftover hook dropped by a TLS destructor would join
35381      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
35382      // RULE at `WorkerTlsDropAction`).
35383      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
35384      assert_eq!(
35385        output,
35386        Some(23),
35387        "the edge-registered timer must have fired and released the gate"
35388      );
35389      executor.active_drainers.store(0, Ordering::SeqCst);
35390      // Teardown inside the bounded window; includes the timekeeper join
35391      // (a timer was polled). See `join_within`.
35392      drop(executor);
35393      let _ = done_tx.send(());
35394    });
35395
35396    match done_rx.recv_timeout(Duration::from_secs(10)) {
35397      Ok(()) => join_within(
35398        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
35399        runner,
35400        Duration::from_secs(30),
35401      ),
35402      Err(error) => panic!(
35403        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
35404      ),
35405    }
35406  }
35407
35408  #[cfg(panic = "unwind")]
35409  #[test]
35410  fn current_thread_sleep_without_host_driver_fails_loud() {
35411    // Missing-driver invariant: a CT runtime without a registered host driver
35412    // must fail LOUD with this exact diagnostic -- never a silent never-firing
35413    // debounce or a misleading certain-deadlock panic later.
35414    let metrics = Arc::new(RuntimeMetrics::default());
35415    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35416    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
35417
35418    let payload = catch_unwind(AssertUnwindSafe(|| {
35419      let _sleep = make_sleep(
35420        &backend,
35421        &Arc::new(TimerDriverRegistry::default()),
35422        Instant::now() + Duration::from_millis(10),
35423      );
35424    }))
35425    .expect_err("sleep_until on CurrentThread without a driver must panic");
35426    let message = payload
35427      .downcast_ref::<String>()
35428      .cloned()
35429      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
35430      .expect("panic payload must be a message");
35431    assert!(
35432      message.contains("CurrentThread runtime has no live timer driver registered"),
35433      "the diagnostic must name the missing driver, got: {message}"
35434    );
35435  }
35436
35437  #[test]
35438  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
35439    // Registry semantics: all-live snapshots with newest-live compatibility
35440    // selection, dead entries swept on contact, explicit unregister, and a
35441    // dead-only registry reading as NO driver.
35442    let registry = TimerDriverRegistry::default();
35443    let a = ManualStubDriver::new();
35444    let b = ManualStubDriver::new();
35445    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
35446    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35447    assert_ne!(a_id, b_id, "registration handles must be distinct");
35448    assert_eq!(
35449      registry.current().map(|(id, _)| id),
35450      Some(b_id),
35451      "newest live registrant serves"
35452    );
35453    assert!(registry.has_live_driver());
35454
35455    // A dead newest (stub for: its owning env torn down) is skipped AND
35456    // swept; selection falls back to the older live registrant.
35457    b.kill();
35458    assert_eq!(
35459      registry.current().map(|(id, _)| id),
35460      Some(a_id),
35461      "dead newest must be skipped"
35462    );
35463    // The sweep is permanent: resurrecting the flag must not bring the
35464    // evicted registration back.
35465    b.live.store(true, Ordering::SeqCst);
35466    assert_eq!(
35467      registry.current().map(|(id, _)| id),
35468      Some(a_id),
35469      "swept entries stay gone"
35470    );
35471
35472    // Explicit unregister (the binding's env-cleanup path) empties the
35473    // registry; empty and dead-only both read as NO live driver.
35474    registry.unregister(a_id);
35475    assert!(registry.current().is_none());
35476    assert!(
35477      !registry.has_live_driver(),
35478      "an emptied registry must not report a live driver"
35479    );
35480  }
35481
35482  #[cfg(panic = "unwind")]
35483  #[test]
35484  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35485    let registry = TimerDriverRegistry::default();
35486    let fallback = ManualStubDriver::new();
35487    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35488    let panicking = Arc::new(PanickingTimerDriver {
35489      swept: AtomicBool::new(false),
35490    });
35491    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35492
35493    assert_eq!(
35494      registry.current().map(|(id, _)| id),
35495      Some(fallback_id),
35496      "a panicking liveness callback must be treated as a dead driver"
35497    );
35498    assert!(
35499      panicking.swept.load(Ordering::SeqCst),
35500      "a panicking liveness callback must still run its contained sweep hook"
35501    );
35502    assert!(
35503      registry.has_live_driver(),
35504      "the healthy fallback must remain selectable"
35505    );
35506  }
35507
35508  #[cfg(napi_runtime_os_threads)]
35509  #[test]
35510  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35511    use std::{sync::mpsc, time::Duration};
35512
35513    let registry = Arc::new(TimerDriverRegistry::default());
35514    let fallback = ManualStubDriver::new();
35515    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35516    let reentrant = Arc::new(ReentrantLivenessDriver {
35517      registry: Arc::downgrade(&registry),
35518      registration: Mutex::new(None),
35519    });
35520    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35521    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35522
35523    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35524    let selection_registry = Arc::clone(&registry);
35525    let selection_thread = std::thread::spawn(move || {
35526      selection_tx
35527        .send(selection_registry.current().map(|(id, _)| id))
35528        .unwrap();
35529    });
35530    assert_eq!(
35531      selection_rx
35532        .recv_timeout(Duration::from_secs(10))
35533        .expect("is_live re-entry deadlocked"),
35534      Some(fallback_id)
35535    );
35536    selection_thread.join().unwrap();
35537
35538    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35539      registry: Arc::downgrade(&registry),
35540      fallback: fallback_id,
35541    });
35542    let drop_id = registry.register(Arc::clone(&drop_driver));
35543    drop(drop_driver);
35544
35545    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35546    let drop_registry = Arc::clone(&registry);
35547    let drop_thread = std::thread::spawn(move || {
35548      drop_registry.unregister(drop_id);
35549      drop_tx.send(()).unwrap();
35550    });
35551    drop_rx
35552      .recv_timeout(Duration::from_secs(10))
35553      .expect("driver destructor re-entry deadlocked");
35554    drop_thread.join().unwrap();
35555    assert!(
35556      registry.current().is_none(),
35557      "the destructor must be able to unregister the fallback"
35558    );
35559  }
35560
35561  #[test]
35562  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35563    const DRIVER_PAIRS: usize = 2_048;
35564
35565    let registry = Arc::new(TimerDriverRegistry::default());
35566    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35567    let mut live = Vec::with_capacity(DRIVER_PAIRS);
35568    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35569    for _ in 0..DRIVER_PAIRS {
35570      let driver = Arc::new(ReentrantSweepTimerDriver {
35571        registry: Arc::downgrade(&registry),
35572        registration: Mutex::new(None),
35573        sweeps: AtomicUsize::new(0),
35574        removed_before_sweep: AtomicBool::new(false),
35575      });
35576      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35577      *driver.registration.lock().unwrap() = Some(id);
35578      dead.push(driver);
35579
35580      let driver = ManualStubDriver::new();
35581      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35582      live.push(driver);
35583    }
35584    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35585      registry: Arc::downgrade(&registry),
35586      registration: Mutex::new(None),
35587      sweeps: AtomicUsize::new(0),
35588      removed_before_sweep: AtomicBool::new(false),
35589    });
35590    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35591    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35592    dead.push(dead_tail);
35593
35594    let selection = registry.live();
35595    assert_eq!(
35596      selection
35597        .as_slice()
35598        .iter()
35599        .map(|(id, _)| *id)
35600        .collect::<Vec<_>>(),
35601      live_ids,
35602      "sweeping every interleaved dead host must retain live registration order"
35603    );
35604    drop(selection);
35605    assert!(
35606      dead.iter().all(|driver| {
35607        driver.sweeps.load(Ordering::SeqCst) == 1
35608          && driver.removed_before_sweep.load(Ordering::SeqCst)
35609          && driver.registration.lock().unwrap().is_none()
35610      }),
35611      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35612    );
35613
35614    {
35615      let state = registry
35616        .state
35617        .lock()
35618        .unwrap_or_else(std::sync::PoisonError::into_inner);
35619      assert_eq!(state.entries.len(), DRIVER_PAIRS);
35620      assert_eq!(state.oldest, live_ids.first().copied());
35621      assert_eq!(state.newest, live_ids.last().copied());
35622      assert_eq!(
35623        state
35624          .ordered_snapshot()
35625          .iter()
35626          .map(|(id, _)| *id)
35627          .collect::<Vec<_>>(),
35628        live_ids,
35629        "keyed removals must preserve the linked order of every live host"
35630      );
35631    }
35632    let second_selection = registry.live();
35633    assert_eq!(
35634      second_selection
35635        .as_slice()
35636        .iter()
35637        .map(|(id, _)| *id)
35638        .collect::<Vec<_>>(),
35639      live_ids,
35640      "a second selection must retain the same live order"
35641    );
35642    drop(second_selection);
35643    assert!(
35644      dead
35645        .iter()
35646        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35647      "permanently swept hosts must not receive repeated eviction callbacks"
35648    );
35649    assert_eq!(
35650      registry.current().map(|(id, _)| id),
35651      live_ids.last().copied(),
35652      "newest-live compatibility selection must survive the mass sweep"
35653    );
35654
35655    drop(live);
35656    drop(dead);
35657  }
35658
35659  #[cfg(not(target_family = "wasm"))]
35660  #[test]
35661  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35662    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35663
35664    if std::env::var_os(CHILD_ENV).is_some() {
35665      let registry = Arc::new(TimerDriverRegistry::default());
35666      let fallback = ManualStubDriver::new();
35667      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35668      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35669        registry: Arc::downgrade(&registry),
35670        registrations: Mutex::new(Vec::new()),
35671      });
35672      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35673
35674      let first_cancels = Arc::new(AtomicUsize::new(0));
35675      let first_drops = Arc::new(AtomicUsize::new(0));
35676      let first = Arc::new(PanickingCancelDropTimerDriver {
35677        cancels: Arc::clone(&first_cancels),
35678        drops: Arc::clone(&first_drops),
35679      });
35680      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35681      drop(first);
35682
35683      let second_cancels = Arc::new(AtomicUsize::new(0));
35684      let second_drops = Arc::new(AtomicUsize::new(0));
35685      let second = Arc::new(PanickingCancelDropTimerDriver {
35686        cancels: Arc::clone(&second_cancels),
35687        drops: Arc::clone(&second_drops),
35688      });
35689      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35690      drop(second);
35691      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35692
35693      assert_eq!(
35694        registry.current().map(|(id, _)| id),
35695        Some(fallback_id),
35696        "selection must retry after reentrant removals and preserve the healthy fallback"
35697      );
35698      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35699      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35700      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35701      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35702      assert_eq!(
35703        registry
35704          .state
35705          .lock()
35706          .unwrap_or_else(std::sync::PoisonError::into_inner)
35707          .entries
35708          .len(),
35709        1
35710      );
35711      return;
35712    }
35713
35714    let output = std::process::Command::new(std::env::current_exe().unwrap())
35715      .arg("--exact")
35716      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35717      .arg("--nocapture")
35718      .env(CHILD_ENV, "1")
35719      .output()
35720      .expect("the hostile timer-driver snapshot subprocess test must start");
35721    assert!(
35722      output.status.success(),
35723      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35724      output.status.code(),
35725      String::from_utf8_lossy(&output.stdout),
35726      String::from_utf8_lossy(&output.stderr)
35727    );
35728  }
35729
35730  #[cfg(not(target_family = "wasm"))]
35731  #[test]
35732  fn timer_driver_registry_live_result_contains_each_driver_drop() {
35733    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
35734
35735    if std::env::var_os(CHILD_ENV).is_some() {
35736      let registry = TimerDriverRegistry::default();
35737
35738      let first_cancels = Arc::new(AtomicUsize::new(0));
35739      let first_drops = Arc::new(AtomicUsize::new(0));
35740      let first = Arc::new(PanickingCancelDropTimerDriver {
35741        cancels: Arc::clone(&first_cancels),
35742        drops: Arc::clone(&first_drops),
35743      });
35744      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35745      drop(first);
35746
35747      let second_cancels = Arc::new(AtomicUsize::new(0));
35748      let second_drops = Arc::new(AtomicUsize::new(0));
35749      let second = Arc::new(PanickingCancelDropTimerDriver {
35750        cancels: Arc::clone(&second_cancels),
35751        drops: Arc::clone(&second_drops),
35752      });
35753      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35754      drop(second);
35755
35756      let live = registry.live();
35757      assert_eq!(live.len(), 2);
35758      registry.unregister(first_id);
35759      registry.unregister(second_id);
35760      drop(live);
35761
35762      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35763      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35764      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35765      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35766      assert!(
35767        registry
35768          .state
35769          .lock()
35770          .unwrap_or_else(std::sync::PoisonError::into_inner)
35771          .entries
35772          .is_empty()
35773      );
35774      return;
35775    }
35776
35777    let output = std::process::Command::new(std::env::current_exe().unwrap())
35778      .arg("--exact")
35779      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
35780      .arg("--nocapture")
35781      .env(CHILD_ENV, "1")
35782      .output()
35783      .expect("the hostile live timer-driver snapshot subprocess test must start");
35784    assert!(
35785      output.status.success(),
35786      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35787      output.status.code(),
35788      String::from_utf8_lossy(&output.stdout),
35789      String::from_utf8_lossy(&output.stderr)
35790    );
35791  }
35792
35793  #[cfg(not(target_family = "wasm"))]
35794  #[test]
35795  fn timer_driver_registry_drop_contains_each_driver_destructor() {
35796    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
35797
35798    if std::env::var_os(CHILD_ENV).is_some() {
35799      let first_drops = Arc::new(AtomicUsize::new(0));
35800      let second_drops = Arc::new(AtomicUsize::new(0));
35801      let registry = TimerDriverRegistry::default();
35802      let first = Arc::new(PanickingCancelDropTimerDriver {
35803        cancels: Arc::new(AtomicUsize::new(0)),
35804        drops: Arc::clone(&first_drops),
35805      });
35806      let second = Arc::new(PanickingCancelDropTimerDriver {
35807        cancels: Arc::new(AtomicUsize::new(0)),
35808        drops: Arc::clone(&second_drops),
35809      });
35810      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35811      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35812      drop(first);
35813      drop(second);
35814
35815      drop(registry);
35816
35817      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35818      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35819      return;
35820    }
35821
35822    let output = std::process::Command::new(std::env::current_exe().unwrap())
35823      .arg("--exact")
35824      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
35825      .arg("--nocapture")
35826      .env(CHILD_ENV, "1")
35827      .output()
35828      .expect("the hostile timer-driver registry drop subprocess test must start");
35829    assert!(
35830      output.status.success(),
35831      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
35832      output.status.code(),
35833      String::from_utf8_lossy(&output.stdout),
35834      String::from_utf8_lossy(&output.stderr)
35835    );
35836  }
35837
35838  #[test]
35839  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
35840    const DRIVERS: usize = 512;
35841
35842    let metrics = Arc::new(RuntimeMetrics::default());
35843    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35844    let backend =
35845      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35846    let registry = Arc::new(TimerDriverRegistry::default());
35847    let drivers = (0..DRIVERS)
35848      .map(|_| {
35849        let driver = ManualStubDriver::new();
35850        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35851        driver
35852      })
35853      .collect::<Vec<_>>();
35854    let mut sleep = make_sleep(
35855      &backend,
35856      &registry,
35857      Instant::now() + Duration::from_secs(60),
35858    );
35859    let mut cx = Context::from_waker(Waker::noop());
35860
35861    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35862    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35863    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
35864    match &sleep.inner {
35865      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
35866      #[cfg(napi_runtime_os_threads)]
35867      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35868    }
35869    for driver in &drivers {
35870      assert_eq!(
35871        driver.registers.lock().unwrap().len(),
35872        2,
35873        "a re-poll must refresh each live driver exactly once"
35874      );
35875    }
35876
35877    for driver in drivers.iter().step_by(2) {
35878      driver.kill();
35879    }
35880    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35881    match &sleep.inner {
35882      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
35883      #[cfg(napi_runtime_os_threads)]
35884      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35885    }
35886    for (index, driver) in drivers.iter().enumerate() {
35887      let registers = driver.registers.lock().unwrap().len();
35888      let cancels = driver.cancels.lock().unwrap().clone();
35889      if index % 2 == 0 {
35890        assert_eq!(registers, 2);
35891        assert_eq!(cancels, vec![timer_id]);
35892      } else {
35893        assert_eq!(registers, 3);
35894        assert!(cancels.is_empty());
35895      }
35896    }
35897
35898    drop(sleep);
35899    for driver in drivers {
35900      assert_eq!(
35901        driver.cancels.lock().unwrap().as_slice(),
35902        &[timer_id],
35903        "every physical arm must be cancelled exactly once"
35904      );
35905    }
35906    assert_eq!(registry.pending_len(), 0);
35907    assert!(!executor.host_timers.has_pending());
35908  }
35909
35910  #[cfg(panic = "unwind")]
35911  #[test]
35912  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
35913    let metrics = Arc::new(RuntimeMetrics::default());
35914    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35915    let backend =
35916      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35917    let healthy = ManualStubDriver::new();
35918    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35919    let deadline = Instant::now() + Duration::from_secs(60);
35920    let mut sleep = make_sleep(&backend, &registry, deadline);
35921    let mut cx = Context::from_waker(Waker::noop());
35922
35923    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35924    let timer_id = healthy.registers.lock().unwrap()[0].0;
35925
35926    let panicking = PanickingRegisterTimerDriver::new();
35927    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35928    let result = catch_unwind(AssertUnwindSafe(|| {
35929      let _ = Pin::new(&mut sleep).poll(&mut cx);
35930    }));
35931    assert!(
35932      result.is_err(),
35933      "the hostile register panic must retain its normal propagation"
35934    );
35935    assert_eq!(
35936      healthy.registers.lock().unwrap().len(),
35937      2,
35938      "the previously armed driver must have completed its refresh before the later panic"
35939    );
35940    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
35941    assert!(
35942      panicking.pending.lock().unwrap().contains_key(&timer_id),
35943      "the hostile driver must model an arm committed before it panics"
35944    );
35945
35946    drop(sleep);
35947    assert_eq!(
35948      healthy.cancels.lock().unwrap().as_slice(),
35949      &[timer_id],
35950      "unwind must preserve cancellation ownership for the previously armed driver"
35951    );
35952    assert_eq!(
35953      panicking.cancels.lock().unwrap().as_slice(),
35954      &[timer_id],
35955      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
35956    );
35957    assert!(panicking.pending.lock().unwrap().is_empty());
35958    assert_eq!(registry.pending_len(), 0);
35959    assert!(!executor.host_timers.has_pending());
35960  }
35961
35962  #[cfg(not(target_family = "wasm"))]
35963  #[test]
35964  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
35965    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
35966
35967    if std::env::var_os(CHILD_ENV).is_some() {
35968      let metrics = Arc::new(RuntimeMetrics::default());
35969      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35970      let backend =
35971        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35972      let registry = Arc::new(TimerDriverRegistry::default());
35973      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
35974        registry: Arc::downgrade(&registry),
35975        later: Mutex::new(None),
35976        registers: AtomicUsize::new(0),
35977      });
35978      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
35979
35980      let later_cancels = Arc::new(AtomicUsize::new(0));
35981      let later_drops = Arc::new(AtomicUsize::new(0));
35982      let later = Arc::new(PanickingCancelDropTimerDriver {
35983        cancels: Arc::clone(&later_cancels),
35984        drops: Arc::clone(&later_drops),
35985      });
35986      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
35987      *earlier.later.lock().unwrap() = Some(later_id);
35988      drop(later);
35989
35990      let mut sleep = make_sleep(
35991        &backend,
35992        &registry,
35993        Instant::now() + Duration::from_secs(60),
35994      );
35995      let mut cx = Context::from_waker(Waker::noop());
35996      let result = catch_unwind(AssertUnwindSafe(|| {
35997        let _ = Pin::new(&mut sleep).poll(&mut cx);
35998      }));
35999
36000      assert!(
36001        result.is_err(),
36002        "the earlier driver's register panic must still propagate"
36003      );
36004      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
36005      assert_eq!(
36006        later_drops.load(Ordering::SeqCst),
36007        0,
36008        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
36009      );
36010
36011      drop(sleep);
36012      assert_eq!(
36013        later_cancels.load(Ordering::SeqCst),
36014        1,
36015        "the later driver must remain owned for cancellation despite its registry removal"
36016      );
36017      assert_eq!(
36018        later_drops.load(Ordering::SeqCst),
36019        1,
36020        "the later driver's final destructor must run once behind containment"
36021      );
36022      assert_eq!(registry.pending_len(), 0);
36023      assert!(!executor.host_timers.has_pending());
36024      return;
36025    }
36026
36027    let output = std::process::Command::new(std::env::current_exe().unwrap())
36028      .arg("--exact")
36029      .arg(
36030        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
36031      )
36032      .arg("--nocapture")
36033      .env(CHILD_ENV, "1")
36034      .output()
36035      .expect("the hostile host timer snapshot subprocess test must start");
36036    assert!(
36037      output.status.success(),
36038      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
36039      output.status.code(),
36040      String::from_utf8_lossy(&output.stdout),
36041      String::from_utf8_lossy(&output.stderr)
36042    );
36043  }
36044
36045  #[cfg(not(target_family = "wasm"))]
36046  #[test]
36047  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
36048    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
36049
36050    if std::env::var_os(CHILD_ENV).is_some() {
36051      let metrics = Arc::new(RuntimeMetrics::default());
36052      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36053      let backend =
36054        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36055      let cancels = Arc::new(AtomicUsize::new(0));
36056      let drops = Arc::new(AtomicUsize::new(0));
36057      let hostile = Arc::new(PanickingCancelDropTimerDriver {
36058        cancels: Arc::clone(&cancels),
36059        drops: Arc::clone(&drops),
36060      });
36061      let healthy = ManualStubDriver::new();
36062      let registry = Arc::new(TimerDriverRegistry::default());
36063      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
36064      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
36065      let mut sleep = make_sleep(
36066        &backend,
36067        &registry,
36068        Instant::now() + Duration::from_secs(60),
36069      );
36070      let mut cx = Context::from_waker(Waker::noop());
36071
36072      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36073      let timer_id = healthy.registers.lock().unwrap()[0].0;
36074
36075      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
36076      // independently contain both its cancel panic and its destructor panic.
36077      registry.unregister(hostile_id);
36078      drop(hostile);
36079      drop(sleep);
36080      assert_eq!(cancels.load(Ordering::SeqCst), 1);
36081      assert_eq!(drops.load(Ordering::SeqCst), 1);
36082      assert_eq!(
36083        healthy.cancels.lock().unwrap().as_slice(),
36084        &[timer_id],
36085        "one hostile cancellation must not skip later armed drivers"
36086      );
36087      assert_eq!(registry.pending_len(), 0);
36088      assert!(!executor.host_timers.has_pending());
36089      return;
36090    }
36091
36092    let output = std::process::Command::new(std::env::current_exe().unwrap())
36093      .arg("--exact")
36094      .arg(
36095        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
36096      )
36097      .arg("--nocapture")
36098      .env(CHILD_ENV, "1")
36099      .output()
36100      .expect("the hostile timer-driver cancellation subprocess test must start");
36101    assert!(
36102      output.status.success(),
36103      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
36104      output.status.code(),
36105      String::from_utf8_lossy(&output.stdout),
36106      String::from_utf8_lossy(&output.stderr)
36107    );
36108  }
36109
36110  #[cfg(not(target_family = "wasm"))]
36111  #[test]
36112  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
36113    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
36114
36115    if std::env::var_os(CHILD_ENV).is_some() {
36116      let metrics = Arc::new(RuntimeMetrics::default());
36117      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36118      let backend =
36119        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36120      let driver = ManualStubDriver::new();
36121      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36122      let state = Arc::new(TestHostileRawWakerState {
36123        clones: AtomicUsize::new(0),
36124        drop_panics: AtomicUsize::new(0),
36125        panic_on_clone: Some(2),
36126      });
36127
36128      let result = catch_unwind(AssertUnwindSafe(|| {
36129        let mut sleep = make_sleep(
36130          &backend,
36131          &registry,
36132          Instant::now() + Duration::from_secs(60),
36133        );
36134        let waker = test_hostile_waker(Arc::clone(&state));
36135        let mut cx = Context::from_waker(&waker);
36136        let _ = Pin::new(&mut sleep).poll(&mut cx);
36137      }));
36138      assert!(
36139        result.is_err(),
36140        "the original RawWaker clone panic must still propagate"
36141      );
36142      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
36143      assert_eq!(
36144        state.drop_panics.load(Ordering::SeqCst),
36145        1,
36146        "the first clone must be destroyed once behind a contained boundary"
36147      );
36148      assert!(driver.registers.lock().unwrap().is_empty());
36149      assert_eq!(registry.pending_len(), 0);
36150      assert!(!executor.host_timers.has_pending());
36151      return;
36152    }
36153
36154    let output = std::process::Command::new(std::env::current_exe().unwrap())
36155      .arg("--exact")
36156      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
36157      .arg("--nocapture")
36158      .env(CHILD_ENV, "1")
36159      .output()
36160      .expect("the hostile RawWaker subprocess test must start");
36161    assert!(
36162      output.status.success(),
36163      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
36164      output.status.code(),
36165      String::from_utf8_lossy(&output.stdout),
36166      String::from_utf8_lossy(&output.stderr)
36167    );
36168  }
36169
36170  #[cfg(not(target_family = "wasm"))]
36171  #[test]
36172  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
36173    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
36174
36175    if std::env::var_os(CHILD_ENV).is_some() {
36176      let metrics = Arc::new(RuntimeMetrics::default());
36177      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36178      let backend =
36179        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36180      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
36181        registers: AtomicUsize::new(0),
36182      });
36183      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36184      let state = Arc::new(TestHostileRawWakerState {
36185        clones: AtomicUsize::new(0),
36186        drop_panics: AtomicUsize::new(0),
36187        panic_on_clone: None,
36188      });
36189      let mut sleep = make_sleep(
36190        &backend,
36191        &registry,
36192        Instant::now() + Duration::from_secs(60),
36193      );
36194      let waker = test_hostile_waker(Arc::clone(&state));
36195      let mut cx = Context::from_waker(&waker);
36196
36197      let result = catch_unwind(AssertUnwindSafe(|| {
36198        let _ = Pin::new(&mut sleep).poll(&mut cx);
36199      }));
36200      assert!(
36201        result.is_err(),
36202        "the timer-driver register panic must still propagate"
36203      );
36204      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
36205      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
36206      assert_eq!(
36207        state.drop_panics.load(Ordering::SeqCst),
36208        1,
36209        "the unretained register argument must be destroyed once behind the proxy boundary"
36210      );
36211      assert_eq!(registry.pending_len(), 1);
36212      assert!(executor.host_timers.has_pending());
36213
36214      drop(sleep);
36215      assert_eq!(
36216        state.drop_panics.load(Ordering::SeqCst),
36217        3,
36218        "repoll and registry wakers must retain their existing contained cleanup"
36219      );
36220      assert_eq!(registry.pending_len(), 0);
36221      assert!(!executor.host_timers.has_pending());
36222      return;
36223    }
36224
36225    let output = std::process::Command::new(std::env::current_exe().unwrap())
36226      .arg("--exact")
36227      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
36228      .arg("--nocapture")
36229      .env(CHILD_ENV, "1")
36230      .output()
36231      .expect("the hostile register-waker subprocess test must start");
36232    assert!(
36233      output.status.success(),
36234      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
36235      output.status.code(),
36236      String::from_utf8_lossy(&output.stdout),
36237      String::from_utf8_lossy(&output.stderr)
36238    );
36239  }
36240
36241  #[test]
36242  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
36243    // A sleep armed on a driver whose host dies must keep every remaining live
36244    // registrant armed with its remaining time preserved
36245    // (absolute deadline), keep the executor's wake-token accounting at
36246    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
36247    // armed on when dropped.
36248    let metrics = Arc::new(RuntimeMetrics::default());
36249    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36250    let backend =
36251      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36252
36253    let a = ManualStubDriver::new();
36254    let b = ManualStubDriver::new();
36255    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36256    let deadline = Instant::now() + Duration::from_secs(60);
36257
36258    let (timer_id, b_id) = {
36259      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
36260      let mut cx = Context::from_waker(Waker::noop());
36261
36262      // First poll arms on A (the only registrant) and takes the wake-token.
36263      assert!(sleep.as_mut().poll(&mut cx).is_pending());
36264      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
36265      assert_eq!(armed_deadline, deadline);
36266      assert!(
36267        executor.host_timers.has_pending(),
36268        "first poll must arm the wake-token"
36269      );
36270
36271      // A's host dies; a newer live registrant appears (the binding shape:
36272      // eviction wakes every pending sleep, which then re-polls -- here the
36273      // re-poll is driven manually).
36274      a.kill();
36275      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36276      assert!(sleep.as_mut().poll(&mut cx).is_pending());
36277      assert_eq!(
36278        b.registers.lock().unwrap().as_slice(),
36279        &[(timer_id, deadline)],
36280        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
36281      );
36282      assert_eq!(
36283        a.cancels.lock().unwrap().as_slice(),
36284        &[timer_id],
36285        "the dead driver gets a best-effort cancel for the old arm"
36286      );
36287      assert_eq!(
36288        registry.current().map(|(id, _)| id),
36289        Some(b_id),
36290        "the dead driver was evicted"
36291      );
36292      assert!(
36293        executor.host_timers.has_pending(),
36294        "the wake-token survives the re-arm (still exactly this sleep's entry)"
36295      );
36296      (timer_id, b_id)
36297    };
36298
36299    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
36300    // wake-token retires.
36301    let _ = b_id;
36302    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
36303    assert!(
36304      !executor.host_timers.has_pending(),
36305      "drop must retire the wake-token"
36306    );
36307  }
36308
36309  #[cfg(napi_runtime_os_threads)]
36310  #[test]
36311  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
36312    let metrics = Arc::new(RuntimeMetrics::default());
36313    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36314    let backend =
36315      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36316    let responsive = Arc::new(StubHostTimerDriver::new());
36317    let starved = ManualStubDriver::new();
36318    let registry = Arc::new(TimerDriverRegistry::default());
36319    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
36320    // Newest-live-only selection would arm exclusively on this host and never
36321    // receive a wake. The responsive older host must be raced in parallel.
36322    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36323
36324    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
36325    let waker = Waker::from(Arc::clone(&wake_count));
36326    let mut cx = Context::from_waker(&waker);
36327    // This deadline's pre-fire budget covers only `make_sleep` plus the very
36328    // next poll (a past-due Sleep completes inline and would bypass the
36329    // racing-arm machinery under test); 500ms dwarfs those two statements on
36330    // a starved runner without dragging the fire wait out.
36331    let deadline = Instant::now() + Duration::from_millis(500);
36332    let mut sleep = make_sleep(&backend, &registry, deadline);
36333    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36334
36335    let responsive_timer = *responsive
36336      .timers
36337      .lock()
36338      .unwrap()
36339      .keys()
36340      .next()
36341      .expect("responsive timer must be armed");
36342    assert_eq!(
36343      starved.registers.lock().unwrap().as_slice(),
36344      &[(responsive_timer, deadline)],
36345      "every live host must receive the same timer identity and absolute deadline"
36346    );
36347
36348    // Event-based fire wait (this was a fixed 140ms sleep, which required
36349    // the responsive stub's freshly spawned fire thread to get CPU inside
36350    // ~80ms on a starved CI host). The wait is structurally sound: the
36351    // starved ManualStubDriver has NO fire path (it only records), so the
36352    // one-and-only wake can come from the responsive host -- the bound is a
36353    // pure hang detector.
36354    let fire_bound = Instant::now() + Duration::from_secs(30);
36355    while wake_count.0.load(Ordering::SeqCst) == 0 {
36356      assert!(
36357        Instant::now() < fire_bound,
36358        "the responsive host must wake a sleep whose newest host is live but starved"
36359      );
36360      std::thread::sleep(Duration::from_millis(10));
36361    }
36362    // The stub fires at (or after) the shared absolute deadline, so the wall
36363    // clock is past `deadline` here and the poll completes.
36364    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36365    assert_eq!(
36366      starved.cancels.lock().unwrap().as_slice(),
36367      &[responsive_timer],
36368      "completion through the winning host must cancel the starved losing arm"
36369    );
36370  }
36371
36372  #[cfg(napi_runtime_os_threads)]
36373  #[test]
36374  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
36375    use std::sync::mpsc;
36376
36377    struct ReentrantTimerRegistrationWake {
36378      registry: Weak<TimerDriverRegistry>,
36379      wakes: AtomicUsize,
36380      reentered: AtomicBool,
36381    }
36382
36383    impl std::task::Wake for ReentrantTimerRegistrationWake {
36384      fn wake(self: Arc<Self>) {
36385        self.wakes.fetch_add(1, Ordering::SeqCst);
36386        if let Some(registry) = self.registry.upgrade() {
36387          let _ = registry.has_live_driver();
36388          self.reentered.store(true, Ordering::SeqCst);
36389        }
36390      }
36391    }
36392
36393    let metrics = Arc::new(RuntimeMetrics::default());
36394    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36395    let backend =
36396      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36397    let registry = Arc::new(TimerDriverRegistry::default());
36398    let starved = ManualStubDriver::new();
36399    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
36400
36401    // The whole pre-fire phase (four polls plus a registration-thread round
36402    // trip) must finish while the wall clock is still BEFORE this shared
36403    // deadline: a past-due `Sleep` completes inline at poll
36404    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
36405    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
36406    // a starved GitHub macOS runner) without dragging the fire wait out.
36407    let deadline = Instant::now() + Duration::from_secs(2);
36408    let mut first = make_sleep(&backend, &registry, deadline);
36409    let mut second = make_sleep(&backend, &registry, deadline);
36410    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
36411      registry: Arc::downgrade(&registry),
36412      wakes: AtomicUsize::new(0),
36413      reentered: AtomicBool::new(false),
36414    });
36415    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
36416      registry: Arc::downgrade(&registry),
36417      wakes: AtomicUsize::new(0),
36418      reentered: AtomicBool::new(false),
36419    });
36420    let first_waker = Waker::from(Arc::clone(&first_wake));
36421    let second_waker = Waker::from(Arc::clone(&second_wake));
36422    let mut first_cx = Context::from_waker(&first_waker);
36423    let mut second_cx = Context::from_waker(&second_waker);
36424
36425    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36426    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36427    let initial = starved.registers.lock().unwrap().clone();
36428    let first_id = initial[0].0;
36429    let second_id = initial[1].0;
36430    assert_ne!(first_id, second_id);
36431    assert_eq!(
36432      registry.pending_len(),
36433      2,
36434      "one stable repoll entry is retained per sleep"
36435    );
36436
36437    let responsive = Arc::new(StubHostTimerDriver::new());
36438    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
36439    let registration_registry = Arc::clone(&registry);
36440    let registration_driver = Arc::clone(&responsive);
36441    let registration = std::thread::spawn(move || {
36442      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
36443      registered_tx.send(id).unwrap();
36444    });
36445    // Hang detector only (the regression it pins is a PERMANENT deadlock:
36446    // `register` re-entering the repoll callback), so the bound is generous
36447    // rather than a latency assertion a starved runner could trip.
36448    registered_rx
36449      .recv_timeout(Duration::from_secs(10))
36450      .expect("late driver registration or repoll callback re-entry deadlocked");
36451    registration.join().unwrap();
36452
36453    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
36454    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
36455    assert!(first_wake.reentered.load(Ordering::SeqCst));
36456    assert!(second_wake.reentered.load(Ordering::SeqCst));
36457    assert!(
36458      responsive.timers.lock().unwrap().is_empty(),
36459      "registration requests bounded re-polls instead of invoking driver callbacks inline"
36460    );
36461
36462    // Both sleeps must still be PENDING when they re-arm on the late driver;
36463    // see the deadline-margin note above. Fail with a diagnosis instead of a
36464    // misleading Pending-assert failure if a pathological stall consumed the
36465    // whole margin anyway.
36466    assert!(
36467      Instant::now() < deadline,
36468      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
36469       this runner is starved beyond the margin this test budgets for"
36470    );
36471    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36472    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36473    let responsive_ids = responsive
36474      .timers
36475      .lock()
36476      .unwrap()
36477      .keys()
36478      .copied()
36479      .collect::<FxHashSet<_>>();
36480    assert_eq!(
36481      responsive_ids,
36482      FxHashSet::from_iter([first_id, second_id]),
36483      "every pending sleep must re-arm its existing identity on the new driver"
36484    );
36485    assert_eq!(
36486      starved
36487        .registers
36488        .lock()
36489        .unwrap()
36490        .iter()
36491        .filter(|(id, _)| *id == first_id)
36492        .count(),
36493      2,
36494      "the old host receives only the normal waker refresh on the one re-poll"
36495    );
36496    assert_eq!(
36497      starved
36498        .registers
36499        .lock()
36500        .unwrap()
36501        .iter()
36502        .filter(|(id, _)| *id == second_id)
36503        .count(),
36504      2,
36505      "the old host receives only the normal waker refresh on the one re-poll"
36506    );
36507    assert_eq!(
36508      registry.pending_len(),
36509      2,
36510      "late registration must not duplicate pending state"
36511    );
36512
36513    // Event-based fire wait instead of a fixed sleep: the starved host has
36514    // NO fire path by construction (ManualStubDriver only records), so the
36515    // second wake per sleep can only come from the late responsive host --
36516    // "while the original host remains starved" is structural, not a
36517    // wall-clock race. The bound is purely a hang detector, generous enough
36518    // for a starved runner to schedule the stub's two fire threads.
36519    let fire_bound = Instant::now() + Duration::from_secs(30);
36520    while first_wake.wakes.load(Ordering::SeqCst) < 2
36521      || second_wake.wakes.load(Ordering::SeqCst) < 2
36522    {
36523      assert!(
36524        Instant::now() < fire_bound,
36525        "the late responsive host must fire both timers while the original host remains starved"
36526      );
36527      std::thread::sleep(Duration::from_millis(10));
36528    }
36529    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36530    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36531
36532    let mut cancelled = starved.cancels.lock().unwrap().clone();
36533    cancelled.sort_unstable();
36534    let mut expected = vec![first_id, second_id];
36535    expected.sort_unstable();
36536    assert_eq!(
36537      cancelled, expected,
36538      "completion must cancel both losing starved-host arms"
36539    );
36540    assert_eq!(
36541      registry.pending_len(),
36542      0,
36543      "completion retires every repoll entry"
36544    );
36545    assert!(!executor.host_timers.has_pending());
36546  }
36547
36548  #[cfg(panic = "unwind")]
36549  #[test]
36550  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36551    // With NO live registrant left mid-flight there is nothing to re-arm on:
36552    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
36553    // sleep silently.
36554    let metrics = Arc::new(RuntimeMetrics::default());
36555    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36556    let backend =
36557      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36558
36559    let a = ManualStubDriver::new();
36560    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36561    let mut sleep = std::pin::pin!(make_sleep(
36562      &backend,
36563      &registry,
36564      Instant::now() + Duration::from_secs(60)
36565    ));
36566    let mut cx = Context::from_waker(Waker::noop());
36567    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36568
36569    a.kill();
36570    let payload = catch_unwind(AssertUnwindSafe(|| {
36571      let _ = sleep.as_mut().poll(&mut cx);
36572    }))
36573    .expect_err("a sleep with every driver dead must panic, not pend silently");
36574    let message = payload
36575      .downcast_ref::<String>()
36576      .cloned()
36577      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36578      .expect("panic payload must be a message");
36579    assert!(
36580      message.contains("lost every live timer driver mid-sleep"),
36581      "the diagnostic must name the mid-flight driver loss, got: {message}"
36582    );
36583  }
36584
36585  #[test]
36586  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36587    // Sweep-eviction invariant: the registry's selection sweep can be the
36588    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
36589    // or any call failure. A retain-only sweep would strand every sleep whose
36590    // armed waker lives in the swept driver's pending map. The sweep must call
36591    // `on_swept` outside the registry lock so the driver wakes those sleeps
36592    // into re-selection.
36593    let metrics = Arc::new(RuntimeMetrics::default());
36594    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36595    let backend =
36596      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36597
36598    let a = ManualStubDriver::new();
36599    let b = ManualStubDriver::new();
36600    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36601    let mut sleep = std::pin::pin!(make_sleep(
36602      &backend,
36603      &registry,
36604      Instant::now() + Duration::from_secs(60)
36605    ));
36606
36607    // Arm on A with an observable waker.
36608    let flag = Arc::new(WakeFlag::default());
36609    let waker = Waker::from(Arc::clone(&flag));
36610    let mut cx = Context::from_waker(&waker);
36611    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36612    assert!(!flag.0.load(Ordering::SeqCst));
36613
36614    // A's host dies; a DIFFERENT selection (any registry touch -- here a
36615    // liveness query, in production another sleep's poll) sweeps it.
36616    a.kill();
36617    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36618    let _ = registry.has_live_driver();
36619
36620    assert!(
36621      a.swept.load(Ordering::SeqCst),
36622      "the sweep must notify the swept driver"
36623    );
36624    assert!(
36625      flag.0.load(Ordering::SeqCst),
36626      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36627    );
36628
36629    // The woken sleep's re-poll lands on the live registrant.
36630    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36631    assert_eq!(
36632      b.registers.lock().unwrap().len(),
36633      1,
36634      "the re-poll must re-arm on the live driver"
36635    );
36636  }
36637
36638  #[cfg(napi_runtime_os_threads)]
36639  #[test]
36640  fn current_thread_sleep_fires_through_stub_host_driver() {
36641    // CT delegation: a registered host driver's fire must complete a sleep
36642    // awaited through the CT `block_on` drive loop.
36643    use std::sync::mpsc;
36644
36645    let (done_tx, done_rx) = mpsc::channel();
36646    let runner = std::thread::spawn(move || {
36647      let metrics = Arc::new(RuntimeMetrics::default());
36648      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36649      let backend =
36650        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36651      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36652
36653      let started = Instant::now();
36654      let mut future = std::pin::pin!(async {
36655        make_sleep(
36656          &backend,
36657          &registry_with(driver),
36658          Instant::now() + Duration::from_millis(60),
36659        )
36660        .await;
36661      });
36662      executor.block_on(future.as_mut());
36663      assert!(started.elapsed() >= Duration::from_millis(60));
36664      assert!(
36665        !executor.host_timers.has_pending(),
36666        "a completed sleep must retire its pending-registry entry"
36667      );
36668      let _ = done_tx.send(());
36669    });
36670
36671    match done_rx.recv_timeout(Duration::from_secs(10)) {
36672      Ok(()) => runner.join().unwrap(),
36673      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36674    }
36675  }
36676
36677  #[test]
36678  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36679    let metrics = Arc::new(RuntimeMetrics::default());
36680    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36681    let backend =
36682      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36683    let drivers = Arc::new(TimerDriverRegistry::default());
36684    let driver = ManualStubDriver::new();
36685    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36686    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36687    let mut cx = Context::from_waker(Waker::noop());
36688
36689    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36690    let timer_id = driver.registers.lock().unwrap()[0].0;
36691    assert_eq!(drivers.pending_len(), 1);
36692
36693    executor.host_timers.shutdown();
36694    assert_eq!(
36695      drivers.pending_len(),
36696      0,
36697      "shutdown must retire the registration-repoll index before the Sleep is dropped"
36698    );
36699    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36700    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36701  }
36702
36703  #[cfg(napi_runtime_os_threads)]
36704  #[test]
36705  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36706    use std::sync::mpsc;
36707
36708    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36709    let backend = controller.backend();
36710    let drivers = Arc::new(TimerDriverRegistry::default());
36711    let driver = ManualStubDriver::new();
36712    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36713    let sleep = make_sleep(
36714      &backend,
36715      &drivers,
36716      Instant::now() + Duration::from_secs(120),
36717    );
36718    drop(backend);
36719
36720    let (polled_tx, polled_rx) = mpsc::channel();
36721    let block_controller = Arc::clone(&controller);
36722    let blocker = std::thread::spawn(move || {
36723      let mut announced = false;
36724      let mut sleep = std::pin::pin!(sleep);
36725      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
36726        let poll = sleep.as_mut().poll(cx);
36727        if !announced {
36728          announced = true;
36729          polled_tx.send(()).unwrap();
36730        }
36731        poll
36732      }));
36733      block_controller.block_on(future.as_mut());
36734    });
36735    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
36736
36737    let (shutdown_tx, shutdown_rx) = mpsc::channel();
36738    let shutdown_controller = Arc::clone(&controller);
36739    let shutdown = std::thread::spawn(move || {
36740      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
36741    });
36742    shutdown_rx
36743      .recv_timeout(Duration::from_secs(2))
36744      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
36745      .unwrap();
36746    shutdown.join().unwrap();
36747    blocker.join().unwrap();
36748    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
36749  }
36750
36751  #[cfg(panic = "unwind")]
36752  #[test]
36753  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
36754    // Threadless pending-timer invariant: on a
36755    // THREADLESS build a pending host timer is NOT a wake-token source -- the
36756    // host event loop's `setTimeout` relay can only run on the very thread
36757    // that is about to park, so it can never fire while `block_on` holds it.
36758    // The certain check must panic with the typed diagnostic anyway (the
36759    // timer-backed kind, so the message names the shape), never fall through
36760    // to the park -- on the real wasip1 target that park is an untyped
36761    // "condvar wait not supported" abort. A native helper thread can fire the
36762    // timer in this test harness, but that concurrency cannot exist on the
36763    // threadless target and must not rescue the park.
36764    use std::sync::mpsc;
36765
36766    let (done_tx, done_rx) = mpsc::channel();
36767    let runner = std::thread::spawn(move || {
36768      let metrics = Arc::new(RuntimeMetrics::default());
36769      // Native stand-in for the threadless wasm build; the stub driver would
36770      // play the host loop from a helper thread, but the panic must land at
36771      // the first park decision, long before its 60ms fire.
36772      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36773      let backend =
36774        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36775      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36776
36777      let payload = catch_unwind(AssertUnwindSafe(|| {
36778        let mut future = std::pin::pin!(async {
36779          make_sleep(
36780            &backend,
36781            &registry_with(driver),
36782            Instant::now() + Duration::from_millis(60),
36783          )
36784          .await;
36785        });
36786        executor.block_on(future.as_mut());
36787      }))
36788      .expect_err("a threadless park must self-detect even while a host timer is pending");
36789      let diagnostic = payload
36790        .downcast_ref::<BlockOnDeadlock>()
36791        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36792      assert_eq!(
36793        diagnostic.kind,
36794        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
36795      );
36796      assert_eq!(
36797        diagnostic.park_deadline, None,
36798        "the certain case carries no deadline"
36799      );
36800      let message = diagnostic.to_string();
36801      assert!(
36802        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
36803        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
36804      );
36805      let _ = done_tx.send(());
36806    });
36807
36808    match done_rx.recv_timeout(Duration::from_secs(10)) {
36809      Ok(()) => runner.join().unwrap(),
36810      Err(error) => panic!(
36811        "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})"
36812      ),
36813    }
36814  }
36815
36816  #[cfg(panic = "unwind")]
36817  #[test]
36818  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
36819    // Unpolled-sleep invariant: the registry entry only exists once the host
36820    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
36821    // creation. A created-but-never-polled Sleep has no host callback behind
36822    // it, and on a parked single thread nothing can ever poll it into
36823    // arming -- it is provably NOT a future wake source. Counting it would
36824    // mislabel the certain diagnostic as timer-backed. (The armed twin above
36825    // pins the polled side: a POLLED pending timer still certain-panics, just
36826    // with the timer-backed kind.)
36827    use std::sync::mpsc;
36828
36829    let (done_tx, done_rx) = mpsc::channel();
36830    let runner = std::thread::spawn(move || {
36831      let metrics = Arc::new(RuntimeMetrics::default());
36832      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36833      let backend =
36834        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36835      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36836
36837      // Created and held across the park decision, but NEVER polled: no host
36838      // timer armed, no wake source.
36839      let _unpolled = make_sleep(
36840        &backend,
36841        &registry_with(driver),
36842        Instant::now() + Duration::from_secs(60),
36843      );
36844
36845      let payload = catch_unwind(AssertUnwindSafe(|| {
36846        let mut future = std::pin::pin!(NeverReady);
36847        executor.block_on(future.as_mut());
36848      }))
36849      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
36850      let diagnostic = payload
36851        .downcast_ref::<BlockOnDeadlock>()
36852        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36853      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
36854      let _ = done_tx.send(());
36855    });
36856
36857    match done_rx.recv_timeout(Duration::from_secs(10)) {
36858      Ok(()) => runner.join().unwrap(),
36859      Err(error) => panic!(
36860        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
36861      ),
36862    }
36863  }
36864
36865  #[cfg(napi_runtime_os_threads)]
36866  #[test]
36867  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
36868    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
36869    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
36870    // legitimate (the wake is scheduled on the host loop); the deadline
36871    // verdict must not fire. Once the timer fires, the sleep completes.
36872    use std::sync::mpsc;
36873
36874    let (done_tx, done_rx) = mpsc::channel();
36875    let runner = std::thread::spawn(move || {
36876      let metrics = Arc::new(RuntimeMetrics::default());
36877      // Deadline/grace ratio: the armed park deadline doubles as the
36878      // `has_live_wait` GRACE -- once the host timer is past due by more
36879      // than one deadline window without firing, the veto stops holding BY
36880      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
36881      // the stub host's freshly spawned fire thread had to get CPU within
36882      // ~230ms of test start or the verdict fired -- the starved-CI failure.
36883      // A 2s window keeps the invariant identical (a LIVE host-timer wait
36884      // with zero progress must not be declared dead) while tolerating
36885      // multi-second scheduling stalls of the fire thread: the veto holds
36886      // until deadline+2s, and the expiry checks land on 2s boundaries, so
36887      // the fire has >4s before a verdict could stick.
36888      let executor = Arc::new(CurrentThreadExecutor::with_detection(
36889        metrics,
36890        false,
36891        Some(Duration::from_secs(2)),
36892      ));
36893      let backend =
36894        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36895      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36896
36897      let started = Instant::now();
36898      let mut future = std::pin::pin!(async {
36899        make_sleep(
36900          &backend,
36901          &registry_with(driver),
36902          Instant::now() + Duration::from_millis(200),
36903        )
36904        .await;
36905      });
36906      executor.block_on(future.as_mut());
36907      assert!(started.elapsed() >= Duration::from_millis(200));
36908      let _ = done_tx.send(());
36909    });
36910
36911    match done_rx.recv_timeout(Duration::from_secs(30)) {
36912      Ok(()) => runner.join().unwrap(),
36913      Err(error) => {
36914        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
36915      }
36916    }
36917  }
36918
36919  #[test]
36920  fn host_timer_unrepresentable_grace_remains_live() {
36921    let registry = HostTimerRegistry::default();
36922    let fired = Arc::new(AtomicBool::new(false));
36923    let drivers = Arc::new(TimerDriverRegistry::default());
36924    let repoll = Arc::new(HostTimerRepoll::default());
36925    assert!(
36926      Instant::now().checked_add(Duration::MAX).is_none(),
36927      "Duration::MAX must exercise the unrepresentable-grace path"
36928    );
36929    assert!(
36930      registry.register(
36931        1,
36932        Instant::now(),
36933        futures::task::noop_waker(),
36934        &fired,
36935        &drivers,
36936        &repoll,
36937      ),
36938      "the open registry must accept the timer"
36939    );
36940
36941    assert!(
36942      registry.has_live_wait(Duration::MAX),
36943      "an unrepresentable grace deadline is effectively unbounded"
36944    );
36945    registry.remove(1);
36946  }
36947
36948  // `BlockOnDeadlock::multi_thread_cooperative` only exists where the
36949  // MultiThread machinery is compiled in.
36950  #[cfg(napi_runtime_os_threads)]
36951  #[test]
36952  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
36953    // A deadline firing inside a spawned task's poll unwinds into the spawn
36954    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
36955    // message must survive that trip (JoinError::from_panic downcasts it), or
36956    // the build error would read "async runtime task panicked" with no clue.
36957    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
36958    let expected = diagnostic.to_string();
36959    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
36960    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
36961  }
36962  // --- wasm32-wasip1-threads ------------------------------------------------
36963  //
36964  // `rustc --print cfg` is identical for the two WASI targets, so these pin
36965  // what the build script's cfgs are supposed to mean. They run only where
36966  // `napi_runtime_wasi_threads` holds; the threadless complement is pinned by
36967  // `threadless_wasm_still_rejects_multi_thread` below.
36968
36969  #[cfg(napi_runtime_wasi_threads)]
36970  #[test]
36971  fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
36972    // Rayon's `THREADS_BITS` is 8 on 32-bit, so its ceiling is 255 and the
36973    // production cap of 256 never binds here. The `>= 2` half is what
36974    // `validate()` needs to accept MultiThread at all.
36975    let ceiling = crate::max_async_runtime_worker_threads();
36976    assert_eq!(
36977      ceiling, 255,
36978      "the threaded WASI target must report Rayon's realizable ceiling"
36979    );
36980    assert!(
36981      ceiling >= 2,
36982      "a ceiling below two would make MultiThread unconfigurable"
36983    );
36984  }
36985
36986  #[cfg(napi_runtime_wasi_threads)]
36987  #[test]
36988  fn wasi_threads_default_flavor_is_still_current_thread() {
36989    // Availability is not a default. Selecting MultiThread on a wasm target
36990    // stays an explicit host act, so a host that changes nothing sees no
36991    // change at all.
36992    assert_eq!(
36993      RuntimeOptions::default().flavor,
36994      RuntimeFlavor::CurrentThread,
36995      "the threaded WASI target must not silently default to MultiThread"
36996    );
36997  }
36998
36999  #[cfg(napi_runtime_wasi_threads)]
37000  #[test]
37001  fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
37002    // `available_parallelism()` returns Ok(1) under a WASI host, so a host
37003    // that flips only the flavor gets MultiThread's truthful minimum -- two
37004    // workers, one blocking lane -- rather than an error or a pool worth
37005    // having. Hosts that want parallelism must pass a worker count they got
37006    // from JavaScript.
37007    let validated = RuntimeOptions {
37008      flavor: RuntimeFlavor::MultiThread,
37009      ..RuntimeOptions::default()
37010    }
37011    .validate()
37012    .expect("MultiThread must validate on the threaded WASI target");
37013    assert_eq!(validated.worker_threads, 2);
37014    assert_eq!(validated.max_blocking_tasks, 1);
37015  }
37016
37017  #[cfg(napi_runtime_wasi_threads)]
37018  #[test]
37019  fn threadless_build_is_false_on_the_threads_target() {
37020    // `THREADLESS_BUILD` and `napi_runtime_os_threads` are orthogonal cfgs
37021    // that must agree on this one target, or the CurrentThread park logic and
37022    // the MultiThread machinery would disagree about whether a second thread
37023    // can deliver a wake.
37024    assert!(
37025      !THREADLESS_BUILD,
37026      "the threaded WASI target has real OS threads"
37027    );
37028    assert!(
37029      cfg!(napi_runtime_os_threads),
37030      "the threaded WASI target must build the OS-thread machinery"
37031    );
37032  }
37033
37034  // Memory-size refresh at scheduler handoffs (`on_thread_handoff`). This
37035  // napi-free binary links no allocator wrappers, so only `grow_zero` and the
37036  // hook ever set a thread's `local_pages`. Each test grows the memory on
37037  // another thread and publishes the new size, as napi's allocator does after
37038  // dlmalloc grows it, then reads `local_pages` inside the work under test.
37039  // Every thread that has not refreshed since is below that size, so a
37040  // reading at or above it proves the hook at that site ran; without the hook
37041  // each test fails its assertion.
37042
37043  /// Grow the memory by one page on a new thread and publish the new size,
37044  /// which this returns.
37045  #[cfg(napi_runtime_wasi_threads)]
37046  fn grow_one_page_elsewhere() -> usize {
37047    std::thread::spawn(|| {
37048      let old = core::arch::wasm32::memory_grow::<0>(1);
37049      assert_ne!(old, usize::MAX, "the WASI host must let the memory grow");
37050      napi_sys::wasi_heap_sync::grow_zero()
37051    })
37052    .join()
37053    .expect("the growing thread must finish")
37054  }
37055
37056  #[cfg(napi_runtime_wasi_threads)]
37057  #[test]
37058  fn wasi_threads_multi_thread_task_polls_refresh_the_memory_size() {
37059    // `MultiThreadExecutor::run_runnable`: a pool worker polls a task spawned
37060    // after another thread grew the memory.
37061    let controller = multi_thread_controller("wasi-handoff-task", 2, 1);
37062    let pages = grow_one_page_elsewhere();
37063    let refreshes = napi_sys::wasi_heap_sync::handoff_refreshes();
37064    let seen = futures::executor::block_on(
37065      controller.spawn(async { napi_sys::wasi_heap_sync::local_pages() }),
37066    )
37067    .expect("the task must finish");
37068    assert!(
37069      seen >= pages,
37070      "the worker polled with a stale memory size: {seen} < {pages} pages"
37071    );
37072    assert!(
37073      napi_sys::wasi_heap_sync::handoff_refreshes() > refreshes,
37074      "the handoff refresh must be counted"
37075    );
37076    controller.shutdown().expect("runtime must stop");
37077  }
37078
37079  #[cfg(napi_runtime_wasi_threads)]
37080  #[test]
37081  fn wasi_threads_current_thread_task_polls_refresh_the_memory_size() {
37082    // The free `run_runnable`: the thread driving a host turn polls a task
37083    // after another thread grew the memory.
37084    use std::sync::mpsc;
37085
37086    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37087      RuntimeMetrics::default(),
37088    )));
37089    let (seen_tx, seen_rx) = mpsc::channel();
37090    let wake_executor = Arc::clone(&executor);
37091    let (runnable, task) = async_task::spawn(
37092      async move {
37093        let _ = seen_tx.send(napi_sys::wasi_heap_sync::local_pages());
37094      },
37095      move |runnable| wake_executor.schedule(runnable),
37096    );
37097    executor.schedule(runnable);
37098    let pages = grow_one_page_elsewhere();
37099    executor.drive_host_turn();
37100    let seen = seen_rx
37101      .try_recv()
37102      .expect("one host turn must poll the task");
37103    futures::executor::block_on(task);
37104    assert!(
37105      seen >= pages,
37106      "the host turn polled with a stale memory size: {seen} < {pages} pages"
37107    );
37108  }
37109
37110  #[cfg(napi_runtime_wasi_threads)]
37111  #[test]
37112  fn wasi_threads_block_on_polls_refresh_the_memory_size() {
37113    // `CurrentThreadExecutor::block_on` and
37114    // `MultiThreadExecutor::parking_block_on`: the caller polls after another
37115    // thread grew the memory.
37116    for controller in [
37117      current_thread_controller("wasi-handoff-block-on"),
37118      multi_thread_controller("wasi-handoff-parking-block-on", 2, 1),
37119    ] {
37120      let pages = grow_one_page_elsewhere();
37121      let mut seen = 0;
37122      {
37123        let mut future = std::pin::pin!(async {
37124          seen = napi_sys::wasi_heap_sync::local_pages();
37125        });
37126        assert_eq!(
37127          controller.block_on(future.as_mut()),
37128          BlockOnOutcome::Completed
37129        );
37130      }
37131      assert!(
37132        seen >= pages,
37133        "block_on polled with a stale memory size: {seen} < {pages} pages"
37134      );
37135      controller.shutdown().expect("runtime must stop");
37136    }
37137  }
37138
37139  #[cfg(napi_runtime_wasi_threads)]
37140  #[test]
37141  fn wasi_threads_blocking_closures_refresh_the_memory_size() {
37142    // `RegisteredBlockingFunction::run`: a pool worker starts a closure
37143    // submitted after another thread grew the memory. Inside it,
37144    // `cooperative_block_on`: the same worker polls after a second grow.
37145    let controller = Arc::new(multi_thread_controller("wasi-handoff-blocking", 2, 1));
37146    let pages = grow_one_page_elsewhere();
37147    let block_on_controller = Arc::clone(&controller);
37148    let (start_seen, block_on_pages, block_on_seen) =
37149      futures::executor::block_on(controller.spawn_blocking(move || {
37150        let start_seen = napi_sys::wasi_heap_sync::local_pages();
37151        let block_on_pages = grow_one_page_elsewhere();
37152        let mut block_on_seen = 0;
37153        {
37154          let mut future = std::pin::pin!(async {
37155            block_on_seen = napi_sys::wasi_heap_sync::local_pages();
37156          });
37157          assert_eq!(
37158            block_on_controller.block_on(future.as_mut()),
37159            BlockOnOutcome::Completed
37160          );
37161        }
37162        (start_seen, block_on_pages, block_on_seen)
37163      }))
37164      .expect("the blocking closure must finish");
37165    assert!(
37166      start_seen >= pages,
37167      "the blocking closure started with a stale memory size: {start_seen} < {pages} pages"
37168    );
37169    assert!(
37170      block_on_seen >= block_on_pages,
37171      "the cooperative block_on polled with a stale memory size: \
37172       {block_on_seen} < {block_on_pages} pages"
37173    );
37174    controller.shutdown().expect("runtime must stop");
37175  }
37176
37177  #[cfg(napi_runtime_wasi_threads)]
37178  #[test]
37179  fn wasi_threads_inline_blocking_refreshes_the_memory_size() {
37180    // The CurrentThread inline blocking path: the submitting thread runs the
37181    // closure itself, after another thread grew the memory.
37182    let controller = current_thread_controller("wasi-handoff-inline-blocking");
37183    let pages = grow_one_page_elsewhere();
37184    let seen =
37185      futures::executor::block_on(controller.spawn_blocking(napi_sys::wasi_heap_sync::local_pages))
37186        .expect("the inline blocking closure must finish");
37187    assert!(
37188      seen >= pages,
37189      "the inline blocking closure ran with a stale memory size: {seen} < {pages} pages"
37190    );
37191    controller.shutdown().expect("runtime must stop");
37192  }
37193
37194  // `not(napi_runtime_os_threads)` is exactly "wasm and not the threaded WASI
37195  // target": the build script sets the cfg for every non-wasm target plus
37196  // `wasm32-wasip1-threads`.
37197  #[cfg(not(napi_runtime_os_threads))]
37198  #[test]
37199  fn threadless_wasm_still_rejects_multi_thread() {
37200    let error = RuntimeOptions {
37201      flavor: RuntimeFlavor::MultiThread,
37202      ..RuntimeOptions::default()
37203    }
37204    .validate()
37205    .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
37206    let message = error.to_string();
37207    assert!(
37208      message.contains("threadless"),
37209      "the rejection must name the property that causes it, not the family: {message}"
37210    );
37211    assert!(
37212      message.contains("wasm32-wasip1-threads"),
37213      "the rejection must name the target that does work: {message}"
37214    );
37215  }
37216
37217  #[cfg(napi_runtime_os_threads)]
37218  #[test]
37219  fn multi_thread_pool_starts_and_joins_every_worker() {
37220    // The disposal order the WASI loader depends on, as a unit test: every
37221    // worker is joined BEFORE anything tears the environment down, so no
37222    // thread can still be running over memory that is about to disappear.
37223    let options = RuntimeOptions {
37224      flavor: RuntimeFlavor::MultiThread,
37225      worker_threads: 3,
37226      max_blocking_tasks: 2,
37227      thread_name_prefix: "rd-os-threads-join".to_string(),
37228      park_deadline: None,
37229      drain_linger: DEFAULT_DRAIN_LINGER,
37230    };
37231    let executor = Arc::new(
37232      MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
37233        .expect("a three-worker pool must build"),
37234    );
37235    assert_eq!(executor.pool.current_num_threads(), 3);
37236
37237    let ran = Arc::new(AtomicBool::new(false));
37238    let task_ran = Arc::clone(&ran);
37239    let (runnable, task) = async_task::spawn(
37240      async move {
37241        task_ran.store(true, Ordering::SeqCst);
37242      },
37243      |_| {},
37244    );
37245    executor.schedule(runnable);
37246    futures::executor::block_on(task);
37247    assert!(
37248      ran.load(Ordering::SeqCst),
37249      "the pool must run scheduled work"
37250    );
37251
37252    let lifecycle = Arc::clone(&executor.worker_lifecycle);
37253    executor.begin_shutdown();
37254    executor.wait_until_scheduler_idle();
37255    // A drainer's Rayon job decrements `active_drainers` before its closure
37256    // is dropped, so the last reference can lag the idle barrier by a moment.
37257    // Spin for it under a deadline: taking the value out is what proves the
37258    // pool really goes away here, and turns a leaked reference into a
37259    // failure rather than a hang in the join below.
37260    let deadline = Instant::now() + Duration::from_secs(10);
37261    while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
37262      std::thread::yield_now();
37263    }
37264    let executor = Arc::into_inner(executor)
37265      .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
37266    drop(executor);
37267    lifecycle.wait_for_all_workers();
37268    assert_eq!(
37269      lifecycle.remaining(),
37270      0,
37271      "every physical worker must have exited before the join returns"
37272    );
37273  }
37274
37275  #[cfg(napi_runtime_os_threads)]
37276  #[test]
37277  fn current_thread_queues_concurrent_blocking_admission() {
37278    // A second OS thread submitting `spawn_blocking` while the lane owner
37279    // runs, carrying no `ambient_owner`. The gate that decides this used to
37280    // read `target_family = "wasm"` while its comment argued "threadless", so
37281    // on a threaded wasm build the closure was dropped and the handle
37282    // resolved to "the async runtime stopped before the blocking task could
37283    // start". It must be QUEUED and serviced on a later host turn.
37284    use std::sync::mpsc;
37285
37286    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
37287      RuntimeMetrics::default(),
37288    )));
37289    let work = GenerationWork::new();
37290    let (outcome_tx, outcome_rx) = mpsc::channel();
37291
37292    let owner_registration = work
37293      .try_register_work()
37294      .expect("the lane owner's blocking job must be accepted");
37295    let concurrent_work = Arc::clone(&work);
37296    let concurrent_executor = Arc::clone(&executor);
37297    let task_executor = Arc::clone(&executor);
37298    let wake_executor = Arc::clone(&executor);
37299    let (runnable, task) = async_task::spawn(
37300      async move {
37301        let concurrent = task_executor
37302          .schedule_blocking(
37303            move || {
37304              std::thread::spawn(move || {
37305                let registration = concurrent_work
37306                  .try_register_work()
37307                  .expect("the concurrent blocking job must be accepted");
37308                concurrent_executor.schedule_blocking(|| 7usize, registration)
37309              })
37310              .join()
37311              .expect("the concurrent submitter thread must finish")
37312            },
37313            owner_registration,
37314          )
37315          .await
37316          .expect("the lane owner's blocking job must finish");
37317        let _ = outcome_tx.send(concurrent.await);
37318      },
37319      move |runnable| wake_executor.schedule(runnable),
37320    );
37321
37322    executor.schedule(runnable);
37323    let mut turns = 0usize;
37324    let outcome = loop {
37325      executor.drive_host_turn();
37326      if let Ok(outcome) = outcome_rx.try_recv() {
37327        break outcome;
37328      }
37329      turns += 1;
37330      assert!(
37331        turns <= 8,
37332        "queued blocking admission must make bounded host-turn progress"
37333      );
37334    };
37335    futures::executor::block_on(task);
37336    assert_eq!(
37337      outcome.expect("a concurrent blocking submission must be queued, not rejected"),
37338      7
37339    );
37340  }
37341}