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, Condvar, LazyLock, Mutex, 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;
24// Both users are MultiThread pool tests.
25#[cfg(all(test, napi_runtime_os_threads))]
26use async_task::Task;
27use async_task::{FallibleTask, Runnable};
28use futures::{
29  FutureExt,
30  future::{AbortHandle, AbortRegistration, Abortable},
31};
32
33#[cfg(napi_runtime_os_threads)]
34use crossbeam_deque::{Injector, Steal};
35#[cfg(napi_runtime_os_threads)]
36use futures::channel::oneshot;
37#[cfg(all(test, napi_runtime_os_threads))]
38use rayon::max_num_threads;
39#[cfg(napi_runtime_os_threads)]
40use rayon::{ThreadPool, ThreadPoolBuilder};
41
42/// Stable scheduler identities are equality capabilities. Exhaustion must fail
43/// before reuse can authorize a stale handle or collide in an indexed queue.
44fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
45  counter
46    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
47    .expect(exhausted)
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum RuntimeFlavor {
52  CurrentThread,
53  MultiThread,
54}
55
56#[derive(Debug, Clone)]
57pub struct RuntimeOptions {
58  pub flavor: RuntimeFlavor,
59  /// Physical worker count for [`RuntimeFlavor::MultiThread`]; forced to 1 on
60  /// [`RuntimeFlavor::CurrentThread`]. Validation clamps it to
61  /// `2..=max_async_runtime_worker_threads()`, so MultiThread always has at
62  /// least two lanes.
63  ///
64  /// The scheduler NEVER probes the CPU count on a wasm target, because there
65  /// is nothing truthful to probe: `std::thread::available_parallelism()`
66  /// answers `Ok(1)` on `wasm32-wasip1-threads`, so the default lands on the
67  /// clamped minimum of two. The host has the real number
68  /// (`os.availableParallelism()` in Node, `navigator.hardwareConcurrency` in
69  /// a browser) and must pass it through [`configure`] or
70  /// [`configure_partial`] before the first async call.
71  ///
72  /// Peak thread count is `worker_threads + 1`: [`sleep_until`] lazily spawns
73  /// one non-Rayon timekeeper thread on the first timer registration. Under
74  /// `wasm32-wasip1-threads` each of those is a Node Worker, so budget for
75  /// `worker_threads + 1` of them, not `worker_threads`.
76  pub worker_threads: usize,
77  pub max_blocking_tasks: usize,
78  pub thread_name_prefix: String,
79  /// Deadline-based `block_on` deadlock detection: bounds how long a
80  /// runtime-owned park (the CurrentThread `block_on` parker and
81  /// MultiThread COOPERATIVE driver parks -- never the foreign/napi
82  /// whole-build park) may sleep with ZERO runtime progress before
83  /// panicking with the typed [`BlockOnDeadlock`] diagnostic. `None` (the
84  /// default) disables deadline detection: a legitimately long park (e.g.
85  /// awaiting a slow JS plugin) must never panic a production build. The
86  /// runtime itself never reads the environment; the embedder resolves its
87  /// own configuration (conventionally a [`PARK_DEADLINE_ENV`]-style
88  /// variable) into this field at addon load. The
89  /// threadless-wasm CERTAIN deadlock check is independent of this knob and
90  /// always on.
91  pub park_deadline: Option<Duration>,
92  /// MultiThread drainer idle-linger budget (see `MultiThreadExecutor::drain`):
93  /// after running the runnable/blocking queues empty a drainer parks on its
94  /// own parker for up to this long before returning its thread to rayon, so
95  /// a wave landing within the budget resumes it with one targeted wake
96  /// instead of a `spawn_fifo` respawn through rayon's idle protocol.
97  /// `Duration::ZERO` disables lingering (legacy exit-and-respawn on every
98  /// empty queue). Validation clamps the budget to
99  /// [`MAX_DRAIN_LINGER_MICROS`]: a linger occupies a pool worker and an
100  /// `active_drainers` slot while idle, so an unbounded value would pin
101  /// workers indefinitely. As with `park_deadline`, the runtime itself never
102  /// reads the environment; the embedder resolves its configuration
103  /// (conventionally [`DRAIN_LINGER_ENV`], which the napi adapter's `install`
104  /// resolves for hosts that use it) into this field at addon load. Ignored
105  /// by the CurrentThread flavor, which has no pool drainers.
106  pub drain_linger: Duration,
107}
108
109/// A partial update to the runtime options exposed by the binding.
110///
111/// [`configure_partial`] merges these fields into the latest configured
112/// options, validates the merged candidate, and commits it while holding the
113/// runtime controller lock. Fields that are not exposed for partial updates,
114/// such as `thread_name_prefix` and `park_deadline`, remain unchanged.
115#[derive(Debug, Clone, Copy, Default)]
116pub struct RuntimeOptionsPatch {
117  pub flavor: Option<RuntimeFlavor>,
118  pub worker_threads: Option<usize>,
119  pub max_blocking_tasks: Option<usize>,
120  /// `Some(Duration::ZERO)` disables lingering; other values are clamped to
121  /// [`MAX_DRAIN_LINGER_MICROS`] by validation.
122  pub drain_linger: Option<Duration>,
123}
124
125impl RuntimeOptionsPatch {
126  fn apply_to(self, options: &mut RuntimeOptions) {
127    if let Some(flavor) = self.flavor {
128      options.flavor = flavor;
129    }
130    if let Some(worker_threads) = self.worker_threads {
131      options.worker_threads = worker_threads;
132    }
133    if let Some(max_blocking_tasks) = self.max_blocking_tasks {
134      options.max_blocking_tasks = max_blocking_tasks;
135    }
136    if let Some(drain_linger) = self.drain_linger {
137      options.drain_linger = drain_linger;
138    }
139  }
140}
141
142impl Default for RuntimeOptions {
143  fn default() -> Self {
144    let worker_threads = std::thread::available_parallelism()
145      .map_or(1, usize::from)
146      .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
147    Self {
148      flavor: if cfg!(target_family = "wasm") {
149        RuntimeFlavor::CurrentThread
150      } else {
151        RuntimeFlavor::MultiThread
152      },
153      worker_threads,
154      max_blocking_tasks: worker_threads,
155      thread_name_prefix: "napi-async-runtime".to_string(),
156      park_deadline: None,
157      drain_linger: DEFAULT_DRAIN_LINGER,
158    }
159  }
160}
161
162impl RuntimeOptions {
163  fn validate(self) -> Result<Self, RuntimeConfigError> {
164    #[cfg(not(napi_runtime_os_threads))]
165    let rayon_max_threads = None;
166    #[cfg(napi_runtime_os_threads)]
167    let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
168    self.validate_with_rayon_max_threads(rayon_max_threads)
169  }
170
171  fn validate_with_rayon_max_threads(
172    mut self,
173    rayon_max_threads: Option<usize>,
174  ) -> Result<Self, RuntimeConfigError> {
175    if self.worker_threads == 0 {
176      return Err(RuntimeConfigError(
177        "worker_threads must be greater than zero".to_string(),
178      ));
179    }
180    if self.max_blocking_tasks == 0 {
181      return Err(RuntimeConfigError(
182        "max_blocking_tasks must be greater than zero".to_string(),
183      ));
184    }
185    // A lingering drainer occupies a pool worker and an `active_drainers`
186    // slot while idle, so an unbounded budget (e.g. an env typo resolving to
187    // u64::MAX microseconds) would pin workers indefinitely and wedge every
188    // idle-wait. Clamp instead of erroring: like the thread-count clamps
189    // below, an out-of-range value is a tuning excess, not a topology error.
190    // ZERO passes through unchanged and means "lingering disabled".
191    self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
192    if self.flavor == RuntimeFlavor::CurrentThread {
193      self.worker_threads = 1;
194      self.max_blocking_tasks = 1;
195    } else {
196      let Some(rayon_max_threads) = rayon_max_threads else {
197        return Err(RuntimeConfigError(
198          "the multi-thread runtime is unavailable in this threadless WebAssembly build \
199           (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
200            .to_string(),
201        ));
202      };
203      if rayon_max_threads < 2 {
204        return Err(RuntimeConfigError(
205          "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
206        ));
207      }
208      // Blocking closures execute on the shared Rayon pool. Keep one execution
209      // lane available for runnable futures and timer service even when every
210      // admitted blocking job is stalled. MultiThread therefore has a truthful
211      // minimum of two physical/configured workers rather than a hidden reserve.
212      // Apply both this crate's production ceiling and Rayon's platform cap
213      // before deriving the blocking allowance, so a silently reduced physical
214      // pool cannot consume that reserved lane.
215      self.worker_threads = self
216        .worker_threads
217        .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
218        .min(rayon_max_threads);
219      let blocking_capacity = self.worker_threads - 1;
220      self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
221    }
222    Ok(self)
223  }
224}
225
226/// Environment variable that arms deadline-based `block_on` deadlock
227/// detection: milliseconds a runtime-owned park may sleep with zero runtime
228/// progress before panicking (see [`RuntimeOptions::park_deadline`]).
229/// Missing, non-numeric or `0` all mean "disabled". The runtime does NOT
230/// read this variable itself: the embedding host's env-resolution parses it
231/// once at addon load and hands the result to [`configure`] through
232/// [`RuntimeOptions::park_deadline`]. The name is the conventional default;
233/// hosts may resolve their own variable into the same field.
234pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
235
236/// Environment variable overriding the MultiThread drainer idle-linger budget
237/// in MICROSECONDS ([`RuntimeOptions::drain_linger`], consumed by
238/// `MultiThreadExecutor::drain`). After a drainer runs the runnable/blocking
239/// queues empty it parks on its own [`DriverParker`] (registered with
240/// `parked_drivers`) for up to this long before returning its thread to
241/// rayon. A wave of work arriving within the budget is resumed by ONE
242/// targeted condvar wake instead of a `spawn_fifo` respawn through rayon's
243/// idle protocol -- whose per-transition cost (a wake cascade plus 33
244/// `sched_yield` rounds and a full steal sweep on EVERY worker that was
245/// woken) is what makes system CPU scale with the worker count under
246/// wave-shaped loads. `0` disables lingering (legacy exit-and-respawn on
247/// every empty queue); values above [`MAX_DRAIN_LINGER_MICROS`] are clamped.
248/// A frame's TOTAL lingering residence is additionally capped at
249/// [`DRAIN_LINGER_FRAME_FACTOR`] times this budget so periodic sub-budget
250/// work cannot pin a rayon worker inside one drain frame forever.
251///
252/// Like [`PARK_DEADLINE_ENV`], the runtime does NOT read this variable
253/// itself: it is resolved once at addon load into
254/// [`RuntimeOptions::drain_linger`] -- by the napi adapter's `install` (via
255/// `resolve_drain_linger`) for hosts that use it, while hosts owning their
256/// own env resolution may resolve their own variable into the same field. A
257/// present-but-non-numeric value keeps the host-provided field (which
258/// defaults to [`DEFAULT_DRAIN_LINGER_MICROS`]).
259pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
260
261/// Default drainer idle-linger budget (µs): [`RuntimeOptions::drain_linger`]'s
262/// default, applied when no host configuration or [`DRAIN_LINGER_ENV`]
263/// override resolves a different value.
264pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
265
266/// Ceiling on [`RuntimeOptions::drain_linger`] in MICROSECONDS (100ms).
267/// Validation (and, defensively, executor construction) clamps the budget
268/// here: lingering exists to bridge micro-scale gaps between scheduling
269/// waves, and a lingering drainer holds a pool worker plus an
270/// `active_drainers` slot while idle, so budgets beyond this bound stop
271/// tuning the wave optimization and start pinning workers.
272pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
273
274/// [`DEFAULT_DRAIN_LINGER_MICROS`] as a `Duration`.
275const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
276
277/// [`MAX_DRAIN_LINGER_MICROS`] as a `Duration`.
278const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
279
280/// Bound on a single drain frame's TOTAL lingering residence, as a multiple
281/// of the per-episode idle budget (default 16 x 500us = 8ms). The per-episode
282/// deadline alone does NOT bound how long a drain frame occupies its rayon
283/// worker: every executed work unit re-arms it, so periodic sub-budget work
284/// (e.g. a timer publishing one short task every <500us) would keep the frame
285/// resident FOREVER -- and a raw rayon job spawned onto the pool (which only
286/// a worker that returned to rayon's scheduler can run) would starve behind
287/// it indefinitely. Once cumulative residence past the frame's FIRST idle
288/// observation reaches this bound, the drainer exits through the unchanged
289/// `finish_draining` path even though more sub-budget work may be arriving;
290/// the exit-and-respawn through rayon's queue is exactly the interleaving
291/// point that lets pending raw rayon work (par_iter splits, `pool.spawn`
292/// jobs) take the worker before the re-armed FIFO drain.
293#[cfg(napi_runtime_os_threads)]
294const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
295
296/// Ceiling on the frame residence bound so an absurd [`DRAIN_LINGER_ENV`]
297/// value cannot arm a deadline beyond the platform's representable range.
298#[cfg(napi_runtime_os_threads)]
299const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
300
301/// True on builds where no second thread can EVER deliver a wake to a parked
302/// `block_on`: every wasm build except the threaded WASI target (i.e. the
303/// single-thread `wasm32-wasip1` build and `wasm32-unknown-unknown`). On such
304/// a build a CurrentThread park decision with an empty queue and no pending
305/// wake token is a PROVABLE deadlock. The threaded wasi build
306/// (`wasm32-wasip1-threads`) has real OS threads, so its parks are not
307/// provably dead and fall under the optional deadline detection instead, like
308/// native builds.
309///
310/// `napi_runtime_wasi_threads` is emitted by this crate's build.rs for the exact
311/// `wasm32-wasip1-threads` cargo TARGET. It is NOT derivable from built-in
312/// cfgs: on current rustc the two WASI targets expose identical cfg sets --
313/// `cfg!(target_feature = "atomics")` is false even on the threads target
314/// (verified empirically; embedders needing the same discrimination use the
315/// same build.rs mechanism).
316const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
317
318#[derive(Debug, Clone)]
319pub struct RuntimeConfigError(String);
320
321impl fmt::Display for RuntimeConfigError {
322  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323    f.write_str(&self.0)
324  }
325}
326
327impl std::error::Error for RuntimeConfigError {}
328
329/// Which self-detected `block_on` deadlock class fired.
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum BlockOnDeadlockKind {
332  /// CurrentThread on a threadless build: the park decision found an empty
333  /// queue and no pending wake token, and no other thread exists that could
334  /// ever deliver one -- a PROVABLE deadlock, detected unconditionally (not
335  /// timing-based).
336  CurrentThreadCertain,
337  /// [`Self::CurrentThreadCertain`] detected while a HOST timer was pending.
338  /// The timer does not make the park any less dead on a threadless build:
339  /// its host callback (the JS `setTimeout` relay) can only run on the very
340  /// thread that is about to park, so it can never fire while `block_on`
341  /// holds that thread. A distinct kind only so the diagnostic can name the
342  /// shape the user actually hit.
343  CurrentThreadCertainTimerBacked,
344  /// Threaded CurrentThread: a `block_on` park outlived the configured
345  /// deadline with zero runtime progress.
346  CurrentThreadDeadline,
347  /// MultiThread: a cooperative pool driver's re-entrant `block_on` park
348  /// outlived the configured deadline with zero executor progress. The
349  /// foreign/napi whole-build park is exempt by design.
350  MultiThreadCooperativeDeadline,
351}
352
353/// Typed panic payload for a self-detected `block_on` deadlock. Thrown with
354/// `std::panic::panic_any` at the park DECISION (never merely on a `Pending`
355/// return, because a self-waking future is not a deadlock), so the
356/// runtime fails loudly instead of freezing the thread (and, in the
357/// CurrentThread-on-JS-thread case, the whole JS event loop, where not even
358/// test-harness timeouts can fire). When the panic unwinds out of a spawned
359/// task's poll, `JoinError::from_panic` preserves this diagnostic's message.
360#[derive(Debug, Clone)]
361pub struct BlockOnDeadlock {
362  pub kind: BlockOnDeadlockKind,
363  /// The armed deadline for the deadline-based kinds; `None` for the provable
364  /// threadless case.
365  pub park_deadline: Option<Duration>,
366}
367
368impl BlockOnDeadlock {
369  fn current_thread_certain() -> Self {
370    Self {
371      kind: BlockOnDeadlockKind::CurrentThreadCertain,
372      park_deadline: None,
373    }
374  }
375
376  fn current_thread_certain_timer_backed() -> Self {
377    Self {
378      kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
379      park_deadline: None,
380    }
381  }
382
383  fn current_thread_deadline(deadline: Duration) -> Self {
384    Self {
385      kind: BlockOnDeadlockKind::CurrentThreadDeadline,
386      park_deadline: Some(deadline),
387    }
388  }
389
390  // The MultiThread executor (and thus this constructor's caller) does not
391  // exist on wasm builds.
392  #[cfg(napi_runtime_os_threads)]
393  fn multi_thread_cooperative(deadline: Duration) -> Self {
394    Self {
395      kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
396      park_deadline: Some(deadline),
397    }
398  }
399}
400
401impl fmt::Display for BlockOnDeadlock {
402  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403    let deadline_ms = self
404      .park_deadline
405      .map_or(0, |deadline| deadline.as_millis());
406    match self.kind {
407      BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
408        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
409         park with an empty task queue and no pending wake on a threadless build -- no other \
410         thread exists that could ever deliver the wake, so this park would never end. This is \
411         the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
412         (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
413         dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
414         continuation is the one now parking.",
415      ),
416      BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
417        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
418         park with an empty task queue on a threadless build while a host timer is pending. \
419         The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
420         only run on the very thread that is now parking, so it can never fire, and no other \
421         thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
422         hazard: native code called `block_on` on a future that (transitively) awaits a JS \
423         continuation, but the only thread that could run that continuation is the one now \
424         parking.",
425      ),
426      BlockOnDeadlockKind::CurrentThreadDeadline => write!(
427        f,
428        "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
429         {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
430         task queue, no pending wake and ZERO runtime progress across the whole window. This is \
431         the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
432         awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
433         once this thread returns to the event loop. If this park is legitimate, raise or unset \
434         the deadline.",
435      ),
436      BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
437        f,
438        "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
439         parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
440         no runnable or blocking work available and ZERO executor progress across the whole \
441         window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
442         The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
443         If this park is legitimate, raise or unset the deadline.",
444      ),
445    }
446  }
447}
448
449impl std::error::Error for BlockOnDeadlock {}
450
451#[derive(Debug)]
452pub struct JoinError {
453  message: String,
454}
455
456impl JoinError {
457  fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
458    Self {
459      // The typed deadlock diagnostic first: a deadline firing inside a
460      // spawned task's poll unwinds into the spawn wrapper's catch_unwind and
461      // surfaces here -- the diagnostic text must survive the trip.
462      message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
463        deadlock.to_string()
464      } else if let Some(message) = panic.downcast_ref::<&str>() {
465        (*message).to_string()
466      } else if let Some(message) = panic.downcast_ref::<String>() {
467        message.clone()
468      } else {
469        "async runtime task panicked".to_string()
470      },
471    }
472  }
473}
474
475impl fmt::Display for JoinError {
476  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477    f.write_str(&self.message)
478  }
479}
480
481impl std::error::Error for JoinError {}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484enum BlockOnOutcome {
485  Completed,
486  Stopped,
487}
488
489fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
490  // The original unwind is already caught, so ordinary payloads should be
491  // destroyed normally. A hostile payload destructor can panic again; catch
492  // that nested unwind and leak only its new payload, whose destructor is
493  // likewise untrusted.
494  if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
495    std::mem::forget(nested_payload);
496  }
497}
498
499#[derive(Default)]
500struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
501
502impl RetainedPanicPayload {
503  fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
504    if self.0.is_some() {
505      discard_panic_payload(payload);
506    } else {
507      self.0 = Some(payload);
508    }
509  }
510
511  fn is_some(&self) -> bool {
512    self.0.is_some()
513  }
514
515  fn resume(mut self) -> ! {
516    resume_unwind(
517      self
518        .0
519        .take()
520        .expect("a retained panic payload must remain owned"),
521    )
522  }
523}
524
525impl Drop for RetainedPanicPayload {
526  fn drop(&mut self) {
527    if let Some(payload) = self.0.take() {
528      discard_panic_payload(payload);
529    }
530  }
531}
532
533fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
534  match catch_unwind(AssertUnwindSafe(function)) {
535    Ok(value) => Some(value),
536    Err(payload) => {
537      discard_panic_payload(payload);
538      None
539    }
540  }
541}
542
543struct ContainedArc<T: ?Sized> {
544  value: Option<Arc<T>>,
545}
546
547impl<T: ?Sized> ContainedArc<T> {
548  fn new(value: Arc<T>) -> Self {
549    Self { value: Some(value) }
550  }
551
552  fn get(&self) -> &T {
553    self
554      .value
555      .as_deref()
556      .expect("the contained Arc must remain owned")
557  }
558
559  #[cfg(test)]
560  fn into_inner(mut self) -> Arc<T> {
561    self
562      .value
563      .take()
564      .expect("the contained Arc must remain owned")
565  }
566}
567
568impl<T: ?Sized> Drop for ContainedArc<T> {
569  fn drop(&mut self) {
570    if let Some(value) = self.value.take() {
571      let _ = catch_unwind_contained(|| drop(value));
572    }
573  }
574}
575
576struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
577  values: Vec<(I, Arc<T>)>,
578}
579
580impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
581  fn new(values: Vec<(I, Arc<T>)>) -> Self {
582    Self { values }
583  }
584
585  fn as_slice(&self) -> &[(I, Arc<T>)] {
586    &self.values
587  }
588
589  fn len(&self) -> usize {
590    self.values.len()
591  }
592
593  fn is_empty(&self) -> bool {
594    self.values.is_empty()
595  }
596
597  fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
598    self
599      .values
600      .pop()
601      .map(|(id, value)| (id, ContainedArc::new(value)))
602  }
603}
604
605impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
606  fn drop(&mut self) {
607    while let Some((_, value)) = self.values.pop() {
608      let _ = catch_unwind_contained(|| drop(value));
609    }
610  }
611}
612
613struct PanicContainedWaker {
614  waker: ManuallyDrop<Waker>,
615  generation: Option<u64>,
616}
617
618impl std::task::Wake for PanicContainedWaker {
619  fn wake(self: Arc<Self>) {
620    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
621    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
622  }
623
624  fn wake_by_ref(self: &Arc<Self>) {
625    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
626    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
627  }
628}
629
630impl Drop for PanicContainedWaker {
631  fn drop(&mut self) {
632    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
633    let _ = catch_unwind_contained(|| {
634      // SAFETY: this is the sole destruction path for the underlying waker.
635      unsafe { ManuallyDrop::drop(&mut self.waker) };
636    });
637  }
638}
639
640fn panic_contained_waker(waker: Waker) -> Waker {
641  Waker::from(Arc::new(PanicContainedWaker {
642    waker: ManuallyDrop::new(waker),
643    generation: active_runtime_generation(),
644  }))
645}
646
647struct PanicContainedWakerSource {
648  waker: ManuallyDrop<Waker>,
649  generation: Option<u64>,
650}
651
652impl PanicContainedWakerSource {
653  fn new(waker: Waker) -> Self {
654    Self {
655      waker: ManuallyDrop::new(waker),
656      generation: active_runtime_generation(),
657    }
658  }
659
660  fn as_waker(&self) -> &Waker {
661    &self.waker
662  }
663}
664
665impl Drop for PanicContainedWakerSource {
666  fn drop(&mut self) {
667    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
668    let _ = catch_unwind_contained(|| {
669      // SAFETY: this is the sole destruction path for the source waker.
670      unsafe { ManuallyDrop::drop(&mut self.waker) };
671    });
672  }
673}
674
675struct PanicContainedWakerCache {
676  source: PanicContainedWakerSource,
677  contained: Waker,
678}
679
680impl PanicContainedWakerCache {
681  fn new(waker: &Waker) -> Self {
682    let generation = active_runtime_generation();
683    let _generation = RuntimeGenerationGuard::enter_optional(generation);
684    let source = PanicContainedWakerSource::new(waker.clone());
685    let contained = panic_contained_waker(source.as_waker().clone());
686    Self { source, contained }
687  }
688
689  fn update(&mut self, waker: &Waker) {
690    let will_wake = {
691      let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
692      self.source.as_waker().will_wake(waker)
693    };
694    if !will_wake {
695      *self = Self::new(waker);
696    }
697  }
698
699  fn contained(&self) -> &Waker {
700    &self.contained
701  }
702}
703
704fn wake_waker_contained(waker: Waker) {
705  // `Waker::wake` consumes the waker, so a panicking wake followed by a
706  // panicking RawWaker destructor would double-panic before the outer catch
707  // could regain control. Borrow for the wake and contain destruction
708  // separately.
709  let _ = catch_unwind_contained(|| waker.wake_by_ref());
710  let _ = catch_unwind_contained(|| drop(waker));
711}
712
713fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
714  match catch_unwind(AssertUnwindSafe(function)) {
715    Ok(value) => Ok(value),
716    Err(payload) => {
717      let error = JoinError::from_panic(&*payload);
718      discard_panic_payload(payload);
719      Err(error)
720    }
721  }
722}
723
724fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
725  let error = JoinError::from_panic(&*payload);
726  discard_panic_payload(payload);
727  error
728}
729
730struct ContainedTaskOutput<T> {
731  output: ManuallyDrop<T>,
732  generation: u64,
733}
734
735impl<T> ContainedTaskOutput<T> {
736  fn new(output: T, generation: u64) -> Self {
737    Self {
738      output: ManuallyDrop::new(output),
739      generation,
740    }
741  }
742
743  fn into_inner(mut self) -> T {
744    // SAFETY: ownership moves to the JoinHandle caller and `self` is then
745    // forgotten so its Drop implementation cannot run a second time.
746    let output = unsafe { ManuallyDrop::take(&mut self.output) };
747    std::mem::forget(self);
748    output
749  }
750}
751
752impl<T> Drop for ContainedTaskOutput<T> {
753  fn drop(&mut self) {
754    let _generation = RuntimeGenerationGuard::enter(self.generation);
755    let _ = catch_unwind_contained(|| {
756      // SAFETY: this is the sole destruction path while the output remains
757      // retained by a JoinHandle.
758      unsafe { ManuallyDrop::drop(&mut self.output) };
759    });
760  }
761}
762
763#[cfg(napi_runtime_os_threads)]
764#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
765struct BlockingJobId(u64);
766
767#[cfg(napi_runtime_os_threads)]
768static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
769
770#[cfg(all(test, napi_runtime_os_threads))]
771thread_local! {
772  static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
773    const { std::cell::RefCell::new(None) };
774  static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
775    const { std::cell::RefCell::new(None) };
776  static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
777    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
778      const { std::cell::RefCell::new(None) };
779  static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
780    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
781      const { std::cell::RefCell::new(None) };
782  static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
783    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
784      const { std::cell::RefCell::new(None) };
785  static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
786    const { std::cell::RefCell::new(None) };
787  static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
788    const { std::cell::RefCell::new(None) };
789  static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
790    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
791      const { std::cell::RefCell::new(None) };
792  static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
793    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
794      const { std::cell::RefCell::new(None) };
795  static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
796    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
797      const { std::cell::RefCell::new(None) };
798  static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
799    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
800      const { std::cell::RefCell::new(None) };
801  static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
802    const { std::cell::RefCell::new(None) };
803  static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
804    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
805      const { std::cell::RefCell::new(None) };
806  static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
807    const { std::cell::RefCell::new(None) };
808}
809
810#[cfg(all(test, napi_runtime_os_threads))]
811struct WorkerTlsDropProbe {
812  action: Option<WorkerTlsDropAction>,
813}
814
815// LOADER-LOCK RULE for every probe action below: TLS destructors run at
816// thread exit -- on Windows inside `LdrShutdownThread`, which HOLDS the
817// process loader lock while walking TLS callbacks. Any cross-thread wait
818// here (condvar, channel recv, barrier, joining a thread) therefore freezes
819// every other `thread::spawn` and thread-exit in the process -- and, with
820// RUST_BACKTRACE=1, even panic reporting (dbghelp loads DLLs under the same
821// lock). A previous `Wait { entered, release }` action parked the FIRST
822// exiting worker's destructor on a condvar; the SECOND worker then could not
823// even enter its destructor (loader lock), the release condition (both
824// entered) became unsatisfiable, and the whole test suite went silent until
825// the CI step timeout (Windows x64 jobs, runs 29195160953 / 86662331487).
826// Destructor actions may only RECORD (atomic stores, non-blocking sends);
827// ordering invariants are asserted from OUTSIDE, after `shutdown()` returns.
828#[cfg(all(test, napi_runtime_os_threads))]
829// `ReenterLifecycle`'s only constructor re-execs the test binary, which WASI
830// cannot do, so on wasm the variant is declared but never built.
831#[cfg_attr(target_family = "wasm", allow(dead_code))]
832enum WorkerTlsDropAction {
833  /// Record-only: bump the shared counter so the harness can assert -- the
834  /// instant `shutdown()` returns -- that every worker TLS destructor had
835  /// already completed (the join barrier includes destructor completion).
836  Record { completed: Arc<AtomicUsize> },
837  ReenterLifecycle {
838    controller: Arc<RuntimeController>,
839    completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
840  },
841}
842
843#[cfg(all(test, napi_runtime_os_threads))]
844impl Drop for WorkerTlsDropProbe {
845  fn drop(&mut self) {
846    match self
847      .action
848      .take()
849      .expect("worker TLS drop probe action already consumed")
850    {
851      WorkerTlsDropAction::Record { completed } => {
852        completed.fetch_add(1, Ordering::SeqCst);
853      }
854      // Both lifecycle re-entries FAIL FAST from a stopping-generation worker
855      // (short state-mutex acquisitions, no cross-thread wait), and the send
856      // is non-blocking -- loader-lock safe. The owning test additionally
857      // isolates itself in a watchdogged subprocess.
858      WorkerTlsDropAction::ReenterLifecycle {
859        controller,
860        completed,
861      } => {
862        let start = controller.start().map_err(|error| error.to_string());
863        let shutdown = controller.shutdown().map_err(|error| error.to_string());
864        let _ = completed.send((start, shutdown));
865      }
866    }
867  }
868}
869
870#[cfg(all(test, napi_runtime_os_threads))]
871fn run_dependency_claim_test_hook() {
872  if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
873    hook();
874  }
875}
876
877#[cfg(all(test, napi_runtime_os_threads))]
878fn run_current_thread_dispatch_claim_test_hook() {
879  if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
880  {
881    hook();
882  }
883}
884
885#[cfg(all(test, napi_runtime_os_threads))]
886fn run_current_thread_dispatch_publication_blocked_test_hook() {
887  if let Some(hook) =
888    CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
889  {
890    hook();
891  }
892}
893
894#[cfg(all(test, napi_runtime_os_threads))]
895fn run_current_thread_dispatch_recovery_test_hook() {
896  if let Some(hook) =
897    CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
898  {
899    hook();
900  }
901}
902
903#[cfg(all(test, napi_runtime_os_threads))]
904fn run_current_thread_failed_publication_retired_test_hook() {
905  if let Some(hook) =
906    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
907  {
908    hook();
909  }
910}
911
912#[cfg(all(test, napi_runtime_os_threads))]
913fn run_current_thread_dispatch_call_test_hook() {
914  if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
915    hook();
916  }
917}
918
919#[cfg(all(test, napi_runtime_os_threads))]
920fn run_current_thread_dispatch_returned_test_hook() {
921  if let Some(hook) =
922    CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
923  {
924    hook();
925  }
926}
927
928#[cfg(all(test, napi_runtime_os_threads))]
929fn run_current_thread_blocking_claim_test_hook() {
930  if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
931  {
932    hook();
933  }
934}
935
936#[cfg(all(test, napi_runtime_os_threads))]
937fn run_current_thread_runnable_claim_test_hook() {
938  if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
939  {
940    hook();
941  }
942}
943
944#[cfg(all(test, napi_runtime_os_threads))]
945fn run_multi_thread_work_claimed_test_hook() {
946  if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
947    hook();
948  }
949}
950
951#[cfg(all(test, napi_runtime_os_threads))]
952fn run_multi_thread_work_started_test_hook() {
953  if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
954    hook();
955  }
956}
957
958#[cfg(all(test, napi_runtime_os_threads))]
959fn run_dependency_predicate_test_hook() {
960  if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
961    hook();
962  }
963}
964
965#[cfg(all(test, napi_runtime_os_threads))]
966fn run_before_owned_blocking_queue_claim_test_hook() {
967  if let Some(hook) =
968    BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
969  {
970    hook();
971  }
972}
973
974#[cfg(napi_runtime_os_threads)]
975#[derive(Clone, Copy, Debug, PartialEq, Eq)]
976struct BlockingDependency {
977  job: BlockingJobId,
978  owner: Option<BlockingOwnerToken>,
979}
980
981#[cfg(napi_runtime_os_threads)]
982#[derive(Debug)]
983struct DependencyClaim {
984  claimed: AtomicBool,
985  transition: Mutex<()>,
986}
987
988#[cfg(napi_runtime_os_threads)]
989impl DependencyClaim {
990  fn new() -> Self {
991    Self {
992      claimed: AtomicBool::new(false),
993      transition: Mutex::new(()),
994    }
995  }
996
997  fn is_live(&self) -> bool {
998    !self.claimed.load(Ordering::Acquire)
999  }
1000
1001  fn cancel_link(&self, link: &DependencyLink) {
1002    let _transition = self
1003      .transition
1004      .lock()
1005      .unwrap_or_else(std::sync::PoisonError::into_inner);
1006    link.cancel();
1007  }
1008
1009  fn try_claim(&self, link: &DependencyLink) -> bool {
1010    let _transition = self
1011      .transition
1012      .lock()
1013      .unwrap_or_else(std::sync::PoisonError::into_inner);
1014    #[cfg(test)]
1015    run_dependency_claim_test_hook();
1016    if self.claimed.load(Ordering::Acquire) || !link.is_live() {
1017      return false;
1018    }
1019    self.claimed.store(true, Ordering::Release);
1020    true
1021  }
1022}
1023
1024#[cfg(napi_runtime_os_threads)]
1025#[derive(Debug)]
1026struct DependencyLink {
1027  live: AtomicBool,
1028  parent: Option<Weak<DependencyLink>>,
1029}
1030
1031#[cfg(napi_runtime_os_threads)]
1032impl DependencyLink {
1033  fn root() -> Arc<Self> {
1034    Arc::new(Self {
1035      live: AtomicBool::new(true),
1036      parent: None,
1037    })
1038  }
1039
1040  fn derived(parent: &Arc<Self>) -> Arc<Self> {
1041    Arc::new(Self {
1042      live: AtomicBool::new(true),
1043      parent: Some(Arc::downgrade(parent)),
1044    })
1045  }
1046
1047  fn is_live(&self) -> bool {
1048    if !self.live.load(Ordering::Acquire) {
1049      return false;
1050    }
1051    let Some(parent) = &self.parent else {
1052      return true;
1053    };
1054    let Some(mut current) = parent.upgrade() else {
1055      return false;
1056    };
1057    loop {
1058      if !current.live.load(Ordering::Acquire) {
1059        return false;
1060      }
1061      let Some(parent) = &current.parent else {
1062        return true;
1063      };
1064      let Some(next) = parent.upgrade() else {
1065        return false;
1066      };
1067      current = next;
1068    }
1069  }
1070
1071  fn cancel(&self) {
1072    self.live.store(false, Ordering::Release);
1073  }
1074}
1075
1076#[cfg(napi_runtime_os_threads)]
1077#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1078struct DependencyPublicationIdentity {
1079  job: BlockingJobId,
1080  claim: *const DependencyClaim,
1081  link: *const DependencyLink,
1082}
1083
1084#[cfg(napi_runtime_os_threads)]
1085#[derive(Clone, Debug)]
1086struct DependencyPublication {
1087  dependency: BlockingDependency,
1088  claim: Arc<DependencyClaim>,
1089  link: Arc<DependencyLink>,
1090}
1091
1092#[cfg(napi_runtime_os_threads)]
1093impl DependencyPublication {
1094  fn owned(dependency: BlockingDependency) -> Self {
1095    Self {
1096      dependency,
1097      claim: Arc::new(DependencyClaim::new()),
1098      link: DependencyLink::root(),
1099    }
1100  }
1101
1102  fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1103    Self {
1104      dependency: BlockingDependency {
1105        owner: self.dependency.owner.or(owner),
1106        ..self.dependency
1107      },
1108      claim: Arc::clone(&self.claim),
1109      link: DependencyLink::derived(&self.link),
1110    }
1111  }
1112
1113  fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1114    self.dependency.owner = self.dependency.owner.or(owner);
1115    self
1116  }
1117
1118  fn same_as(&self, other: &Self) -> bool {
1119    self.dependency == other.dependency
1120      && Arc::ptr_eq(&self.claim, &other.claim)
1121      && Arc::ptr_eq(&self.link, &other.link)
1122  }
1123
1124  fn identity(&self) -> DependencyPublicationIdentity {
1125    DependencyPublicationIdentity {
1126      job: self.dependency.job,
1127      claim: Arc::as_ptr(&self.claim),
1128      link: Arc::as_ptr(&self.link),
1129    }
1130  }
1131
1132  fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1133    Some(DependencyPublicationIdentity {
1134      job: self.dependency.job,
1135      claim: Arc::as_ptr(&self.claim),
1136      link: self.link.parent.as_ref()?.as_ptr(),
1137    })
1138  }
1139
1140  fn is_derived_from(&self, source: &Self) -> bool {
1141    self.immediate_source_identity() == Some(source.identity())
1142  }
1143
1144  fn is_live(&self) -> bool {
1145    self.claim.is_live() && self.link.is_live()
1146  }
1147
1148  fn cancel(&self) {
1149    self.claim.cancel_link(&self.link);
1150  }
1151
1152  fn try_claim(&self) -> bool {
1153    self.claim.try_claim(&self.link)
1154  }
1155}
1156
1157#[cfg(napi_runtime_os_threads)]
1158struct TaskDependencyEntry {
1159  sequence: u64,
1160  publication: DependencyPublication,
1161}
1162
1163#[cfg(napi_runtime_os_threads)]
1164struct SelectedDependencyPublication {
1165  sequence: u64,
1166  publication: DependencyPublication,
1167}
1168
1169#[cfg(napi_runtime_os_threads)]
1170#[derive(Clone, Copy)]
1171enum OwnerDependencyMode {
1172  Available,
1173  Exact,
1174}
1175
1176#[cfg(napi_runtime_os_threads)]
1177#[derive(Default)]
1178struct TaskDependencyState {
1179  current: VecDeque<TaskDependencyEntry>,
1180  next_sequence: u64,
1181  unowned_sequences: VecDeque<u64>,
1182  owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1183  // NOTE: there is deliberately no `job_sequences` index. It used to be a
1184  // `FxHashMap<BlockingJobId, FxHashSet<u64>>` costing TWO heap allocations
1185  // per blocking dispatch (the map's table plus an inner set that almost
1186  // always held exactly one sequence). Its only job was to let a dropped
1187  // blocking `JoinHandle` find the entry it had published, and the handle can
1188  // simply remember the `sequence` `append` gave it: `current` is positionally
1189  // indexed by sequence via `entry_in`, so the handle retires its own entry in
1190  // O(1) with no map, no hash, and no scan. `next_sequence` never rewinds, so
1191  // a remembered sequence can never alias a later entry.
1192  waiter: Option<Waker>,
1193}
1194
1195#[cfg(napi_runtime_os_threads)]
1196impl TaskDependencyState {
1197  /// Publish `publication` and return the sequence it was filed under, so the
1198  /// publisher can retire exactly this entry later in O(1).
1199  fn append(&mut self, publication: DependencyPublication) -> u64 {
1200    let sequence = self.next_sequence;
1201    self.next_sequence = self
1202      .next_sequence
1203      .checked_add(1)
1204      .expect("task dependency sequence space exhausted");
1205    match publication.dependency.owner {
1206      Some(owner) => self
1207        .owner_sequences
1208        .entry(owner)
1209        .or_default()
1210        .push_back(sequence),
1211      None => self.unowned_sequences.push_back(sequence),
1212    }
1213    self.current.push_back(TaskDependencyEntry {
1214      sequence,
1215      publication,
1216    });
1217    sequence
1218  }
1219
1220  fn entry_in(
1221    current: &VecDeque<TaskDependencyEntry>,
1222    sequence: u64,
1223  ) -> Option<&TaskDependencyEntry> {
1224    let front = current.front()?.sequence;
1225    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1226    let entry = current.get(index)?;
1227    (entry.sequence == sequence).then_some(entry)
1228  }
1229
1230  fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1231    Self::entry_in(&self.current, sequence)
1232  }
1233
1234  fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1235    let front = self.current.front()?.sequence;
1236    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1237    let entry = self.current.get_mut(index)?;
1238    (entry.sequence == sequence).then_some(entry)
1239  }
1240
1241  fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1242    match dependency.owner {
1243      Some(owner) => {
1244        let remove_owner = self
1245          .owner_sequences
1246          .get_mut(&owner)
1247          .is_some_and(|sequences| {
1248            if sequences.front() == Some(&sequence) {
1249              sequences.pop_front();
1250            }
1251            sequences.is_empty()
1252          });
1253        if remove_owner {
1254          self.owner_sequences.remove(&owner);
1255        }
1256      }
1257      None => {
1258        if self.unowned_sequences.front() == Some(&sequence) {
1259          self.unowned_sequences.pop_front();
1260        }
1261      }
1262    }
1263  }
1264
1265  fn clear_indexes(&mut self) {
1266    self.unowned_sequences.clear();
1267    self.owner_sequences.clear();
1268  }
1269}
1270
1271#[cfg(napi_runtime_os_threads)]
1272struct TaskDependency {
1273  state: Mutex<TaskDependencyState>,
1274  owner_hint: Option<BlockingOwnerToken>,
1275  has_current: AtomicBool,
1276  generation: u64,
1277  #[cfg(test)]
1278  entry_visits: AtomicUsize,
1279}
1280
1281#[cfg(napi_runtime_os_threads)]
1282impl Default for TaskDependency {
1283  fn default() -> Self {
1284    Self::new(u64::MAX, None)
1285  }
1286}
1287
1288#[cfg(napi_runtime_os_threads)]
1289impl TaskDependency {
1290  fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1291    Self {
1292      state: Mutex::new(TaskDependencyState::default()),
1293      owner_hint,
1294      has_current: AtomicBool::new(false),
1295      generation,
1296      #[cfg(test)]
1297      entry_visits: AtomicUsize::new(0),
1298    }
1299  }
1300
1301  fn new_task(generation: u64) -> Self {
1302    Self::new(generation, None)
1303  }
1304
1305  fn notify_waiter(&self, waiter: Option<Waker>) {
1306    if let Some(waiter) = waiter {
1307      let _generation = RuntimeGenerationGuard::enter(self.generation);
1308      wake_waker_contained(waiter);
1309    }
1310  }
1311
1312  fn clear_waiter(&self) {
1313    let waiter = self
1314      .state
1315      .lock()
1316      .unwrap_or_else(std::sync::PoisonError::into_inner)
1317      .waiter
1318      .take();
1319    if let Some(waiter) = waiter {
1320      let _generation = RuntimeGenerationGuard::enter(self.generation);
1321      let _ = catch_unwind_contained(|| drop(waiter));
1322    }
1323  }
1324
1325  fn clear(&self) {
1326    let waiter = {
1327      let mut state = self
1328        .state
1329        .lock()
1330        .unwrap_or_else(std::sync::PoisonError::into_inner);
1331      if state.current.is_empty() {
1332        None
1333      } else {
1334        for current in state.current.drain(..) {
1335          current.publication.cancel();
1336        }
1337        state.clear_indexes();
1338        self.has_current.store(false, Ordering::Release);
1339        state.waiter.take()
1340      }
1341    };
1342    self.notify_waiter(waiter);
1343  }
1344
1345  /// Retire the single entry filed under `sequence`, in O(1).
1346  ///
1347  /// This replaces a job-id lookup through a `job_sequences` index. `current`
1348  /// is positionally indexed by sequence, so the publisher that remembers the
1349  /// sequence `append` returned can cancel exactly its own entry without a
1350  /// map and without a scan. `entry_in` re-checks `entry.sequence == sequence`
1351  /// after the positional lookup and `next_sequence` never rewinds, so a
1352  /// sequence whose entry has already been drained (`begin_poll`) or pruned
1353  /// resolves to `None` rather than aliasing a later entry.
1354  fn retire(&self, sequence: u64) {
1355    let waiter = {
1356      let mut state = self
1357        .state
1358        .lock()
1359        .unwrap_or_else(std::sync::PoisonError::into_inner);
1360      let cancelled = match state.entry_mut(sequence) {
1361        Some(current) => {
1362          self.note_entry_visit();
1363          current.publication.cancel();
1364          true
1365        }
1366        None => false,
1367      };
1368      if cancelled {
1369        self.prune_stale_prefix(&mut state);
1370        state.waiter.take()
1371      } else {
1372        None
1373      }
1374    };
1375    self.notify_waiter(waiter);
1376  }
1377
1378  fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1379    let Some(first_publication) = publications.first() else {
1380      return;
1381    };
1382    let publication_identities = (publications.len() > 1).then(|| {
1383      publications
1384        .iter()
1385        .map(DependencyPublication::identity)
1386        .collect::<FxHashSet<_>>()
1387    });
1388    let waiter = {
1389      let mut state = self
1390        .state
1391        .lock()
1392        .unwrap_or_else(std::sync::PoisonError::into_inner);
1393      let removed = state
1394        .current
1395        .iter_mut()
1396        .filter_map(|current| {
1397          self.note_entry_visit();
1398          let is_derived = publication_identities.as_ref().map_or_else(
1399            || current.publication.is_derived_from(first_publication),
1400            |identities| {
1401              current
1402                .publication
1403                .immediate_source_identity()
1404                .is_some_and(|identity| identities.contains(&identity))
1405            },
1406          );
1407          if !is_derived {
1408            return None;
1409          }
1410          current.publication.cancel();
1411          Some(current.sequence)
1412        })
1413        .collect::<Vec<_>>();
1414      // No `job_sequences` index to maintain; the cancelled entries stay in
1415      // `current` until `prune_stale_prefix` retires them, exactly as before.
1416      if removed.is_empty() {
1417        None
1418      } else {
1419        self.prune_stale_prefix(&mut state);
1420        state.waiter.take()
1421      }
1422    };
1423    self.notify_waiter(waiter);
1424  }
1425
1426  /// Publish an owned dependency and return the sequence it was filed under,
1427  /// so the caller can [`retire`](Self::retire) exactly this entry in O(1).
1428  fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1429    let publication = DependencyPublication::owned(BlockingDependency {
1430      owner: dependency.owner.or(self.owner_hint),
1431      ..dependency
1432    });
1433    self.set_entry(publication)
1434  }
1435
1436  /// Whether `sequence` still resolves to a LIVE entry in this state.
1437  ///
1438  /// A publisher uses this to decide whether its earlier publication still
1439  /// stands. `false` means the entry was drained by `begin_poll`, pruned, or
1440  /// cancelled (by a lending claim, say), so a fresh publication is needed.
1441  fn holds_live(&self, sequence: u64) -> bool {
1442    let state = self
1443      .state
1444      .lock()
1445      .unwrap_or_else(std::sync::PoisonError::into_inner);
1446    TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1447      self.note_entry_visit();
1448      entry.publication.is_live()
1449    })
1450  }
1451
1452  fn set_borrowed(&self, publication: &DependencyPublication) {
1453    let _sequence = self.set_entry(publication.derived(self.owner_hint));
1454  }
1455
1456  fn set_entry(&self, publication: DependencyPublication) -> u64 {
1457    let (sequence, waiter) = {
1458      let mut state = self
1459        .state
1460        .lock()
1461        .unwrap_or_else(std::sync::PoisonError::into_inner);
1462      // Owned publications and propagated publications both create a fresh
1463      // local liveness link, so identity cannot already exist in this state.
1464      let sequence = state.append(publication);
1465      self.has_current.store(true, Ordering::Release);
1466      (sequence, state.waiter.take())
1467    };
1468    self.notify_waiter(waiter);
1469    sequence
1470  }
1471
1472  fn note_entry_visit(&self) {
1473    #[cfg(test)]
1474    self.entry_visits.fetch_add(1, Ordering::Relaxed);
1475  }
1476
1477  fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1478    while state.current.front().is_some_and(|current| {
1479      self.note_entry_visit();
1480      !current.publication.is_live()
1481    }) {
1482      let current = state
1483        .current
1484        .pop_front()
1485        .expect("the stale prefix entry must exist");
1486      state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1487    }
1488    self
1489      .has_current
1490      .store(!state.current.is_empty(), Ordering::Release);
1491  }
1492
1493  fn first_live_sequence(
1494    &self,
1495    current: &VecDeque<TaskDependencyEntry>,
1496    sequences: &mut VecDeque<u64>,
1497  ) -> Option<u64> {
1498    loop {
1499      let sequence = *sequences.front()?;
1500      let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1501        sequences.pop_front();
1502        continue;
1503      };
1504      self.note_entry_visit();
1505      if entry.publication.is_live() {
1506        return Some(sequence);
1507      }
1508      sequences.pop_front();
1509    }
1510  }
1511
1512  fn first_live_owner_sequence(
1513    &self,
1514    state: &mut TaskDependencyState,
1515    owner: BlockingOwnerToken,
1516  ) -> Option<u64> {
1517    let sequence = {
1518      let current = &state.current;
1519      state
1520        .owner_sequences
1521        .get_mut(&owner)
1522        .and_then(|sequences| self.first_live_sequence(current, sequences))
1523    };
1524    if sequence.is_none() {
1525      state.owner_sequences.remove(&owner);
1526    }
1527    sequence
1528  }
1529
1530  fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1531    self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1532  }
1533
1534  #[cfg(test)]
1535  fn get(&self) -> Option<DependencyPublication> {
1536    self
1537      .state
1538      .lock()
1539      .unwrap_or_else(std::sync::PoisonError::into_inner)
1540      .current
1541      .iter()
1542      .find(|current| current.publication.is_live())
1543      .map(|current| current.publication.clone())
1544  }
1545
1546  fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1547    self
1548      .state
1549      .lock()
1550      .unwrap_or_else(std::sync::PoisonError::into_inner)
1551      .current
1552      .iter()
1553      .filter(|current| {
1554        self.note_entry_visit();
1555        current.publication.is_live()
1556      })
1557      .map(|current| current.publication.clone())
1558      .collect()
1559  }
1560
1561  fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1562    let mut state = self
1563      .state
1564      .lock()
1565      .unwrap_or_else(std::sync::PoisonError::into_inner);
1566    self.prune_stale_prefix(&mut state);
1567    let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1568    let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1569    let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1570      (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1571      (Some(_) | None, Some(unowned)) => (unowned, true),
1572      (Some(exact), None) => (exact, false),
1573      (None, None) => return None,
1574    };
1575    if bind_owner {
1576      let removed = state.unowned_sequences.pop_front();
1577      debug_assert_eq!(removed, Some(sequence));
1578      let current = state
1579        .entry_mut(sequence)
1580        .expect("the selected dependency must remain indexed");
1581      debug_assert_eq!(current.publication.dependency.owner, None);
1582      current.publication.dependency.owner = Some(owner);
1583      // A previous exact-owner query may have observed no entry. Publishing
1584      // this sequence at the front makes the newly bound dependency visible
1585      // immediately without restarting a global scan.
1586      state
1587        .owner_sequences
1588        .entry(owner)
1589        .or_default()
1590        .push_front(sequence);
1591    }
1592    let publication = state
1593      .entry(sequence)
1594      .expect("the selected dependency must remain indexed")
1595      .publication
1596      .clone();
1597    Some(SelectedDependencyPublication {
1598      sequence,
1599      publication,
1600    })
1601  }
1602
1603  fn has_live_owner_dependency(
1604    &self,
1605    owner: BlockingOwnerToken,
1606    mode: OwnerDependencyMode,
1607  ) -> bool {
1608    #[cfg(all(test, napi_runtime_os_threads))]
1609    run_dependency_predicate_test_hook();
1610    let mut state = self
1611      .state
1612      .lock()
1613      .unwrap_or_else(std::sync::PoisonError::into_inner);
1614    self.prune_stale_prefix(&mut state);
1615    if self.first_live_owner_sequence(&mut state, owner).is_some() {
1616      return true;
1617    }
1618    matches!(mode, OwnerDependencyMode::Available)
1619      && self.first_live_unowned_sequence(&mut state).is_some()
1620  }
1621
1622  #[cfg(test)]
1623  fn get_dependency(&self) -> Option<BlockingDependency> {
1624    self.get().map(|publication| publication.dependency)
1625  }
1626
1627  #[cfg(test)]
1628  fn get_dependencies(&self) -> Vec<BlockingDependency> {
1629    self
1630      .snapshot_all_live()
1631      .into_iter()
1632      .map(|publication| publication.dependency)
1633      .collect()
1634  }
1635
1636  #[cfg(test)]
1637  fn reset_entry_visits(&self) {
1638    self.entry_visits.store(0, Ordering::Relaxed);
1639  }
1640
1641  #[cfg(test)]
1642  fn entry_visits(&self) -> usize {
1643    self.entry_visits.load(Ordering::Relaxed)
1644  }
1645
1646  /// Atomically reserve `job` for exact-dependency execution. A dependency
1647  /// transition that wins this race makes the claim fail, so lending can never
1648  /// execute a stale job copied from an earlier poll.
1649  fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1650    let mut state = self
1651      .state
1652      .lock()
1653      .unwrap_or_else(std::sync::PoisonError::into_inner);
1654    let Some(current) = state.entry(selected.sequence) else {
1655      return false;
1656    };
1657    self.note_entry_visit();
1658    if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1659      return false;
1660    }
1661    let dependency = current.publication.dependency;
1662    state
1663      .entry_mut(selected.sequence)
1664      .expect("the claimed dependency must remain indexed")
1665      .publication
1666      .cancel();
1667    state.remove_sequence_indexes(selected.sequence, dependency);
1668    self.prune_stale_prefix(&mut state);
1669    true
1670  }
1671
1672  fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1673    let _generation = RuntimeGenerationGuard::enter(self.generation);
1674    let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1675      return Vec::new();
1676    };
1677    let (previous, unused, current) = {
1678      let mut state = self
1679        .state
1680        .lock()
1681        .unwrap_or_else(std::sync::PoisonError::into_inner);
1682      let current = state
1683        .current
1684        .iter()
1685        .filter(|current| current.publication.is_live())
1686        .map(|current| current.publication.clone())
1687        .collect();
1688      if state
1689        .waiter
1690        .as_ref()
1691        .is_some_and(|previous| previous.will_wake(&waker))
1692      {
1693        (None, Some(waker), current)
1694      } else {
1695        (state.waiter.replace(waker), None, current)
1696      }
1697    };
1698    if let Some(previous) = previous {
1699      let _ = catch_unwind_contained(|| drop(previous));
1700    }
1701    if let Some(unused) = unused {
1702      let _ = catch_unwind_contained(|| drop(unused));
1703    }
1704    current
1705  }
1706
1707  fn begin_poll(&self) {
1708    if !self.has_current.load(Ordering::Acquire) {
1709      return;
1710    }
1711    let waiter = {
1712      let mut state = self
1713        .state
1714        .lock()
1715        .unwrap_or_else(std::sync::PoisonError::into_inner);
1716      if state.current.is_empty() {
1717        self.has_current.store(false, Ordering::Release);
1718        None
1719      } else {
1720        for previous in state.current.drain(..) {
1721          previous.publication.cancel();
1722        }
1723        state.clear_indexes();
1724        self.has_current.store(false, Ordering::Release);
1725        state.waiter.take()
1726      }
1727    };
1728    self.notify_waiter(waiter);
1729  }
1730
1731  #[cfg(test)]
1732  fn register_waiter(&self, waker: &Waker) {
1733    let _ = self.register_waiter_and_get(waker);
1734  }
1735}
1736
1737#[cfg(napi_runtime_os_threads)]
1738impl Drop for TaskDependency {
1739  fn drop(&mut self) {
1740    let state = self
1741      .state
1742      .get_mut()
1743      .unwrap_or_else(std::sync::PoisonError::into_inner);
1744    for current in state.current.drain(..) {
1745      current.publication.cancel();
1746    }
1747    if let Some(waiter) = state.waiter.take() {
1748      let _generation = RuntimeGenerationGuard::enter(self.generation);
1749      let _ = catch_unwind_contained(|| drop(waiter));
1750    }
1751  }
1752}
1753
1754#[cfg(napi_runtime_os_threads)]
1755thread_local! {
1756  static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1757    const { std::cell::RefCell::new(Vec::new()) };
1758}
1759
1760#[cfg(napi_runtime_os_threads)]
1761struct TaskDependencyGuard(Arc<TaskDependency>);
1762
1763#[cfg(napi_runtime_os_threads)]
1764impl TaskDependencyGuard {
1765  fn enter(dependency: &Arc<TaskDependency>) -> Self {
1766    let dependency = Arc::clone(dependency);
1767    CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1768    Self(dependency)
1769  }
1770}
1771
1772#[cfg(napi_runtime_os_threads)]
1773impl Drop for TaskDependencyGuard {
1774  fn drop(&mut self) {
1775    CURRENT_DEPENDENCY_STACK.with(|stack| {
1776      let current = stack
1777        .borrow_mut()
1778        .pop()
1779        .expect("blocking dependency context underflow");
1780      debug_assert!(Arc::ptr_eq(&current, &self.0));
1781    });
1782  }
1783}
1784
1785#[cfg(napi_runtime_os_threads)]
1786struct BlockOnDependencyGuard {
1787  dependency: Arc<TaskDependency>,
1788  _context: TaskDependencyGuard,
1789}
1790
1791#[cfg(napi_runtime_os_threads)]
1792impl BlockOnDependencyGuard {
1793  fn enter(generation: u64) -> Self {
1794    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1795    let dependency = Arc::new(TaskDependency::new(generation, owner));
1796    let context = TaskDependencyGuard::enter(&dependency);
1797    Self {
1798      dependency,
1799      _context: context,
1800    }
1801  }
1802
1803  fn clear(&self) {
1804    self.dependency.clear();
1805  }
1806}
1807
1808/// A dependency this handle published, remembered well enough to retire it in
1809/// O(1).
1810///
1811/// The sequence alone is not enough: sequences are allocated per
1812/// `TaskDependency`, so applying one to a different state would cancel an
1813/// unrelated entry. The state's identity therefore travels with it, as a
1814/// `Weak` rather than a raw pointer so a freed state cannot be impersonated by
1815/// a later allocation reusing its address -- a `Weak` keeps the allocation
1816/// itself reserved, so the pointer stays unique even after the state is gone.
1817///
1818/// It is deliberately NOT a strong `Arc`: a handle can be polled under many
1819/// frames over its life, and holding each one alive to remember it would retain
1820/// one dead `TaskDependency`, with its deque and index maps, per frame the
1821/// handle has ever seen.
1822#[cfg(napi_runtime_os_threads)]
1823struct PublishedBlockingDependency {
1824  state: Weak<TaskDependency>,
1825  sequence: u64,
1826}
1827
1828/// Every publication a blocking handle has made, one per dependency frame it
1829/// has been polled under.
1830///
1831/// A handle publishes once per Pending poll into whichever frame is on top of
1832/// `CURRENT_DEPENDENCY_STACK`. Re-polling under a frame it has already
1833/// published into supersedes that frame's entry, so the common case is exactly
1834/// one publication and `rest` never allocates.
1835///
1836/// Retiring EVERY publication is load-bearing. Dropping a blocking handle
1837/// detaches rather than dequeues, so any entry left live keeps a job that
1838/// nobody awaits selectable for owner-lane lending -- and `select_for_owner`
1839/// picks the LOWEST live sequence, so a forgotten entry outranks dependencies
1840/// published after it. Lending then runs the detached job inline in the owner's
1841/// single lane, which deadlocks if that job waits on the dependency it
1842/// displaced. The job-id index this replaces cancelled every entry for the job
1843/// in the frame it was called on, so nothing was forgotten; a handle that
1844/// remembered only its newest publication would forget both a repeat poll in
1845/// one frame and a poll under a nested frame.
1846#[cfg(napi_runtime_os_threads)]
1847#[derive(Default)]
1848struct PublishedBlockingDependencies {
1849  first: Option<PublishedBlockingDependency>,
1850  rest: Vec<PublishedBlockingDependency>,
1851}
1852
1853#[cfg(napi_runtime_os_threads)]
1854impl PublishedBlockingDependency {
1855  /// Whether this publication is still worth remembering.
1856  ///
1857  /// A token stops standing when its frame is gone -- a dropped
1858  /// `TaskDependency` already cancelled every entry it held -- or when the
1859  /// frame no longer holds the entry as live, because its `begin_poll` drained
1860  /// `current`, `prune_stale_prefix` retired it, or a lending claim took it.
1861  ///
1862  /// Absence from this thread's dependency stack deliberately does NOT count.
1863  /// It would bound this list more tightly, but it is not sound: a `JoinHandle`
1864  /// is `Send + Unpin`, so a safe adapter can serialise one behind a mutex and
1865  /// poll it from several tasks, and then a frame parked on another thread can
1866  /// still be awaiting a handle that is being polled here. Retiring on local
1867  /// absence would take that frame's publication away while it still needs it
1868  /// for owner-lane lending.
1869  fn still_stands(&self) -> bool {
1870    self
1871      .state
1872      .upgrade()
1873      .is_some_and(|state| state.holds_live(self.sequence))
1874  }
1875}
1876
1877#[cfg(napi_runtime_os_threads)]
1878impl PublishedBlockingDependencies {
1879  /// Forget publications whose frame is gone.
1880  ///
1881  /// A dropped `TaskDependency` cancels every entry it holds, so an expired
1882  /// token has nothing left to retire. Discarding it here is what keeps this
1883  /// list at the handle's currently-live frame count rather than the total
1884  /// number of frames it has ever been polled under: without it, a pending
1885  /// handle carried through a sequence of completed tasks would accumulate one
1886  /// token per task and the lookup below would grow with it.
1887  /// Forget publications that no longer stand.
1888  ///
1889  /// A token is obsolete once its frame is gone -- a dropped `TaskDependency`
1890  /// cancels every entry it holds, so there is nothing left to retire -- or
1891  /// once that frame no longer holds the entry, because its `begin_poll`
1892  /// drained `current`. Dropping both kinds keeps this list at the handle's
1893  /// currently-live frame count rather than the number of frames it has ever
1894  /// been polled under.
1895  ///
1896  /// Testing the second condition costs a lock, so it is confined to the
1897  /// multi-frame path: a handle polled under a single frame -- the
1898  /// overwhelmingly common case -- reuses its one token and never reaches it.
1899  /// Without that condition a handle carried through many tasks whose
1900  /// `JoinHandle`s are still held would keep one token per task, since those
1901  /// handles keep the states alive, and both this walk and detach would grow
1902  /// with that count.
1903  fn prune(&mut self) {
1904    if self.rest.is_empty() {
1905      if self
1906        .first
1907        .as_ref()
1908        .is_some_and(|first| first.state.strong_count() == 0)
1909      {
1910        self.first = None;
1911      }
1912      return;
1913    }
1914    self.rest.retain(PublishedBlockingDependency::still_stands);
1915    if self
1916      .first
1917      .as_ref()
1918      .is_some_and(|first| !first.still_stands())
1919    {
1920      self.first = self.rest.pop();
1921    }
1922  }
1923
1924  fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1925    self.prune();
1926    let target = Arc::as_ptr(&state);
1927    if let Some(existing) = self
1928      .first
1929      .iter_mut()
1930      .chain(self.rest.iter_mut())
1931      .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1932    {
1933      // The entry this handle already published into this frame still says the
1934      // frame depends on this job, so a repeat Pending poll must LEAVE IT
1935      // ALONE. Publishing again would append at the tail, and
1936      // `select_for_owner` picks the LOWEST live sequence, so re-polling a
1937      // handle would push its own dependency behind ones published after it.
1938      // In an H1 -> H2 -> H1 poll pass that reorders H1 behind H2, and if H2
1939      // waits on H1 the owner lane lends H2 inline while H1 stays queued
1940      // forever. Keeping the original sequence preserves the publication order
1941      // the job index produced, and skips a `DependencyClaim`/`DependencyLink`
1942      // pair per repeat poll.
1943      if state.holds_live(existing.sequence) {
1944        return;
1945      }
1946      // The entry was drained, pruned or claimed, so it no longer stands and
1947      // this frame needs a fresh one. Reusing the token keeps the list at one
1948      // entry per frame.
1949      existing.sequence = state.set_owned(dependency);
1950      return;
1951    }
1952    let sequence = state.set_owned(dependency);
1953    let published = PublishedBlockingDependency {
1954      state: Arc::downgrade(&state),
1955      sequence,
1956    };
1957    match &mut self.first {
1958      slot @ None => *slot = Some(published),
1959      Some(_) => self.rest.push(published),
1960    }
1961  }
1962
1963  /// Retire every publication, each against the state it was filed in.
1964  ///
1965  /// This is exactly inverse to publication, so it can never cancel an entry
1966  /// this handle did not create. It cannot strand a frame either: a frame is
1967  /// only stranded if it is parked awaiting something, and it cannot be parked
1968  /// awaiting a handle that is concurrently being dropped, because the handle
1969  /// is owned by the future that frame is driving. A frame that has already
1970  /// gone cancelled its entries in its own `Drop`, so an expired token is
1971  /// simply skipped.
1972  fn retire_all(self) {
1973    for published in self.first.into_iter().chain(self.rest) {
1974      if let Some(state) = published.state.upgrade() {
1975        state.retire(published.sequence);
1976      }
1977    }
1978  }
1979}
1980
1981#[cfg(napi_runtime_os_threads)]
1982fn record_blocking_dependency(
1983  published: &mut PublishedBlockingDependencies,
1984  dependency: BlockingDependency,
1985) {
1986  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1987  if let Some(current) = current {
1988    let dependency = BlockingDependency {
1989      owner: dependency
1990        .owner
1991        .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1992      ..dependency
1993    };
1994    published.publish(current, dependency);
1995  }
1996}
1997
1998#[cfg(napi_runtime_os_threads)]
1999fn propagate_blocking_dependency(publication: DependencyPublication) {
2000  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2001  if let Some(current) = current {
2002    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
2003    let publication = publication.with_owner_if_none(owner);
2004    current.set_borrowed(&publication);
2005  }
2006}
2007
2008#[cfg(napi_runtime_os_threads)]
2009fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
2010  // Retire against the states that were published to, NOT against whatever
2011  // frame happens to be on top of the stack at drop time. The frame that
2012  // published is the frame whose dependency graph is now wrong.
2013  published.retire_all();
2014}
2015
2016#[cfg(napi_runtime_os_threads)]
2017fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
2018  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
2019  if let Some(current) = current {
2020    current.clear_if_publications(publications);
2021  }
2022}
2023
2024enum JoinHandleInner<T> {
2025  Task {
2026    task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2027    awaiter: Option<PanicContainedWakerCache>,
2028    #[cfg(napi_runtime_os_threads)]
2029    dependency: Arc<TaskDependency>,
2030  },
2031  #[cfg(napi_runtime_os_threads)]
2032  Blocking {
2033    receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2034    dependency: BlockingDependency,
2035    /// Every dependency this handle has published, so detaching retires each
2036    /// in O(1) instead of searching for them by job id.
2037    published: PublishedBlockingDependencies,
2038  },
2039  Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2040}
2041
2042pub struct JoinHandle<T>(JoinHandleInner<T>);
2043
2044impl<T> Unpin for JoinHandle<T> {}
2045
2046impl<T> JoinHandle<T> {
2047  fn detach_task(&mut self) {
2048    let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2049    match inner {
2050      JoinHandleInner::Task {
2051        task,
2052        awaiter: _,
2053        #[cfg(napi_runtime_os_threads)]
2054        dependency,
2055      } => {
2056        #[cfg(napi_runtime_os_threads)]
2057        let current_dependencies = dependency.snapshot_all_live();
2058        #[cfg(napi_runtime_os_threads)]
2059        dependency.clear_waiter();
2060        task.detach();
2061        #[cfg(napi_runtime_os_threads)]
2062        // Dependency cleanup may wake an arbitrary caller-provided waker.
2063        // Retire the task first, then contain the notification boundary so
2064        // reentrant wake code cannot observe a half-detached handle.
2065        let _ = catch_unwind_contained(|| {
2066          clear_propagated_blocking_dependencies(&current_dependencies);
2067        });
2068      }
2069      #[cfg(napi_runtime_os_threads)]
2070      JoinHandleInner::Blocking {
2071        receiver,
2072        dependency: _,
2073        published,
2074      } => {
2075        // A completed result may already be buffered in the receiver. Its
2076        // destructor is user code and must receive the same containment as an
2077        // immediate CurrentThread result.
2078        let _ = catch_unwind_contained(|| drop(receiver));
2079        // Notify only after receiver retirement, and keep that arbitrary waker
2080        // behind an independent unwind boundary.
2081        let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2082      }
2083      JoinHandleInner::Ready(result) => {
2084        let _ = catch_unwind_contained(|| drop(result));
2085      }
2086    }
2087  }
2088
2089  pub fn detach(mut self) {
2090    self.detach_task();
2091  }
2092}
2093
2094impl<T> Drop for JoinHandle<T> {
2095  fn drop(&mut self) {
2096    // Tokio's JoinHandle detaches on drop. Preserve that compatibility:
2097    // fire-and-forget callers must not silently cancel work merely because
2098    // they ignore the returned handle.
2099    self.detach_task();
2100  }
2101}
2102
2103impl<T> Future for JoinHandle<T> {
2104  type Output = Result<T, JoinError>;
2105
2106  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2107    match &mut self.get_mut().0 {
2108      JoinHandleInner::Task {
2109        task,
2110        awaiter: awaiter_slot,
2111        #[cfg(napi_runtime_os_threads)]
2112        dependency,
2113      } => {
2114        let awaiter = match awaiter_slot {
2115          Some(awaiter) => {
2116            awaiter.update(cx.waker());
2117            awaiter
2118          }
2119          None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2120        };
2121        let contained_waker = awaiter.contained();
2122        let mut contained_cx = Context::from_waker(contained_waker);
2123        let poll = Pin::new(task).poll(&mut contained_cx);
2124        #[cfg(napi_runtime_os_threads)]
2125        if poll.is_pending() {
2126          for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2127            propagate_blocking_dependency(current_dependency);
2128          }
2129        } else {
2130          dependency.clear_waiter();
2131        }
2132        if poll.is_ready() {
2133          *awaiter_slot = None;
2134        }
2135        match poll {
2136          Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2137          Poll::Ready(None) => Poll::Ready(Err(JoinError {
2138            message: "async runtime stopped before the task completed".to_string(),
2139          })),
2140          Poll::Pending => Poll::Pending,
2141        }
2142      }
2143      #[cfg(napi_runtime_os_threads)]
2144      JoinHandleInner::Blocking {
2145        receiver,
2146        dependency,
2147        published,
2148      } => {
2149        let poll = Pin::new(receiver).poll(cx);
2150        if poll.is_pending() {
2151          record_blocking_dependency(published, *dependency);
2152        }
2153        match poll {
2154          Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2155          Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2156            message: "async runtime stopped before the blocking task completed".to_string(),
2157          })),
2158          Poll::Pending => Poll::Pending,
2159        }
2160      }
2161      JoinHandleInner::Ready(result) => Poll::Ready(
2162        result
2163          .take()
2164          .expect("JoinHandle polled after completion")
2165          .map(ContainedTaskOutput::into_inner),
2166      ),
2167    }
2168  }
2169}
2170
2171#[derive(Debug, Clone, Copy)]
2172pub struct RuntimeMetricsSnapshot {
2173  pub flavor: RuntimeFlavor,
2174  pub worker_threads: usize,
2175  pub max_blocking_tasks: usize,
2176  pub tasks_spawned: u64,
2177  pub tasks_completed: u64,
2178  pub tasks_panicked: u64,
2179  pub runnable_schedules: u64,
2180  pub runnable_polls: u64,
2181  pub queued_runnables: u64,
2182  pub max_queued_runnables: u64,
2183  pub active_runnables: u64,
2184  pub max_active_runnables: u64,
2185  pub blocking_tasks_started: u64,
2186  pub blocking_tasks_completed: u64,
2187  pub active_blocking_tasks: u64,
2188  pub max_active_blocking_tasks: u64,
2189}
2190
2191#[derive(Default)]
2192struct RuntimeMetrics {
2193  tasks_spawned: AtomicU64,
2194  tasks_completed: AtomicU64,
2195  tasks_panicked: AtomicU64,
2196  runnable_schedules: AtomicU64,
2197  runnable_polls: AtomicU64,
2198  queued_runnables: AtomicU64,
2199  max_queued_runnables: AtomicU64,
2200  active_runnables: AtomicU64,
2201  max_active_runnables: AtomicU64,
2202  // Bumped at ENQUEUE time (before the queue push), the blocking-side twin of
2203  // `runnable_schedules`. Internal-only (not exported in the public snapshot):
2204  // it exists so the deadlock detector's progress fingerprint can observe a
2205  // blocking submission the moment it is made -- `blocking_tasks_started`
2206  // only advances once a worker RUNS the job, which may be arbitrarily later.
2207  blocking_tasks_scheduled: AtomicU64,
2208  blocking_tasks_started: AtomicU64,
2209  blocking_tasks_completed: AtomicU64,
2210  active_blocking_tasks: AtomicU64,
2211  max_active_blocking_tasks: AtomicU64,
2212  // Seqlock generation around resets. The deadlock detector snapshots
2213  // resettable counters, so it must distinguish identical counter values from
2214  // different reset generations instead of mistaking that ABA for no progress.
2215  reset_generation: AtomicU64,
2216  reset_lock: Mutex<()>,
2217}
2218
2219#[cfg(test)]
2220thread_local! {
2221  static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2222    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2223      const { std::cell::RefCell::new(None) };
2224}
2225
2226#[cfg(test)]
2227fn run_runtime_metrics_high_water_test_hook() {
2228  if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2229    hook();
2230  }
2231}
2232
2233impl RuntimeMetrics {
2234  fn runnable_scheduled(&self) {
2235    self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2236    let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2237    #[cfg(test)]
2238    run_runtime_metrics_high_water_test_hook();
2239    self
2240      .max_queued_runnables
2241      .fetch_max(queued, Ordering::Relaxed);
2242  }
2243
2244  fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2245    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2246    self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2247    let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2248    #[cfg(test)]
2249    run_runtime_metrics_high_water_test_hook();
2250    self
2251      .max_active_runnables
2252      .fetch_max(active, Ordering::Relaxed);
2253    ActiveRunnableGuard { metrics: self }
2254  }
2255
2256  #[cfg(any(napi_runtime_os_threads, test))]
2257  fn runnable_cancelled(&self) {
2258    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2259  }
2260
2261  /// Enqueue-time twin of [`Self::runnable_scheduled`] for blocking work:
2262  /// called BEFORE the blocking-queue push, so the deadlock detector's
2263  /// fingerprint sees a submission even when no worker has started the job.
2264  fn blocking_scheduled(&self) {
2265    self
2266      .blocking_tasks_scheduled
2267      .fetch_add(1, Ordering::Relaxed);
2268  }
2269
2270  fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2271    self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2272    if count_active_lane {
2273      let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2274      #[cfg(test)]
2275      run_runtime_metrics_high_water_test_hook();
2276      self
2277        .max_active_blocking_tasks
2278        .fetch_max(active, Ordering::Relaxed);
2279    }
2280    BlockingTaskGuard {
2281      metrics: Arc::clone(self),
2282      count_active_lane,
2283    }
2284  }
2285
2286  fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2287    let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2288    let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2289    let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2290    RuntimeMetricsSnapshot {
2291      flavor: options.flavor,
2292      worker_threads: options.worker_threads,
2293      max_blocking_tasks: options.max_blocking_tasks,
2294      tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2295      tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2296      tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2297      runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2298      runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2299      queued_runnables,
2300      max_queued_runnables: self
2301        .max_queued_runnables
2302        .load(Ordering::Relaxed)
2303        .max(queued_runnables),
2304      active_runnables,
2305      max_active_runnables: self
2306        .max_active_runnables
2307        .load(Ordering::Relaxed)
2308        .max(active_runnables),
2309      blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2310      blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2311      active_blocking_tasks,
2312      max_active_blocking_tasks: self
2313        .max_active_blocking_tasks
2314        .load(Ordering::Relaxed)
2315        .max(active_blocking_tasks),
2316    }
2317  }
2318
2319  /// Coarse liveness fingerprint for deadline-based deadlock detection (wake
2320  /// -path §8.5): any of these counters advancing between two reads means the
2321  /// runtime did SOMETHING (scheduled or polled a runnable, completed a task,
2322  /// scheduled, started or finished a blocking job), so a parked driver
2323  /// observing an advance re-arms its deadline instead of firing. Both ENQUEUE
2324  /// counters (`runnable_schedules`, `blocking_tasks_scheduled`) are included
2325  /// so a submission is progress the moment it is made -- not only once a
2326  /// worker gets to run it.
2327  fn progress_fingerprint(&self) -> ProgressFingerprint {
2328    loop {
2329      let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2330      if !reset_generation.is_multiple_of(2) {
2331        std::hint::spin_loop();
2332        continue;
2333      }
2334      let fingerprint = ProgressFingerprint {
2335        reset_generation,
2336        runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2337        runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2338        tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2339        blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2340        blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2341        blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2342      };
2343      if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2344        return fingerprint;
2345      }
2346    }
2347  }
2348
2349  fn reset(&self) {
2350    let reset_guard = self
2351      .reset_lock
2352      .lock()
2353      .unwrap_or_else(std::sync::PoisonError::into_inner);
2354    // Event counters are safe to reset independently. Live gauges are not:
2355    // queued/active guards may still hold a future decrement, and zeroing a
2356    // gauge underneath them wraps the unsigned counter on completion.
2357    //
2358    // High-water gauges remain lifetime values. Resetting them concurrently
2359    // with fetch_max would either lose a real peak or require synchronization
2360    // on every hot-path metric update.
2361    let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2362    let Some(resetting_generation) = reset_generation.checked_add(1) else {
2363      drop(reset_guard);
2364      panic!("runtime metrics reset generation space exhausted");
2365    };
2366    let Some(settled_generation) = resetting_generation.checked_add(1) else {
2367      drop(reset_guard);
2368      panic!("runtime metrics reset generation space exhausted");
2369    };
2370    self
2371      .reset_generation
2372      .store(resetting_generation, Ordering::SeqCst);
2373    self.tasks_spawned.store(0, Ordering::Relaxed);
2374    self.tasks_completed.store(0, Ordering::Relaxed);
2375    self.tasks_panicked.store(0, Ordering::Relaxed);
2376    self.runnable_schedules.store(0, Ordering::Relaxed);
2377    self.runnable_polls.store(0, Ordering::Relaxed);
2378    self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2379    self.blocking_tasks_started.store(0, Ordering::Relaxed);
2380    self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2381    self
2382      .reset_generation
2383      .store(settled_generation, Ordering::SeqCst);
2384  }
2385}
2386
2387/// See [`RuntimeMetrics::progress_fingerprint`].
2388#[derive(Clone, Copy, PartialEq, Eq)]
2389struct ProgressFingerprint {
2390  reset_generation: u64,
2391  runnable_schedules: u64,
2392  runnable_polls: u64,
2393  tasks_completed: u64,
2394  blocking_tasks_scheduled: u64,
2395  blocking_tasks_started: u64,
2396  blocking_tasks_completed: u64,
2397}
2398
2399struct ActiveRunnableGuard<'a> {
2400  metrics: &'a RuntimeMetrics,
2401}
2402
2403impl Drop for ActiveRunnableGuard<'_> {
2404  fn drop(&mut self) {
2405    self
2406      .metrics
2407      .active_runnables
2408      .fetch_sub(1, Ordering::Relaxed);
2409  }
2410}
2411
2412struct BlockingTaskGuard {
2413  metrics: Arc<RuntimeMetrics>,
2414  count_active_lane: bool,
2415}
2416
2417impl Drop for BlockingTaskGuard {
2418  fn drop(&mut self) {
2419    if self.count_active_lane {
2420      self
2421        .metrics
2422        .active_blocking_tasks
2423        .fetch_sub(1, Ordering::Relaxed);
2424    }
2425    self
2426      .metrics
2427      .blocking_tasks_completed
2428      .fetch_add(1, Ordering::Relaxed);
2429  }
2430}
2431
2432static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2433
2434fn next_runtime_generation() -> u64 {
2435  next_unique_id(
2436    &NEXT_RUNTIME_GENERATION,
2437    "async runtime generation id space exhausted",
2438  )
2439}
2440
2441thread_local! {
2442  static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2443    const { std::cell::Cell::new(None) };
2444}
2445
2446fn active_runtime_generation() -> Option<u64> {
2447  ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2448}
2449
2450struct RuntimeGenerationGuard {
2451  previous: Option<u64>,
2452}
2453
2454impl RuntimeGenerationGuard {
2455  fn enter(generation: u64) -> Self {
2456    Self::enter_optional(Some(generation))
2457  }
2458
2459  fn enter_optional(generation: Option<u64>) -> Self {
2460    Self {
2461      previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2462    }
2463  }
2464}
2465
2466impl Drop for RuntimeGenerationGuard {
2467  fn drop(&mut self) {
2468    ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2469  }
2470}
2471
2472struct ContainedFuture<F> {
2473  future: ManuallyDrop<F>,
2474  generation: u64,
2475}
2476
2477impl<F> ContainedFuture<F> {
2478  fn new(future: F, generation: u64) -> Self {
2479    Self {
2480      future: ManuallyDrop::new(future),
2481      generation,
2482    }
2483  }
2484}
2485
2486impl<F: Future> Future for ContainedFuture<F> {
2487  type Output = F::Output;
2488
2489  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2490    // SAFETY: `future` is never moved after `ContainedFuture` is pinned.
2491    let this = unsafe { self.get_unchecked_mut() };
2492    let _generation = RuntimeGenerationGuard::enter(this.generation);
2493    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2494    // structurally pinned for the lifetime of `self`.
2495    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2496  }
2497}
2498
2499impl<F> Drop for ContainedFuture<F> {
2500  fn drop(&mut self) {
2501    let _generation = RuntimeGenerationGuard::enter(self.generation);
2502    let _ = catch_unwind_contained(|| {
2503      // SAFETY: this is the sole destruction path for `future`.
2504      unsafe { ManuallyDrop::drop(&mut self.future) };
2505    });
2506  }
2507}
2508
2509#[derive(Default)]
2510struct GenerationStop {
2511  stopping: AtomicBool,
2512  publication: Mutex<()>,
2513  parkers: Mutex<Vec<Weak<DriverParker>>>,
2514}
2515
2516impl GenerationStop {
2517  fn is_stopping(&self) -> bool {
2518    self.stopping.load(Ordering::Acquire)
2519  }
2520
2521  fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2522    #[cfg(all(test, napi_runtime_os_threads))]
2523    run_before_generation_stop_publication_lock_test_hook();
2524    self.runnable_claim_guard()
2525  }
2526
2527  fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2528    GenerationStopPublicationGuard {
2529      stop: self,
2530      _publication: self
2531        .publication
2532        .lock()
2533        .unwrap_or_else(std::sync::PoisonError::into_inner),
2534    }
2535  }
2536
2537  fn publish_stopping(&self) {
2538    self.publication_guard().publish_stopping();
2539  }
2540
2541  fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2542    {
2543      let mut parkers = self
2544        .parkers
2545        .lock()
2546        .unwrap_or_else(std::sync::PoisonError::into_inner);
2547      parkers.retain(|candidate| candidate.strong_count() != 0);
2548      parkers.push(Arc::downgrade(parker));
2549    }
2550    if self.is_stopping() {
2551      parker.unpark();
2552    }
2553    GenerationStopGuard {
2554      stop: Arc::clone(self),
2555      parker: Arc::downgrade(parker),
2556    }
2557  }
2558
2559  /// Wake every active `block_on` frame without changing the generation state.
2560  /// The number of parkers is bounded by concurrent explicit drivers. Waking
2561  /// all avoids directing a queue wake only to a newer driver that is blocked
2562  /// inside its poll while an older driver is already asleep.
2563  fn wake_all(&self) -> usize {
2564    let mut count = 0;
2565    let mut registered = self
2566      .parkers
2567      .lock()
2568      .unwrap_or_else(std::sync::PoisonError::into_inner);
2569    registered.retain(|candidate| {
2570      let Some(parker) = candidate.upgrade() else {
2571        return false;
2572      };
2573      count += 1;
2574      // `DriverParker::unpark` invokes no user code and never enters the
2575      // generation registry, so waking under this lock is non-reentrant.
2576      parker.unpark();
2577      true
2578    });
2579    count
2580  }
2581
2582  fn begin_shutdown(&self) {
2583    self.publish_stopping();
2584    let parkers = {
2585      let mut registered = self
2586        .parkers
2587        .lock()
2588        .unwrap_or_else(std::sync::PoisonError::into_inner);
2589      let parkers = registered
2590        .iter()
2591        .filter_map(Weak::upgrade)
2592        .collect::<Vec<_>>();
2593      registered.clear();
2594      parkers
2595    };
2596    for parker in parkers {
2597      parker.unpark();
2598    }
2599  }
2600}
2601
2602struct GenerationStopPublicationGuard<'a> {
2603  stop: &'a GenerationStop,
2604  _publication: std::sync::MutexGuard<'a, ()>,
2605}
2606
2607impl GenerationStopPublicationGuard<'_> {
2608  fn is_stopping(&self) -> bool {
2609    self.stop.is_stopping()
2610  }
2611
2612  fn publish_stopping(&self) {
2613    self.stop.stopping.store(true, Ordering::Release);
2614  }
2615}
2616
2617struct GenerationStopGuard {
2618  stop: Arc<GenerationStop>,
2619  parker: Weak<DriverParker>,
2620}
2621
2622impl Drop for GenerationStopGuard {
2623  fn drop(&mut self) {
2624    let Some(parker) = self.parker.upgrade() else {
2625      return;
2626    };
2627    let mut registered = self
2628      .stop
2629      .parkers
2630      .lock()
2631      .unwrap_or_else(std::sync::PoisonError::into_inner);
2632    if let Some(index) = registered.iter().position(|candidate| {
2633      candidate
2634        .upgrade()
2635        .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2636    }) {
2637      registered.swap_remove(index);
2638    }
2639  }
2640}
2641
2642struct GenerationWorkState {
2643  closed: bool,
2644  next_task_id: u64,
2645  active: usize,
2646  abort_handles: FxHashMap<u64, AbortHandle>,
2647}
2648
2649struct GenerationWork {
2650  id: u64,
2651  state: Mutex<GenerationWorkState>,
2652  idle: Condvar,
2653}
2654
2655impl GenerationWork {
2656  fn new() -> Arc<Self> {
2657    Arc::new(Self {
2658      id: next_runtime_generation(),
2659      state: Mutex::new(GenerationWorkState {
2660        closed: false,
2661        next_task_id: 0,
2662        active: 0,
2663        abort_handles: FxHashMap::default(),
2664      }),
2665      idle: Condvar::new(),
2666    })
2667  }
2668
2669  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2670    let (abort_handle, abort_registration) = AbortHandle::new_pair();
2671    let mut state = self
2672      .state
2673      .lock()
2674      .unwrap_or_else(std::sync::PoisonError::into_inner);
2675    if state.closed {
2676      return None;
2677    }
2678    let task_id = state.next_task_id;
2679    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2680      drop(state);
2681      panic!("async runtime generation task id space exhausted");
2682    };
2683    state.next_task_id = next_task_id;
2684    state.active += 1;
2685    state.abort_handles.insert(task_id, abort_handle);
2686    Some((
2687      abort_registration,
2688      GenerationWorkGuard {
2689        work: Arc::clone(self),
2690        task_id: Some(task_id),
2691      },
2692    ))
2693  }
2694
2695  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2696    let mut state = self
2697      .state
2698      .lock()
2699      .unwrap_or_else(std::sync::PoisonError::into_inner);
2700    if state.closed {
2701      return None;
2702    }
2703    state.active += 1;
2704    Some(GenerationWorkGuard {
2705      work: Arc::clone(self),
2706      task_id: None,
2707    })
2708  }
2709
2710  fn close_and_abort(&self) {
2711    let abort_handles = {
2712      let mut state = self
2713        .state
2714        .lock()
2715        .unwrap_or_else(std::sync::PoisonError::into_inner);
2716      state.closed = true;
2717      state.abort_handles.values().cloned().collect::<Vec<_>>()
2718    };
2719    for abort_handle in abort_handles {
2720      abort_handle.abort();
2721    }
2722  }
2723
2724  fn wait_until_idle(&self) {
2725    self.wait_until_idle_retiring(|| false, || {});
2726  }
2727
2728  /// Non-blocking read of the predicate `wait_until_idle` sleeps on.
2729  fn has_active_work(&self) -> bool {
2730    self
2731      .state
2732      .lock()
2733      .unwrap_or_else(std::sync::PoisonError::into_inner)
2734      .active
2735      != 0
2736  }
2737
2738  /// Wait until every registered guard has retired, retiring deferred work
2739  /// on this stack whenever `has_deferred` reports some. Such work (a runnable
2740  /// rejected after the executor queue closed, or the runnables a terminal
2741  /// failure transition moved aside on a waker's stack) holds guards, so the
2742  /// generation can only become idle once the waiter drops them; `retire`
2743  /// runs with no lock held. `notify_idle_waiters` publishes new deferred
2744  /// work under the state lock, so a check made here cannot miss it.
2745  fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2746    let mut state = self
2747      .state
2748      .lock()
2749      .unwrap_or_else(std::sync::PoisonError::into_inner);
2750    while state.active != 0 {
2751      if has_deferred() {
2752        drop(state);
2753        retire();
2754        state = self
2755          .state
2756          .lock()
2757          .unwrap_or_else(std::sync::PoisonError::into_inner);
2758        continue;
2759      }
2760      state = self
2761        .idle
2762        .wait(state)
2763        .unwrap_or_else(std::sync::PoisonError::into_inner);
2764    }
2765  }
2766
2767  fn notify_idle_waiters(&self) {
2768    let _state = self
2769      .state
2770      .lock()
2771      .unwrap_or_else(std::sync::PoisonError::into_inner);
2772    self.idle.notify_all();
2773  }
2774}
2775
2776struct GenerationWorkGuard {
2777  work: Arc<GenerationWork>,
2778  task_id: Option<u64>,
2779}
2780
2781impl Drop for GenerationWorkGuard {
2782  fn drop(&mut self) {
2783    let mut state = self
2784      .work
2785      .state
2786      .lock()
2787      .unwrap_or_else(std::sync::PoisonError::into_inner);
2788    if let Some(task_id) = self.task_id {
2789      state.abort_handles.remove(&task_id);
2790    }
2791    state.active = state
2792      .active
2793      .checked_sub(1)
2794      .expect("generation work count underflow");
2795    if state.active == 0 {
2796      self.work.idle.notify_all();
2797    }
2798  }
2799}
2800
2801/// Keeps accepted async work registered until the complete task generator has
2802/// been destroyed. In particular, an executor queue may close after
2803/// registration but before the initial runnable is published. `async-task`
2804/// then drops the generator unpolled, whose compiler-generated capture order
2805/// must not be allowed to retire the generation guard before user captures.
2806struct RegisteredTaskFuture<F> {
2807  future: ManuallyDrop<F>,
2808  registration: Option<GenerationWorkGuard>,
2809  generation: u64,
2810}
2811
2812impl<F> RegisteredTaskFuture<F> {
2813  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2814    Self {
2815      future: ManuallyDrop::new(future),
2816      registration: Some(registration),
2817      generation,
2818    }
2819  }
2820}
2821
2822impl<F: Future> Future for RegisteredTaskFuture<F> {
2823  type Output = F::Output;
2824
2825  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2826    // SAFETY: `future` is never moved after `RegisteredTaskFuture` is pinned.
2827    let this = unsafe { self.get_unchecked_mut() };
2828    let _generation = RuntimeGenerationGuard::enter(this.generation);
2829    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2830    // structurally pinned for the lifetime of `self`.
2831    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2832  }
2833}
2834
2835impl<F> Drop for RegisteredTaskFuture<F> {
2836  fn drop(&mut self) {
2837    let _generation = RuntimeGenerationGuard::enter(self.generation);
2838    let _ = catch_unwind_contained(|| {
2839      // SAFETY: this is the sole destruction path for `future`.
2840      unsafe { ManuallyDrop::drop(&mut self.future) };
2841    });
2842    // Shutdown/restart may observe this generation idle only after every user
2843    // capture and caught panic payload above has retired.
2844    drop(self.registration.take());
2845  }
2846}
2847
2848#[cfg(napi_runtime_os_threads)]
2849struct RegisteredBlockingFunction<F> {
2850  function: Option<F>,
2851  registration: Option<GenerationWorkGuard>,
2852  generation: u64,
2853}
2854
2855#[cfg(napi_runtime_os_threads)]
2856impl<F> RegisteredBlockingFunction<F> {
2857  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2858    Self {
2859      function: Some(function),
2860      registration: Some(registration),
2861      generation,
2862    }
2863  }
2864
2865  fn run<T>(mut self) -> Result<T, JoinError>
2866  where
2867    F: FnOnce() -> T,
2868  {
2869    let _generation = RuntimeGenerationGuard::enter(self.generation);
2870    let result = catch_unwind_join_error(
2871      self
2872        .function
2873        .take()
2874        .expect("blocking function must be available"),
2875    );
2876    drop(self.registration.take());
2877    result
2878  }
2879}
2880
2881#[cfg(napi_runtime_os_threads)]
2882impl<F> Drop for RegisteredBlockingFunction<F> {
2883  fn drop(&mut self) {
2884    let _generation = RuntimeGenerationGuard::enter(self.generation);
2885    if let Some(function) = self.function.take() {
2886      let _ = catch_unwind_contained(|| drop(function));
2887    }
2888    // A submission accepted by the controller may reach a queue that shutdown
2889    // already closed. Keep it registered until all captured user state above
2890    // has been destroyed on the submitting thread.
2891    drop(self.registration.take());
2892  }
2893}
2894
2895fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2896  let _active = metrics.runnable_started();
2897  let _ = catch_unwind_contained(|| runnable.run());
2898}
2899
2900#[cfg(napi_runtime_os_threads)]
2901enum RunnableClaim {
2902  Empty,
2903  Run(Runnable),
2904  Cancel(Runnable),
2905}
2906
2907static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2908static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2909
2910fn next_current_thread_dispatch_id() -> u64 {
2911  next_unique_id(
2912    &NEXT_CURRENT_THREAD_DISPATCH_ID,
2913    "CurrentThread dispatch id space exhausted",
2914  )
2915  .checked_add(1)
2916  .expect("CurrentThread dispatch id space exhausted")
2917}
2918
2919struct CurrentThreadBlockingAdmission {
2920  state: Mutex<CurrentThreadBlockingAdmissionState>,
2921}
2922
2923struct CurrentThreadBlockingAdmissionState {
2924  owner: Option<BlockingOwnerToken>,
2925  closed: bool,
2926  #[cfg(napi_runtime_os_threads)]
2927  queue: BlockingQueue,
2928}
2929
2930struct CurrentThreadExecutor {
2931  id: u64,
2932  generation: u64,
2933  stop: Arc<GenerationStop>,
2934  queue: Mutex<CurrentThreadQueue>,
2935  blocking_admission: CurrentThreadBlockingAdmission,
2936  draining: AtomicBool,
2937  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2938  scheduler_idle: Condvar,
2939  /// Exact nonzero internal capability for the currently published host turn.
2940  /// The host registry maps this to distinct registration-scoped delivery
2941  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
2942  dispatch_pending: AtomicU64,
2943  /// Exact capability of the one replacement dispatch allowed after a host
2944  /// scheduler rejects an accepted callback. A second matching cancellation
2945  /// fails the queued work instead of retrying forever.
2946  recovery_dispatch: AtomicU64,
2947  /// Terminal dispatch failure rejects scheduler publications and admission
2948  /// from the moment it is claimed (under `scheduler_idle_lock`) until
2949  /// `cancel_work_after_host_dispatch_failure` reopens the executor under the
2950  /// queue lock: after cancelling the queued work on a clean stack, or as
2951  /// soon as it has moved that work aside on a waker's stack.
2952  cancelling_failed_dispatch: AtomicBool,
2953  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2954  task_drivers: &'static CurrentThreadTaskDriverRegistry,
2955  metrics: Arc<RuntimeMetrics>,
2956  // Deadlock detection. `threadless`: no second thread can EVER deliver a
2957  // wake to a parked `block_on`, so a park decision with an
2958  // empty queue and no pending wake token is a PROVABLE deadlock (panic
2959  // immediately, no timing involved). `park_deadline`: on threaded builds,
2960  // the optional bound for a park with zero runtime progress -- off unless
2961  // configured, because a legitimately long park must never panic a
2962  // production build.
2963  threadless: bool,
2964  park_deadline: Option<Duration>,
2965  // HOST-driver timers currently armed on this runtime.
2966  // On the THREADED CT flavor a pending entry is a future wake token -- the
2967  // host event loop's timer callback -- so the deadline verdict treats a LIVE
2968  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
2969  // build it is NOT a wake token (the host relay shares the parked thread and
2970  // can never run); there the CERTAIN check only consults it to pick the
2971  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
2972  // futures minted for this executor (they insert at first poll and remove on
2973  // completion/drop).
2974  host_timers: Arc<HostTimerRegistry>,
2975  // The generation this executor serves, attached by its `RuntimeBackend`.
2976  // A runnable rejected after the queue closed still owns its
2977  // `GenerationWorkGuard`, so `schedule` must wake the generation's idle
2978  // waiter, which retires such runnables on its own stack.
2979  generation_work: ArcSwapOption<GenerationWork>,
2980}
2981
2982impl Drop for CurrentThreadExecutor {
2983  fn drop(&mut self) {
2984    // Last drain point for rejected work (each runnable still owns its
2985    // `GenerationWorkGuard`) when no idle wait ran after the queue closed.
2986    self.retire_rejected_work();
2987  }
2988}
2989
2990struct CurrentThreadQueue {
2991  closed: bool,
2992  runnables: VecDeque<Runnable>,
2993  /// Runnables the executor owes a cancellation: the work that was queued
2994  /// when a terminal dispatch failure happened (moved here by
2995  /// `cancel_work_after_host_dispatch_failure`), a schedule that arrived
2996  /// while that transition held the executor closed, or a schedule that
2997  /// arrived after shutdown closed the queue. Their `queued_runnables` debit
2998  /// has already been taken. They are dropped only on clean stacks: by the
2999  /// transition itself when it is not on a waker's stack, and otherwise by
3000  /// `retire_rejected_work` at host entry points (a host turn, delivery
3001  /// completion, driver unregistration, a drain request) outside `schedule`
3002  /// and on the shutdown side (`begin_shutdown`, the generation's idle wait,
3003  /// `wait_until_scheduler_idle`, the executor's destructor). Never on the
3004  /// waker's stack: that waker may run while its producer holds a mutex
3005  /// (`futures::Shared` wakes awaiters under its `wakers` lock) that the
3006  /// rejected future's destructor re-locks. See `CurrentThreadScheduleScope`.
3007  rejected: Vec<Runnable>,
3008  /// Blocking jobs queued when a terminal dispatch failure happened. Same
3009  /// contract as `rejected`: dropping a job destroys its closure and its
3010  /// result sender, which wakes the awaiting task.
3011  #[cfg(napi_runtime_os_threads)]
3012  rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
3013}
3014
3015#[derive(Default)]
3016struct CurrentThreadSchedulerState {
3017  active_host_turns: usize,
3018  active_dispatch_calls: usize,
3019  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
3020}
3021
3022struct CurrentThreadDispatchPublication {
3023  dispatch: u64,
3024  republish_requested: bool,
3025}
3026
3027#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3028enum CurrentThreadDispatchRequest {
3029  Accepted,
3030  Coalesced,
3031  Unavailable,
3032}
3033
3034#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3035struct CurrentThreadTaskDispatchFailure {
3036  dispatch: u64,
3037  epoch: u64,
3038}
3039
3040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3041enum CurrentThreadHostDispatchResult {
3042  Accepted,
3043  Unavailable,
3044  Failed(CurrentThreadTaskDispatchFailure),
3045}
3046
3047enum CurrentThreadDispatchPublicationStep {
3048  Republish,
3049  Complete(Option<CurrentThreadHostDispatchResult>),
3050}
3051
3052struct CurrentThreadHostTurn {
3053  executor: Arc<CurrentThreadExecutor>,
3054  draining: bool,
3055  _generation: RuntimeGenerationGuard,
3056}
3057
3058impl CurrentThreadHostTurn {
3059  fn drive(&mut self) {
3060    let executor = Arc::clone(&self.executor);
3061    executor.drive_admitted_host_turn(self);
3062  }
3063
3064  fn release_draining(&mut self) {
3065    if !self.draining {
3066      return;
3067    }
3068    let _scheduler = self
3069      .executor
3070      .scheduler_idle_lock
3071      .lock()
3072      .unwrap_or_else(std::sync::PoisonError::into_inner);
3073    self.executor.draining.store(false, Ordering::Release);
3074    self.draining = false;
3075  }
3076}
3077
3078impl Drop for CurrentThreadHostTurn {
3079  fn drop(&mut self) {
3080    let mut scheduler = self
3081      .executor
3082      .scheduler_idle_lock
3083      .lock()
3084      .unwrap_or_else(std::sync::PoisonError::into_inner);
3085    if self.draining {
3086      self.executor.draining.store(false, Ordering::Release);
3087      self.draining = false;
3088    }
3089    scheduler.active_host_turns = scheduler
3090      .active_host_turns
3091      .checked_sub(1)
3092      .expect("CurrentThread active host-turn count underflow");
3093    self.executor.scheduler_idle.notify_all();
3094  }
3095}
3096
3097/// Retains one admitted CurrentThread host callback in its runtime generation.
3098///
3099/// The host must keep this opaque lease alive until its native callback has
3100/// acknowledged or failed the delivery and destroyed every callback-owned
3101/// payload. The `Rc` marker prevents moving a TLS generation guard to another
3102/// thread before it restores the callback thread's prior runtime identity.
3103#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3104pub struct CurrentThreadTaskCallbackLease {
3105  _host_turn: CurrentThreadHostTurn,
3106  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3107}
3108
3109impl CurrentThreadTaskCallbackLease {
3110  fn new(host_turn: CurrentThreadHostTurn) -> Self {
3111    Self {
3112      _host_turn: host_turn,
3113      _not_send: std::marker::PhantomData,
3114    }
3115  }
3116}
3117
3118struct CurrentThreadDispatchCall<'a> {
3119  executor: &'a CurrentThreadExecutor,
3120  publication: Option<u64>,
3121  _generation: RuntimeGenerationGuard,
3122}
3123
3124impl CurrentThreadDispatchCall<'_> {
3125  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3126    let dispatch = self
3127      .publication
3128      .expect("the CurrentThread dispatch call must own a publication");
3129    let index = scheduler
3130      .dispatch_publications
3131      .iter()
3132      .position(|publication| publication.dispatch == dispatch)
3133      .expect("the CurrentThread dispatch publication must remain registered");
3134    scheduler.dispatch_publications.swap_remove(index);
3135    self.publication = None;
3136  }
3137}
3138
3139impl Drop for CurrentThreadDispatchCall<'_> {
3140  fn drop(&mut self) {
3141    let mut scheduler = self
3142      .executor
3143      .scheduler_idle_lock
3144      .lock()
3145      .unwrap_or_else(std::sync::PoisonError::into_inner);
3146    if let Some(dispatch) = self.publication.take()
3147      && let Some(index) = scheduler
3148        .dispatch_publications
3149        .iter()
3150        .position(|publication| publication.dispatch == dispatch)
3151    {
3152      scheduler.dispatch_publications.swap_remove(index);
3153    }
3154    scheduler.active_dispatch_calls = scheduler
3155      .active_dispatch_calls
3156      .checked_sub(1)
3157      .expect("CurrentThread active dispatch-call count underflow");
3158    self.executor.scheduler_idle.notify_all();
3159  }
3160}
3161
3162thread_local! {
3163  /// Depth of the `CurrentThreadExecutor::schedule` frames on this thread that
3164  /// a waker could have entered. `async-task` runs its scheduler callback for
3165  /// two different reasons: a wake, which any `Waker` can raise from any stack,
3166  /// and the very first schedule of a freshly submitted task, which `spawn`
3167  /// makes on its caller's own stack. Only the wake can arrive while a foreign
3168  /// mutex is held (`futures::Shared` wakes its awaiters under its `wakers`
3169  /// lock), so only `schedule` enters this scope; `schedule_submission` does
3170  /// not. A nonzero depth therefore marks a stack that may be a waker's, and
3171  /// nothing that destroys user futures inline may run on it: their destructors
3172  /// can re-lock that same mutex.
3173  static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3174}
3175
3176/// RAII marker for one waker-reachable `CurrentThreadExecutor::schedule` frame;
3177/// see `CURRENT_THREAD_SCHEDULE_DEPTH`.
3178struct CurrentThreadScheduleScope;
3179
3180impl CurrentThreadScheduleScope {
3181  fn enter() -> Self {
3182    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3183      depth.set(
3184        depth
3185          .get()
3186          .checked_add(1)
3187          .expect("CurrentThread schedule depth exhausted"),
3188      );
3189    });
3190    Self
3191  }
3192
3193  /// True while this thread is inside `schedule`: the stack may be a waker's.
3194  fn is_active() -> bool {
3195    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3196  }
3197}
3198
3199impl Drop for CurrentThreadScheduleScope {
3200  fn drop(&mut self) {
3201    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3202      depth.set(
3203        depth
3204          .get()
3205          .checked_sub(1)
3206          .expect("CurrentThread schedule depth underflow"),
3207      );
3208    });
3209  }
3210}
3211
3212thread_local! {
3213  /// Set while this thread is inside the retirement publication a waker-stack
3214  /// terminal failure issues for itself; see
3215  /// `CurrentThreadExecutor::request_rejected_work_retirement`. That
3216  /// publication calls the host driver again, so a runnable another thread
3217  /// queues meanwhile can drive a nested terminal transition on this same
3218  /// stack. Without this flag the nested transition would publish again, and a
3219  /// producer that keeps queueing work could recurse the stack without bound.
3220  ///
3221  /// It is thread-local, not per-executor: the bound it enforces is a stack
3222  /// bound. A terminal transition on ANOTHER thread finds this flag clear and
3223  /// keeps publishing its own continuation, which is what it should do -- its
3224  /// stack is its own.
3225  ///
3226  /// A nested transition records nothing when it returns. What it owes is
3227  /// executor state, not a message: it moved work into `queue.rejected` and
3228  /// consumed the dispatch that was outstanding, and the owning frame re-reads
3229  /// exactly that (`needs_rejected_work_retirement_dispatch`) before each of
3230  /// its turns. A separate "requested" flag used to carry it and could be
3231  /// discarded -- by a nested call whose request the owner never took, and
3232  /// then by the owner itself when the attempt budget ran out under it.
3233  static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3234    const { std::cell::Cell::new(false) };
3235}
3236
3237/// RAII marker for the frame that owns this thread's retirement publication;
3238/// see `CURRENT_THREAD_RETIRING_REJECTED_WORK`.
3239struct CurrentThreadRejectedWorkRetirementScope;
3240
3241impl CurrentThreadRejectedWorkRetirementScope {
3242  /// `None` when this thread is already publishing a retirement turn. The
3243  /// caller returns without publishing, and loses nothing by it: what it owes
3244  /// is the executor state the owning frame re-reads before its next turn.
3245  fn enter() -> Option<Self> {
3246    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3247      if active.replace(true) {
3248        // The owning frame keeps the flag. Do not write this as `then_some`:
3249        // its argument is eager, so the guard would be constructed here and
3250        // its destructor would clear the owner's flag on the way out.
3251        return None;
3252      }
3253      Some(Self)
3254    })
3255  }
3256}
3257
3258impl Drop for CurrentThreadRejectedWorkRetirementScope {
3259  fn drop(&mut self) {
3260    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3261  }
3262}
3263
3264struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3265
3266impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3267  fn drop(&mut self) {
3268    self
3269      .0
3270      .cancelling_failed_dispatch
3271      .store(false, Ordering::Release);
3272  }
3273}
3274
3275enum CurrentThreadDispatchFailureAction<'a> {
3276  None,
3277  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3278  Publish {
3279    dispatch: u64,
3280    call: CurrentThreadDispatchCall<'a>,
3281  },
3282}
3283
3284struct CurrentThreadDispatchFailureRecovery<'a> {
3285  action: CurrentThreadDispatchFailureAction<'a>,
3286  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3287}
3288
3289struct CurrentThreadRegistryCancellationGuard<'a> {
3290  registry: &'a CurrentThreadTaskDriverRegistry,
3291  failure: Option<CurrentThreadTaskDispatchFailure>,
3292}
3293
3294impl CurrentThreadRegistryCancellationGuard<'_> {
3295  fn new(
3296    registry: &CurrentThreadTaskDriverRegistry,
3297    failure: CurrentThreadTaskDispatchFailure,
3298  ) -> CurrentThreadRegistryCancellationGuard<'_> {
3299    CurrentThreadRegistryCancellationGuard {
3300      registry,
3301      failure: Some(failure),
3302    }
3303  }
3304}
3305
3306impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3307  fn drop(&mut self) {
3308    if let Some(failure) = self.failure.take() {
3309      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3310    }
3311  }
3312}
3313
3314struct CurrentThreadBlockingGuard<'a> {
3315  executor: &'a CurrentThreadExecutor,
3316  owner: BlockingOwnerToken,
3317  previous_owner: Option<BlockingOwnerToken>,
3318  owns_lane: bool,
3319}
3320
3321impl CurrentThreadBlockingGuard<'_> {
3322  fn counts_active_lane(&self) -> bool {
3323    self.owns_lane
3324  }
3325}
3326
3327impl Drop for CurrentThreadBlockingGuard<'_> {
3328  fn drop(&mut self) {
3329    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3330    if !self.owns_lane {
3331      return;
3332    }
3333    let mut state = self
3334      .executor
3335      .blocking_admission
3336      .state
3337      .lock()
3338      .unwrap_or_else(std::sync::PoisonError::into_inner);
3339    debug_assert_eq!(state.owner, Some(self.owner));
3340    state.owner = None;
3341    drop(state);
3342    self.executor.stop.wake_all();
3343  }
3344}
3345
3346impl CurrentThreadExecutor {
3347  /// Bound one host callback so a self-waking task cannot monopolize the
3348  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
3349  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3350  /// Force one blocking turn after this many consecutive runnable polls.
3351  #[cfg(napi_runtime_os_threads)]
3352  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3353  /// How many retirement turns one waker-stack terminal failure may take --
3354  /// the turns another thread's submissions can inflate. The one offer nothing
3355  /// can inflate follows them; see `request_rejected_work_retirement`.
3356  const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3357
3358  /// Build-default construction, kept for the pre-existing unit tests
3359  /// (production goes through [`Self::with_detection`] in
3360  /// `RuntimeBackend::new`, which also resolves the env deadline).
3361  #[cfg(test)]
3362  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3363    Self::with_detection(metrics, THREADLESS_BUILD, None)
3364  }
3365
3366  /// Full constructor for the deadlock-detection knobs: `new` fixes
3367  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
3368  /// `RuntimeBackend::new` resolves the deadline from
3369  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
3370  /// native `threadless` stand-in and short deadlines through it.
3371  #[cfg(test)]
3372  fn with_detection(
3373    metrics: Arc<RuntimeMetrics>,
3374    threadless: bool,
3375    park_deadline: Option<Duration>,
3376  ) -> Self {
3377    Self::with_generation(
3378      metrics,
3379      threadless,
3380      park_deadline,
3381      u64::MAX,
3382      Arc::new(GenerationStop::default()),
3383    )
3384  }
3385
3386  fn with_generation(
3387    metrics: Arc<RuntimeMetrics>,
3388    threadless: bool,
3389    park_deadline: Option<Duration>,
3390    generation: u64,
3391    stop: Arc<GenerationStop>,
3392  ) -> Self {
3393    Self {
3394      id: next_unique_id(
3395        &NEXT_RUNTIME_EXECUTOR_ID,
3396        "async runtime executor id space exhausted",
3397      ),
3398      generation,
3399      stop,
3400      queue: Mutex::new(CurrentThreadQueue {
3401        closed: false,
3402        runnables: VecDeque::new(),
3403        rejected: Vec::new(),
3404        #[cfg(napi_runtime_os_threads)]
3405        rejected_blocking: Vec::new(),
3406      }),
3407      blocking_admission: CurrentThreadBlockingAdmission {
3408        state: Mutex::new(CurrentThreadBlockingAdmissionState {
3409          owner: None,
3410          closed: false,
3411          #[cfg(napi_runtime_os_threads)]
3412          queue: BlockingQueue {
3413            closed: false,
3414            head: None,
3415            tail: None,
3416            jobs: FxHashMap::default(),
3417          },
3418        }),
3419      },
3420      draining: AtomicBool::new(false),
3421      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3422      scheduler_idle: Condvar::new(),
3423      dispatch_pending: AtomicU64::new(0),
3424      recovery_dispatch: AtomicU64::new(0),
3425      cancelling_failed_dispatch: AtomicBool::new(false),
3426      task_dispatch: dispatch_current_thread_tasks,
3427      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3428      metrics,
3429      threadless,
3430      park_deadline,
3431      host_timers: Arc::new(HostTimerRegistry::default()),
3432      generation_work: ArcSwapOption::empty(),
3433    }
3434  }
3435
3436  fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3437    self.generation_work.store(Some(Arc::clone(work)));
3438  }
3439
3440  /// Whether `queue.rejected` (or `queue.rejected_blocking`) holds work the
3441  /// executor still owes a cancellation.
3442  fn has_rejected_work(&self) -> bool {
3443    let queue = self
3444      .queue
3445      .lock()
3446      .unwrap_or_else(std::sync::PoisonError::into_inner);
3447    #[cfg(napi_runtime_os_threads)]
3448    let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3449    #[cfg(not(napi_runtime_os_threads))]
3450    let has_rejected_blocking = false;
3451    !queue.rejected.is_empty() || has_rejected_blocking
3452  }
3453
3454  /// Non-blocking read of the predicate `wait_until_scheduler_idle` sleeps on.
3455  fn scheduler_work_pending(&self) -> bool {
3456    let idle = self
3457      .scheduler_idle_lock
3458      .lock()
3459      .unwrap_or_else(std::sync::PoisonError::into_inner);
3460    idle.active_host_turns != 0 || idle.active_dispatch_calls != 0
3461  }
3462
3463  /// Cancel every rejected runnable and blocking job. Runs on the caller's
3464  /// stack with no executor lock held; the caller must not be inside a waker
3465  /// invocation (see `CurrentThreadQueue::rejected`). Dropping a runnable can
3466  /// wake a parked task whose schedule is rejected again (the queue closed,
3467  /// or a nested terminal transition moved it here), so drain until nothing
3468  /// remains. This terminates: every drop closes one task for good, so the
3469  /// closed-queue rejections are bounded by the live tasks, and a nested
3470  /// transition needs two more host-failed deliveries of its own.
3471  fn retire_rejected_work(&self) {
3472    if !self.has_rejected_work() {
3473      return;
3474    }
3475    let _generation = RuntimeGenerationGuard::enter(self.generation);
3476    loop {
3477      #[cfg(napi_runtime_os_threads)]
3478      let (rejected, rejected_blocking) = {
3479        let mut queue = self
3480          .queue
3481          .lock()
3482          .unwrap_or_else(std::sync::PoisonError::into_inner);
3483        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3484          return;
3485        }
3486        (
3487          std::mem::take(&mut queue.rejected),
3488          std::mem::take(&mut queue.rejected_blocking),
3489        )
3490      };
3491      #[cfg(not(napi_runtime_os_threads))]
3492      let rejected = {
3493        let mut queue = self
3494          .queue
3495          .lock()
3496          .unwrap_or_else(std::sync::PoisonError::into_inner);
3497        if queue.rejected.is_empty() {
3498          return;
3499        }
3500        std::mem::take(&mut queue.rejected)
3501      };
3502      for runnable in rejected {
3503        let _ = catch_unwind_contained(|| drop(runnable));
3504      }
3505      #[cfg(napi_runtime_os_threads)]
3506      for job in rejected_blocking {
3507        let _ = catch_unwind_contained(|| drop(job));
3508      }
3509    }
3510  }
3511
3512  /// Host-side entry points (a host turn, delivery completion, driver
3513  /// registration or unregistration, drain requests) retire rejected work
3514  /// here. They are clean stacks unless the host called back synchronously
3515  /// from inside a publication that a waker's `schedule` started; that case
3516  /// leaves the work for the next entry.
3517  fn retire_rejected_work_off_waker_stack(&self) {
3518    if CurrentThreadScheduleScope::is_active() {
3519      return;
3520    }
3521    self.retire_rejected_work();
3522  }
3523
3524  /// Whether the executor still owes its rejected work a continuation: work is
3525  /// parked in `queue.rejected` (or `queue.rejected_blocking`) and no host
3526  /// dispatch is outstanding to bring the clean stack that retires it.
3527  ///
3528  /// This is the whole state a retirement turn acts on. Nothing else needs to
3529  /// be remembered across a turn, on this thread or on the executor: a nested
3530  /// terminal transition that ran inside a turn's publication changed exactly
3531  /// these two things (it moved more work aside and consumed the dispatch),
3532  /// so re-reading them is the same as being told.
3533  fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3534    self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3535  }
3536
3537  /// The continuation a terminal failure reached on a waker's stack owes
3538  /// itself.
3539  ///
3540  /// That branch of `cancel_work_after_host_dispatch_failure` may not destroy
3541  /// anything where it stands, so it parks the work in `queue.rejected` and
3542  /// reopens. Reopening alone is not a continuation: the transition consumed
3543  /// the executor's one outstanding host request (`dispatch_pending` is 0) and
3544  /// left the queue empty, so `has_serviceable_work` is false and nothing --
3545  /// not `request_drain_if_work_remains`, not a wake of an already-parked task
3546  /// -- publishes again. A wake that loses both of its publications therefore
3547  /// produced no host turn at all, and the parked runnables kept their
3548  /// `GenerationWorkGuard`s and their `JoinHandle`s forever.
3549  ///
3550  /// A host turn is the clean stack that retires them: `drive_admitted_host_turn`
3551  /// calls `retire_rejected_work_off_waker_stack` before it polls anything. So
3552  /// ask for one, and keep asking while the host refuses and the work is still
3553  /// owed a stack -- the host is racing its own teardown, not necessarily dead,
3554  /// and a refusal says nothing about the driver that answers next (a second
3555  /// registered host, or the same one once its delivery backlog clears).
3556  ///
3557  /// What may be published on this stack is bounded, in two different ways,
3558  /// because there are two different things to bound.
3559  ///
3560  /// * `REJECTED_WORK_RETIREMENT_ATTEMPTS` bounds the turns a PRODUCER can
3561  ///   inflate. Each turn calls the driver again, so another thread can queue
3562  ///   a runnable while the publication is in flight; the host can then fail
3563  ///   that delivery and the replacement it reserves for the new work, and a
3564  ///   second terminal transition runs nested on this stack. It may not
3565  ///   publish where it stands -- that is what would recurse without bound --
3566  ///   so `CURRENT_THREAD_RETIRING_REJECTED_WORK` sends it back, and this
3567  ///   frame takes its next turn instead. Every such turn is two more host
3568  ///   callbacks and two more producer submissions, and the producer's own
3569  ///   drain request coalesces behind the publication already in flight, so it
3570  ///   never runs dry: measured without a budget, 32768 submissions produced
3571  ///   32769 deliveries inside one wake, and a producer left running for 400ms
3572  ///   held this single `schedule` callback -- and the `futures::Shared`
3573  ///   `wakers` mutex under it -- for 432ms. Eight turns cap that at 16 host
3574  ///   callbacks, a small constant to run under a foreign lock; two turns are
3575  ///   all the known interleaving needs.
3576  /// * `publish_final_rejected_work_retirement` bounds nothing, because
3577  ///   nothing can inflate it. It is the one delivery this stack offers with
3578  ///   the terminal-cancellation claim held, so no producer can interleave
3579  ///   with it and no nested transition can consume it. It is what the budget
3580  ///   may not be allowed to swallow: a producer that queues its last runnable
3581  ///   inside the eighth turn spends the budget and then goes quiescent, and
3582  ///   without this offer a host that would have accepted the very next
3583  ///   delivery is never asked, leaving the `JoinHandle`s pending and their
3584  ///   `GenerationWorkGuard`s held until unrelated work or shutdown arrives.
3585  ///
3586  /// * One ordinary publication follows an ACCEPTED final offer whose dispatch
3587  ///   the host consumed on this stack. A driver may claim, drive and
3588  ///   acknowledge the delivery inside `dispatch`, and that turn can do
3589  ///   nothing: it is on this waker's stack, so
3590  ///   `retire_rejected_work_off_waker_stack` returns at nonzero depth, and the
3591  ///   claim is still held, so `take_runnable` returns `None`. All it did was
3592  ///   take the continuation. The state it leaves -- work parked, no dispatch
3593  ///   outstanding -- is the state a REFUSED offer leaves, so it is owed the
3594  ///   same thing, and the same condition
3595  ///   (`needs_rejected_work_retirement_dispatch`) says so. The executor is
3596  ///   open again by then, which is what `5857092f` reopened it for, so this
3597  ///   takes the ordinary path. It is one publication and not a loop: a
3598  ///   terminal transition nested inside it finds
3599  ///   `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and publishes
3600  ///   nothing, and this frame is returning.
3601  ///
3602  /// So the turns stop at the first of: a dispatch outstanding (a delivery the
3603  /// host accepted, or one it failed and replaced, is the continuation --
3604  /// `drive_admitted_host_turn` retires on it), nothing left owed, no live
3605  /// host, and the budget -- and the budget is followed by the final offer,
3606  /// which is followed by one ordinary publication if the host took it without
3607  /// leaving the work a stack.
3608  ///
3609  /// Nothing is destroyed here: this adds no `retire_rejected_work` call. A
3610  /// nested transition inside any of these publications still sees the outer
3611  /// `CurrentThreadScheduleScope` and takes the waker branch, and the
3612  /// `complete_current_thread_task_delivery` the driver runs there reaches only
3613  /// `retire_rejected_work_off_waker_stack`, which returns at nonzero depth.
3614  ///
3615  /// When the host refuses the final offer too -- or consumes it and then the
3616  /// publication that follows it, never leaving this waker's stack -- that is
3617  /// terminal by construction and honest: the work waits for the next host that
3618  /// registers
3619  /// (`request_drain_if_queued` retires before it publishes) or for shutdown
3620  /// (`begin_shutdown`, `wait_until_idle_retiring`, `wait_until_scheduler_idle`,
3621  /// `Drop`). With no host that accepts there is no stack on which user futures
3622  /// may be destroyed. Handing them to a helper thread was rejected --
3623  /// threadless wasm has none, and host-affine futures must be destroyed on a
3624  /// host or shutdown thread.
3625  ///
3626  /// Rejected alternatives: a time budget -- it leaves the number of host
3627  /// callbacks open, pins nothing an assertion can check, and reads a clock on
3628  /// a stack meant to do as little as possible; yielding the thread -- it does
3629  /// not release the producer's mutex, which is the thing being held; parking
3630  /// the leftover need on the executor for the next depth-0 entry -- those
3631  /// entries already retire `queue.rejected` unconditionally, and in the
3632  /// interleaving that needs it there is no next entry at all, which is why
3633  /// the continuation has to be a delivery; and making EVERY turn a claimed
3634  /// one like the final offer -- it is uninflatable for the same reason it
3635  /// cancels a runnable that races it, and paying that for the ordinary turns
3636  /// would undo what `5857092f` reopened the executor for.
3637  fn request_rejected_work_retirement(self: &Arc<Self>) {
3638    if !self.has_rejected_work() {
3639      return;
3640    }
3641    let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3642      // Nested inside this thread's own retirement publication. Publishing
3643      // here is what would recurse the stack; the frame that owns the
3644      // publication takes the next turn, and reads what this one moved aside
3645      // off the executor rather than off a flag.
3646      return;
3647    };
3648    for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3649      if !self.needs_rejected_work_retirement_dispatch() {
3650        return;
3651      }
3652      if matches!(
3653        self.request_drain_with(next_current_thread_dispatch_id),
3654        CurrentThreadDispatchRequest::Unavailable
3655      ) {
3656        // Stopping, or no live driver: there is no host to offer anything to.
3657        return;
3658      }
3659    }
3660    if self.publish_final_rejected_work_retirement()
3661      && self.needs_rejected_work_retirement_dispatch()
3662    {
3663      // The host ACCEPTED the offer and consumed its dispatch without leaving
3664      // the work a stack: a turn it claims and drives inside `dispatch` runs
3665      // on this same waker's stack, where `retire_rejected_work_off_waker_stack`
3666      // returns at nonzero depth, and it runs with the claim held, where
3667      // `take_runnable` returns `None`. Such a turn can do nothing at all; all
3668      // it did was take the continuation. The state it left is the state a
3669      // refused offer leaves, and the invariant is the same, so it is owed the
3670      // same thing: work is parked and no dispatch is outstanding, so publish
3671      // one.
3672      //
3673      // This is the ordinary publication path -- the executor is open again,
3674      // which is what `5857092f` reopened it for -- and it is made once, not
3675      // looped. It cannot recurse: a terminal transition nested inside it
3676      // finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and
3677      // publishes nothing, and this frame has already returned its budget. A
3678      // host that consumes this one on the waker's stack too has never left
3679      // that stack, which is the limit already documented above: with no host
3680      // turn on a clean stack there is nowhere to destroy a user future.
3681      let _ = self.request_drain_with(next_current_thread_dispatch_id);
3682    }
3683  }
3684
3685  /// The one delivery this stack offers that a producer cannot inflate, made
3686  /// after `REJECTED_WORK_RETIREMENT_ATTEMPTS` turns were spent without
3687  /// leaving a dispatch outstanding.
3688  ///
3689  /// It holds the terminal-cancellation claim across the host callback. That
3690  /// claim is what makes it terminal rather than the ninth of an unbounded
3691  /// series: `schedule` consults it under the queue lock and parks a racing
3692  /// runnable in `queue.rejected` instead of queueing it, so no serviceable
3693  /// work can appear during the publication, and
3694  /// `begin_host_dispatch_failure_transition_locked` returns `None` while it is
3695  /// held, so a failure of this delivery reserves no replacement and reaches no
3696  /// terminal transition. One host callback, no recursion, no budget needed.
3697  ///
3698  /// A runnable that does race it is cancelled with the work already parked
3699  /// rather than queued. That is the same outcome the nested terminal
3700  /// transitions in the turns above give it, and this is reached only after
3701  /// the host has refused every one of them.
3702  ///
3703  /// Returns whether the host ACCEPTED the offer. An accepted offer is not by
3704  /// itself a continuation: a driver may claim, drive and acknowledge the
3705  /// delivery inside `dispatch`, and that turn runs on this waker's stack with
3706  /// the claim held, so it can neither retire (`retire_rejected_work_off_waker_stack`
3707  /// returns at nonzero depth) nor drain (`take_runnable` returns `None` while
3708  /// the claim is held) -- it only consumes `dispatch_pending`. The caller
3709  /// re-reads `needs_rejected_work_retirement_dispatch` and publishes once more
3710  /// when that happened.
3711  fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3712    if !self.needs_rejected_work_retirement_dispatch() {
3713      return false;
3714    }
3715    let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3716      // Another terminal transition owns the executor; it carries its own
3717      // continuation.
3718      return false;
3719    };
3720    let dispatch = next_current_thread_dispatch_id();
3721    let dispatch_call = {
3722      let mut scheduler = self.lock_scheduler_for_publication();
3723      self.dispatch_pending.store(dispatch, Ordering::Release);
3724      self
3725        .begin_host_dispatch_publication(&mut scheduler, dispatch)
3726        .expect("a fresh CurrentThread retirement dispatch must own its publication")
3727    };
3728    let result = self.publish_host_dispatch(dispatch, dispatch_call);
3729    {
3730      // Reopen while holding the queue lock, the way the transition does:
3731      // `schedule` consults the flag under it, so every runnable parked during
3732      // the publication is already in `queue.rejected` when the executor opens.
3733      let _queue = self
3734        .queue
3735        .lock()
3736        .unwrap_or_else(std::sync::PoisonError::into_inner);
3737      drop(cancelling);
3738    }
3739    // Those parked runnables carry their `GenerationWorkGuard`s and were
3740    // pushed without notifying; a waiting shutdown retires them on its stack.
3741    if let Some(work) = self.generation_work.load_full() {
3742      work.notify_idle_waiters();
3743    }
3744    result == CurrentThreadHostDispatchResult::Accepted
3745  }
3746
3747  fn fresh_owner_token(&self) -> BlockingOwnerToken {
3748    BlockingOwnerToken {
3749      executor_id: self.id,
3750      frame: next_unique_id(
3751        &NEXT_BLOCKING_FRAME,
3752        "blocking owner frame id space exhausted",
3753      ),
3754    }
3755  }
3756
3757  fn enter_blocking_owner(
3758    &self,
3759    owner: BlockingOwnerToken,
3760    owns_lane: bool,
3761  ) -> CurrentThreadBlockingGuard<'_> {
3762    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3763    CurrentThreadBlockingGuard {
3764      executor: self,
3765      owner,
3766      previous_owner,
3767      owns_lane,
3768    }
3769  }
3770
3771  fn schedule_blocking<F, T>(
3772    self: &Arc<Self>,
3773    function: F,
3774    registration: GenerationWorkGuard,
3775  ) -> JoinHandle<T>
3776  where
3777    F: FnOnce() -> T + Send + 'static,
3778    T: Send + 'static,
3779  {
3780    // See the implementation notes in this crate's README.
3781    self.metrics.blocking_scheduled();
3782    let mut function = Some(function);
3783    let mut registration = Some(registration);
3784    let ambient_owner = BLOCKING_OWNER
3785      .with(std::cell::Cell::get)
3786      .filter(|owner| owner.executor_id == self.id);
3787    let mut run_owner = None;
3788    let mut owns_lane = false;
3789    let mut rejected = false;
3790
3791    #[cfg(napi_runtime_os_threads)]
3792    let (sender, receiver) = oneshot::channel();
3793    #[cfg(napi_runtime_os_threads)]
3794    let dependency = BlockingDependency {
3795      job: BlockingJobId(next_unique_id(
3796        &NEXT_BLOCKING_JOB_ID,
3797        "blocking job id space exhausted",
3798      )),
3799      owner: ambient_owner,
3800    };
3801    #[cfg(napi_runtime_os_threads)]
3802    let mut queued = false;
3803
3804    {
3805      let mut state = self
3806        .blocking_admission
3807        .state
3808        .lock()
3809        .unwrap_or_else(std::sync::PoisonError::into_inner);
3810      #[cfg(all(test, napi_runtime_os_threads))]
3811      run_current_thread_blocking_claim_test_hook();
3812      // Shutdown publishes generation stop before taking this mutex. Recheck
3813      // it while admission is serialized so work cannot claim or queue for the
3814      // CurrentThread lane after stop publication.
3815      if self.stop.is_stopping()
3816        || state.closed
3817        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3818      {
3819        rejected = true;
3820      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3821        run_owner = ambient_owner;
3822      } else {
3823        #[cfg(napi_runtime_os_threads)]
3824        let queue_empty = state.queue.is_empty();
3825        #[cfg(not(napi_runtime_os_threads))]
3826        let queue_empty = true;
3827
3828        if state.owner.is_none() && queue_empty {
3829          let owner = self.fresh_owner_token();
3830          state.owner = Some(owner);
3831          run_owner = Some(owner);
3832          owns_lane = true;
3833        } else {
3834          #[cfg(napi_runtime_os_threads)]
3835          {
3836            let function = function
3837              .take()
3838              .expect("blocking function must be available");
3839            let registration = registration
3840              .take()
3841              .expect("blocking registration must be available");
3842            let generation = self.generation;
3843            let function = RegisteredBlockingFunction::new(function, registration, generation);
3844            state.queue.push(QueuedBlocking {
3845              id: dependency.job,
3846              run: Box::new(move || {
3847                let _generation = RuntimeGenerationGuard::enter(generation);
3848                let result = function
3849                  .run()
3850                  .map(|output| ContainedTaskOutput::new(output, generation));
3851                let _ = sender.send(result);
3852              }),
3853            });
3854            queued = true;
3855          }
3856          #[cfg(not(napi_runtime_os_threads))]
3857          {
3858            // This arm is threadless by construction: without
3859            // `napi_runtime_os_threads` no second thread exists to reach
3860            // unrelated concurrent admission. Same-stack nesting carries
3861            // `ambient_owner` and takes the borrowing branch above. On
3862            // `wasm32-wasip1-threads` a second thread DOES reach here, which
3863            // is why that target takes the queueing branch instead.
3864            rejected = true;
3865          }
3866        }
3867      }
3868    }
3869
3870    #[cfg(napi_runtime_os_threads)]
3871    if queued {
3872      self.stop.wake_all();
3873      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3874        self.request_drain();
3875      }
3876      return JoinHandle(JoinHandleInner::Blocking {
3877        receiver,
3878        dependency,
3879        published: PublishedBlockingDependencies::default(),
3880      });
3881    }
3882
3883    if rejected {
3884      let _generation = RuntimeGenerationGuard::enter(self.generation);
3885      let _ = catch_unwind_contained(|| drop(function.take()));
3886      drop(registration.take());
3887      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3888        message: "the async runtime stopped before the blocking task could start".to_string(),
3889      }))));
3890    }
3891
3892    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3893    let blocking = self.enter_blocking_owner(owner, owns_lane);
3894    let _registration = registration
3895      .take()
3896      .expect("blocking registration must be available");
3897    let _generation = RuntimeGenerationGuard::enter(self.generation);
3898    let result = {
3899      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3900      catch_unwind_join_error(
3901        function
3902          .take()
3903          .expect("blocking function must be available"),
3904      )
3905    }
3906    .map(|output| ContainedTaskOutput::new(output, self.generation));
3907    drop(blocking);
3908    #[cfg(napi_runtime_os_threads)]
3909    if owns_lane && !self.draining.load(Ordering::Acquire) {
3910      // An admitted host turn already owns a bounded queue driver. Let its
3911      // outer loop service the released FIFO so blocking work cannot bypass
3912      // the host-turn budget and monopolize the JavaScript event loop.
3913      self.service_queued_blocking_work();
3914    }
3915    JoinHandle(JoinHandleInner::Ready(Some(result)))
3916  }
3917
3918  #[cfg(napi_runtime_os_threads)]
3919  fn take_queued_blocking(
3920    &self,
3921  ) -> Option<(
3922    Box<dyn FnOnce() + Send + 'static>,
3923    CurrentThreadBlockingGuard<'_>,
3924  )> {
3925    let (job, owner) = {
3926      let mut state = self
3927        .blocking_admission
3928        .state
3929        .lock()
3930        .unwrap_or_else(std::sync::PoisonError::into_inner);
3931      #[cfg(all(test, napi_runtime_os_threads))]
3932      run_current_thread_blocking_claim_test_hook();
3933      if self.stop.is_stopping()
3934        || state.closed
3935        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3936        || state.owner.is_some()
3937      {
3938        return None;
3939      }
3940      let job = state.queue.pop_front()?;
3941      let owner = self.fresh_owner_token();
3942      state.owner = Some(owner);
3943      (job, owner)
3944    };
3945    Some((job, self.enter_blocking_owner(owner, true)))
3946  }
3947
3948  #[cfg(napi_runtime_os_threads)]
3949  fn run_one_blocking(&self) -> bool {
3950    let Some((job, blocking)) = self.take_queued_blocking() else {
3951      return false;
3952    };
3953    let _generation = RuntimeGenerationGuard::enter(self.generation);
3954    {
3955      let _task = self.metrics.blocking_started(true);
3956      let _ = catch_unwind_contained(job);
3957    }
3958    drop(blocking);
3959    true
3960  }
3961
3962  #[cfg(napi_runtime_os_threads)]
3963  fn service_queued_blocking_work(&self) {
3964    while self.run_one_blocking() {}
3965  }
3966
3967  #[cfg(napi_runtime_os_threads)]
3968  fn has_serviceable_blocking_work(&self) -> bool {
3969    let state = self
3970      .blocking_admission
3971      .state
3972      .lock()
3973      .unwrap_or_else(std::sync::PoisonError::into_inner);
3974    !state.closed && state.owner.is_none() && !state.queue.is_empty()
3975  }
3976
3977  #[cfg(napi_runtime_os_threads)]
3978  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3979    let Some(ambient_owner) = BLOCKING_OWNER
3980      .with(std::cell::Cell::get)
3981      .filter(|owner| owner.executor_id == self.id)
3982    else {
3983      return false;
3984    };
3985    let Some(current) = dependency.select_for_owner(ambient_owner) else {
3986      return false;
3987    };
3988    let job = {
3989      let mut state = self
3990        .blocking_admission
3991        .state
3992        .lock()
3993        .unwrap_or_else(std::sync::PoisonError::into_inner);
3994      #[cfg(all(test, napi_runtime_os_threads))]
3995      run_current_thread_blocking_claim_test_hook();
3996      if self.stop.is_stopping()
3997        || state.closed
3998        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3999        || state.owner != Some(ambient_owner)
4000      {
4001        return false;
4002      }
4003      if dependency.try_claim(&current) {
4004        state.queue.remove(current.publication.dependency.job)
4005      } else {
4006        None
4007      }
4008    };
4009    let Some(job) = job else {
4010      return false;
4011    };
4012    let _generation = RuntimeGenerationGuard::enter(self.generation);
4013    let _task = self.metrics.blocking_started(false);
4014    let _ = catch_unwind_contained(job);
4015    true
4016  }
4017
4018  fn close_blocking_admission(&self) {
4019    #[cfg(napi_runtime_os_threads)]
4020    let queued = {
4021      let mut state = self
4022        .blocking_admission
4023        .state
4024        .lock()
4025        .unwrap_or_else(std::sync::PoisonError::into_inner);
4026      state.closed = true;
4027      state.queue.closed = true;
4028      state.queue.take_all()
4029    };
4030    #[cfg(not(napi_runtime_os_threads))]
4031    {
4032      let mut state = self
4033        .blocking_admission
4034        .state
4035        .lock()
4036        .unwrap_or_else(std::sync::PoisonError::into_inner);
4037      state.closed = true;
4038    }
4039    #[cfg(napi_runtime_os_threads)]
4040    for job in queued {
4041      let _ = catch_unwind_contained(|| drop(job));
4042    }
4043  }
4044
4045  #[cfg(test)]
4046  fn with_task_dispatch(
4047    metrics: Arc<RuntimeMetrics>,
4048    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4049  ) -> Self {
4050    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4051  }
4052
4053  #[cfg(test)]
4054  fn with_task_dispatch_registry(
4055    metrics: Arc<RuntimeMetrics>,
4056    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4057    task_drivers: &'static CurrentThreadTaskDriverRegistry,
4058  ) -> Self {
4059    let mut executor = Self::new(metrics);
4060    executor.task_dispatch = task_dispatch;
4061    executor.task_drivers = task_drivers;
4062    executor
4063  }
4064
4065  /// A wake, or any other schedule whose stack the executor does not own.
4066  /// `async-task` hands every `Waker::wake` to this callback, so the frame may
4067  /// belong to a producer that is holding its own mutex; see
4068  /// `CURRENT_THREAD_SCHEDULE_DEPTH`.
4069  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4070    let _scope = CurrentThreadScheduleScope::enter();
4071    self.schedule_runnable(runnable);
4072  }
4073
4074  /// The first schedule of a freshly submitted task, made by the caller of
4075  /// `spawn` on its own stack. No waker is involved, so a terminal dispatch
4076  /// failure reached from here may cancel the rejected work inline -- and it
4077  /// must, because a submission that publishes no host delivery has no other
4078  /// continuation: nothing would ever reach `retire_rejected_work`, and the
4079  /// returned `JoinHandle` would stay pending with its generation guard held.
4080  fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4081    self.schedule_runnable(runnable);
4082  }
4083
4084  fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4085    self.metrics.runnable_scheduled();
4086    {
4087      let mut queue = self
4088        .queue
4089        .lock()
4090        .unwrap_or_else(std::sync::PoisonError::into_inner);
4091      // Never drop a rejected runnable here: that destroys the future inline
4092      // on the waker's thread, and a waker can be invoked while its producer
4093      // holds a mutex that this future's destructor needs (`futures::Shared`
4094      // wakes under its `wakers` lock and its clone re-locks it on drop).
4095      if queue.closed {
4096        // A wake can be in flight when shutdown starts: `async-task` marks
4097        // the task SCHEDULED before it invokes this closure, so the abort
4098        // that `close_and_abort` issues coalesces with the in-flight wake and
4099        // `begin_shutdown` closes the queue without waiting for it to land.
4100        // The runnable still owns its `GenerationWorkGuard`; hand it to the
4101        // shutdown side and wake the generation's idle waiter to retire it.
4102        self
4103          .metrics
4104          .queued_runnables
4105          .fetch_sub(1, Ordering::Relaxed);
4106        queue.rejected.push(runnable);
4107        drop(queue);
4108        if let Some(work) = self.generation_work.load_full() {
4109          work.notify_idle_waiters();
4110        }
4111        return;
4112      }
4113      if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4114        // A terminal failure transition is claimed but has not yet moved the
4115        // queue and reopened (it does both under this lock): the runnable
4116        // joins the work that transition cancels.
4117        self
4118          .metrics
4119          .queued_runnables
4120          .fetch_sub(1, Ordering::Relaxed);
4121        queue.rejected.push(runnable);
4122        return;
4123      }
4124      queue.runnables.push_back(runnable);
4125    }
4126    // Queue work and only then wake an explicit block_on driver. This is still
4127    // enqueue-only with respect to the wake caller: polling happens on the
4128    // driver's thread, never inline from an arbitrary waker invocation.
4129    self.stop.wake_all();
4130    if !self.draining.load(Ordering::Acquire) {
4131      self.request_drain();
4132    }
4133  }
4134
4135  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
4136  ///
4137  /// A `Waker` may be invoked while its producer holds an internal mutex
4138  /// (`futures::Shared` does this). Polling inline from the scheduler callback
4139  /// can then re-enter that future and self-deadlock on the same mutex. Once a
4140  /// host dispatcher has accepted work, this executor stays host-driven for
4141  /// the rest of its generation; a temporarily missing env leaves work queued
4142  /// for the next registration or for shutdown cancellation.
4143  fn request_drain(self: &Arc<Self>) {
4144    let _ = self.request_drain_with(next_current_thread_dispatch_id);
4145  }
4146
4147  fn lock_scheduler_for_publication(
4148    &self,
4149  ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
4150    #[cfg(all(test, napi_runtime_os_threads))]
4151    match self.scheduler_idle_lock.try_lock() {
4152      Ok(scheduler) => scheduler,
4153      Err(std::sync::TryLockError::WouldBlock) => {
4154        run_current_thread_dispatch_publication_blocked_test_hook();
4155        self
4156          .scheduler_idle_lock
4157          .lock()
4158          .unwrap_or_else(std::sync::PoisonError::into_inner)
4159      }
4160      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4161    }
4162    #[cfg(any(not(test), not(napi_runtime_os_threads)))]
4163    self
4164      .scheduler_idle_lock
4165      .lock()
4166      .unwrap_or_else(std::sync::PoisonError::into_inner)
4167  }
4168
4169  fn request_drain_with(
4170    self: &Arc<Self>,
4171    next_dispatch: impl FnOnce() -> u64,
4172  ) -> CurrentThreadDispatchRequest {
4173    let mut scheduler = self.lock_scheduler_for_publication();
4174    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4175      return CurrentThreadDispatchRequest::Unavailable;
4176    }
4177    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4178      return CurrentThreadDispatchRequest::Coalesced;
4179    }
4180    let dispatch = next_dispatch();
4181    if self
4182      .dispatch_pending
4183      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4184      .is_err()
4185    {
4186      return CurrentThreadDispatchRequest::Coalesced;
4187    }
4188    let dispatch_call = self
4189      .begin_host_dispatch_publication(&mut scheduler, dispatch)
4190      .expect("a fresh CurrentThread dispatch must own its publication");
4191    drop(scheduler);
4192    match self.publish_host_dispatch(dispatch, dispatch_call) {
4193      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4194        CurrentThreadDispatchRequest::Accepted
4195      }
4196      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4197    }
4198  }
4199
4200  fn claim_dispatch_call<'a>(
4201    &'a self,
4202    scheduler: &mut CurrentThreadSchedulerState,
4203  ) -> CurrentThreadDispatchCall<'a> {
4204    let generation = RuntimeGenerationGuard::enter(self.generation);
4205    let active_dispatch_calls = scheduler
4206      .active_dispatch_calls
4207      .checked_add(1)
4208      .expect("CurrentThread active dispatch-call count exhausted");
4209    scheduler.active_dispatch_calls = active_dispatch_calls;
4210    CurrentThreadDispatchCall {
4211      executor: self,
4212      publication: None,
4213      _generation: generation,
4214    }
4215  }
4216
4217  fn begin_host_dispatch_publication<'a>(
4218    &'a self,
4219    scheduler: &mut CurrentThreadSchedulerState,
4220    dispatch: u64,
4221  ) -> Option<CurrentThreadDispatchCall<'a>> {
4222    // See the implementation notes in this crate's README.
4223    if let Some(publication) = scheduler
4224      .dispatch_publications
4225      .iter_mut()
4226      .find(|publication| publication.dispatch == dispatch)
4227    {
4228      publication.republish_requested = true;
4229      return None;
4230    }
4231    let mut dispatch_call = self.claim_dispatch_call(scheduler);
4232    scheduler
4233      .dispatch_publications
4234      .push(CurrentThreadDispatchPublication {
4235        dispatch,
4236        republish_requested: false,
4237      });
4238    dispatch_call.publication = Some(dispatch);
4239    Some(dispatch_call)
4240  }
4241
4242  fn publish_host_dispatch(
4243    self: &Arc<Self>,
4244    dispatch: u64,
4245    mut dispatch_call: CurrentThreadDispatchCall<'_>,
4246  ) -> CurrentThreadHostDispatchResult {
4247    let mut panic_payload = RetainedPanicPayload::default();
4248    loop {
4249      let result = match catch_unwind(AssertUnwindSafe(|| {
4250        #[cfg(all(test, napi_runtime_os_threads))]
4251        run_current_thread_dispatch_call_test_hook();
4252        let result = (self.task_dispatch)(dispatch);
4253        #[cfg(all(test, napi_runtime_os_threads))]
4254        run_current_thread_dispatch_returned_test_hook();
4255        result
4256      })) {
4257        Ok(result) => Some(result),
4258        Err(payload) => {
4259          panic_payload.capture(payload);
4260          None
4261        }
4262      };
4263
4264      let step = catch_unwind(AssertUnwindSafe(|| {
4265        let (republish, cancelling, failure, result) = {
4266          let mut scheduler = self
4267            .scheduler_idle_lock
4268            .lock()
4269            .unwrap_or_else(std::sync::PoisonError::into_inner);
4270          let index = scheduler
4271            .dispatch_publications
4272            .iter()
4273            .position(|publication| publication.dispatch == dispatch)
4274            .expect("the CurrentThread dispatch publication must remain registered");
4275          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4276          let republish = dispatch_remains_pending
4277            && !self.stop.is_stopping()
4278            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4279            && scheduler.dispatch_publications[index].republish_requested;
4280          if republish {
4281            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4282              let _ = self.task_drivers.rearm_failed_publication(failure);
4283            }
4284            scheduler.dispatch_publications[index].republish_requested = false;
4285            (true, None, None, result)
4286          } else {
4287            dispatch_call.finish_publication(&mut scheduler);
4288            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4289              #[cfg(all(test, napi_runtime_os_threads))]
4290              run_current_thread_failed_publication_retired_test_hook();
4291              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4292            } else {
4293              None
4294            };
4295            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4296              && dispatch_remains_pending)
4297              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4298              .flatten();
4299            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4300              && !dispatch_remains_pending
4301            {
4302              // A synchronous callback may already have consumed, cancelled, or
4303              // superseded this exact capability.
4304              Some(CurrentThreadHostDispatchResult::Accepted)
4305            } else {
4306              result
4307            };
4308            (false, cancelling, failure, result)
4309          }
4310        };
4311
4312        if republish {
4313          return CurrentThreadDispatchPublicationStep::Republish;
4314        }
4315        if let Some(cancelling) = cancelling {
4316          self.cancel_work_after_host_dispatch_failure(cancelling);
4317        }
4318        if let Some(failure) = failure {
4319          self.run_host_dispatch_failure(failure.action);
4320        }
4321        CurrentThreadDispatchPublicationStep::Complete(result)
4322      }));
4323
4324      match step {
4325        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4326        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4327          if panic_payload.is_some() {
4328            panic_payload.resume();
4329          }
4330          return result
4331            .expect("a CurrentThread host dispatch without a result must resume its panic");
4332        }
4333        Err(payload) => {
4334          panic_payload.capture(payload);
4335          panic_payload.resume();
4336        }
4337      }
4338    }
4339  }
4340
4341  fn claim_host_turn(
4342    self: &Arc<Self>,
4343    scheduler: &mut CurrentThreadSchedulerState,
4344  ) -> CurrentThreadHostTurn {
4345    scheduler.active_host_turns = scheduler
4346      .active_host_turns
4347      .checked_add(1)
4348      .expect("CurrentThread active host-turn count exhausted");
4349    self.draining.store(true, Ordering::Release);
4350    CurrentThreadHostTurn {
4351      executor: Arc::clone(self),
4352      draining: true,
4353      _generation: RuntimeGenerationGuard::enter(self.generation),
4354    }
4355  }
4356
4357  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4358    let mut scheduler = self
4359      .scheduler_idle_lock
4360      .lock()
4361      .unwrap_or_else(std::sync::PoisonError::into_inner);
4362    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4363      return None;
4364    }
4365    if dispatch == 0
4366      || self
4367        .dispatch_pending
4368        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4369        .is_err()
4370    {
4371      return None;
4372    }
4373    #[cfg(all(test, napi_runtime_os_threads))]
4374    run_current_thread_dispatch_claim_test_hook();
4375    let _ =
4376      self
4377        .recovery_dispatch
4378        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4379    Some(self.claim_host_turn(&mut scheduler))
4380  }
4381
4382  #[cfg(test)]
4383  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4384    let mut scheduler = self
4385      .scheduler_idle_lock
4386      .lock()
4387      .unwrap_or_else(std::sync::PoisonError::into_inner);
4388    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4389      return None;
4390    }
4391    Some(self.claim_host_turn(&mut scheduler))
4392  }
4393
4394  #[cfg(test)]
4395  fn drive_host_turn(self: &Arc<Self>) {
4396    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4397    let host_turn = if dispatch == 0 {
4398      self.try_admit_host_turn_without_dispatch()
4399    } else {
4400      self.try_admit_host_turn(dispatch)
4401    };
4402    if let Some(mut host_turn) = host_turn {
4403      host_turn.drive();
4404    }
4405  }
4406
4407  #[cfg(test)]
4408  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4409    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4410      host_turn.drive();
4411    }
4412  }
4413
4414  /// Drive a host turn whose scheduler role was claimed while the controller
4415  /// still held the lifecycle mutex. See
4416  /// the implementation notes in this crate's README.
4417  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4418    // A host turn is the continuation a terminal failure on a waker's stack
4419    // relies on: cancel the work it moved aside before polling anything new.
4420    self.retire_rejected_work_off_waker_stack();
4421    #[cfg(napi_runtime_os_threads)]
4422    let mut runnable_streak = 0usize;
4423    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4424      #[cfg(napi_runtime_os_threads)]
4425      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4426        runnable_streak = 0;
4427        if self.run_one_blocking() {
4428          continue;
4429        }
4430      }
4431      if self.drain_one() {
4432        #[cfg(napi_runtime_os_threads)]
4433        {
4434          runnable_streak += 1;
4435        }
4436        continue;
4437      }
4438      #[cfg(napi_runtime_os_threads)]
4439      if self.run_one_blocking() {
4440        runnable_streak = 0;
4441        continue;
4442      }
4443      break;
4444    }
4445    // A bounded turn releases queue-drain exclusivity before publishing its
4446    // continuation, but remains an active host turn until that publication
4447    // returns. Shutdown waits for the latter lifecycle role, not merely the
4448    // former exclusivity bit.
4449    host_turn.release_draining();
4450    self.request_drain_if_work_remains();
4451  }
4452
4453  fn request_drain_if_queued(self: &Arc<Self>) {
4454    self.retire_rejected_work_off_waker_stack();
4455    if !self.has_serviceable_work() {
4456      return;
4457    }
4458    let mut scheduler = self.lock_scheduler_for_publication();
4459    if self.stop.is_stopping()
4460      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4461      || self.draining.load(Ordering::Acquire)
4462    {
4463      return;
4464    }
4465    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4466    if dispatch == 0 {
4467      drop(scheduler);
4468      self.request_drain_if_work_remains();
4469      return;
4470    }
4471
4472    // A newly registered host joins the existing all-host race through its own
4473    // delivery token. Reusing the executor capability preserves every other
4474    // host's accepted attempt and lets old registration tokens fail closed.
4475    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4476      return;
4477    };
4478    drop(scheduler);
4479    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4480  }
4481
4482  fn retire_unavailable_host_dispatch_locked(
4483    &self,
4484    dispatch: u64,
4485  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4486    if self
4487      .dispatch_pending
4488      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4489      .is_err()
4490    {
4491      return None;
4492    }
4493    if self
4494      .recovery_dispatch
4495      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4496      .is_ok()
4497    {
4498      self.begin_failed_dispatch_cancellation()
4499    } else {
4500      None
4501    }
4502  }
4503
4504  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4505    // See the implementation notes in this crate's README.
4506    let failure = {
4507      let mut scheduler = self
4508        .scheduler_idle_lock
4509        .lock()
4510        .unwrap_or_else(std::sync::PoisonError::into_inner);
4511      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4512    };
4513    if let Some(failure) = failure {
4514      self.run_host_dispatch_failure(failure.action);
4515    }
4516    self.retire_rejected_work_off_waker_stack();
4517  }
4518
4519  fn begin_host_dispatch_failure_locked<'a>(
4520    &'a self,
4521    scheduler: &mut CurrentThreadSchedulerState,
4522    failure: CurrentThreadTaskDispatchFailure,
4523  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4524    if !self.task_drivers.is_current_failure(failure) {
4525      return None;
4526    }
4527    let registry_cancellation =
4528      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4529    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4530    Some(CurrentThreadDispatchFailureRecovery {
4531      action,
4532      _registry_cancellation: registry_cancellation,
4533    })
4534  }
4535
4536  fn begin_host_dispatch_failure_transition_locked<'a>(
4537    &'a self,
4538    scheduler: &mut CurrentThreadSchedulerState,
4539    dispatch: u64,
4540  ) -> CurrentThreadDispatchFailureAction<'a> {
4541    if dispatch == 0
4542      || self
4543        .dispatch_pending
4544        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4545        .is_err()
4546    {
4547      return CurrentThreadDispatchFailureAction::None;
4548    }
4549    #[cfg(all(test, napi_runtime_os_threads))]
4550    run_current_thread_dispatch_recovery_test_hook();
4551
4552    if self
4553      .recovery_dispatch
4554      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4555      .is_ok()
4556    {
4557      // The one fresh-turn replacement failed too. Polling inline here would
4558      // re-enter the host callback, while another retry could spin forever.
4559      // Claim terminal cancellation before allowing another publication.
4560      return self.begin_failed_dispatch_cancellation().map_or(
4561        CurrentThreadDispatchFailureAction::None,
4562        CurrentThreadDispatchFailureAction::Cancel,
4563      );
4564    }
4565
4566    if self.stop.is_stopping()
4567      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4568      || self.draining.load(Ordering::Acquire)
4569      || !self.has_serviceable_work()
4570    {
4571      return CurrentThreadDispatchFailureAction::None;
4572    }
4573
4574    // Consume the failed capability and reserve its sole replacement while
4575    // publication is excluded. A newly registered host can only join this
4576    // tagged replacement, never publish an untagged dispatch in between.
4577    let replacement = next_current_thread_dispatch_id();
4578    self.recovery_dispatch.store(replacement, Ordering::Release);
4579    self.dispatch_pending.store(replacement, Ordering::Release);
4580    let dispatch_call = self
4581      .begin_host_dispatch_publication(scheduler, replacement)
4582      .expect("a reserved CurrentThread replacement must own its publication");
4583    CurrentThreadDispatchFailureAction::Publish {
4584      dispatch: replacement,
4585      call: dispatch_call,
4586    }
4587  }
4588
4589  #[cfg(test)]
4590  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4591    let failure = {
4592      let mut scheduler = self
4593        .scheduler_idle_lock
4594        .lock()
4595        .unwrap_or_else(std::sync::PoisonError::into_inner);
4596      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4597    };
4598    self.run_host_dispatch_failure(failure);
4599  }
4600
4601  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4602    match failure {
4603      CurrentThreadDispatchFailureAction::None => {}
4604      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4605        self.cancel_work_after_host_dispatch_failure(cancelling);
4606      }
4607      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4608        let _ = self.publish_host_dispatch(dispatch, call);
4609      }
4610    }
4611  }
4612
4613  fn has_serviceable_work(&self) -> bool {
4614    let has_queued_runnable = {
4615      let queue = self
4616        .queue
4617        .lock()
4618        .unwrap_or_else(std::sync::PoisonError::into_inner);
4619      !queue.closed && !queue.runnables.is_empty()
4620    };
4621    #[cfg(napi_runtime_os_threads)]
4622    let has_queued_blocking = self.has_serviceable_blocking_work();
4623    #[cfg(not(napi_runtime_os_threads))]
4624    let has_queued_blocking = false;
4625    has_queued_runnable || has_queued_blocking
4626  }
4627
4628  fn request_drain_if_work_remains(self: &Arc<Self>) {
4629    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4630      self.request_drain();
4631    }
4632  }
4633
4634  fn begin_failed_dispatch_cancellation(
4635    &self,
4636  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4637    if self
4638      .cancelling_failed_dispatch
4639      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4640      .is_err()
4641    {
4642      return None;
4643    }
4644    self.dispatch_pending.store(0, Ordering::Release);
4645    self.recovery_dispatch.store(0, Ordering::Release);
4646    Some(CurrentThreadDispatchCancellationGuard(self))
4647  }
4648
4649  /// Terminal failure transition. The work queued at this point (runnables
4650  /// and blocking jobs) is what the failure cancels. It is MOVED into the
4651  /// rejected lists under the queue lock, with its `queued_runnables` debit,
4652  /// and what happens next depends on the stack:
4653  ///
4654  /// * On a clean stack -- a host entry point, or the submission `spawn` made
4655  ///   on its caller's own stack -- the rejected work is dropped right here,
4656  ///   with the executor still closed so that the tasks those drops wake are
4657  ///   rejected and cancelled with it, and the executor reopens under the
4658  ///   queue lock once nothing remains. This is the observable behaviour
4659  ///   terminal cancellation always had: queued work and everything it wakes
4660  ///   settle at once, and no later host work is published. A submission whose
4661  ///   two publications both failed has no other continuation -- it produced no
4662  ///   host turn -- so cancelling here is what keeps its `JoinHandle` from
4663  ///   staying pending forever with its generation guard held.
4664  /// * On a waker's stack (reached through `schedule`, whose publication the
4665  ///   host failed synchronously) nothing may be dropped: the waker's
4666  ///   producer may hold a lock the destructors re-lock. The executor reopens
4667  ///   at once instead -- staying closed would leave it without a continuation
4668  ///   while the driver stays registered -- and then publishes one fresh host
4669  ///   dispatch of its own (`request_rejected_work_retirement`). That turn is
4670  ///   the clean stack on which `retire_rejected_work` cancels the moved work;
4671  ///   without it a wake that lost both of its publications left no request
4672  ///   behind at all, and the moved runnables kept their `JoinHandle`s and
4673  ///   `GenerationWorkGuard`s until an unrelated entry happened to arrive.
4674  ///
4675  /// This cannot repeat unboundedly inside one `schedule`. Reaching here
4676  /// again needs a fresh publication and two more host-failed deliveries, and
4677  /// the queue is already empty once the move is done; the publications
4678  /// `request_rejected_work_retirement` makes are the only way such a fresh
4679  /// publication can start on this stack, and
4680  /// `CURRENT_THREAD_RETIRING_REJECTED_WORK` keeps a nested transition from
4681  /// publishing there -- it returns, and the frame that owns the publication
4682  /// reads what it moved aside off the executor and takes the next turn. Those
4683  /// turns stop at `REJECTED_WORK_RETIREMENT_ATTEMPTS`, so a sustained
4684  /// producer cannot turn this stack into a service loop, and are followed by
4685  /// the one offer a producer cannot inflate; see
4686  /// `request_rejected_work_retirement`.
4687  fn cancel_work_after_host_dispatch_failure(
4688    self: &Arc<Self>,
4689    cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4690  ) {
4691    let mut cancelling = Some(cancelling);
4692    // Blocking work first: the claimed flag blocks admission and claims, so
4693    // nothing can join or leave the FIFO between this take and the reopen.
4694    #[cfg(napi_runtime_os_threads)]
4695    let queued_blocking = {
4696      let mut state = self
4697        .blocking_admission
4698        .state
4699        .lock()
4700        .unwrap_or_else(std::sync::PoisonError::into_inner);
4701      state.queue.take_all()
4702    };
4703    {
4704      let mut queue = self
4705        .queue
4706        .lock()
4707        .unwrap_or_else(std::sync::PoisonError::into_inner);
4708      let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4709      // Rejected runnables carry their `queued_runnables` debit already.
4710      self
4711        .metrics
4712        .queued_runnables
4713        .fetch_sub(moved, Ordering::Relaxed);
4714      let runnables = std::mem::take(&mut queue.runnables);
4715      queue.rejected.extend(runnables);
4716      #[cfg(napi_runtime_os_threads)]
4717      queue.rejected_blocking.extend(queued_blocking);
4718      if CurrentThreadScheduleScope::is_active() {
4719        // Reopen while holding the queue lock: `schedule` consults the flag
4720        // under this same lock, so every runnable this transition rejects is
4721        // already in the list, and every later schedule is queued and
4722        // published.
4723        drop(cancelling.take());
4724        drop(queue);
4725        // The moved runnables still own their `GenerationWorkGuard`s; a
4726        // waiting shutdown retires them on its own stack.
4727        if let Some(work) = self.generation_work.load_full() {
4728          work.notify_idle_waiters();
4729        }
4730        // During normal operation there is no such waiter, and this branch
4731        // published no host delivery. Ask for the host turn that retires the
4732        // moved work: it is the only continuation this stack may leave behind.
4733        self.request_rejected_work_retirement();
4734        return;
4735      }
4736    }
4737
4738    // Clean stack: cancel now. Dropping a runnable can wake a parked task
4739    // whose schedule `schedule` then rejects into `queue.rejected` (the flag
4740    // is still set); drop those too, outside any lock, until none remain.
4741    // Reopen while holding the queue lock, so no runnable can be rejected
4742    // after the final pass.
4743    let _generation = RuntimeGenerationGuard::enter(self.generation);
4744    loop {
4745      #[cfg(napi_runtime_os_threads)]
4746      let (rejected, rejected_blocking) = {
4747        let mut queue = self
4748          .queue
4749          .lock()
4750          .unwrap_or_else(std::sync::PoisonError::into_inner);
4751        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4752          drop(cancelling.take());
4753          break;
4754        }
4755        (
4756          std::mem::take(&mut queue.rejected),
4757          std::mem::take(&mut queue.rejected_blocking),
4758        )
4759      };
4760      #[cfg(not(napi_runtime_os_threads))]
4761      let rejected = {
4762        let mut queue = self
4763          .queue
4764          .lock()
4765          .unwrap_or_else(std::sync::PoisonError::into_inner);
4766        if queue.rejected.is_empty() {
4767          drop(cancelling.take());
4768          break;
4769        }
4770        std::mem::take(&mut queue.rejected)
4771      };
4772      for runnable in rejected {
4773        let _ = catch_unwind_contained(|| drop(runnable));
4774      }
4775      #[cfg(napi_runtime_os_threads)]
4776      for job in rejected_blocking {
4777        let _ = catch_unwind_contained(|| drop(job));
4778      }
4779    }
4780  }
4781
4782  fn take_runnable(&self) -> Option<Runnable> {
4783    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
4784    // generation shutdown publishes while holding `stop.publication`. Claiming
4785    // under both locks gives each publication one exact ordering edge with the
4786    // runnable queue. The guards are released before polling user code.
4787    let scheduler = self
4788      .scheduler_idle_lock
4789      .lock()
4790      .unwrap_or_else(std::sync::PoisonError::into_inner);
4791    #[cfg(all(test, napi_runtime_os_threads))]
4792    run_current_thread_runnable_claim_test_hook();
4793    let stop_publication = self.stop.publication_guard();
4794    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4795      return None;
4796    }
4797    let runnable = {
4798      let mut queue = self
4799        .queue
4800        .lock()
4801        .unwrap_or_else(std::sync::PoisonError::into_inner);
4802      if queue.closed {
4803        None
4804      } else {
4805        queue.runnables.pop_front()
4806      }
4807    };
4808    drop(stop_publication);
4809    drop(scheduler);
4810    runnable
4811  }
4812
4813  fn drain_one(&self) -> bool {
4814    let runnable = self.take_runnable();
4815    if let Some(runnable) = runnable {
4816      #[cfg(napi_runtime_os_threads)]
4817      let _non_owner = BlockingOwnerGuard::enter(None);
4818      run_runnable(&self.metrics, runnable);
4819      true
4820    } else {
4821      false
4822    }
4823  }
4824
4825  /// Drive `future` to completion on this thread, draining queued runnables
4826  /// between polls. This loop OWNS its park site: the future is polled with
4827  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
4828  /// inspect the wake token directly instead of handing it to
4829  /// `futures::executor::block_on`'s opaque parker:
4830  ///
4831  ///   * `Pending` + queue empty + wake token pending: a self-waking future
4832  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
4833  ///     park, NOT a deadlock; panic only at the park decision, never at
4834  ///     Pending-return.
4835  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
4836  ///     deadlock -- no other thread exists that could ever deliver the wake
4837  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
4838  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
4839  ///     rescue here: its `setTimeout` relay shares this very thread, so it
4840  ///     can never fire while the park holds it -- it only selects the
4841  ///     timer-backed variant of the same certain diagnostic.
4842  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
4843  ///     `park_deadline` is armed, the park is bounded with progress-based
4844  ///     reset (see [`park_with_deadline`]) and panics typed if a full
4845  ///     window passes with zero runtime progress.
4846  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4847    let parker = Arc::new(DriverParker::default());
4848    let _stop_registration = self.stop.register(&parker);
4849    #[cfg(napi_runtime_os_threads)]
4850    let dependency = BlockOnDependencyGuard::enter(self.generation);
4851    let waker = std::task::Waker::from(Arc::clone(&parker));
4852    let mut cx = Context::from_waker(&waker);
4853    #[cfg(napi_runtime_os_threads)]
4854    let mut runnable_streak = 0usize;
4855    loop {
4856      if self.stop.is_stopping() {
4857        return BlockOnOutcome::Stopped;
4858      }
4859      #[cfg(napi_runtime_os_threads)]
4860      dependency.clear();
4861      if future.as_mut().poll(&mut cx).is_ready() {
4862        return BlockOnOutcome::Completed;
4863      }
4864      // Shutdown must outrank queue work and self-wake permits. Otherwise an
4865      // always-self-waking future can continue before reaching the park-site
4866      // check forever while its generation work guard blocks quiescence.
4867      if self.stop.is_stopping() {
4868        return BlockOnOutcome::Stopped;
4869      }
4870      #[cfg(napi_runtime_os_threads)]
4871      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4872        runnable_streak = 0;
4873        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4874          continue;
4875        }
4876      }
4877      if self.drain_one() {
4878        #[cfg(napi_runtime_os_threads)]
4879        {
4880          runnable_streak += 1;
4881        }
4882        continue;
4883      }
4884      #[cfg(napi_runtime_os_threads)]
4885      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4886        runnable_streak = 0;
4887        continue;
4888      }
4889      #[cfg(napi_runtime_os_threads)]
4890      {
4891        runnable_streak = 0;
4892      }
4893      // PARK DECISION: the future is Pending and the queue is empty. A token
4894      // stored during the poll (or by a racing thread) means a wake is
4895      // already pending: consume it and re-poll instead of parking.
4896      if parker.consume_permit() {
4897        continue;
4898      }
4899      // Threadless timer invariant: on a THREADLESS build even a pending
4900      // HOST timer is NOT a wake-token source -- the host event loop's
4901      // `setTimeout` relay can only run on the very thread that is about to
4902      // park, so it can never fire while `block_on` holds it. EVERY threadless
4903      // park at this decision is therefore provably dead; a pending timer only
4904      // selects the more precise diagnostic. Falling through instead would hit
4905      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
4906      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
4907      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
4908      // below stays: it serves the THREADED CT flavor, where the host loop
4909      // genuinely runs concurrently.)
4910      if self.threadless {
4911        if self.host_timers.has_pending() {
4912          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4913        }
4914        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4915      }
4916      match self.park_deadline {
4917        // A wake landing between `consume_permit` above and the park is
4918        // stored as the parker's permit, so `park` returns immediately --
4919        // never lost.
4920        None => parker.park(),
4921        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4922          DeadlineParkOutcome::Woken => {}
4923          DeadlineParkOutcome::Expired(armed) => {
4924            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
4925            // a wake can land in the instructions after the window
4926            // closed, and the fingerprint read inside `park_with_deadline`
4927            // is stale by now. Re-check both before declaring death; a hit
4928            // means the park was healthy -- continue the loop (re-poll,
4929            // drain, fresh full window on the next park). A LIVE host-timer
4930            // wait also vetoes, re-checked LAST -- arming a host timer bumps
4931            // no progress
4932            // counter, so it is invisible to the fingerprint read: waiting
4933            // out a host timer with zero progress is legitimate, its wake is
4934            // scheduled on the host loop. `deadline` doubles as the grace
4935            // for host firing lag; a timer past due by more than one full
4936            // window without firing is the frozen-JS-loop signature this
4937            // detection exists for, so it stops vetoing.
4938            if parker.consume_permit()
4939              || self.metrics.progress_fingerprint() != armed
4940              || self.host_timers.has_live_wait(deadline)
4941            {
4942              continue;
4943            }
4944            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4945          }
4946        },
4947      }
4948    }
4949  }
4950
4951  fn begin_shutdown(&self) {
4952    let _generation = RuntimeGenerationGuard::enter(self.generation);
4953    self.stop.begin_shutdown();
4954    self.close_blocking_admission();
4955    let (queued, rejected) = {
4956      let mut queue = self
4957        .queue
4958        .lock()
4959        .unwrap_or_else(std::sync::PoisonError::into_inner);
4960      queue.closed = true;
4961      #[cfg(napi_runtime_os_threads)]
4962      let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
4963      let rejected = std::mem::take(&mut queue.rejected);
4964      #[cfg(napi_runtime_os_threads)]
4965      let rejected = (rejected, rejected_blocking);
4966      (std::mem::take(&mut queue.runnables), rejected)
4967    };
4968    self.dispatch_pending.store(0, Ordering::Release);
4969    self.recovery_dispatch.store(0, Ordering::Release);
4970    self.task_drivers.retire_for_shutdown();
4971    for runnable in queued {
4972      self
4973        .metrics
4974        .queued_runnables
4975        .fetch_sub(1, Ordering::Relaxed);
4976      // Dropping the last runnable cancels its detached async-task and retires
4977      // the generation guard. Isolate user future destructors from shutdown.
4978      let _ = catch_unwind_contained(|| drop(runnable));
4979    }
4980    // Rejected work (already unaccounted above) is cancelled by whichever
4981    // side takes it first: this one, or a host entry that got there earlier.
4982    #[cfg(napi_runtime_os_threads)]
4983    let (rejected, rejected_blocking) = rejected;
4984    for runnable in rejected {
4985      let _ = catch_unwind_contained(|| drop(runnable));
4986    }
4987    #[cfg(napi_runtime_os_threads)]
4988    for job in rejected_blocking {
4989      let _ = catch_unwind_contained(|| drop(job));
4990    }
4991    self.host_timers.shutdown();
4992  }
4993
4994  fn wait_until_scheduler_idle(&self) {
4995    // Registered work has retired by now on the backend path; this catches
4996    // rejections from unregistered tasks (tests) before the executor goes.
4997    self.retire_rejected_work();
4998    let mut idle = self
4999      .scheduler_idle_lock
5000      .lock()
5001      .unwrap_or_else(std::sync::PoisonError::into_inner);
5002    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
5003      idle = self
5004        .scheduler_idle
5005        .wait(idle)
5006        .unwrap_or_else(std::sync::PoisonError::into_inner);
5007    }
5008    debug_assert!(
5009      idle.dispatch_publications.is_empty(),
5010      "an idle CurrentThread scheduler must not retain dispatch publications"
5011    );
5012  }
5013}
5014
5015// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
5016// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
5017// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
5018// cooperatively instead of parking the worker. Carrying the executor id (not a
5019// bare bool) keeps this marker scoped exactly like
5020// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
5021// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
5022// none of B's accounting, so it parks like any foreign-thread caller rather
5023// than driving B's queues as an unaccounted within-cap driver. Under the single
5024// global executor used in production these ids always match, so this is a
5025// defensive scoping gate (consistent with the blocking-owner machinery), not a
5026// reachable production change.
5027#[cfg(napi_runtime_os_threads)]
5028thread_local! {
5029  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5030  /// Identifies the dedicated timer thread for lifecycle self-wait
5031  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
5032  /// timer thread must never become a cooperative Rayon scheduler driver.
5033  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
5034}
5035
5036// Identifies scheduler-driver frames on a pool worker. Unlike
5037// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
5038// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
5039// must not place work in the per-thread LIFO slot because no drain frame may
5040// remain to consume it after the Rayon job returns.
5041#[cfg(napi_runtime_os_threads)]
5042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5043struct SchedulerDriverRole {
5044  executor_id: u64,
5045  can_run_blocking: bool,
5046}
5047
5048#[cfg(napi_runtime_os_threads)]
5049thread_local! {
5050  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5051    const { std::cell::Cell::new(None) };
5052}
5053
5054// Per-worker LIFO slot (tokio-style): holds at most ONE
5055// runnable that was scheduled FROM this pool worker (typically a task the
5056// currently running runnable just woke). It is popped by this same thread's
5057// next drain/run_one iteration BEFORE the shared FIFO, so the hot
5058// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
5059// wake and no drainer spawn. Tagged with the executor id -- the same scoping
5060// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
5061// drain/run_one/flush of the SAME executor may pop it.
5062//
5063// STRANDING INVARIANT: slot work is invisible to every other
5064// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
5065// owning thread MUST either run its slot entry or flush it to the shared FIFO
5066// before it stops draining -- see the flush calls at `drain`'s returns, the
5067// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
5068// unwinds. A runnable left here while the thread returns to rayon would be a
5069// silently lost task and a `queued_runnables` leak.
5070#[cfg(napi_runtime_os_threads)]
5071thread_local! {
5072  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5073    const { std::cell::Cell::new(None) };
5074}
5075
5076#[cfg(napi_runtime_os_threads)]
5077struct OnPoolWorkerGuard {
5078  previous_worker: Option<u64>,
5079  previous_driver: Option<SchedulerDriverRole>,
5080}
5081
5082#[cfg(napi_runtime_os_threads)]
5083impl OnPoolWorkerGuard {
5084  // Save the previous marker and install `id`, so nested/re-entrant drains
5085  // (possibly from different executors on the same thread) restore the exact
5086  // prior executor id on drop instead of unconditionally clearing it.
5087  fn enter(id: u64) -> Self {
5088    Self::enter_with_role(id, true)
5089  }
5090
5091  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5092    Self {
5093      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5094      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5095        flag.replace(Some(SchedulerDriverRole {
5096          executor_id: id,
5097          can_run_blocking,
5098        }))
5099      }),
5100    }
5101  }
5102}
5103
5104#[cfg(napi_runtime_os_threads)]
5105impl Drop for OnPoolWorkerGuard {
5106  fn drop(&mut self) {
5107    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5108    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5109  }
5110}
5111
5112// Identifies one specific counted-blocking owner frame on one specific executor.
5113//
5114// `executor_id` is assigned once per executor (so a stale token left on a
5115// thread by a shut-down executor can never authorize work on a replacement);
5116// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
5117// half of dependency lending: the exact job half comes from the pending
5118// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
5119// runnable driver owns no frame, so it must respect normal blocking admission.
5120//
5121// CurrentThread uses the same identity to distinguish same-stack lane borrowing
5122// from cross-driver contention. MultiThread additionally reserves active owner
5123// frames while an otherwise-idle cooperative driver reuses their admitted lane.
5124#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5125struct BlockingOwnerToken {
5126  executor_id: u64,
5127  frame: u64,
5128}
5129
5130#[cfg(napi_runtime_os_threads)]
5131enum OwnedBlockingOutcome {
5132  Ineligible,
5133  OwnerBusy(BlockingOwnerToken),
5134  ReservationReleased {
5135    owner: BlockingOwnerToken,
5136    ran: bool,
5137  },
5138}
5139
5140// Process-global owner-frame source for per-frame dependency-lending
5141// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
5142static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5143#[cfg(napi_runtime_os_threads)]
5144static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5145
5146// The owner frame (if any) currently running a counted blocking closure on THIS
5147// thread. The exact frame identity scopes any over-cap dependency lending.
5148thread_local! {
5149  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5150    const { std::cell::Cell::new(None) };
5151}
5152
5153// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
5154// running a counted blocking closure, or with `None` while a cooperative driver
5155// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
5156// runnable an owner happens to drive via `run_one` is a logical non-owner and
5157// must not inherit the owner's over-cap privilege). Restored on drop, so the
5158// pattern is panic-safe even though `run_runnable` already catches panics.
5159#[cfg(napi_runtime_os_threads)]
5160struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5161
5162#[cfg(napi_runtime_os_threads)]
5163impl BlockingOwnerGuard {
5164  fn enter(token: Option<BlockingOwnerToken>) -> Self {
5165    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5166  }
5167}
5168
5169#[cfg(napi_runtime_os_threads)]
5170impl Drop for BlockingOwnerGuard {
5171  fn drop(&mut self) {
5172    BLOCKING_OWNER.with(|cell| cell.set(self.0));
5173  }
5174}
5175
5176#[cfg(napi_runtime_os_threads)]
5177#[derive(Default)]
5178struct ActiveBlockingOwners {
5179  // `None` is available; `Some(id)` is reserved by exactly that lending
5180  // transfer. The identity prevents a delayed stale drop from releasing a
5181  // newer reservation of the same owner frame.
5182  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5183}
5184
5185#[cfg(napi_runtime_os_threads)]
5186impl ActiveBlockingOwners {
5187  fn register(&self, owner: BlockingOwnerToken) {
5188    let previous = self
5189      .owners
5190      .lock()
5191      .unwrap_or_else(std::sync::PoisonError::into_inner)
5192      .insert(owner, None);
5193    debug_assert!(previous.is_none());
5194  }
5195
5196  fn unregister(&self, owner: BlockingOwnerToken) {
5197    let removed = self
5198      .owners
5199      .lock()
5200      .unwrap_or_else(std::sync::PoisonError::into_inner)
5201      .remove(&owner);
5202    debug_assert!(removed.is_some());
5203  }
5204
5205  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5206    let mut owners = self
5207      .owners
5208      .lock()
5209      .unwrap_or_else(std::sync::PoisonError::into_inner);
5210    let reservation = owners.get_mut(&owner)?;
5211    if reservation.is_some() {
5212      return None;
5213    }
5214    let id = next_unique_id(
5215      &NEXT_OWNER_RESERVATION,
5216      "blocking owner reservation id space exhausted",
5217    );
5218    *reservation = Some(id);
5219    Some(id)
5220  }
5221
5222  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5223    let mut owners = self
5224      .owners
5225      .lock()
5226      .unwrap_or_else(std::sync::PoisonError::into_inner);
5227    if let Some(current) = owners.get_mut(&owner)
5228      && *current == Some(reservation)
5229    {
5230      *current = None;
5231    }
5232  }
5233
5234  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5235    self
5236      .owners
5237      .lock()
5238      .unwrap_or_else(std::sync::PoisonError::into_inner)
5239      .get(&owner)
5240      .is_some_and(Option::is_none)
5241  }
5242}
5243
5244#[cfg(napi_runtime_os_threads)]
5245struct ActiveBlockingOwnerGuard<'a> {
5246  owners: &'a ActiveBlockingOwners,
5247  owner: BlockingOwnerToken,
5248}
5249
5250#[cfg(napi_runtime_os_threads)]
5251impl<'a> ActiveBlockingOwnerGuard<'a> {
5252  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5253    owners.register(owner);
5254    Self { owners, owner }
5255  }
5256}
5257
5258#[cfg(napi_runtime_os_threads)]
5259impl Drop for ActiveBlockingOwnerGuard<'_> {
5260  fn drop(&mut self) {
5261    self.owners.unregister(self.owner);
5262  }
5263}
5264
5265#[cfg(napi_runtime_os_threads)]
5266struct OwnerLaneReservation<'a> {
5267  owners: &'a ActiveBlockingOwners,
5268  owner: BlockingOwnerToken,
5269  reservation: u64,
5270}
5271
5272#[cfg(napi_runtime_os_threads)]
5273impl<'a> OwnerLaneReservation<'a> {
5274  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5275    let reservation = owners.reserve(owner)?;
5276    Some(Self {
5277      owners,
5278      owner,
5279      reservation,
5280    })
5281  }
5282}
5283
5284#[cfg(napi_runtime_os_threads)]
5285impl Drop for OwnerLaneReservation<'_> {
5286  fn drop(&mut self) {
5287    self.owners.release(self.owner, self.reservation);
5288  }
5289}
5290
5291// A blocking job queued for the pool. Its stable id is copied into the returned
5292// JoinHandle and propagated through async JoinHandle dependencies, allowing a
5293// saturated owner to run exactly the job its nested block_on awaits.
5294#[cfg(napi_runtime_os_threads)]
5295type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5296
5297#[cfg(napi_runtime_os_threads)]
5298struct QueuedBlocking {
5299  id: BlockingJobId,
5300  run: BlockingClosure,
5301}
5302
5303#[cfg(napi_runtime_os_threads)]
5304struct BlockingQueueNode {
5305  previous: Option<BlockingJobId>,
5306  next: Option<BlockingJobId>,
5307  run: Box<dyn FnOnce() + Send + 'static>,
5308}
5309
5310#[cfg(napi_runtime_os_threads)]
5311struct BlockingQueue {
5312  closed: bool,
5313  head: Option<BlockingJobId>,
5314  tail: Option<BlockingJobId>,
5315  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5316}
5317
5318#[cfg(napi_runtime_os_threads)]
5319impl BlockingQueue {
5320  fn push(&mut self, job: QueuedBlocking) {
5321    let previous = self.tail;
5322    let replaced = self.jobs.insert(
5323      job.id,
5324      BlockingQueueNode {
5325        previous,
5326        next: None,
5327        run: job.run,
5328      },
5329    );
5330    debug_assert!(replaced.is_none());
5331    if let Some(previous) = previous {
5332      self
5333        .jobs
5334        .get_mut(&previous)
5335        .expect("blocking queue tail must be indexed")
5336        .next = Some(job.id);
5337    } else {
5338      self.head = Some(job.id);
5339    }
5340    self.tail = Some(job.id);
5341  }
5342
5343  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5344    self.head.and_then(|id| self.remove(id))
5345  }
5346
5347  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5348    let node = self.jobs.remove(&id)?;
5349    if let Some(previous) = node.previous {
5350      self
5351        .jobs
5352        .get_mut(&previous)
5353        .expect("blocking queue predecessor must be indexed")
5354        .next = node.next;
5355    } else {
5356      self.head = node.next;
5357    }
5358    if let Some(next) = node.next {
5359      self
5360        .jobs
5361        .get_mut(&next)
5362        .expect("blocking queue successor must be indexed")
5363        .previous = node.previous;
5364    } else {
5365      self.tail = node.previous;
5366    }
5367    Some(node.run)
5368  }
5369
5370  fn is_empty(&self) -> bool {
5371    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5372    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5373    self.jobs.is_empty()
5374  }
5375
5376  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5377    self.head = None;
5378    self.tail = None;
5379    std::mem::take(&mut self.jobs)
5380      .into_values()
5381      .map(|node| node.run)
5382      .collect()
5383  }
5384}
5385
5386#[cfg(napi_runtime_os_threads)]
5387struct WorkerLifecycle {
5388  remaining: Mutex<usize>,
5389  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5390}
5391
5392#[cfg(napi_runtime_os_threads)]
5393impl WorkerLifecycle {
5394  fn new(worker_threads: usize) -> Arc<Self> {
5395    Arc::new(Self {
5396      remaining: Mutex::new(worker_threads),
5397      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5398    })
5399  }
5400
5401  fn retain(&self, handle: std::thread::JoinHandle<()>) {
5402    self
5403      .handles
5404      .lock()
5405      .unwrap_or_else(std::sync::PoisonError::into_inner)
5406      .as_mut()
5407      .expect("runtime worker handles already joined")
5408      .push(handle);
5409  }
5410
5411  fn worker_exited(&self) {
5412    let mut remaining = self
5413      .remaining
5414      .lock()
5415      .unwrap_or_else(std::sync::PoisonError::into_inner);
5416    *remaining = remaining
5417      .checked_sub(1)
5418      .expect("runtime worker exit count underflow");
5419  }
5420
5421  fn wait_for_all_workers(&self) {
5422    let handles = self
5423      .handles
5424      .lock()
5425      .unwrap_or_else(std::sync::PoisonError::into_inner)
5426      .take()
5427      .expect("runtime worker handles already joined");
5428    for handle in handles {
5429      if let Err(payload) = handle.join() {
5430        discard_panic_payload(payload);
5431      }
5432    }
5433  }
5434
5435  #[cfg(test)]
5436  fn remaining(&self) -> usize {
5437    *self
5438      .remaining
5439      .lock()
5440      .unwrap_or_else(std::sync::PoisonError::into_inner)
5441  }
5442}
5443
5444#[cfg(napi_runtime_os_threads)]
5445struct MultiThreadExecutor {
5446  generation: u64,
5447  stop: Arc<GenerationStop>,
5448  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
5449  // executor can never authorize an over-cap escape on a replacement.
5450  id: u64,
5451  pool: ThreadPool,
5452  // Lock-free MPMC injector replacing the former `Mutex<VecDeque<Runnable>>`.
5453  // Invariant: every push goes through `push_to_queue_and_wake` and every
5454  // emptiness check through `has_queued_runnable` -- their paired SeqCst fences
5455  // restore the removed mutex's synchronizes-with edge (full lost-wakeup proof
5456  // at `push_to_queue_and_wake`). A raw `injector.push`/`is_empty` at a new site
5457  // silently breaks the pairing.
5458  injector: Injector<Runnable>,
5459  blocking_queue: Mutex<BlockingQueue>,
5460  active_drainers: AtomicUsize,
5461  active_blocking: AtomicUsize,
5462  // Guards the idle-waiter count. `finish_draining` runs once per drain batch
5463  // -- i.e. per task whenever the drainer catches the producer -- and used to
5464  // call `notify_all` unconditionally. On Linux `Condvar::notify_all` issues a
5465  // `futex_wake_all` SYSCALL even with an empty waiter set, and the only waiter
5466  // site in this crate (`wait_until_scheduler_idle`) is reachable only from
5467  // shutdown/restart. Counting waiters under this same mutex lets the steady
5468  // state skip the syscall entirely.
5469  scheduler_idle_lock: Mutex<usize>,
5470  scheduler_idle: Condvar,
5471  worker_lifecycle: Arc<WorkerLifecycle>,
5472  // Registry of pool workers currently parked inside a re-entrant cooperative
5473  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
5474  // has a fixed number of OS threads, so a parked worker cannot rely on
5475  // `ensure_drainer` spawning a replacement (there is no free thread to run
5476  // it) -- it must wake and run the work itself. Future-wakes do NOT go
5477  // through this registry: each driver's own `DriverParker` is its awaited
5478  // future's waker (see the wake-domain note at `DriverParker`).
5479  parked_drivers: ParkedDrivers,
5480  active_blocking_owners: ActiveBlockingOwners,
5481  #[cfg(test)]
5482  owner_lending_attempts: AtomicUsize,
5483  // Linger-lifecycle observability for tests (see the idle-linger block in
5484  // `drain`): counts every linger-parker REGISTRATION into `parked_drivers`
5485  // and every idle-budget EXPIRY exit (the deadline-elapsed `break` paths,
5486  // counted AFTER the parker is deregistered, so `expiries` rising implies
5487  // the linger parker has permanently left the registry for that episode).
5488  // Tests that must observe a lingering drainer's full register->expire
5489  // lifecycle wait on these instead of sampling the non-monotonic registry
5490  // count, which is transiently at its settled value BEFORE the linger
5491  // parker registers.
5492  #[cfg(test)]
5493  drain_linger_registrations: AtomicUsize,
5494  #[cfg(test)]
5495  drain_linger_expiries: AtomicUsize,
5496  max_drainers: usize,
5497  max_blocking: usize,
5498  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
5499  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
5500  // design because it parks for entire builds and a deadline there would
5501  // panic healthy production runs. Off unless
5502  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
5503  // `PARK_DEADLINE_ENV` into it).
5504  park_deadline: Option<Duration>,
5505  // Idle-linger budget for pool drainers (`None` = disabled); resolved from
5506  // `RuntimeOptions::drain_linger` (`ZERO` = disabled, embedder-resolved from
5507  // `DRAIN_LINGER_ENV` at addon load) at construction and ceiling-clamped
5508  // there so even direct unvalidated test constructions stay bounded.
5509  drain_linger: Option<Duration>,
5510  // Shared with the timer heap because firing removes the timer before invoking
5511  // its waker. The admission remains active until that wake has published any
5512  // resulting runnable, so a final verdict cannot observe both sources empty.
5513  deadlock_state: Arc<MultiThreadDeadlockState>,
5514  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
5515  // Cooperative parks also fire due timers after timer-bounded waits.
5516  timers: TimerHeap,
5517  metrics: Arc<RuntimeMetrics>,
5518}
5519
5520#[cfg(napi_runtime_os_threads)]
5521impl MultiThreadExecutor {
5522  /// Max work units before a drainer yields. Cooperative drivers use the same
5523  /// value as the LIFO streak cap.
5524  const RUNNABLE_BUDGET: usize = 64;
5525  /// Force one blocking admission after this many consecutive runnable polls
5526  /// when blocking capacity and queued work are both available.
5527  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5528
5529  #[cfg(test)]
5530  fn new(
5531    options: &RuntimeOptions,
5532    metrics: Arc<RuntimeMetrics>,
5533  ) -> Result<Self, RuntimeConfigError> {
5534    Self::new_for_generation(
5535      options,
5536      metrics,
5537      u64::MAX,
5538      Arc::new(GenerationStop::default()),
5539    )
5540  }
5541
5542  fn new_for_generation(
5543    options: &RuntimeOptions,
5544    metrics: Arc<RuntimeMetrics>,
5545    generation: u64,
5546    stop: Arc<GenerationStop>,
5547  ) -> Result<Self, RuntimeConfigError> {
5548    let id = next_unique_id(
5549      &NEXT_RUNTIME_EXECUTOR_ID,
5550      "async runtime executor id space exhausted",
5551    );
5552    let thread_name_prefix = options.thread_name_prefix.clone();
5553    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5554      options.park_deadline.is_some(),
5555    ));
5556    let timers = TimerHeap::new(
5557      generation,
5558      id,
5559      format!("{thread_name_prefix}-timer"),
5560      Arc::clone(&deadlock_state),
5561    );
5562    // Production options are validated before construction, so this exact
5563    // physical count is also the configured/reported count. Direct executor
5564    // unit tests may still construct a one-thread topology intentionally.
5565    let pool_threads = options.worker_threads;
5566    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5567    let exit_lifecycle = Arc::clone(&worker_lifecycle);
5568    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5569    let pool = ThreadPoolBuilder::new()
5570      .num_threads(pool_threads)
5571      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5572      .start_handler(move |_| {
5573        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5574      })
5575      .exit_handler(move |_| {
5576        IN_SCHEDULER_DRIVER.with(|driver| {
5577          if driver.get().is_some_and(|role| role.executor_id == id) {
5578            driver.set(None);
5579          }
5580        });
5581        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
5582        // run after this Rayon hook and may re-enter lifecycle APIs while the
5583        // external shutdown caller is joining this worker; retaining the
5584        // executor identity makes that re-entry fail fast instead of waiting
5585        // in Stopping and deadlocking the join.
5586        exit_lifecycle.worker_exited();
5587      })
5588      .spawn_handler(move |thread| {
5589        let mut builder = std::thread::Builder::new();
5590        if let Some(name) = thread.name() {
5591          builder = builder.name(name.to_string());
5592        }
5593        if let Some(stack_size) = thread.stack_size() {
5594          builder = builder.stack_size(stack_size);
5595        }
5596        let handle = builder.spawn(move || thread.run())?;
5597        spawn_lifecycle.retain(handle);
5598        Ok(())
5599      })
5600      .build()
5601      .map_err(|error| {
5602        worker_lifecycle.wait_for_all_workers();
5603        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5604      })?;
5605    Ok(Self {
5606      generation,
5607      stop,
5608      id,
5609      pool,
5610      injector: Injector::new(),
5611      blocking_queue: Mutex::new(BlockingQueue {
5612        closed: false,
5613        head: None,
5614        tail: None,
5615        jobs: FxHashMap::default(),
5616      }),
5617      active_drainers: AtomicUsize::new(0),
5618      active_blocking: AtomicUsize::new(0),
5619      scheduler_idle_lock: Mutex::new(0),
5620      scheduler_idle: Condvar::new(),
5621      worker_lifecycle,
5622      parked_drivers: ParkedDrivers::default(),
5623      active_blocking_owners: ActiveBlockingOwners::default(),
5624      #[cfg(test)]
5625      owner_lending_attempts: AtomicUsize::new(0),
5626      #[cfg(test)]
5627      drain_linger_registrations: AtomicUsize::new(0),
5628      #[cfg(test)]
5629      drain_linger_expiries: AtomicUsize::new(0),
5630      max_drainers: pool_threads,
5631      max_blocking: options.max_blocking_tasks,
5632      park_deadline: options.park_deadline,
5633      drain_linger: {
5634        // Defensive re-clamp: production options were already clamped by
5635        // `RuntimeOptions::validate`, but direct executor unit tests construct
5636        // from unvalidated options.
5637        let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5638        (!budget.is_zero()).then_some(budget)
5639      },
5640      deadlock_state,
5641      timers,
5642      metrics,
5643    })
5644  }
5645
5646  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5647    self.deadlock_state.begin_admission()
5648  }
5649
5650  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5651    self.deadlock_state.try_begin_verdict()
5652  }
5653
5654  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5655    // See the implementation notes in this crate's README.
5656    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5657    self.parked_drivers.wake_one_owner_dependency(owner)
5658  }
5659
5660  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5661    let owner = reservation.owner;
5662    // Keep release and its targeted handoff in one detector-visible critical
5663    // section. Unwind drops the mutex after the lane is already available, so
5664    // a later verdict rechecks that availability even if selection panicked.
5665    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5666    drop(reservation);
5667    self.parked_drivers.wake_one_owner_dependency(owner)
5668  }
5669
5670  fn schedule(self: &Arc<Self>, runnable: Runnable) {
5671    let mut admission = self.begin_deadlock_admission();
5672    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
5673    // still "queued" until popped (`runnable_started` balances it either way).
5674    self.metrics.runnable_scheduled();
5675    // LIFO fast path: a runnable scheduled from a pool
5676    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
5677    // shared FIFO -- this thread pops it before its next FIFO look, so no
5678    // queue mutex, no wake, no drainer spawn.
5679    //
5680    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
5681    // on the stack (drain/run_one loop iterations, cooperative polls). It is
5682    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
5683    // frame including `blocking()` calls, but the slot is only popped after
5684    // the closure returns -- so a closure that spawns and then synchronously
5685    // waits on the child would deadlock with the child slotted (no wake, no
5686    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
5687    // while straight-line blocking-closure code runs (drain/run_one blocking
5688    // branches, the over-cap escape) and `None` while runnables run (the
5689    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
5690    // slot and take the FIFO+wake path whenever an owner frame is ambient.
5691    // (A cooperative poll entered FROM a blocking closure also carries the
5692    // closure's token and loses the fast path -- a rare, conservative,
5693    // correctness-first trade.)
5694    if IN_SCHEDULER_DRIVER
5695      .with(std::cell::Cell::get)
5696      .is_some_and(|role| role.executor_id == self.id)
5697      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5698    {
5699      match LIFO_SLOT.with(std::cell::Cell::take) {
5700        None => {
5701          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5702          if let Some(admission) = admission.as_mut() {
5703            admission.mark_published();
5704          }
5705          return;
5706        }
5707        // Same-executor occupant: the NEWEST runnable takes the slot (it is
5708        // the hottest); the displaced occupant falls back to the shared FIFO
5709        // with a normal wake so other workers can pick it up.
5710        Some((id, displaced)) if id == self.id => {
5711          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5712          self.push_to_queue_and_wake(displaced);
5713          if let Some(admission) = admission.as_mut() {
5714            admission.mark_published();
5715          }
5716          return;
5717        }
5718        // Foreign-executor occupant (defensive: requires a thread acting as a
5719        // worker of two executors, e.g. under a test-installed marker; the
5720        // production process has one global executor). We must not flush
5721        // another executor's runnable onto OUR queue, so leave it in place --
5722        // its own executor's drain on this thread pops or flushes it -- and
5723        // route the new runnable to the FIFO.
5724        Some(foreign) => {
5725          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5726        }
5727      }
5728    }
5729    self.push_to_queue_and_wake(runnable);
5730    if let Some(admission) = admission.as_mut() {
5731      admission.mark_published();
5732    }
5733  }
5734
5735  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
5736  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
5737  /// already happened at their original schedule).
5738  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5739    self.injector.push(runnable);
5740    // The former queue mutex provided the synchronizes-with edge the
5741    // lost-wakeup proof depends on (see `wake_one`). With a lock-free injector
5742    // that edge is gone, so restore it explicitly: a SeqCst fence between the
5743    // push and BOTH wake mechanisms (`wake_one`'s SeqCst `count` load and
5744    // `ensure_drainer`'s `active_drainers` load), mirrored by the SeqCst fence
5745    // in `has_queued_runnable` before the waiter/drainer re-check. This is the
5746    // Dekker ordering rayon-core's injector+sleep protocol uses; without both
5747    // fences a worker can park next to stealable work.
5748    std::sync::atomic::fence(Ordering::SeqCst);
5749    self.wake_for_new_work();
5750  }
5751
5752  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
5753  /// parked cooperative driver; when none is parked, fall back to spawning a
5754  /// drainer. A single wake suffices because the woken driver re-checks
5755  /// every work source in its loop before re-parking, and a wake it absorbs
5756  /// without consuming is handed on at `cooperative_block_on` exit (miss
5757  /// compensation). MUST be called AFTER the work was pushed
5758  /// under its queue mutex -- see the lost-wakeup argument at
5759  /// [`ParkedDrivers::wake_one`].
5760  fn wake_for_new_work(self: &Arc<Self>) {
5761    if !self.parked_drivers.wake_one() {
5762      self.ensure_drainer();
5763    }
5764  }
5765
5766  fn wake_for_blocking_work(self: &Arc<Self>) {
5767    // Prefer a cooperative driver that can consume the blocking FIFO;
5768    // otherwise arm a normal Rayon drainer.
5769    if !self.parked_drivers.wake_one_blocking() {
5770      self.ensure_drainer();
5771    }
5772  }
5773
5774  #[cfg(test)]
5775  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5776  where
5777    F: FnOnce() -> T + Send + 'static,
5778    T: Send + 'static,
5779  {
5780    self.schedule_blocking_result(move || catch_unwind_join_error(function))
5781  }
5782
5783  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5784  where
5785    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5786    T: Send + 'static,
5787  {
5788    let mut admission = self.begin_deadlock_admission();
5789    let (sender, receiver) = oneshot::channel();
5790    let generation = self.generation;
5791    let dependency = BlockingDependency {
5792      job: BlockingJobId(next_unique_id(
5793        &NEXT_BLOCKING_JOB_ID,
5794        "blocking job id space exhausted",
5795      )),
5796      owner: BLOCKING_OWNER
5797        .with(std::cell::Cell::get)
5798        .filter(|owner| owner.executor_id == self.id),
5799    };
5800    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
5801    // detector's fingerprint must be able to observe
5802    // this submission even while the job merely sits queued -- symmetric with
5803    // `runnable_scheduled` on the runnable path.
5804    self.metrics.blocking_scheduled();
5805    let queued = QueuedBlocking {
5806      id: dependency.job,
5807      run: Box::new(move || {
5808        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5809        let _ = sender.send(result);
5810      }),
5811    };
5812    let rejected = {
5813      let mut queue = self
5814        .blocking_queue
5815        .lock()
5816        .unwrap_or_else(std::sync::PoisonError::into_inner);
5817      if queue.closed {
5818        Some(queued)
5819      } else {
5820        queue.push(queued);
5821        None
5822      }
5823    };
5824    if let Some(rejected) = rejected {
5825      // Shutdown may close the queue after the controller registered this
5826      // generation work but before it reaches the executor. Drop captured
5827      // user values outside the queue lock and contain hostile destructors,
5828      // matching the queued-job shutdown path.
5829      let _generation = RuntimeGenerationGuard::enter(self.generation);
5830      let _ = catch_unwind_contained(|| drop(rejected));
5831    } else {
5832      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
5833      self.wake_for_blocking_work();
5834      if let Some(admission) = admission.as_mut() {
5835        admission.mark_published();
5836      }
5837    }
5838    JoinHandle(JoinHandleInner::Blocking {
5839      receiver,
5840      dependency,
5841      published: PublishedBlockingDependencies::default(),
5842    })
5843  }
5844
5845  fn ensure_drainer(self: &Arc<Self>) {
5846    loop {
5847      let active = self.active_drainers.load(Ordering::Acquire);
5848      if active >= self.max_drainers {
5849        return;
5850      }
5851      if self
5852        .active_drainers
5853        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5854        .is_ok()
5855      {
5856        let executor = Arc::clone(self);
5857        self.pool.spawn_fifo(move || executor.drain());
5858        return;
5859      }
5860    }
5861  }
5862
5863  fn drain(self: Arc<Self>) {
5864    // LINGER GATE: captured BEFORE the guard installs the marker below. In
5865    // production `drain` only ever runs via `pool.spawn_fifo`, i.e. on a pool
5866    // worker whose start handler already set `ON_POOL_WORKER`, so lingering is
5867    // active there; a direct test call from a foreign thread keeps the legacy
5868    // return-when-empty shape.
5869    let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5870    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
5871    // drives the queue cooperatively instead of parking the worker.
5872    let _on_pool = OnPoolWorkerGuard::enter(self.id);
5873    // Unwind backstop: never leave a runnable stranded in
5874    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
5875    // below (so its re-check can see the flushed runnable); this guard only
5876    // covers unwinds, which should be impossible (runnables and blocking
5877    // wrappers are catch_unwind'd) but must not lose a task if they happen.
5878    let _slot_backstop = LifoSlotFlushGuard(&self);
5879
5880    let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5881    // Linger state (parker + generation-stop registration), created lazily on
5882    // this frame's first idle episode and reused for the rest of the frame.
5883    let mut linger: Option<DrainLinger> = None;
5884    // Absolute exit deadline of the CURRENT idle episode, armed on the first
5885    // empty observation after work ran. Spurious wakes (timer churn, a stolen
5886    // queue wake) re-park with the REMAINING time rather than a fresh budget,
5887    // so a lingering drainer occupies its pool slot for at most
5888    // `linger_budget` past the last work unit it executed.
5889    let mut idle_deadline: Option<Instant> = None;
5890    // Absolute residence deadline of the WHOLE frame, armed on the frame's
5891    // FIRST idle observation and -- unlike `idle_deadline` -- never reset by
5892    // executed work. Without it, periodic sub-budget work re-arms the episode
5893    // deadline forever and this frame never returns its rayon worker; see
5894    // [`DRAIN_LINGER_FRAME_FACTOR`].
5895    let mut frame_deadline: Option<Instant> = None;
5896
5897    'frame: loop {
5898      let mut runnable_streak = 0usize;
5899      for _ in 0..Self::RUNNABLE_BUDGET {
5900        if self.run_one_fair(&mut runnable_streak) {
5901          idle_deadline = None;
5902          continue;
5903        }
5904        // Queue observed empty. MANDATORY flush before the shared exit AND
5905        // before any park: slot work is invisible to other threads and to
5906        // `finish_draining`'s re-check, so no path may leave this loop (or
5907        // sleep) with an occupied slot. On this path the slot was observed
5908        // empty at the top of the iteration and no user code ran since, so
5909        // the flush is defensive -- kept unconditional so every exit upholds
5910        // the stranding invariant by construction.
5911        self.flush_lifo_slot();
5912        // IDLE LINGER: instead of exiting and paying a full
5913        // spawn_fifo/rayon-idle-protocol round trip on the NEXT wave, park on
5914        // our own parker, registered in `parked_drivers` so
5915        // `wake_for_new_work`/`wake_for_blocking_work` deliver ONE targeted
5916        // wake. `active_drainers` stays counted for the whole linger: this
5917        // frame is still a live drainer, so `ensure_drainer`'s cap and the
5918        // idle-wait predicate keep their meaning.
5919        //
5920        // LOST-WAKEUP ARGUMENT (identical protocol to a cooperative driver's
5921        // park): register FIRST (the registry `count` store), THEN re-check
5922        // the shared queues (`has_queued_work_for_role`'s SeqCst fence pairs
5923        // with `push_to_queue_and_wake`'s post-push fence), THEN park -- see
5924        // [`ParkedDrivers::wake_one`]. On the timeout path, `wake_one` may
5925        // have popped this parker (counting the wake as DELIVERED, so the
5926        // producer spawned no drainer) while its `unpark` is still in flight:
5927        // the pop precedes our `deregister` on the registry mutex, so the
5928        // producer's push (made BEFORE its wake) is visible to the
5929        // post-deregister permit/queue re-checks below, which then RESUME
5930        // draining instead of exiting. Only a truly empty re-check falls
5931        // through to `finish_draining`, whose own post-decrement re-check
5932        // remains the existing final net for pushes racing the exit.
5933        let Some(budget) = linger_budget else {
5934          break 'frame;
5935        };
5936        if self.stop.is_stopping() {
5937          break 'frame;
5938        }
5939        let now = Instant::now();
5940        // FRAME RESIDENCE BOUND: exiting here (or via the min-capped park
5941        // below) funnels through the UNCHANGED finish_draining path, whose
5942        // re-check re-arms a FIFO drain via ensure_drainer/spawn_fifo when
5943        // work remains -- that re-entry through rayon's queue is exactly the
5944        // interleaving point that lets pending raw rayon work run.
5945        let frame = *frame_deadline.get_or_insert_with(|| {
5946          now
5947            + budget
5948              .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5949              .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5950        });
5951        let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5952        if now >= deadline {
5953          // Deadline EXPIRY observed at the top: either a wake consumed the
5954          // rest of the episode's idle budget, or the frame residence bound
5955          // elapsed while work kept re-arming the episode deadline. Either
5956          // way this frame's lingering is over, and the linger parker of the
5957          // previous episode -- if one registered -- was already deregistered
5958          // after its park.
5959          #[cfg(test)]
5960          self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5961          break 'frame;
5962        }
5963        let state = match &linger {
5964          Some(state) => state,
5965          None => linger.insert(DrainLinger::new(&self)),
5966        };
5967        self
5968          .parked_drivers
5969          .register_with_role(&state.parker, true, None);
5970        #[cfg(test)]
5971        self
5972          .drain_linger_registrations
5973          .fetch_add(1, Ordering::SeqCst);
5974        if self.has_queued_work_for_role(true) {
5975          self.parked_drivers.deregister(&state.parker);
5976          continue 'frame;
5977        }
5978        // Stop re-check AFTER registering: `begin_shutdown` unparks every
5979        // generation-stop-registered parker (ours included, via
5980        // `DrainLinger`'s registration), so a stop publishing after this
5981        // check still cuts the park short instead of waiting out the budget.
5982        if self.stop.is_stopping() {
5983          self.parked_drivers.deregister(&state.parker);
5984          break 'frame;
5985        }
5986        let woken = state.parker.park_timeout(deadline - now);
5987        self.parked_drivers.deregister(&state.parker);
5988        if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5989          continue 'frame;
5990        }
5991        // Idle-budget EXPIRY: the park timed out with no wake, no permit and
5992        // no queued work. Counted AFTER the deregister above, so a test
5993        // observing `drain_linger_expiries` rise knows the linger parker has
5994        // permanently left the registry for this idle episode.
5995        #[cfg(test)]
5996        self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5997        break 'frame;
5998      }
5999      // Budget exhausted: yield this worker back to rayon exactly as before
6000      // the linger existed -- `finish_draining` re-arms a FIFO drain job
6001      // BEHIND any pending rayon work (par_iter splits, nested jobs), which
6002      // is the pool-fairness property the budget exists for.
6003      break 'frame;
6004    }
6005
6006    // MANDATORY flush before `finish_draining` so its
6007    // re-check sees a slot runnable on the shared FIFO and re-arms a drainer.
6008    self.flush_lifo_slot();
6009    self.finish_draining();
6010  }
6011
6012  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
6013  /// A foreign executor's entry is left untouched (see the scoping note at
6014  /// `LIFO_SLOT`).
6015  fn pop_lifo_slot(&self) -> Option<Runnable> {
6016    LIFO_SLOT.with(|slot| match slot.take() {
6017      Some((id, runnable)) if id == self.id => Some(runnable),
6018      other => {
6019        slot.set(other);
6020        None
6021      }
6022    })
6023  }
6024
6025  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
6026  /// with a wake so any worker can pick it up. MANDATORY on every path where
6027  /// this thread stops draining this executor: `drain`'s
6028  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
6029  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
6030  /// slot while its thread leaves executor code is a silently lost task.
6031  fn flush_lifo_slot(self: &Arc<Self>) {
6032    let mut admission = self.begin_deadlock_admission();
6033    if let Some(runnable) = self.pop_lifo_slot() {
6034      self.push_to_queue_and_wake(runnable);
6035      if let Some(admission) = admission.as_mut() {
6036        admission.mark_published();
6037      }
6038    }
6039  }
6040
6041  /// Mint a fresh owner token for a counted blocking closure entering on this
6042  /// executor. Each entry gets a unique `frame` so nested owners on the same
6043  /// thread can never run each other's queued jobs over the cap.
6044  fn fresh_owner_token(&self) -> BlockingOwnerToken {
6045    BlockingOwnerToken {
6046      executor_id: self.id,
6047      frame: next_unique_id(
6048        &NEXT_BLOCKING_FRAME,
6049        "blocking owner frame id space exhausted",
6050      ),
6051    }
6052  }
6053
6054  fn finish_draining(self: &Arc<Self>) {
6055    // LIFO-slot invariant: this re-check reads only the
6056    // SHARED queues -- per-worker slots are invisible to it, and that is
6057    // sound because slot work never needs a foreign observer. A slot entry is
6058    // always either popped and run by its owning thread (drain / run_one) or
6059    // flushed to the shared FIFO by that same thread BEFORE it stops draining
6060    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
6061    // covers unwinds; cooperative parks flush defensively). No thread leaves
6062    // executor code with its slot occupied, so the exit-then-respawn window
6063    // below cannot hide slot work from `ensure_drainer`.
6064    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6065    let has_runnable = self.has_queued_runnable();
6066    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6067      && !self
6068        .blocking_queue
6069        .lock()
6070        .unwrap_or_else(std::sync::PoisonError::into_inner)
6071        .is_empty();
6072    if has_runnable || has_blocking {
6073      self.ensure_drainer();
6074    }
6075    // The lock is still taken unconditionally: it is the release edge that
6076    // publishes the `active_drainers` decrement above to a waiter that is about
6077    // to evaluate the predicate. Only the wake is now conditional, and the
6078    // waiter count is read under the very mutex the waiter must hold to
6079    // register, so a waiter is never missed. See `wait_until_scheduler_idle`.
6080    let idle = self
6081      .scheduler_idle_lock
6082      .lock()
6083      .unwrap_or_else(std::sync::PoisonError::into_inner);
6084    if *idle > 0 {
6085      self.scheduler_idle.notify_all();
6086    }
6087  }
6088
6089  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6090    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6091      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
6092      // freeze a worker that the awaited future may itself need to make
6093      // progress. Drive the queue cooperatively instead (mirrors the
6094      // CurrentThread drive loop). The id check mirrors the
6095      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
6096      // owns none of this executor's accounting, so it must take the parking
6097      // path below rather than drive these queues as an unaccounted driver.
6098      self.cooperative_block_on(future)
6099    } else {
6100      // Non-pool (e.g. napi caller) thread, or a pool worker of another
6101      // executor: park without driving queues. The runtime owns this park so
6102      // generation shutdown can wake and cancel it.
6103      self.parking_block_on(future)
6104    }
6105  }
6106
6107  fn parking_block_on(
6108    self: &Arc<Self>,
6109    mut future: Pin<&mut dyn Future<Output = ()>>,
6110  ) -> BlockOnOutcome {
6111    let parker = Arc::new(DriverParker::default());
6112    let _stop_registration = self.stop.register(&parker);
6113    let waker = Waker::from(Arc::clone(&parker));
6114    let mut cx = Context::from_waker(&waker);
6115    loop {
6116      if self.stop.is_stopping() {
6117        return BlockOnOutcome::Stopped;
6118      }
6119      if future.as_mut().poll(&mut cx).is_ready() {
6120        return BlockOnOutcome::Completed;
6121      }
6122      if self.stop.is_stopping() {
6123        return BlockOnOutcome::Stopped;
6124      }
6125      parker.park();
6126    }
6127  }
6128
6129  fn run_runnable(
6130    self: &Arc<Self>,
6131    runnable: Runnable,
6132    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6133  ) {
6134    // Ownership is lexical to a blocking closure. A runnable driven from that
6135    // closure must not borrow its over-cap privilege.
6136    let _non_owner = BlockingOwnerGuard::enter(None);
6137    let _active = self.metrics.runnable_started();
6138    #[cfg(test)]
6139    run_multi_thread_work_started_test_hook();
6140    // The queue/slot removal is invisible to the deadline verdict until start
6141    // accounting advances. Keep the detector gate open across exactly that gap,
6142    // but never across a user poll.
6143    drop(admission);
6144    let _ = catch_unwind_contained(|| runnable.run());
6145  }
6146
6147  fn cancel_runnable(
6148    &self,
6149    runnable: Runnable,
6150    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6151  ) {
6152    self.metrics.runnable_cancelled();
6153    // Cancellation accounting closes the dequeue gap just as start accounting
6154    // does for a runnable that will be polled.
6155    drop(admission);
6156    let _generation = RuntimeGenerationGuard::enter(self.generation);
6157    let _ = catch_unwind_contained(|| drop(runnable));
6158  }
6159
6160  fn finish_runnable_claim(
6161    self: &Arc<Self>,
6162    claim: RunnableClaim,
6163    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6164  ) -> bool {
6165    match claim {
6166      RunnableClaim::Empty => {
6167        drop(admission);
6168        false
6169      }
6170      RunnableClaim::Run(runnable) => {
6171        #[cfg(test)]
6172        run_multi_thread_work_claimed_test_hook();
6173        self.run_runnable(runnable, admission);
6174        true
6175      }
6176      RunnableClaim::Cancel(runnable) => {
6177        #[cfg(test)]
6178        run_multi_thread_work_claimed_test_hook();
6179        self.cancel_runnable(runnable, admission);
6180        true
6181      }
6182    }
6183  }
6184
6185  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6186    match runnable {
6187      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6188      Some(runnable) => RunnableClaim::Run(runnable),
6189      None => RunnableClaim::Empty,
6190    }
6191  }
6192
6193  /// Lock-free single-item steal from the shared injector, retrying the
6194  /// transient `Steal::Retry` contention state. Replaces the former
6195  /// `queue.lock().pop_front()`; `Injector` preserves FIFO order.
6196  fn steal_one(&self) -> Option<Runnable> {
6197    loop {
6198      match self.injector.steal() {
6199        Steal::Success(runnable) => return Some(runnable),
6200        Steal::Empty => return None,
6201        Steal::Retry => continue,
6202      }
6203    }
6204  }
6205
6206  /// Test-only raw FIFO enqueue: the former `queue.lock().push_back()` staging
6207  /// that tests use to seed queued work WITHOUT the accompanying wake (so no
6208  /// drainer races the manual `run_one`/`drain` calls). Keeps the post-push
6209  /// SeqCst fence from `push_to_queue_and_wake` so any wake/park interaction a
6210  /// test performs afterwards still sees the Dekker pairing documented on
6211  /// `injector`.
6212  #[cfg(test)]
6213  fn push_fifo_raw(&self, runnable: Runnable) {
6214    self.injector.push(runnable);
6215    std::sync::atomic::fence(Ordering::SeqCst);
6216  }
6217
6218  fn claim_fifo_runnable(&self) -> RunnableClaim {
6219    let stop_publication = self.stop.runnable_claim_guard();
6220    let runnable = self.steal_one();
6221    let stopping = stop_publication.is_stopping();
6222    drop(stop_publication);
6223    Self::runnable_claim(stopping, runnable)
6224  }
6225
6226  fn claim_runnable(&self) -> RunnableClaim {
6227    let stop_publication = self.stop.runnable_claim_guard();
6228    let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6229    let stopping = stop_publication.is_stopping();
6230    drop(stop_publication);
6231    Self::runnable_claim(stopping, runnable)
6232  }
6233
6234  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6235    let admission = self.begin_deadlock_admission();
6236    let claim = self.claim_fifo_runnable();
6237    self.finish_runnable_claim(claim, admission)
6238  }
6239
6240  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
6241  fn run_one_runnable(self: &Arc<Self>) -> bool {
6242    let admission = self.begin_deadlock_admission();
6243    // Same owner-clear as the FIFO-only path: a runnable from the slot must
6244    // not smuggle an owner frame's over-cap privilege.
6245    let claim = self.claim_runnable();
6246    self.finish_runnable_claim(claim, admission)
6247  }
6248
6249  fn run_one_blocking(self: &Arc<Self>) -> bool {
6250    if let Some((blocking, admission)) = self.take_blocking() {
6251      let _generation = RuntimeGenerationGuard::enter(self.generation);
6252      {
6253        let _task = self.metrics.blocking_started(true);
6254        #[cfg(test)]
6255        run_multi_thread_work_started_test_hook();
6256        // Publish start before the dequeue admission retires. The detector can
6257        // then observe either the queued job, the active admission, or progress.
6258        drop(admission);
6259        let owner = self.fresh_owner_token();
6260        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6261        let _owner = BlockingOwnerGuard::enter(Some(owner));
6262        // The queued wrapper contains panics from the user function, but
6263        // delivering its result can still drop user-owned state when the
6264        // receiver is gone. Contain that destructor boundary as well so an
6265        // unwind cannot bypass blocking-slot and drainer retirement.
6266        let _ = catch_unwind_contained(blocking);
6267      }
6268      self.active_blocking.fetch_sub(1, Ordering::Release);
6269      return true;
6270    }
6271    false
6272  }
6273
6274  /// Run one fair scheduler unit. Runnable locality remains the normal
6275  /// priority, but a continuously hot runnable stream must yield one quantum
6276  /// to the blocking FIFO.
6277  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6278    self.run_one_fair_with_exact(runnable_streak, None)
6279  }
6280
6281  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
6282  /// been satisfied, a live exact dependency must claim its owner's lane before
6283  /// unrelated FIFO blocking work can consume the last ordinary slot.
6284  fn run_one_fair_with_exact(
6285    self: &Arc<Self>,
6286    runnable_streak: &mut usize,
6287    exact_dependency: Option<&TaskDependency>,
6288  ) -> bool {
6289    let mut exact_attempted = false;
6290    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6291      *runnable_streak = 0;
6292      if let Some(dependency) = exact_dependency {
6293        exact_attempted = true;
6294        if self.try_owned_blocking_over_cap(dependency) {
6295          return true;
6296        }
6297      }
6298      if self.run_one_blocking() {
6299        return true;
6300      }
6301    }
6302    if self.run_one_runnable() {
6303      *runnable_streak += 1;
6304      return true;
6305    }
6306    if !exact_attempted
6307      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6308    {
6309      *runnable_streak = 0;
6310      return true;
6311    }
6312    if self.run_one_blocking() {
6313      *runnable_streak = 0;
6314      return true;
6315    }
6316    false
6317  }
6318
6319  #[cfg(test)]
6320  fn run_one(self: &Arc<Self>) -> bool {
6321    let mut runnable_streak = 0;
6322    self.run_one_fair(&mut runnable_streak)
6323  }
6324
6325  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
6326  /// park while the blocking cap is saturated. The dependency carries its
6327  /// exact owner frame through JoinHandle lineage. Reserving that frame
6328  /// transfers its counted lane to this otherwise-idle cooperative driver.
6329  ///
6330  /// Owner lending is lexical: the cooperative driver must carry the exact
6331  /// blocking frame whose counted lane it reuses. Rayon does not propagate
6332  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
6333  /// prove that an untagged worker belongs to the sole active owner.
6334  fn run_owned_blocking_over_cap(
6335    self: &Arc<Self>,
6336    dependency: &TaskDependency,
6337  ) -> OwnedBlockingOutcome {
6338    #[cfg(test)]
6339    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6340
6341    let Some(ambient_owner) = BLOCKING_OWNER
6342      .with(std::cell::Cell::get)
6343      .filter(|owner| owner.executor_id == self.id)
6344    else {
6345      return OwnedBlockingOutcome::Ineligible;
6346    };
6347    let Some(current) = dependency.select_for_owner(ambient_owner) else {
6348      return OwnedBlockingOutcome::Ineligible;
6349    };
6350    let admission = self.begin_deadlock_admission();
6351    let owner = current
6352      .publication
6353      .dependency
6354      .owner
6355      .expect("selected dependency must carry its owner");
6356    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6357    else {
6358      return OwnedBlockingOutcome::OwnerBusy(owner);
6359    };
6360    #[cfg(test)]
6361    run_before_owned_blocking_queue_claim_test_hook();
6362    let job = {
6363      let mut queue = self
6364        .blocking_queue
6365        .lock()
6366        .unwrap_or_else(std::sync::PoisonError::into_inner);
6367      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
6368      // admission states before consuming the dependency so a shutdown that
6369      // reached this claim first leaves the job queued for cancellation.
6370      if queue.closed || self.stop.is_stopping() {
6371        None
6372      // Claim while holding the queue lock. A concurrent clear/switch that
6373      // wins first makes this fail; once the claim succeeds, no stale snapshot
6374      // can authorize removal of the old job.
6375      } else if dependency.try_claim(&current) {
6376        queue.remove(current.publication.dependency.job)
6377      } else {
6378        None
6379      }
6380    };
6381    let ran = if let Some(job) = job {
6382      #[cfg(test)]
6383      run_multi_thread_work_claimed_test_hook();
6384      let _generation = RuntimeGenerationGuard::enter(self.generation);
6385      {
6386        let _task = self.metrics.blocking_started(false);
6387        #[cfg(test)]
6388        run_multi_thread_work_started_test_hook();
6389        // The exact job is no longer visible in the indexed FIFO. Publish its
6390        // start before allowing a deadline verdict to close the admission gate.
6391        drop(admission);
6392        let nested_owner = self.fresh_owner_token();
6393        let _active_owner =
6394          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6395        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6396        let _ = catch_unwind_contained(job);
6397      }
6398      true
6399    } else {
6400      drop(admission);
6401      false
6402    };
6403    // Every reservation release can make another exact dependency serviceable,
6404    // including failed-claim and already-removed-job exits.
6405    self.release_owner_lane_and_wake(reservation);
6406    OwnedBlockingOutcome::ReservationReleased { owner, ran }
6407  }
6408
6409  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6410    matches!(
6411      self.run_owned_blocking_over_cap(dependency),
6412      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6413    )
6414  }
6415
6416  fn service_owner_handoff(
6417    self: &Arc<Self>,
6418    parker: &DriverParker,
6419    dependency: &TaskDependency,
6420    can_run_blocking: bool,
6421  ) -> Option<bool> {
6422    let handoff_owner = parker.take_owner_handoff()?;
6423    if !can_run_blocking {
6424      self.wake_one_owner_dependency(handoff_owner);
6425      return Some(false);
6426    }
6427    match self.run_owned_blocking_over_cap(dependency) {
6428      OwnedBlockingOutcome::Ineligible => {
6429        self.wake_one_owner_dependency(handoff_owner);
6430        Some(false)
6431      }
6432      OwnedBlockingOutcome::OwnerBusy(owner) => {
6433        if owner != handoff_owner {
6434          self.wake_one_owner_dependency(handoff_owner);
6435        }
6436        Some(false)
6437      }
6438      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6439        if owner != handoff_owner {
6440          self.wake_one_owner_dependency(handoff_owner);
6441        }
6442        Some(ran)
6443      }
6444    }
6445  }
6446
6447  fn forward_owner_handoff(&self, parker: &DriverParker) {
6448    if let Some(owner) = parker.take_owner_handoff() {
6449      self.wake_one_owner_dependency(owner);
6450    }
6451  }
6452
6453  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6454    let Some(ambient_owner) = BLOCKING_OWNER
6455      .with(std::cell::Cell::get)
6456      .filter(|owner| owner.executor_id == self.id)
6457    else {
6458      return false;
6459    };
6460    self.active_blocking_owners.is_available(ambient_owner)
6461      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6462  }
6463
6464  fn has_exact_owner_work_for_role(
6465    &self,
6466    parker: &DriverParker,
6467    dependency: &TaskDependency,
6468    can_run_blocking: bool,
6469  ) -> bool {
6470    parker.has_owner_handoff()
6471      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6472  }
6473
6474  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
6475  /// of parking the worker (which would freeze one of the pool's fixed OS
6476  /// threads), it drives the awaited future while servicing queued work. When
6477  /// nothing is immediately runnable it parks on its own [`DriverParker`],
6478  /// which is woken directly by the awaited future's waker and through the
6479  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
6480  /// wake reaches this worker even when every worker is parked and
6481  /// `ensure_drainer` has no free thread to spawn a replacement.
6482  fn cooperative_block_on(
6483    self: &Arc<Self>,
6484    mut future: Pin<&mut dyn Future<Output = ()>>,
6485  ) -> BlockOnOutcome {
6486    // One parker per `block_on` frame. Nested re-entrant frames each own one:
6487    // only the innermost frame runs or parks at any moment, and an outer
6488    // frame's future-wake is stored as its parker's permit, consumed when
6489    // control returns to that frame's loop.
6490    let parker = Arc::new(DriverParker::default());
6491    let _stop_registration = self.stop.register(&parker);
6492    let dependency = BlockOnDependencyGuard::enter(self.generation);
6493    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6494    let can_run_blocking =
6495      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6496    let _exit = CooperativeDriverExitGuard {
6497      executor: self,
6498      parker: &parker,
6499      can_run_blocking,
6500    };
6501    // WAKE-DOMAIN SPLIT: the awaited future is polled with
6502    // THIS driver's own parker as its waker, so a future-wake targets exactly
6503    // the driver that awaits it -- delivered as a stored permit (skip next
6504    // park) when this driver is currently running, so it is never lost.
6505    let waker = std::task::Waker::from(Arc::clone(&parker));
6506    let mut cx = Context::from_waker(&waker);
6507    // Consecutive `run_one` successes since the last yield point: the
6508    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
6509    // it, a hot slot chain (each runnable schedules a successor into this
6510    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
6511    // loop inside `run_one` forever and the shared FIFO would never be
6512    // reached from this thread -- a deadlock on a 1-worker pool when the
6513    // awaited future depends on a FIFO task.
6514    let mut runnable_streak = 0usize;
6515    let mut work_streak = 0usize;
6516    let mut force_fifo = false;
6517    loop {
6518      if self.stop.is_stopping() {
6519        return BlockOnOutcome::Stopped;
6520      }
6521      dependency.clear();
6522      if future.as_mut().poll(&mut cx).is_ready() {
6523        return BlockOnOutcome::Completed;
6524      }
6525      if self.stop.is_stopping() {
6526        return BlockOnOutcome::Stopped;
6527      }
6528      if self
6529        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6530        .is_some_and(|ran| ran)
6531      {
6532        continue;
6533      }
6534      if force_fifo {
6535        force_fifo = false;
6536        if self.run_one_fifo_runnable() {
6537          runnable_streak += 1;
6538          work_streak += 1;
6539          continue;
6540        }
6541      }
6542      let ran_work = if can_run_blocking {
6543        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6544      } else if self.run_one_runnable() {
6545        runnable_streak += 1;
6546        true
6547      } else {
6548        false
6549      };
6550      if ran_work {
6551        work_streak += 1;
6552        if work_streak >= Self::RUNNABLE_BUDGET {
6553          // Budget yield: move a hot slot occupant to the shared FIFO (with
6554          // a wake), so the next `run_one` pops the FIFO front (the oldest
6555          // work) and other workers can steal the chain. The awaited future
6556          // is polled every iteration regardless, so it is never starved.
6557          self.flush_lifo_slot();
6558          work_streak = 0;
6559          force_fifo = true;
6560        }
6561        continue;
6562      }
6563      runnable_streak = 0;
6564      work_streak = 0;
6565      // MANDATORY pre-park flush. Defensive on every
6566      // currently reachable path: `run_one` above popped the slot and no user
6567      // code ran since -- but parking with a stranded slot runnable would
6568      // silently lose that task, so the invariant is upheld unconditionally.
6569      self.flush_lifo_slot();
6570      // Park protocol: register FIRST, then re-check, then park -- the
6571      // waiter-side mirror of the push-then-wake schedule side. See the
6572      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
6573      self.parked_drivers.register_with_role(
6574        &parker,
6575        can_run_blocking,
6576        Some(Arc::clone(&dependency.dependency)),
6577      );
6578      if self.has_queued_work_for_role(can_run_blocking)
6579        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6580      {
6581        self.parked_drivers.deregister(&parker);
6582        continue;
6583      }
6584      if self.stop.is_stopping() {
6585        return BlockOnOutcome::Stopped;
6586      }
6587      // TIMER-BOUNDED PARK: while any heap
6588      // timer is pending, this park is a TIMER WAIT, not a deadlock
6589      // candidate -- the wall clock is a guaranteed wake source -- so it uses
6590      // its own bounded primitive instead of `park`/`park_with_deadline` and
6591      // fires whatever came due on the way out. An armed park deadline is
6592      // deliberately NOT evaluated during a timer wait: the fire schedules
6593      // the woken task (fingerprint progress), and the next timerless park
6594      // arms a fresh full window, so genuine-deadlock detection is delayed
6595      // past the timer, never lost. This is also load-bearing for liveness:
6596      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
6597      // job has no free thread to run on, so this driver must fire due
6598      // timers itself. (A timer registered between the deadline read and the
6599      // park re-arms this parker through `note_earlier_timer`'s
6600      // `wake_one` -- we are registered -- or wakes the timekeeper.)
6601      if let Some(next) = self.next_timer_deadline() {
6602        let now = Instant::now();
6603        if next > now {
6604          parker.park_timeout(next - now);
6605        }
6606        self.parked_drivers.deregister(&parker);
6607        self.fire_due_timers();
6608        continue;
6609      }
6610      match self.park_deadline {
6611        None => parker.park(),
6612        Some(deadline) => {
6613          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
6614          // candidate deadlock only when a
6615          // FULL deadline window passes with zero executor progress. The
6616          // foreign-thread whole-build park in `block_on`'s else-branch is
6617          // exempt by design.
6618          let deadlock_publications = self.deadlock_state.publications();
6619          match park_with_deadline(&parker, deadline, &self.metrics) {
6620            DeadlineParkOutcome::Woken => {}
6621            DeadlineParkOutcome::Expired(armed) => {
6622              #[cfg(test)]
6623              run_deadline_expiry_test_hook();
6624              // EXPIRY DECISION. While this parker was
6625              // registered, a racing `wake_one` may have popped it, stored
6626              // its permit and reported the wake as DELIVERED (so the
6627              // scheduler spawned no drainer) -- panicking now would swallow
6628              // a healthy wake and kill a live build. Deregister FIRST so no
6629              // FURTHER wake can be counted against this parker, then
6630              // re-check every way a wake could have landed around the
6631              // expiry:
6632              //   * permit: a `wake_one` (or direct future-wake) that landed
6633              //     while still registered MUST be acted on;
6634              //   * queued work: the work behind such a wake was pushed
6635              //     under the queue mutex BEFORE `wake_one` popped us, so
6636              //     even if its `unpark` is still in flight the queue
6637              //     re-check observes it;
6638              //   * pending timers: arming a timer bumps NO progress counter
6639              //     by design, so a registration racing this already-
6640              //     committed park (it read an empty heap before parking) is
6641              //     invisible to every other re-check -- yet its wake (the
6642              //     fire) is wall-clock-guaranteed, so this park is healthy.
6643              //     The loop then re-parks TIMER-BOUNDED (see above) and
6644              //     fires the timer itself if needed;
6645              //   * fingerprint: the verdict requires ZERO progress, and the
6646              //     read inside `park_with_deadline` is stale by the time we
6647              //     get here -- re-read as the LAST step. Both ENQUEUE
6648              //     counters are in the fingerprint (`runnable_schedules`
6649              //     and `blocking_tasks_scheduled`, both bumped before the
6650              //     push), so a submission of EITHER kind
6651              //     landing after the queue re-check above is still caught
6652              //     here.
6653              // Any hit => not a deadlock: continue the cooperative loop
6654              // (fresh future poll, `run_one` picks up the work, and the
6655              // next park arms a fresh FULL deadline window). The residue is
6656              // exactly a BARE direct future-wake -- no queue push, no
6657              // counter movement -- landing in the few instructions after
6658              // these re-checks: the inherent, unclosable residue of any
6659              // timing-based detector, one reason the deadline is opt-in.
6660              self.parked_drivers.deregister(&parker);
6661              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6662                continue;
6663              }
6664              // Test-only seam between the permit/queued-work re-checks and
6665              // the fingerprint verdict.
6666              #[cfg(test)]
6667              run_deadline_verdict_test_hook();
6668              if self.metrics.progress_fingerprint() != armed {
6669                continue;
6670              }
6671              // Pending-timer re-check LAST, after even the fingerprint:
6672              // arming a timer bumps no counter, so it is the one healthy
6673              // wake source that can land after every earlier re-check and
6674              // still be invisible to the fingerprint read above. (A
6675              // registration racing the instructions after this line is the
6676              // same inherent residue class as the bare future-wake -- and
6677              // its timer is still served: `note_earlier_timer` claims the
6678              // timekeeper independently of this driver.)
6679              if self.next_timer_deadline().is_some() {
6680                continue;
6681              }
6682              // Test-only seam for a cross-thread scheduler admission after
6683              // every legacy final check and before the synchronized verdict.
6684              #[cfg(test)]
6685              run_deadline_final_verdict_test_hook();
6686              let Some(verdict) = self.try_begin_deadlock_verdict() else {
6687                continue;
6688              };
6689              #[cfg(test)]
6690              run_deadline_gated_verdict_test_hook();
6691              // Lock order is verdict gate -> stop publication -> exact-owner
6692              // publication. The controller takes lifecycle -> stop
6693              // publication and never acquires the verdict gate or exact-owner
6694              // publication while retaining either lock.
6695              let stop_publication = self.stop.publication_guard();
6696              #[cfg(test)]
6697              run_deadline_synchronized_stop_test_hook();
6698              if stop_publication.is_stopping() {
6699                return BlockOnOutcome::Stopped;
6700              }
6701              let owner_handoff_publication = self
6702                .deadlock_state
6703                .owner_handoff_publication_guard()
6704                .expect("a deadline verdict requires exact-owner publication");
6705              #[cfg(test)]
6706              run_deadline_exact_owner_verdict_test_hook(&parker);
6707              // The closed gate excludes later publications. Any admission
6708              // that began earlier either remains counted (so gate acquisition
6709              // failed above) or completed before this recheck and advanced
6710              // the publication epoch. The stop-publication guard also keeps
6711              // the lifecycle in Running if this verdict linearized first. The
6712              // exact-owner publication guard similarly orders reservation
6713              // release and targeted handoff with the final predicates below.
6714              if self.has_exact_owner_work_for_role(
6715                &parker,
6716                &dependency.dependency,
6717                can_run_blocking,
6718              ) || parker.consume_permit()
6719                || self.has_queued_work_for_role(can_run_blocking)
6720                || self.deadlock_state.publications() != deadlock_publications
6721                || self.metrics.progress_fingerprint() != armed
6722                || self.next_timer_deadline().is_some()
6723              {
6724                continue;
6725              }
6726              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6727              // Panic hooks run before unwind. Release every synchronization
6728              // guard first so a hook that submits work, releases an owner
6729              // lane, or starts shutdown cannot wait for the panic that invokes
6730              // it to unwind.
6731              drop(owner_handoff_publication);
6732              drop(stop_publication);
6733              drop(verdict);
6734              std::panic::panic_any(diagnostic);
6735            }
6736          }
6737        }
6738      }
6739      // `wake_one` removes the parkers it pops; a direct future-wake does
6740      // not. Deregister-if-present covers both.
6741      self.parked_drivers.deregister(&parker);
6742    }
6743  }
6744
6745  /// Is there work a cooperative driver could pick up right now? Used by the
6746  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
6747  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
6748  /// blocking job within the cap). The exact over-cap dependency is handled
6749  /// separately before registration.
6750  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6751    if self.has_queued_runnable() {
6752      return true;
6753    }
6754    can_run_blocking
6755      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6756      && !self
6757        .blocking_queue
6758        .lock()
6759        .unwrap_or_else(std::sync::PoisonError::into_inner)
6760        .is_empty()
6761  }
6762
6763  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6764    if self.has_queued_runnable() {
6765      self.wake_for_new_work();
6766    }
6767    // Shutdown-aborted runnables still need a driver so their generators can
6768    // retire, but queued blocking closures are cancelled by begin_shutdown and
6769    // must never be admitted by exit compensation after stop is observable.
6770    if self.stop.is_stopping() {
6771      return;
6772    }
6773    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6774      && !self
6775        .blocking_queue
6776        .lock()
6777        .unwrap_or_else(std::sync::PoisonError::into_inner)
6778        .is_empty()
6779    {
6780      // This driver may have absorbed the queue wake that made its awaited
6781      // future ready. Runnable and blocking residue are independent. If no
6782      // other blocking-capable driver is available, merely spawning a Rayon
6783      // job is insufficient: this worker may return into arbitrary
6784      // synchronous user code while every other Rayon lane is busy.
6785      // Consume one admitted job before exit, then hand any residue to another
6786      // blocking-capable driver.
6787      let consumed = can_run_blocking && self.run_one_blocking();
6788      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6789        && !self
6790          .blocking_queue
6791          .lock()
6792          .unwrap_or_else(std::sync::PoisonError::into_inner)
6793          .is_empty();
6794      if !self.stop.is_stopping() && (!consumed || has_residue) {
6795        self.wake_for_blocking_work();
6796      }
6797    }
6798  }
6799
6800  fn has_queued_runnable(&self) -> bool {
6801    // Waiter-side mirror of the `push_to_queue_and_wake` fence: a parking
6802    // driver (pre-park re-check) and the drainer-exit re-check in
6803    // `finish_draining` must not observe the injector empty when a racing push
6804    // is already visible to a wake. This SeqCst fence pairs with the post-push
6805    // fence so the Dekker ordering holds without the former queue mutex. Only
6806    // on park/quiesce decision paths, never the per-task hot path.
6807    std::sync::atomic::fence(Ordering::SeqCst);
6808    !self.injector.is_empty()
6809  }
6810
6811  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
6812  /// driver may run any job within `max_blocking`; only the over-cap escape is
6813  /// restricted to an exact dependency id.
6814  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6815    let admission = self.begin_deadlock_admission();
6816    loop {
6817      let active = self.active_blocking.load(Ordering::Acquire);
6818      if active >= self.max_blocking {
6819        return None;
6820      }
6821      let mut queue = self
6822        .blocking_queue
6823        .lock()
6824        .unwrap_or_else(std::sync::PoisonError::into_inner);
6825      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6826        return None;
6827      }
6828      if self
6829        .active_blocking
6830        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6831        .is_ok()
6832      {
6833        let blocking = queue
6834          .pop_front()
6835          .expect("a non-empty blocking queue must have a FIFO head");
6836        drop(queue);
6837        #[cfg(test)]
6838        run_multi_thread_work_claimed_test_hook();
6839        return Some((blocking, admission));
6840      }
6841    }
6842  }
6843}
6844
6845#[cfg(napi_runtime_os_threads)]
6846struct MultiThreadDeadlockState {
6847  enabled: bool,
6848  verdict_closed: AtomicBool,
6849  admissions: AtomicUsize,
6850  publications: AtomicU64,
6851  owner_handoff_publication: Mutex<()>,
6852}
6853
6854#[cfg(napi_runtime_os_threads)]
6855impl MultiThreadDeadlockState {
6856  fn new(enabled: bool) -> Self {
6857    Self {
6858      enabled,
6859      verdict_closed: AtomicBool::new(false),
6860      admissions: AtomicUsize::new(0),
6861      publications: AtomicU64::new(0),
6862      owner_handoff_publication: Mutex::new(()),
6863    }
6864  }
6865
6866  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6867    if !self.enabled {
6868      return None;
6869    }
6870    loop {
6871      while self.verdict_closed.load(Ordering::SeqCst) {
6872        #[cfg(test)]
6873        run_deadlock_closed_gate_admission_test_hook();
6874        std::hint::spin_loop();
6875      }
6876      self.admissions.fetch_add(1, Ordering::SeqCst);
6877      if !self.verdict_closed.load(Ordering::SeqCst) {
6878        return Some(MultiThreadDeadlockAdmissionGuard {
6879          state: Arc::clone(self),
6880          published: false,
6881        });
6882      }
6883      self.admissions.fetch_sub(1, Ordering::SeqCst);
6884    }
6885  }
6886
6887  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6888    debug_assert!(
6889      self.enabled,
6890      "a deadline verdict requires an enabled admission state"
6891    );
6892    if self
6893      .verdict_closed
6894      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6895      .is_err()
6896    {
6897      return None;
6898    }
6899    if self.admissions.load(Ordering::SeqCst) != 0 {
6900      self.verdict_closed.store(false, Ordering::SeqCst);
6901      return None;
6902    }
6903    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6904  }
6905
6906  fn publications(&self) -> u64 {
6907    self.publications.load(Ordering::SeqCst)
6908  }
6909
6910  fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6911    self.enabled.then(|| {
6912      self
6913        .owner_handoff_publication
6914        .lock()
6915        .unwrap_or_else(std::sync::PoisonError::into_inner)
6916    })
6917  }
6918}
6919
6920#[cfg(napi_runtime_os_threads)]
6921struct MultiThreadDeadlockAdmissionGuard {
6922  state: Arc<MultiThreadDeadlockState>,
6923  published: bool,
6924}
6925
6926#[cfg(napi_runtime_os_threads)]
6927impl MultiThreadDeadlockAdmissionGuard {
6928  fn mark_published(&mut self) {
6929    self.published = true;
6930  }
6931}
6932
6933#[cfg(napi_runtime_os_threads)]
6934impl Drop for MultiThreadDeadlockAdmissionGuard {
6935  fn drop(&mut self) {
6936    if self.published {
6937      self.state.publications.fetch_add(1, Ordering::SeqCst);
6938    }
6939    // Publication and every scheduler-state write above must precede the final
6940    // admission decrement observed by a closed verdict gate.
6941    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6942  }
6943}
6944
6945#[cfg(napi_runtime_os_threads)]
6946struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6947
6948#[cfg(napi_runtime_os_threads)]
6949impl Drop for MultiThreadDeadlockVerdictGuard {
6950  fn drop(&mut self) {
6951    self.0.verdict_closed.store(false, Ordering::SeqCst);
6952  }
6953}
6954
6955/// Cooperative-driver exit backstop. A user-future panic can unwind from any
6956/// poll, including after this driver absorbed a queue wake, received an exact
6957/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
6958/// independently so one cleanup failure cannot suppress the others or
6959/// double-panic the process.
6960#[cfg(napi_runtime_os_threads)]
6961struct CooperativeDriverExitGuard<'a> {
6962  executor: &'a Arc<MultiThreadExecutor>,
6963  parker: &'a Arc<DriverParker>,
6964  can_run_blocking: bool,
6965}
6966
6967#[cfg(napi_runtime_os_threads)]
6968impl Drop for CooperativeDriverExitGuard<'_> {
6969  fn drop(&mut self) {
6970    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6971    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6972    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6973    let _ = catch_unwind_contained(|| {
6974      self
6975        .executor
6976        .compensate_queued_work_wake(self.can_run_blocking);
6977    });
6978  }
6979}
6980
6981/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
6982/// unwinding drain can never strand a runnable in the
6983/// thread-local slot of a thread that is about to return to rayon. Normal
6984/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
6985/// no-op (the slot is already empty).
6986#[cfg(napi_runtime_os_threads)]
6987struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6988
6989#[cfg(napi_runtime_os_threads)]
6990impl Drop for LifoSlotFlushGuard<'_> {
6991  fn drop(&mut self) {
6992    self.0.flush_lifo_slot();
6993  }
6994}
6995
6996/// Per-drain-frame idle-linger state, created lazily on the frame's first
6997/// idle episode (see the linger block in `MultiThreadExecutor::drain`): the
6998/// parker queue wakes are delivered to while this drainer lingers, plus its
6999/// generation-stop registration so `begin_shutdown` cuts a linger short
7000/// instead of `wait_until_scheduler_idle` waiting out the idle budget. The
7001/// registration guard deregisters on drop when the drain frame exits.
7002#[cfg(napi_runtime_os_threads)]
7003struct DrainLinger {
7004  parker: Arc<DriverParker>,
7005  _stop_registration: GenerationStopGuard,
7006}
7007
7008#[cfg(napi_runtime_os_threads)]
7009impl DrainLinger {
7010  fn new(executor: &MultiThreadExecutor) -> Self {
7011    let parker = Arc::new(DriverParker::default());
7012    let stop_registration = executor.stop.register(&parker);
7013    Self {
7014      parker,
7015      _stop_registration: stop_registration,
7016    }
7017  }
7018}
7019
7020// ---------------------------------------------------------------------------
7021// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
7022//
7023// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
7024//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
7025//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
7026//     driver that awaits it. The permit bit makes a wake delivered while that
7027//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
7028//     next park instead of losing the wake.
7029//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
7030//     serve queue work, so these go through the `ParkedDrivers` registry and
7031//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
7032//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
7033//     exit (miss compensation).
7034// Foreign-thread (napi caller) parks in `block_on` use
7035// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
7036// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
7037// frame, no registry) so it too OWNS its park site for deadlock detection
7038// while `ParkedDrivers` remains MultiThread-only.
7039
7040/// No permit; the driver is running (or consumed its last permit).
7041const PARKER_EMPTY: usize = 0;
7042/// A wake permit is stored; the next `park` consumes it without sleeping.
7043const PARKER_NOTIFIED: usize = 1;
7044/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
7045const PARKER_SLEEPING: usize = 2;
7046
7047/// One driver's private parker: a saturating one-permit token plus a condvar
7048/// to sleep on when no permit is stored. Also the `Waker` for the future that
7049/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
7050#[derive(Default)]
7051struct DriverParker {
7052  state: AtomicUsize,
7053  lock: Mutex<()>,
7054  condvar: std::sync::Condvar,
7055  #[cfg(napi_runtime_os_threads)]
7056  owner_handoff_pending: AtomicBool,
7057  #[cfg(napi_runtime_os_threads)]
7058  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7059}
7060
7061impl DriverParker {
7062  #[cfg(napi_runtime_os_threads)]
7063  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7064    let mut pending = self
7065      .owner_handoff
7066      .lock()
7067      .unwrap_or_else(std::sync::PoisonError::into_inner);
7068    debug_assert!(pending.is_none());
7069    *pending = Some(owner);
7070    self.owner_handoff_pending.store(true, Ordering::Release);
7071  }
7072
7073  #[cfg(napi_runtime_os_threads)]
7074  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7075    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7076      return None;
7077    }
7078    self
7079      .owner_handoff
7080      .lock()
7081      .unwrap_or_else(std::sync::PoisonError::into_inner)
7082      .take()
7083  }
7084
7085  #[cfg(napi_runtime_os_threads)]
7086  fn has_owner_handoff(&self) -> bool {
7087    self.owner_handoff_pending.load(Ordering::Acquire)
7088  }
7089
7090  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
7091  /// permit is stored and the driver's next [`Self::park`] returns
7092  /// immediately -- a wake delivered while the driver is running is never
7093  /// lost. Multiple grants coalesce into one permit, which is safe because a
7094  /// woken driver re-checks every work source in its loop before it can park
7095  /// again.
7096  fn unpark(&self) {
7097    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7098      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
7099      // releases the lock only inside `condvar.wait`; the empty critical
7100      // section here fences the notify so it cannot fire in the window
7101      // between the sleeper's state store and its wait.
7102      drop(
7103        self
7104          .lock
7105          .lock()
7106          .unwrap_or_else(std::sync::PoisonError::into_inner),
7107      );
7108      self.condvar.notify_one();
7109    }
7110  }
7111
7112  /// Consume a stored wake permit if one is present, without ever sleeping.
7113  /// This is the park DECISION's token check: a `true` return means a wake is
7114  /// already pending, so the caller must re-poll instead of parking (or
7115  /// panicking).
7116  fn consume_permit(&self) -> bool {
7117    self
7118      .state
7119      .compare_exchange(
7120        PARKER_NOTIFIED,
7121        PARKER_EMPTY,
7122        Ordering::SeqCst,
7123        Ordering::SeqCst,
7124      )
7125      .is_ok()
7126  }
7127
7128  /// Block until the permit is granted, consuming it. Returns immediately if
7129  /// a permit is already stored. Only the owning driver calls `park`, so at
7130  /// most one thread ever sleeps here.
7131  fn park(&self) {
7132    // Fast path: consume a stored permit without touching the lock.
7133    if self.consume_permit() {
7134      return;
7135    }
7136    let mut guard = self
7137      .lock
7138      .lock()
7139      .unwrap_or_else(std::sync::PoisonError::into_inner);
7140    match self.state.compare_exchange(
7141      PARKER_EMPTY,
7142      PARKER_SLEEPING,
7143      Ordering::SeqCst,
7144      Ordering::SeqCst,
7145    ) {
7146      Ok(_) => {}
7147      // The permit arrived between the fast path and here: consume it. (Only
7148      // this thread stores PARKER_SLEEPING, so the observed value can only be
7149      // PARKER_NOTIFIED.)
7150      Err(_) => {
7151        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7152        return;
7153      }
7154    }
7155    loop {
7156      guard = self
7157        .condvar
7158        .wait(guard)
7159        .unwrap_or_else(std::sync::PoisonError::into_inner);
7160      if self
7161        .state
7162        .compare_exchange(
7163          PARKER_NOTIFIED,
7164          PARKER_EMPTY,
7165          Ordering::SeqCst,
7166          Ordering::SeqCst,
7167        )
7168        .is_ok()
7169      {
7170        return;
7171      }
7172      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
7173    }
7174  }
7175
7176  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
7177  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
7178  /// A `false` return has retracted the SLEEPING claim and left the parker
7179  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
7180  /// detection.
7181  fn park_timeout(&self, timeout: Duration) -> bool {
7182    if self.consume_permit() {
7183      return true;
7184    }
7185    let mut guard = self
7186      .lock
7187      .lock()
7188      .unwrap_or_else(std::sync::PoisonError::into_inner);
7189    match self.state.compare_exchange(
7190      PARKER_EMPTY,
7191      PARKER_SLEEPING,
7192      Ordering::SeqCst,
7193      Ordering::SeqCst,
7194    ) {
7195      Ok(_) => {}
7196      // The permit arrived between the fast path and here: consume it. (Only
7197      // this thread stores PARKER_SLEEPING, so the observed value can only be
7198      // PARKER_NOTIFIED.)
7199      Err(_) => {
7200        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7201        return true;
7202      }
7203    }
7204    let Some(deadline) = Instant::now().checked_add(timeout) else {
7205      // An unrepresentable deadline is farther away than this platform can
7206      // observe. Treat it as an unbounded wait instead of panicking on a
7207      // user-provided deadlock-detection duration.
7208      loop {
7209        guard = self
7210          .condvar
7211          .wait(guard)
7212          .unwrap_or_else(std::sync::PoisonError::into_inner);
7213        if self
7214          .state
7215          .compare_exchange(
7216            PARKER_NOTIFIED,
7217            PARKER_EMPTY,
7218            Ordering::SeqCst,
7219            Ordering::SeqCst,
7220          )
7221          .is_ok()
7222        {
7223          return true;
7224        }
7225      }
7226    };
7227    loop {
7228      if self
7229        .state
7230        .compare_exchange(
7231          PARKER_NOTIFIED,
7232          PARKER_EMPTY,
7233          Ordering::SeqCst,
7234          Ordering::SeqCst,
7235        )
7236        .is_ok()
7237      {
7238        return true;
7239      }
7240      let now = Instant::now();
7241      if now >= deadline {
7242        // Timed out: retract the SLEEPING claim -- unless a permit landed in
7243        // the meantime (an `unpark` that already saw SLEEPING may be waiting
7244        // on `lock` to notify; consuming its permit here IS the wake it
7245        // intended to deliver, so report "woken", not "timed out").
7246        return match self.state.compare_exchange(
7247          PARKER_SLEEPING,
7248          PARKER_EMPTY,
7249          Ordering::SeqCst,
7250          Ordering::SeqCst,
7251        ) {
7252          Ok(_) => false,
7253          Err(_) => {
7254            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7255            true
7256          }
7257        };
7258      }
7259      let (returned_guard, _timed_out) = self
7260        .condvar
7261        .wait_timeout(guard, deadline - now)
7262        .unwrap_or_else(std::sync::PoisonError::into_inner);
7263      guard = returned_guard;
7264      // Loop re-checks the permit first, then the deadline (spurious wakeups
7265      // simply wait out the remaining time).
7266    }
7267  }
7268}
7269
7270impl std::task::Wake for DriverParker {
7271  fn wake(self: Arc<Self>) {
7272    self.unpark();
7273  }
7274
7275  fn wake_by_ref(self: &Arc<Self>) {
7276    self.unpark();
7277  }
7278}
7279
7280/// Outcome of [`park_with_deadline`].
7281enum DeadlineParkOutcome {
7282  /// Woken by a permit (consumed); the park was healthy.
7283  Woken,
7284  /// A full deadline window elapsed with zero observed progress. Carries the
7285  /// fingerprint captured when that window was ARMED so the caller can make
7286  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
7287  /// verdict: a wake can land in the instructions after the window closes,
7288  /// and for MultiThread a `wake_one` can still pop the
7289  /// still-registered parker and count its wake as DELIVERED. Callers must
7290  /// deregister (MT), then re-check permit / queued work / fingerprint before
7291  /// panicking.
7292  Expired(ProgressFingerprint),
7293}
7294
7295/// Deadline-bounded park with PROGRESS-BASED reset.
7296/// Every time `deadline` elapses without a wake, the runtime's progress
7297/// fingerprint is compared against the value captured when the deadline was
7298/// ARMED: any advance re-arms the deadline instead of firing, because a
7299/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
7300/// proceeds -- must never be declared dead. `Expired` is a *candidate*
7301/// deadlock only; the panic decision lives with the caller (see
7302/// [`DeadlineParkOutcome`]).
7303fn park_with_deadline(
7304  parker: &DriverParker,
7305  deadline: Duration,
7306  metrics: &RuntimeMetrics,
7307) -> DeadlineParkOutcome {
7308  let mut armed = metrics.progress_fingerprint();
7309  loop {
7310    if parker.park_timeout(deadline) {
7311      return DeadlineParkOutcome::Woken;
7312    }
7313    let current = metrics.progress_fingerprint();
7314    if current != armed {
7315      armed = current;
7316      continue;
7317    }
7318    return DeadlineParkOutcome::Expired(armed);
7319  }
7320}
7321
7322// Test-only injection seam for the deadline-expiry race: fired on the driver
7323// thread immediately after
7324// `park_with_deadline` reports `Expired`, while the parker is STILL
7325// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
7326// exact window in which a racing `wake_one` can pop the parker, store its
7327// permit and report the wake as delivered. Lets a test interleave that race
7328// deterministically instead of wall-clock lottery.
7329#[cfg(all(test, napi_runtime_os_threads))]
7330thread_local! {
7331  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7332    const { std::cell::RefCell::new(None) };
7333}
7334
7335#[cfg(all(test, napi_runtime_os_threads))]
7336fn run_deadline_expiry_test_hook() {
7337  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7338    hook();
7339  }
7340}
7341
7342// Second test-only injection seam for the enqueue/verdict race: fired AFTER
7343// the expiry decision's permit and queued-work re-checks both came up
7344// empty (parker already deregistered) and BEFORE the final fingerprint
7345// verdict -- the window in which an enqueue is invisible to every earlier
7346// re-check and only its ENQUEUE counter in the fingerprint can prevent a
7347// false BlockOnDeadlock.
7348#[cfg(all(test, napi_runtime_os_threads))]
7349thread_local! {
7350  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7351    const { std::cell::RefCell::new(None) };
7352}
7353
7354#[cfg(all(test, napi_runtime_os_threads))]
7355fn run_deadline_verdict_test_hook() {
7356  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7357    hook();
7358  }
7359}
7360
7361// Final test-only injection seam: fired after every permit/queue/fingerprint/
7362// timer check from the legacy verdict and immediately before the synchronized
7363// admission gate closes. A separate OS thread can complete a publication here
7364// to prove the final verdict recheck has a real cross-thread ordering edge.
7365#[cfg(all(test, napi_runtime_os_threads))]
7366thread_local! {
7367  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7368    const { std::cell::RefCell::new(None) };
7369}
7370
7371#[cfg(all(test, napi_runtime_os_threads))]
7372fn run_deadline_final_verdict_test_hook() {
7373  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7374    hook();
7375  }
7376}
7377
7378// Fired after the synchronized verdict gate has closed and before the verdict
7379// takes the stop-publication mutex for its final rechecks. A test can pause
7380// here while shutdown publishes generation stop and its abort wake reaches the
7381// closed admission gate.
7382#[cfg(all(test, napi_runtime_os_threads))]
7383thread_local! {
7384  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7385    const { std::cell::RefCell::new(None) };
7386}
7387
7388#[cfg(all(test, napi_runtime_os_threads))]
7389fn run_deadline_gated_verdict_test_hook() {
7390  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7391    hook();
7392  }
7393}
7394
7395// Fired after the final verdict has acquired the stop-publication mutex. A
7396// shutdown test can retain that guard while another thread reaches the
7397// lifecycle -> stop-publication lock edge.
7398#[cfg(all(test, napi_runtime_os_threads))]
7399thread_local! {
7400  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7401    const { std::cell::RefCell::new(None) };
7402}
7403
7404#[cfg(all(test, napi_runtime_os_threads))]
7405fn run_deadline_synchronized_stop_test_hook() {
7406  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7407    hook();
7408  }
7409}
7410
7411// Fired after the final verdict owns exact-owner publication and before it
7412// checks handoff/lane predicates. Tests can remove unrelated saving predicates
7413// while retaining the synchronized owner state under examination.
7414#[cfg(all(test, napi_runtime_os_threads))]
7415type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7416
7417#[cfg(all(test, napi_runtime_os_threads))]
7418thread_local! {
7419  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7420    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7421      const { std::cell::RefCell::new(None) };
7422}
7423
7424#[cfg(all(test, napi_runtime_os_threads))]
7425fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7426  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7427    hook(parker);
7428  }
7429}
7430
7431// Fired immediately before a test thread takes the stop-publication mutex.
7432// Installed only on the shutdown thread, this proves the controller still
7433// holds the lifecycle in Running while waiting for a verdict-owned guard.
7434#[cfg(all(test, napi_runtime_os_threads))]
7435thread_local! {
7436  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7437    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7438      const { std::cell::RefCell::new(None) };
7439}
7440
7441#[cfg(all(test, napi_runtime_os_threads))]
7442fn run_before_generation_stop_publication_lock_test_hook() {
7443  if let Some(hook) =
7444    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7445  {
7446    hook();
7447  }
7448}
7449
7450// Fired on an admission thread after it observes a closed verdict gate. The
7451// hook is thread-local so a shutdown test can prove that an abort wake reached
7452// this exact wait without affecting unrelated scheduler threads.
7453#[cfg(all(test, napi_runtime_os_threads))]
7454thread_local! {
7455  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7456    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7457      const { std::cell::RefCell::new(None) };
7458}
7459
7460#[cfg(all(test, napi_runtime_os_threads))]
7461fn run_deadlock_closed_gate_admission_test_hook() {
7462  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7463  {
7464    hook();
7465  }
7466}
7467
7468// Timer registration enters the shared verdict admission before this seam.
7469// Tests can pause there to prove the timer remains an announced publication
7470// until its heap entry becomes visible.
7471#[cfg(all(test, napi_runtime_os_threads))]
7472thread_local! {
7473  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7474    const { std::cell::RefCell::new(None) };
7475}
7476
7477#[cfg(all(test, napi_runtime_os_threads))]
7478fn run_timer_registration_admission_test_hook() {
7479  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7480    hook();
7481  }
7482}
7483
7484#[cfg(all(test, napi_runtime_os_threads))]
7485thread_local! {
7486  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7487    const { std::cell::RefCell::new(None) };
7488}
7489
7490#[cfg(all(test, napi_runtime_os_threads))]
7491fn run_owner_handoff_selected_test_hook() {
7492  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7493    hook();
7494  }
7495}
7496
7497/// Registry of parked (or committed-to-parking) cooperative drivers, used by
7498/// queue-wakes to wake exactly one of them.
7499#[cfg(napi_runtime_os_threads)]
7500#[derive(Default)]
7501struct ParkedDrivers {
7502  parked: Mutex<Vec<ParkedDriver>>,
7503  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
7504  // by `wake_one`'s no-waiter fast path.
7505  count: AtomicUsize,
7506}
7507
7508#[cfg(napi_runtime_os_threads)]
7509struct ParkedDriver {
7510  parker: Arc<DriverParker>,
7511  can_run_blocking: bool,
7512  dependency: Option<Arc<TaskDependency>>,
7513}
7514
7515#[cfg(napi_runtime_os_threads)]
7516impl ParkedDrivers {
7517  /// Register `parker` as parked-or-parking. MUST precede the caller's final
7518  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
7519  #[cfg(test)]
7520  fn register(&self, parker: &Arc<DriverParker>) {
7521    self.register_with_role(parker, true, None);
7522  }
7523
7524  fn register_with_role(
7525    &self,
7526    parker: &Arc<DriverParker>,
7527    can_run_blocking: bool,
7528    dependency: Option<Arc<TaskDependency>>,
7529  ) {
7530    let mut parked = self
7531      .parked
7532      .lock()
7533      .unwrap_or_else(std::sync::PoisonError::into_inner);
7534    parked.push(ParkedDriver {
7535      parker: Arc::clone(parker),
7536      can_run_blocking,
7537      dependency,
7538    });
7539    self.count.store(parked.len(), Ordering::SeqCst);
7540  }
7541
7542  /// Remove `parker` if still registered. `wake_one` removes the parkers it
7543  /// pops, while a direct future-wake does not, so drivers call this after
7544  /// every park (and after a re-check aborts one).
7545  fn deregister(&self, parker: &Arc<DriverParker>) {
7546    let removed = {
7547      let mut parked = self
7548        .parked
7549        .lock()
7550        .unwrap_or_else(std::sync::PoisonError::into_inner);
7551      let removed = parked
7552        .iter()
7553        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7554        .map(|index| parked.swap_remove(index));
7555      self.count.store(parked.len(), Ordering::SeqCst);
7556      removed
7557    };
7558    drop(removed);
7559  }
7560
7561  /// Wake one parked driver -- most recently parked first, its stack and
7562  /// caches are the warmest -- returning whether anyone was woken.
7563  ///
7564  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
7565  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
7566  /// registers itself (the `count` store above, under the registry mutex),
7567  /// THEN re-checks the work queues under Q, and only then parks. Order the
7568  /// two Q critical sections:
7569  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
7570  ///     it, the re-check observes the work and the driver does not park --
7571  ///     skipping the wake (fast path below) is safe.
7572  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
7573  ///     push locked it. The driver's `count` store is sequenced before its
7574  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
7575  ///     lock of Q, and the push is sequenced before the fast-path load
7576  ///     below; the load therefore happens-after the `count` store and must
7577  ///     observe `count >= 1`: the slow path pops a registered parker and
7578  ///     wakes it.
7579  ///
7580  /// A wake delivered to a driver that raced out of its park (or aborted it)
7581  /// is stored as that driver's permit; the driver re-checks every work
7582  /// source before it can sleep on the next park, so the wake still lands.
7583  fn wake_one(&self) -> bool {
7584    if self.count.load(Ordering::SeqCst) == 0 {
7585      // No-waiter fast path: nobody is parked or committed to parking (any
7586      // in-flight parker that missed this check re-checks the queues after
7587      // registering, per the argument above).
7588      return false;
7589    }
7590    let driver = {
7591      let mut parked = self
7592        .parked
7593        .lock()
7594        .unwrap_or_else(std::sync::PoisonError::into_inner);
7595      let driver = parked.pop();
7596      self.count.store(parked.len(), Ordering::SeqCst);
7597      driver
7598    };
7599    match driver {
7600      Some(driver) => {
7601        driver.parker.unpark();
7602        true
7603      }
7604      None => false,
7605    }
7606  }
7607
7608  /// Wake the most recently parked cooperative driver that is eligible to
7609  /// consume blocking work.
7610  fn wake_one_blocking(&self) -> bool {
7611    if self.count.load(Ordering::SeqCst) == 0 {
7612      return false;
7613    }
7614    let driver = {
7615      let mut parked = self
7616        .parked
7617        .lock()
7618        .unwrap_or_else(std::sync::PoisonError::into_inner);
7619      let driver = parked
7620        .iter()
7621        .rposition(|driver| driver.can_run_blocking)
7622        .map(|index| parked.swap_remove(index));
7623      self.count.store(parked.len(), Ordering::SeqCst);
7624      driver
7625    };
7626    match driver {
7627      Some(driver) => {
7628        driver.parker.unpark();
7629        true
7630      }
7631      None => false,
7632    }
7633  }
7634
7635  /// Wake one parked driver whose live dependency belongs to `owner`.
7636  ///
7637  /// The dependency is published with the parker, so an unrelated newer
7638  /// driver cannot consume the bounded owner-lane handoff.
7639  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7640    loop {
7641      if self.count.load(Ordering::SeqCst) == 0 {
7642        return false;
7643      }
7644      let candidates = {
7645        let parked = self
7646          .parked
7647          .lock()
7648          .unwrap_or_else(std::sync::PoisonError::into_inner);
7649        parked
7650          .iter()
7651          .rev()
7652          .filter_map(|driver| {
7653            if !driver.can_run_blocking {
7654              return None;
7655            }
7656            driver
7657              .dependency
7658              .as_ref()
7659              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7660          })
7661          .collect::<Vec<_>>()
7662      };
7663      let selected = candidates.into_iter().find(|(_, dependency)| {
7664        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7665      });
7666      let Some((selected_parker, selected_dependency)) = selected else {
7667        return false;
7668      };
7669      let driver = {
7670        let mut parked = self
7671          .parked
7672          .lock()
7673          .unwrap_or_else(std::sync::PoisonError::into_inner);
7674        let driver = parked
7675          .iter()
7676          .rposition(|driver| {
7677            Arc::ptr_eq(&driver.parker, &selected_parker)
7678              && driver
7679                .dependency
7680                .as_ref()
7681                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7682          })
7683          .map(|index| parked.swap_remove(index));
7684        // Publish the exact-owner token before releasing the registry lock. A
7685        // direct future wake can race this selection, but the driver must
7686        // deregister under this same lock before it can poll Ready and forward
7687        // an unconsumed handoff on exit.
7688        if let Some(driver) = &driver {
7689          driver.parker.grant_owner_handoff(owner);
7690        }
7691        self.count.store(parked.len(), Ordering::SeqCst);
7692        driver
7693      };
7694      if let Some(driver) = driver {
7695        #[cfg(test)]
7696        run_owner_handoff_selected_test_hook();
7697        driver.parker.unpark();
7698        return true;
7699      }
7700    }
7701  }
7702
7703  /// Wake every timer-bounded driver so cancellation of the earliest timer
7704  /// cannot leave pool workers parked until a stale deadline.
7705  fn wake_all(&self) {
7706    if self.count.load(Ordering::SeqCst) == 0 {
7707      return;
7708    }
7709    let drivers = {
7710      let mut parked = self
7711        .parked
7712        .lock()
7713        .unwrap_or_else(std::sync::PoisonError::into_inner);
7714      let drivers = parked.drain(..).collect::<Vec<_>>();
7715      self.count.store(0, Ordering::SeqCst);
7716      drivers
7717    };
7718    for driver in drivers {
7719      driver.parker.unpark();
7720    }
7721  }
7722}
7723
7724// ---------------------------------------------------------------------------
7725// Runtime timer facility: `sleep_until` without a tokio
7726// reactor. Two drivers, selected by runtime flavor:
7727//
7728//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
7729//     timekeeper thread. Timer waits never consume the configured CPU workers.
7730//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
7731//     deadline, so the parked driver can fire due timers without depending on
7732//     any particular inter-thread wake ordering.
7733//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
7734//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
7735//     driver at import; wasi-single uses the same path). A CT runtime without
7736//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
7737//
7738// DEADLOCK-DETECTION INTERACTIONS:
7739//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
7740//     clock is a guaranteed wake source. Timer waits therefore use their own
7741//     primitive (a plain `park_timeout` bounded by the earliest deadline),
7742//     NEVER `park_with_deadline`, so an armed park deadline
7743//     shorter than the next timer cannot fire on a legitimate timer wait; the
7744//     fire itself schedules the woken task, which IS fingerprint progress for
7745//     every other parked driver. The expiry VERDICT additionally re-checks
7746//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
7747//     counter by design, so a registration racing an already-committed
7748//     deadline park must veto the panic there.
7749//   * CT certain check (threadless): a pending HOST timer is NOT a wake
7750//     token -- the JS setTimeout relay can only run on the very thread that
7751//     is parking, so it can never fire while `block_on` holds it. The check
7752//     panics unconditionally on a threadless build; a pending timer merely
7753//     selects the timer-backed variant of the typed diagnostic. The CT
7754//     deadline verdict (threaded flavor, where the host loop genuinely runs
7755//     concurrently) counts only LIVE waits (deadline still in the future): a
7756//     pending timer whose deadline passed without firing is the
7757//     frozen-JS-event-loop signature that the deadline-based detection
7758//     exists to catch, so it must NOT suppress the panic.
7759
7760/// Host-turn dispatcher for the CurrentThread runnable queue.
7761///
7762/// The NAPI binding registers one weak native threadsafe-function driver per
7763/// importing environment. Its custom Node-API callback owns the fresh host turn
7764/// and keeps delivery capabilities out of JavaScript. Scheduling through a
7765/// fresh host turn is required for soundness: arbitrary futures may invoke
7766/// their waker while holding internal locks, so polling the runnable inline
7767/// from that wake can self-deadlock.
7768pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7769  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
7770  /// [`CurrentThreadTaskDelivery::capability`].
7771  ///
7772  /// The delivery capability is scoped to this exact host registration and
7773  /// attempt. The registry maps it back to the executor's one internal
7774  /// dispatch capability only while this attempt remains current.
7775  /// `false` means this driver can no longer dispatch and must be swept. After
7776  /// returning `true`, the driver must eventually call either
7777  /// [`acknowledge_current_thread_task_delivery`] or
7778  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
7779  /// acknowledgement is accepted only after
7780  /// [`drive_current_thread_tasks`] claimed this exact delivery.
7781  ///
7782  /// `dispatch` must return promptly and must not wait on another thread. In
7783  /// particular it must never block on the thread that will acknowledge or
7784  /// fail this delivery (for instance by spawning a helper that calls
7785  /// [`fail_current_thread_task_delivery`] and joining it). The executor
7786  /// calls `dispatch` from `schedule`, which may be a waker's stack: a waker
7787  /// can be invoked while its producer holds a mutex (`futures::Shared` wakes
7788  /// its awaiters under its `wakers` lock). When a delivery failure turns
7789  /// terminal, the rejected work is cancelled on the stack of the thread that
7790  /// failed it, and a rejected future's destructor may re-lock the very mutex
7791  /// the blocked waker still holds. A `dispatch` that waits on that thread
7792  /// therefore deadlocks with it.
7793  ///
7794  /// Failing the delivery synchronously from inside `dispatch`, on the
7795  /// calling thread, is handled: the executor recognises that stack as a
7796  /// possible waker's, moves the rejected work aside and cancels it on the
7797  /// next clean stack (see `CurrentThreadQueue::rejected`). Failing or
7798  /// acknowledging it later, from any thread, is handled too. The Node-API
7799  /// driver satisfies this contract: it makes one nonblocking
7800  /// threadsafe-function call and returns `false` at once when the
7801  /// environment is closing.
7802  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7803
7804  fn is_live(&self) -> bool {
7805    true
7806  }
7807
7808  /// Called after this driver has been removed from the registry, with no
7809  /// registry lock held. Must be idempotent with explicit host cleanup.
7810  fn on_swept(&self) {}
7811}
7812
7813#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7814pub struct CurrentThreadTaskDriverId(u64);
7815
7816type CurrentThreadTaskDriverSnapshot =
7817  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7818
7819#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7820pub struct CurrentThreadTaskDelivery {
7821  driver: CurrentThreadTaskDriverId,
7822  capability: u64,
7823  dispatch: u64,
7824}
7825
7826impl CurrentThreadTaskDelivery {
7827  /// Opaque driver capability for this exact registration attempt.
7828  pub fn capability(self) -> u64 {
7829    self.capability
7830  }
7831}
7832
7833struct CurrentThreadTaskDeliveryState {
7834  delivery: CurrentThreadTaskDelivery,
7835  claimed: bool,
7836  accepted: bool,
7837}
7838
7839struct CurrentThreadTaskDriverEntry {
7840  driver: Arc<dyn CurrentThreadTaskDriver>,
7841  delivery: Option<CurrentThreadTaskDeliveryState>,
7842  pending: Option<u64>,
7843  previous: Option<CurrentThreadTaskDriverId>,
7844  next: Option<CurrentThreadTaskDriverId>,
7845}
7846
7847#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7848enum CurrentThreadDispatchPhase {
7849  #[default]
7850  Pending,
7851  Serviced,
7852  Cancelling,
7853}
7854
7855#[derive(Default)]
7856struct CurrentThreadDispatchState {
7857  phase: CurrentThreadDispatchPhase,
7858  epoch: u64,
7859  publications: usize,
7860  references: usize,
7861  accepted: bool,
7862  accepted_failure: bool,
7863}
7864
7865impl CurrentThreadDispatchState {
7866  fn is_serviced(&self) -> bool {
7867    self.phase == CurrentThreadDispatchPhase::Serviced
7868  }
7869
7870  fn is_cancelling(&self) -> bool {
7871    self.phase == CurrentThreadDispatchPhase::Cancelling
7872  }
7873
7874  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7875    CurrentThreadTaskDispatchFailure {
7876      dispatch,
7877      epoch: self.epoch,
7878    }
7879  }
7880}
7881
7882#[derive(Default)]
7883struct CurrentThreadTaskDriverRegistryState {
7884  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7885  oldest: Option<CurrentThreadTaskDriverId>,
7886  newest: Option<CurrentThreadTaskDriverId>,
7887  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7888  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7889}
7890
7891impl CurrentThreadTaskDriverRegistryState {
7892  fn insert_driver(
7893    &mut self,
7894    id: CurrentThreadTaskDriverId,
7895    driver: Arc<dyn CurrentThreadTaskDriver>,
7896  ) {
7897    let previous = self.newest;
7898    let replaced = self.entries.insert(
7899      id,
7900      CurrentThreadTaskDriverEntry {
7901        driver,
7902        delivery: None,
7903        pending: None,
7904        previous,
7905        next: None,
7906      },
7907    );
7908    assert!(
7909      replaced.is_none(),
7910      "CurrentThread task driver id must be unique"
7911    );
7912    if let Some(previous) = previous {
7913      self
7914        .entries
7915        .get_mut(&previous)
7916        .expect("the newest CurrentThread task driver must remain registered")
7917        .next = Some(id);
7918    } else {
7919      self.oldest = Some(id);
7920    }
7921    self.newest = Some(id);
7922  }
7923
7924  fn remove_driver(
7925    &mut self,
7926    id: CurrentThreadTaskDriverId,
7927  ) -> Option<CurrentThreadTaskDriverEntry> {
7928    let entry = self.entries.remove(&id)?;
7929    if let Some(previous) = entry.previous {
7930      self
7931        .entries
7932        .get_mut(&previous)
7933        .expect("the previous CurrentThread task driver must remain registered")
7934        .next = entry.next;
7935    } else {
7936      self.oldest = entry.next;
7937    }
7938    if let Some(next) = entry.next {
7939      self
7940        .entries
7941        .get_mut(&next)
7942        .expect("the next CurrentThread task driver must remain registered")
7943        .previous = entry.previous;
7944    } else {
7945      self.newest = entry.previous;
7946    }
7947    if let Some(delivery) = &entry.delivery {
7948      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7949      debug_assert_eq!(
7950        indexed,
7951        Some(id),
7952        "delivery capability index must match its driver"
7953      );
7954    }
7955    Some(entry)
7956  }
7957
7958  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7959    let mut snapshot = Vec::with_capacity(self.entries.len());
7960    let mut current = self.oldest;
7961    while let Some(id) = current {
7962      let entry = self
7963        .entries
7964        .get(&id)
7965        .expect("CurrentThread task driver order must reference a live entry");
7966      snapshot.push((id, Arc::clone(&entry.driver)));
7967      current = entry.next;
7968    }
7969    debug_assert_eq!(
7970      snapshot.len(),
7971      self.entries.len(),
7972      "CurrentThread task driver order must contain every entry exactly once"
7973    );
7974    snapshot
7975  }
7976
7977  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7978    if self.entries.len() != expected.len() {
7979      return false;
7980    }
7981    let mut current = self.oldest;
7982    for expected in expected {
7983      if current != Some(*expected) {
7984        return false;
7985      }
7986      current = self
7987        .entries
7988        .get(expected)
7989        .expect("CurrentThread task driver order must reference a live entry")
7990        .next;
7991    }
7992    current.is_none()
7993  }
7994}
7995
7996struct CurrentThreadTaskDriverSelection {
7997  live: CurrentThreadTaskDriverSnapshot,
7998  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7999}
8000
8001#[derive(Default)]
8002struct CurrentThreadTaskDeliveryCompletion {
8003  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8004  redispatch: bool,
8005}
8006
8007struct CurrentThreadTaskDispatchCompletion {
8008  result: CurrentThreadHostDispatchResult,
8009  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
8010}
8011
8012struct CurrentThreadTaskDispatchPublicationGuard<'a> {
8013  registry: &'a CurrentThreadTaskDriverRegistry,
8014  dispatch: Option<u64>,
8015}
8016
8017impl CurrentThreadTaskDispatchPublicationGuard<'_> {
8018  fn finish(mut self) -> CurrentThreadHostDispatchResult {
8019    let dispatch = self
8020      .dispatch
8021      .expect("the CurrentThread task dispatch publication must remain armed");
8022    let result = self.registry.finish_publication(dispatch);
8023    self.dispatch = None;
8024    result
8025  }
8026}
8027
8028impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
8029  fn drop(&mut self) {
8030    if let Some(dispatch) = self.dispatch.take() {
8031      self.registry.abort_publication(dispatch);
8032    }
8033  }
8034}
8035
8036#[derive(Default)]
8037struct CurrentThreadTaskDriverRegistry {
8038  state: Mutex<CurrentThreadTaskDriverRegistryState>,
8039  next_id: AtomicU64,
8040  next_delivery: AtomicU64,
8041}
8042
8043impl CurrentThreadTaskDriverRegistry {
8044  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8045    let id = next_unique_id(
8046      &self.next_id,
8047      "CurrentThread task driver id space exhausted",
8048    );
8049    let id = CurrentThreadTaskDriverId(id);
8050    self
8051      .state
8052      .lock()
8053      .unwrap_or_else(std::sync::PoisonError::into_inner)
8054      .insert_driver(id, driver);
8055    id
8056  }
8057
8058  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8059    let (removed, failed_dispatches) = {
8060      let mut state = self
8061        .state
8062        .lock()
8063        .unwrap_or_else(std::sync::PoisonError::into_inner);
8064      let removed = state.remove_driver(id);
8065      let mut failed_dispatches = Vec::new();
8066      if let Some(entry) = &removed {
8067        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8068      }
8069      (removed, failed_dispatches)
8070    };
8071    let _ = catch_unwind_contained(|| drop(removed));
8072    CurrentThreadTaskDeliveryCompletion {
8073      failed_dispatches,
8074      redispatch: false,
8075    }
8076  }
8077
8078  fn live(&self) -> CurrentThreadTaskDriverSelection {
8079    let mut failed_dispatches = Vec::new();
8080    loop {
8081      let snapshot = CurrentThreadTaskDriverSnapshot::new({
8082        let state = self
8083          .state
8084          .lock()
8085          .unwrap_or_else(std::sync::PoisonError::into_inner);
8086        state.ordered_snapshot()
8087      });
8088      let mut dead_ids = FxHashSet::default();
8089      let mut live_ids = Vec::new();
8090      for (id, driver) in snapshot.as_slice() {
8091        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8092          live_ids.push(*id);
8093        } else {
8094          dead_ids.insert(*id);
8095        }
8096      }
8097
8098      let (live, swept, retry) = {
8099        let mut state = self
8100          .state
8101          .lock()
8102          .unwrap_or_else(std::sync::PoisonError::into_inner);
8103        let mut swept = Vec::new();
8104        let mut current = state.oldest;
8105        while let Some(id) = current {
8106          let next = state
8107            .entries
8108            .get(&id)
8109            .expect("CurrentThread task driver order must reference a live entry")
8110            .next;
8111          if dead_ids.contains(&id)
8112            && let Some(entry) = state.remove_driver(id)
8113          {
8114            swept.push(entry);
8115          }
8116          current = next;
8117        }
8118        for entry in &swept {
8119          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8120        }
8121        let retry = !state.ordered_ids_match(&live_ids);
8122        let live = if retry {
8123          Vec::new()
8124        } else {
8125          state.ordered_snapshot()
8126        };
8127        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8128      };
8129
8130      // A reentrant probe can unregister multiple snapshot-only drivers.
8131      // See the implementation notes in this crate's README.
8132      drop(snapshot);
8133      for entry in swept {
8134        let _ = catch_unwind_contained(|| entry.driver.on_swept());
8135        let _ = catch_unwind_contained(|| drop(entry));
8136      }
8137      if !retry {
8138        return CurrentThreadTaskDriverSelection {
8139          live,
8140          failed_dispatches,
8141        };
8142      }
8143    }
8144  }
8145
8146  #[cfg(test)]
8147  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8148    let mut live = self.live().live;
8149    live.pop().map(|(id, driver)| (id, driver.into_inner()))
8150  }
8151
8152  fn next_delivery(
8153    &self,
8154    driver: CurrentThreadTaskDriverId,
8155    dispatch: u64,
8156  ) -> CurrentThreadTaskDelivery {
8157    let capability = next_unique_id(
8158      &self.next_delivery,
8159      "CurrentThread task delivery id space exhausted",
8160    )
8161    .checked_add(1)
8162    .expect("CurrentThread task delivery id space exhausted");
8163    CurrentThreadTaskDelivery {
8164      driver,
8165      capability,
8166      dispatch,
8167    }
8168  }
8169
8170  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8171    let dispatch_state = state
8172      .dispatches
8173      .get_mut(&dispatch)
8174      .expect("a referenced CurrentThread dispatch must remain registered");
8175    dispatch_state.references = dispatch_state
8176      .references
8177      .checked_add(1)
8178      .expect("CurrentThread dispatch reference count exhausted");
8179  }
8180
8181  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8182    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8183      return;
8184    };
8185    dispatch_state.references = dispatch_state
8186      .references
8187      .checked_sub(1)
8188      .expect("CurrentThread dispatch reference count underflow");
8189  }
8190
8191  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8192    let publication = {
8193      let mut state = self
8194        .state
8195        .lock()
8196        .unwrap_or_else(std::sync::PoisonError::into_inner);
8197      let dispatch_state = state.dispatches.entry(dispatch).or_default();
8198      if dispatch_state.is_serviced() {
8199        return CurrentThreadTaskDispatchCompletion {
8200          result: CurrentThreadHostDispatchResult::Accepted,
8201          failed_dispatches: Vec::new(),
8202        };
8203      }
8204      if dispatch_state.is_cancelling() {
8205        return CurrentThreadTaskDispatchCompletion {
8206          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8207          failed_dispatches: Vec::new(),
8208        };
8209      }
8210      dispatch_state.publications = dispatch_state
8211        .publications
8212        .checked_add(1)
8213        .expect("CurrentThread dispatch publication count exhausted");
8214      CurrentThreadTaskDispatchPublicationGuard {
8215        registry: self,
8216        dispatch: Some(dispatch),
8217      }
8218    };
8219
8220    let CurrentThreadTaskDriverSelection {
8221      mut live,
8222      mut failed_dispatches,
8223    } = self.live();
8224    let planned_deliveries = {
8225      let mut state = self
8226        .state
8227        .lock()
8228        .unwrap_or_else(std::sync::PoisonError::into_inner);
8229      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8230        return CurrentThreadTaskDispatchCompletion {
8231          result: CurrentThreadHostDispatchResult::Unavailable,
8232          failed_dispatches,
8233        };
8234      };
8235      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8236        Vec::new()
8237      } else {
8238        // Reserve every capability and all output storage before mutating an
8239        // entry. If ID allocation unwinds, the publication guard can retire
8240        // the barrier without leaving an undispatched physical slot occupied.
8241        let mut reserved_deliveries = Vec::with_capacity(live.len());
8242        for (id, _) in live.as_slice().iter().rev() {
8243          let Some(entry) = state.entries.get(id) else {
8244            continue;
8245          };
8246          if entry.delivery.is_none() {
8247            reserved_deliveries.push(self.next_delivery(*id, dispatch));
8248          }
8249        }
8250        state.delivery_drivers.reserve(reserved_deliveries.len());
8251        let mut reserved_deliveries = reserved_deliveries.into_iter();
8252        let mut planned_deliveries = Vec::with_capacity(live.len());
8253        let mut retired = Vec::with_capacity(live.len());
8254        let mut accepted_reservation = false;
8255        for (id, _) in live.as_slice().iter().rev() {
8256          let Some(entry) = state.entries.get(id) else {
8257            continue;
8258          };
8259          if let Some(delivery) = &entry.delivery
8260            && delivery.delivery.dispatch == dispatch
8261          {
8262            accepted_reservation |= delivery.accepted;
8263            continue;
8264          }
8265          if entry.delivery.is_some() {
8266            let previous = state
8267              .entries
8268              .get_mut(id)
8269              .expect("the CurrentThread task driver must remain registered")
8270              .pending
8271              .replace(dispatch);
8272            if previous != Some(dispatch) {
8273              if let Some(previous) = previous {
8274                Self::remove_dispatch_reference(&mut state, previous);
8275              }
8276              Self::add_dispatch_reference(&mut state, dispatch);
8277            }
8278            retired.push(previous);
8279            accepted_reservation = true;
8280            continue;
8281          }
8282          let delivery = reserved_deliveries
8283            .next()
8284            .expect("every available CurrentThread host must have a reserved delivery");
8285          let pending = state
8286            .entries
8287            .get_mut(id)
8288            .expect("the CurrentThread task driver must remain registered")
8289            .pending
8290            .take();
8291          let accepted = pending == Some(dispatch);
8292          if !accepted && pending.is_some() {
8293            retired.push(pending);
8294          }
8295          match pending {
8296            Some(pending) if pending == dispatch => {}
8297            Some(pending) => {
8298              Self::remove_dispatch_reference(&mut state, pending);
8299              Self::add_dispatch_reference(&mut state, dispatch);
8300            }
8301            None => Self::add_dispatch_reference(&mut state, dispatch),
8302          }
8303          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8304          assert!(
8305            indexed.is_none(),
8306            "CurrentThread delivery capability must be unique"
8307          );
8308          state
8309            .entries
8310            .get_mut(id)
8311            .expect("the CurrentThread task driver must remain registered")
8312            .delivery = Some(CurrentThreadTaskDeliveryState {
8313            delivery,
8314            claimed: false,
8315            accepted,
8316          });
8317          accepted_reservation |= accepted;
8318          planned_deliveries.push((*id, delivery));
8319        }
8320        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8321        debug_assert!(
8322          all_reserved_deliveries_installed,
8323          "every reserved CurrentThread delivery must be installed"
8324        );
8325        if accepted_reservation {
8326          state
8327            .dispatches
8328            .get_mut(&dispatch)
8329            .expect("the publishing dispatch state must remain registered")
8330            .accepted = true;
8331        }
8332        for dispatch in retired.into_iter().flatten() {
8333          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8334        }
8335        planned_deliveries
8336      }
8337    };
8338
8339    // Pair newest-first delivery plans with contained driver owners after
8340    // releasing the registry lock. Normal, rejection, and unwind cleanup can
8341    // then destroy every selected driver independently without re-entering the
8342    // registry under its mutex.
8343    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8344    let mut planned_deliveries = planned_deliveries.into_iter();
8345    let mut next_delivery = planned_deliveries.next();
8346    while let Some((id, driver)) = live.pop() {
8347      let Some((planned_id, delivery)) = next_delivery else {
8348        break;
8349      };
8350      if planned_id == id {
8351        deliveries.push((id, driver, delivery));
8352        next_delivery = planned_deliveries.next();
8353      } else {
8354        next_delivery = Some((planned_id, delivery));
8355      }
8356    }
8357    debug_assert!(
8358      next_delivery.is_none() && planned_deliveries.len() == 0,
8359      "every planned CurrentThread delivery must retain its selected driver"
8360    );
8361
8362    for (id, driver, delivery) in deliveries {
8363      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8364        self.accept_delivery(id, delivery);
8365      } else {
8366        let (entry, failures) = self.reject_delivery(id, delivery);
8367        failed_dispatches.extend(failures);
8368        if let Some(entry) = entry {
8369          let _ = catch_unwind_contained(|| entry.driver.on_swept());
8370          let _ = catch_unwind_contained(|| drop(entry));
8371        }
8372      }
8373    }
8374
8375    let result = publication.finish();
8376    debug_assert!(
8377      !failed_dispatches
8378        .iter()
8379        .any(|failure| failure.dispatch == dispatch),
8380      "the active publication must report its own terminal outcome directly"
8381    );
8382    CurrentThreadTaskDispatchCompletion {
8383      result,
8384      failed_dispatches,
8385    }
8386  }
8387
8388  #[cfg(test)]
8389  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8390    self.dispatch_with_completion(dispatch).result
8391  }
8392
8393  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8394    let mut state = self
8395      .state
8396      .lock()
8397      .unwrap_or_else(std::sync::PoisonError::into_inner);
8398    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8399      entry
8400        .delivery
8401        .as_ref()
8402        .is_some_and(|current| current.delivery == delivery)
8403    }) {
8404      state
8405        .entries
8406        .get_mut(&id)
8407        .expect("the matching CurrentThread task driver must remain registered")
8408        .delivery
8409        .as_mut()
8410        .expect("the matching delivery must remain installed")
8411        .accepted = true;
8412      true
8413    } else {
8414      // A callback may complete or unregister synchronously before
8415      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
8416      // proves acceptance; if no host serviced it, losing the reference is an
8417      // accepted delivery failure retained behind this publication barrier.
8418      false
8419    };
8420    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8421      dispatch_state.accepted = true;
8422      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8423    }
8424  }
8425
8426  fn reject_delivery(
8427    &self,
8428    id: CurrentThreadTaskDriverId,
8429    delivery: CurrentThreadTaskDelivery,
8430  ) -> (
8431    Option<CurrentThreadTaskDriverEntry>,
8432    Vec<CurrentThreadTaskDispatchFailure>,
8433  ) {
8434    let mut state = self
8435      .state
8436      .lock()
8437      .unwrap_or_else(std::sync::PoisonError::into_inner);
8438    if !state.entries.get(&id).is_some_and(|entry| {
8439      entry
8440        .delivery
8441        .as_ref()
8442        .is_some_and(|current| current.delivery == delivery)
8443    }) {
8444      return (None, Vec::new());
8445    }
8446    let entry = state
8447      .remove_driver(id)
8448      .expect("the matching CurrentThread task driver must remain registered");
8449    let mut failures = Vec::new();
8450    Self::finish_entry_references(&mut state, &entry, &mut failures);
8451    (Some(entry), failures)
8452  }
8453
8454  fn finish_delivery(
8455    &self,
8456    delivery: CurrentThreadTaskDelivery,
8457    failed: bool,
8458  ) -> CurrentThreadTaskDeliveryCompletion {
8459    let mut state = self
8460      .state
8461      .lock()
8462      .unwrap_or_else(std::sync::PoisonError::into_inner);
8463    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8464      entry
8465        .delivery
8466        .as_ref()
8467        .is_some_and(|current| current.delivery == delivery)
8468    }) {
8469      return CurrentThreadTaskDeliveryCompletion::default();
8470    }
8471    let current = state
8472      .entries
8473      .get_mut(&delivery.driver)
8474      .expect("the matching CurrentThread task driver must remain registered")
8475      .delivery
8476      .take()
8477      .expect("the matching delivery must remain installed");
8478    let indexed = state.delivery_drivers.remove(&delivery.capability);
8479    debug_assert_eq!(
8480      indexed,
8481      Some(delivery.driver),
8482      "delivery capability index must match its driver"
8483    );
8484    let redispatch = state
8485      .entries
8486      .get(&delivery.driver)
8487      .expect("the matching CurrentThread task driver must remain registered")
8488      .pending
8489      .is_some_and(|dispatch| {
8490        state
8491          .dispatches
8492          .get(&dispatch)
8493          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8494      });
8495    let mut failed_dispatches = Vec::new();
8496    // Callback completion proves only that JavaScript ran. A success return is
8497    // authoritative only after `drive_current_thread_tasks` claimed this exact
8498    // host attempt; otherwise a no-op or malicious callback could retire the
8499    // registry reference while leaving the executor dispatch pending forever.
8500    Self::finish_dispatch_reference(
8501      &mut state,
8502      delivery.dispatch,
8503      true,
8504      failed || !current.claimed,
8505      &mut failed_dispatches,
8506    );
8507    CurrentThreadTaskDeliveryCompletion {
8508      failed_dispatches,
8509      redispatch,
8510    }
8511  }
8512
8513  fn claim_delivery(&self, capability: u64) -> Option<u64> {
8514    let mut state = self
8515      .state
8516      .lock()
8517      .unwrap_or_else(std::sync::PoisonError::into_inner);
8518    let driver = *state.delivery_drivers.get(&capability)?;
8519    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8520    if delivery.capability != capability {
8521      return None;
8522    }
8523    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8524    if dispatch_state.is_serviced()
8525      || dispatch_state.is_cancelling()
8526      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8527    {
8528      return None;
8529    }
8530    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8531    current.claimed = true;
8532    current.accepted = true;
8533    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8534    Some(delivery.dispatch)
8535  }
8536
8537  fn mark_serviced(&self, dispatch: u64) {
8538    let mut state = self
8539      .state
8540      .lock()
8541      .unwrap_or_else(std::sync::PoisonError::into_inner);
8542    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8543      return;
8544    };
8545    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8546    let mut removed_references = 0usize;
8547    for entry in state.entries.values_mut() {
8548      if entry.pending == Some(dispatch) {
8549        entry.pending = None;
8550        removed_references = removed_references
8551          .checked_add(1)
8552          .expect("CurrentThread dispatch reference count exhausted");
8553      }
8554    }
8555    let dispatch_state = state
8556      .dispatches
8557      .get_mut(&dispatch)
8558      .expect("the serviced CurrentThread dispatch must remain registered");
8559    dispatch_state.references = dispatch_state
8560      .references
8561      .checked_sub(removed_references)
8562      .expect("CurrentThread dispatch reference count underflow");
8563    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8564  }
8565
8566  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8567    let delivery = {
8568      let state = self
8569        .state
8570        .lock()
8571        .unwrap_or_else(std::sync::PoisonError::into_inner);
8572      state
8573        .delivery_drivers
8574        .get(&capability)
8575        .and_then(|driver| state.entries.get(driver))
8576        .and_then(|entry| entry.delivery.as_ref())
8577        .map(|delivery| delivery.delivery)
8578        .filter(|delivery| delivery.capability == capability)
8579    };
8580    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8581      self.finish_delivery(delivery, true)
8582    })
8583  }
8584
8585  fn finish_dispatch_reference(
8586    state: &mut CurrentThreadTaskDriverRegistryState,
8587    dispatch: u64,
8588    accepted: bool,
8589    failed: bool,
8590    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8591  ) {
8592    let (publications, references) = {
8593      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8594        return;
8595      };
8596      dispatch_state.references = dispatch_state
8597        .references
8598        .checked_sub(1)
8599        .expect("CurrentThread dispatch reference count underflow");
8600      dispatch_state.accepted |= accepted;
8601      dispatch_state.accepted_failure |= accepted && failed;
8602      (dispatch_state.publications, dispatch_state.references)
8603    };
8604    if references != 0 || publications != 0 {
8605      return;
8606    }
8607    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8608      return;
8609    };
8610    if dispatch_state.accepted_failure
8611      && !dispatch_state.is_serviced()
8612      && !dispatch_state.is_cancelling()
8613    {
8614      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8615      failures.push(dispatch_state.failure(dispatch));
8616    } else if !dispatch_state.is_cancelling() {
8617      state.dispatches.remove(&dispatch);
8618    }
8619  }
8620
8621  fn finish_entry_references(
8622    state: &mut CurrentThreadTaskDriverRegistryState,
8623    entry: &CurrentThreadTaskDriverEntry,
8624    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8625  ) {
8626    if let Some(delivery) = &entry.delivery {
8627      Self::finish_dispatch_reference(
8628        state,
8629        delivery.delivery.dispatch,
8630        delivery.accepted,
8631        delivery.accepted,
8632        failures,
8633      );
8634    }
8635    if let Some(pending) = entry.pending {
8636      Self::finish_dispatch_reference(state, pending, true, true, failures);
8637    }
8638  }
8639
8640  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8641    let mut state = self
8642      .state
8643      .lock()
8644      .unwrap_or_else(std::sync::PoisonError::into_inner);
8645    let publications = {
8646      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8647        return CurrentThreadHostDispatchResult::Unavailable;
8648      };
8649      dispatch_state.publications = dispatch_state
8650        .publications
8651        .checked_sub(1)
8652        .expect("CurrentThread dispatch publication count underflow");
8653      dispatch_state.publications
8654    };
8655    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8656      return CurrentThreadHostDispatchResult::Accepted;
8657    }
8658    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8659      return CurrentThreadHostDispatchResult::Unavailable;
8660    };
8661    if dispatch_state.is_cancelling() {
8662      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8663    }
8664    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8665      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8666      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8667    }
8668    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8669    state.dispatches.remove(&dispatch);
8670    if accepted {
8671      CurrentThreadHostDispatchResult::Accepted
8672    } else {
8673      CurrentThreadHostDispatchResult::Unavailable
8674    }
8675  }
8676
8677  fn abort_publication(&self, dispatch: u64) {
8678    let mut state = self
8679      .state
8680      .lock()
8681      .unwrap_or_else(std::sync::PoisonError::into_inner);
8682    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8683      return;
8684    };
8685    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8686      return;
8687    };
8688    dispatch_state.publications = publications;
8689    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8690  }
8691
8692  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8693    let mut state = self
8694      .state
8695      .lock()
8696      .unwrap_or_else(std::sync::PoisonError::into_inner);
8697    let can_rearm = state
8698      .dispatches
8699      .get(&failure.dispatch)
8700      .is_some_and(|dispatch_state| {
8701        dispatch_state.is_cancelling()
8702          && dispatch_state.epoch == failure.epoch
8703          && dispatch_state.publications == 0
8704      })
8705      && !Self::has_dispatch_reference(&state, failure.dispatch);
8706    if !can_rearm {
8707      return false;
8708    }
8709    let dispatch_state = state
8710      .dispatches
8711      .get_mut(&failure.dispatch)
8712      .expect("the exact failed CurrentThread dispatch must remain registered");
8713    let epoch = dispatch_state
8714      .epoch
8715      .checked_add(1)
8716      .expect("CurrentThread dispatch failure epoch space exhausted");
8717    *dispatch_state = CurrentThreadDispatchState {
8718      epoch,
8719      ..CurrentThreadDispatchState::default()
8720    };
8721    true
8722  }
8723
8724  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8725    self
8726      .state
8727      .lock()
8728      .unwrap_or_else(std::sync::PoisonError::into_inner)
8729      .dispatches
8730      .get(&failure.dispatch)
8731      .is_some_and(|dispatch_state| {
8732        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8733      })
8734  }
8735
8736  fn retire_unreferenced_dispatch(
8737    state: &mut CurrentThreadTaskDriverRegistryState,
8738    dispatch: Option<u64>,
8739  ) {
8740    let Some(dispatch) = dispatch else {
8741      return;
8742    };
8743    if Self::has_dispatch_reference(state, dispatch) {
8744      return;
8745    }
8746    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8747      return;
8748    };
8749    if dispatch_state.publications == 0
8750      && !dispatch_state.is_cancelling()
8751      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8752    {
8753      state.dispatches.remove(&dispatch);
8754    }
8755  }
8756
8757  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8758    state
8759      .dispatches
8760      .get(&dispatch)
8761      .is_some_and(|dispatch_state| dispatch_state.references != 0)
8762  }
8763
8764  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8765    let mut state = self
8766      .state
8767      .lock()
8768      .unwrap_or_else(std::sync::PoisonError::into_inner);
8769    if state
8770      .dispatches
8771      .get(&failure.dispatch)
8772      .is_some_and(|dispatch_state| {
8773        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8774      })
8775    {
8776      state.dispatches.remove(&failure.dispatch);
8777    }
8778  }
8779
8780  fn retire_for_shutdown(&self) {
8781    let mut state = self
8782      .state
8783      .lock()
8784      .unwrap_or_else(std::sync::PoisonError::into_inner);
8785    state.dispatches.clear();
8786    for entry in state.entries.values_mut() {
8787      entry.pending = None;
8788    }
8789  }
8790}
8791
8792static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8793  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8794
8795pub fn register_current_thread_task_driver(
8796  driver: Arc<dyn CurrentThreadTaskDriver>,
8797) -> CurrentThreadTaskDriverId {
8798  CURRENT_THREAD_TASK_DRIVERS.register(driver)
8799}
8800
8801/// Request service for work that may have accumulated while no live host
8802/// dispatcher was registered. A new host receives the existing internal
8803/// dispatch capability through a fresh registration-scoped delivery token;
8804/// already accepting hosts keep their attempts.
8805pub fn request_current_thread_task_drain() {
8806  RUNTIME.request_current_thread_drain();
8807}
8808
8809pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8810  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8811}
8812
8813fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8814  let CurrentThreadTaskDispatchCompletion {
8815    result,
8816    failed_dispatches,
8817  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8818  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8819    failed_dispatches,
8820    redispatch: false,
8821  });
8822  result
8823}
8824
8825pub type TimerId = u64;
8826
8827/// Process-global id source for [`Sleep`] futures (both drivers).
8828static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8829
8830/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
8831/// helper thread on a threadless build, so it delegates
8832/// `sleep_until` to the host event loop through this trait. The Node binding
8833/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
8834/// at import -- one per importing napi env (main thread AND workers).
8835pub trait TimerDriver: Send + Sync + 'static {
8836  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
8837  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
8838  /// waker refresh, not a second timer.
8839  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8840  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
8841  /// raced ahead is acceptable (the woken task observes the sleep completed).
8842  fn cancel(&self, id: TimerId);
8843  /// Whether this driver can still deliver wakes. `false` once the driver's
8844  /// host is gone (the Node binding's driver dies with its owning napi env:
8845  /// worker exit aborts the weak threadsafe function). Selection skips and
8846  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
8847  fn is_live(&self) -> bool {
8848    true
8849  }
8850  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
8851  /// entries (its `is_live` turned false), with NO registry lock held.
8852  /// Implementations with pending-waker bookkeeping must wake everything
8853  /// armed on them here, so those sleeps re-poll onto the next live
8854  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
8855  /// host (before its env-cleanup hook or any call failure runs), and a
8856  /// silent `retain` would strand sleeps whose only wake source was the swept
8857  /// driver. Idempotent with the owner's other eviction paths. Default no-op
8858  /// for drivers without waker state.
8859  fn on_swept(&self) {}
8860}
8861
8862/// Handle to one registration in a [`TimerDriverRegistry`], returned by
8863/// [`TimerDriverRegistry::register`] and consumed by
8864/// [`TimerDriverRegistry::unregister`].
8865#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8866pub struct TimerDriverId(u64);
8867
8868type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8869
8870struct TimerDriverEntry {
8871  driver: Arc<dyn TimerDriver>,
8872  previous: Option<TimerDriverId>,
8873  next: Option<TimerDriverId>,
8874}
8875
8876#[derive(Default)]
8877struct TimerDriverRegistryState {
8878  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8879  oldest: Option<TimerDriverId>,
8880  newest: Option<TimerDriverId>,
8881}
8882
8883impl TimerDriverRegistryState {
8884  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8885    let previous = self.newest;
8886    let replaced = self.entries.insert(
8887      id,
8888      TimerDriverEntry {
8889        driver,
8890        previous,
8891        next: None,
8892      },
8893    );
8894    assert!(replaced.is_none(), "timer driver id must be unique");
8895    if let Some(previous) = previous {
8896      self
8897        .entries
8898        .get_mut(&previous)
8899        .expect("the newest timer driver must remain registered")
8900        .next = Some(id);
8901    } else {
8902      self.oldest = Some(id);
8903    }
8904    self.newest = Some(id);
8905  }
8906
8907  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8908    let entry = self.entries.remove(&id)?;
8909    if let Some(previous) = entry.previous {
8910      self
8911        .entries
8912        .get_mut(&previous)
8913        .expect("the previous timer driver must remain registered")
8914        .next = entry.next;
8915    } else {
8916      self.oldest = entry.next;
8917    }
8918    if let Some(next) = entry.next {
8919      self
8920        .entries
8921        .get_mut(&next)
8922        .expect("the next timer driver must remain registered")
8923        .previous = entry.previous;
8924    } else {
8925      self.newest = entry.previous;
8926    }
8927    Some(entry)
8928  }
8929
8930  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8931    let mut snapshot = Vec::with_capacity(self.entries.len());
8932    let mut current = self.oldest;
8933    while let Some(id) = current {
8934      let entry = self
8935        .entries
8936        .get(&id)
8937        .expect("timer driver order must reference a live entry");
8938      snapshot.push((id, Arc::clone(&entry.driver)));
8939      current = entry.next;
8940    }
8941    debug_assert_eq!(
8942      snapshot.len(),
8943      self.entries.len(),
8944      "timer driver order must contain every entry exactly once"
8945    );
8946    snapshot
8947  }
8948
8949  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8950    if self.entries.len() != expected.len() {
8951      return false;
8952    }
8953    let mut current = self.oldest;
8954    for expected in expected {
8955      if current != Some(*expected) {
8956        return false;
8957      }
8958      current = self
8959        .entries
8960        .get(expected)
8961        .expect("timer driver order must reference a live entry")
8962        .next;
8963    }
8964    current.is_none()
8965  }
8966}
8967
8968/// Host timer drivers keyed by registration order. A driver is owned by a host
8969/// context that can die independently
8970/// of the process -- the Node binding registers one driver per importing napi
8971/// env, and a worker's env teardown kills its driver's threadsafe function. A
8972/// single first-wins slot is therefore unsound: a worker that imports the
8973/// binding first and then exits would permanently shadow the slot with a dead
8974/// driver, and every CT timer armed afterwards would busy-fail against it.
8975/// Selecting only the newest live driver is also
8976/// insufficient: a live worker can be CPU-blocked indefinitely while another
8977/// environment remains responsive. The registry therefore keeps all
8978/// registrants and returns a stable snapshot of every live driver at each
8979/// [`Sleep`] poll, sweeping dead entries out as it goes.
8980///
8981/// Instance-based (not a hidden global) so tests exercise
8982/// registration/eviction/selection against local registries; production uses
8983/// the process-global one behind
8984/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
8985#[derive(Default)]
8986pub struct TimerDriverRegistry {
8987  /// Keyed lookup with a linked registration order. Exact unregister and a
8988  /// swept binding host's idempotent unregister are expected O(1), while
8989  /// snapshots keep deterministic oldest-to-newest fallback order.
8990  state: Mutex<TimerDriverRegistryState>,
8991  /// Stable repoll signals for every currently polled host sleep. A newly
8992  /// registered driver snapshots these after publishing itself, then wakes
8993  /// them outside both registry locks so each sleep re-arms the same timer id
8994  /// on the expanded all-live driver set.
8995  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8996  next_id: AtomicU64,
8997}
8998
8999impl Drop for TimerDriverRegistry {
9000  fn drop(&mut self) {
9001    let state = self
9002      .state
9003      .get_mut()
9004      .unwrap_or_else(std::sync::PoisonError::into_inner);
9005    state.oldest = None;
9006    state.newest = None;
9007    let entries = std::mem::take(&mut state.entries);
9008    for (_, entry) in entries {
9009      let _ = catch_unwind_contained(|| drop(entry));
9010    }
9011  }
9012}
9013
9014impl TimerDriverRegistry {
9015  /// Add `driver` as the newest registrant and return its handle.
9016  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9017    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
9018    let id = TimerDriverId(id);
9019    self
9020      .state
9021      .lock()
9022      .unwrap_or_else(std::sync::PoisonError::into_inner)
9023      .insert_driver(id, driver);
9024    // Publish the driver before snapshotting pending sleeps. A sleep publishes
9025    // its repoll signal before selecting live drivers. Therefore either this
9026    // snapshot sees the sleep, or that sleep's subsequent selection sees this
9027    // driver; no registration/insertion interleaving can miss both sides.
9028    let pending = {
9029      let mut entries = self
9030        .pending
9031        .lock()
9032        .unwrap_or_else(std::sync::PoisonError::into_inner);
9033      let mut pending = Vec::with_capacity(entries.len());
9034      entries.retain(|_, repoll| {
9035        if let Some(repoll) = repoll.upgrade() {
9036          pending.push(repoll);
9037          true
9038        } else {
9039          false
9040        }
9041      });
9042      pending
9043    };
9044    for repoll in pending {
9045      repoll.wake();
9046    }
9047    id
9048  }
9049
9050  /// Drop the registration behind `id` (no-op when already swept/removed).
9051  pub fn unregister(&self, id: TimerDriverId) {
9052    let removed = self
9053      .state
9054      .lock()
9055      .unwrap_or_else(std::sync::PoisonError::into_inner)
9056      .remove_driver(id);
9057    // A driver's destructor may re-enter this registry. Keep it outside the
9058    // entries lock just like is_live/on_swept callbacks.
9059    let _ = catch_unwind_contained(|| drop(removed));
9060  }
9061
9062  /// Every LIVE driver in registration order, sweeping dead entries out as a
9063  /// side effect.
9064  ///
9065  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
9066  /// lock held. `is_live` is externally implemented and may re-enter this
9067  /// registry; `on_swept` does so in the binding by calling `unregister` and
9068  /// waking pending sleeps whose re-polls call `current()` again. Selection
9069  /// therefore probes a snapshot, removes dead entries under the lock, then
9070  /// runs hooks after release. A concurrent registration/unregistration can
9071  /// stale the snapshot, in which case selection retries.
9072  fn live(&self) -> HostTimerDriverSnapshot {
9073    loop {
9074      let snapshot = HostTimerDriverSnapshot::new({
9075        let state = self
9076          .state
9077          .lock()
9078          .unwrap_or_else(std::sync::PoisonError::into_inner);
9079        state.ordered_snapshot()
9080      });
9081      let mut dead_ids = FxHashSet::default();
9082      let mut live_ids = Vec::new();
9083      for (id, driver) in snapshot.as_slice() {
9084        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9085          live_ids.push(*id);
9086        } else {
9087          dead_ids.insert(*id);
9088        }
9089      }
9090
9091      let (live, swept, retry) = {
9092        let mut state = self
9093          .state
9094          .lock()
9095          .unwrap_or_else(std::sync::PoisonError::into_inner);
9096        let mut swept = Vec::new();
9097        let mut current = state.oldest;
9098        while let Some(id) = current {
9099          let next = state
9100            .entries
9101            .get(&id)
9102            .expect("timer driver order must reference a live entry")
9103            .next;
9104          if dead_ids.contains(&id)
9105            && let Some(entry) = state.remove_driver(id)
9106          {
9107            swept.push(entry);
9108          }
9109          current = next;
9110        }
9111        let retry = !state.ordered_ids_match(&live_ids);
9112        let live = if retry {
9113          Vec::new()
9114        } else {
9115          state.ordered_snapshot()
9116        };
9117        (HostTimerDriverSnapshot::new(live), swept, retry)
9118      };
9119
9120      // A reentrant probe can unregister multiple snapshot-only drivers.
9121      // See the implementation notes in this crate's README.
9122      drop(snapshot);
9123      for entry in swept {
9124        let _ = catch_unwind_contained(|| entry.driver.on_swept());
9125        let _ = catch_unwind_contained(|| drop(entry));
9126      }
9127      if !retry {
9128        return live;
9129      }
9130    }
9131  }
9132
9133  #[cfg(test)]
9134  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9135    let mut live = self.live();
9136    live.pop().map(|(id, driver)| (id, driver.into_inner()))
9137  }
9138
9139  /// Whether a LIVE driver is currently registered (dead-only counts as no).
9140  pub fn has_live_driver(&self) -> bool {
9141    !self.live().is_empty()
9142  }
9143
9144  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9145    let replaced = self
9146      .pending
9147      .lock()
9148      .unwrap_or_else(std::sync::PoisonError::into_inner)
9149      .insert(id, Arc::downgrade(repoll));
9150    assert!(
9151      replaced.is_none(),
9152      "host timer {id} registered its repoll signal twice"
9153    );
9154    // Shutdown can close and try to remove the signal immediately before this
9155    // insertion. The post-insert check is the other side of that race.
9156    if repoll.is_closed() {
9157      self.remove_pending(id);
9158    }
9159  }
9160
9161  fn remove_pending(&self, id: TimerId) {
9162    let removed = self
9163      .pending
9164      .lock()
9165      .unwrap_or_else(std::sync::PoisonError::into_inner)
9166      .remove(&id);
9167    let _ = catch_unwind_contained(|| drop(removed));
9168  }
9169
9170  #[cfg(test)]
9171  fn pending_len(&self) -> usize {
9172    self
9173      .pending
9174      .lock()
9175      .unwrap_or_else(std::sync::PoisonError::into_inner)
9176      .len()
9177  }
9178}
9179
9180/// The process-global registry behind [`sleep_until`] (see
9181/// [`TimerDriverRegistry`] for the lifetime story).
9182static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9183  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9184
9185/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
9186/// Every host context that can serve timers registers its own driver (the
9187/// Node binding: one per importing napi env); every LIVE registrant receives
9188/// each timer so a starved environment cannot own its only wake source.
9189/// Returns the handle for [`unregister_timer_driver`].
9190pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9191  TIMER_DRIVERS.register(driver)
9192}
9193
9194/// Remove a driver registered via [`register_timer_driver`] -- called when
9195/// its host context dies (the Node binding evicts on env teardown and on
9196/// callback failure). Idempotent.
9197pub fn unregister_timer_driver(id: TimerDriverId) {
9198  TIMER_DRIVERS.unregister(id);
9199}
9200
9201/// Whether a LIVE host timer driver is currently registered. Lets the
9202/// embedder report CurrentThread timer availability honestly (e.g. through a
9203/// capabilities export) instead of guessing: with no
9204/// live driver a CurrentThread `sleep_until` would panic, so the capability
9205/// must read `false` -- including when the only registrants are DEAD (their
9206/// owning envs torn down), not merely when none ever registered.
9207pub fn has_live_timer_driver() -> bool {
9208  TIMER_DRIVERS.has_live_driver()
9209}
9210
9211/// Coalescing wake signal used only to make an already-pending [`Sleep`]
9212/// re-select host drivers after a new registration. The task's arbitrary
9213/// waker is never cloned, invoked, or dropped while this mutex is held.
9214#[derive(Default)]
9215struct HostTimerRepoll {
9216  state: Mutex<HostTimerRepollState>,
9217}
9218
9219#[derive(Default)]
9220struct HostTimerRepollState {
9221  closed: bool,
9222  notified: bool,
9223  waker: Option<Waker>,
9224}
9225
9226impl HostTimerRepoll {
9227  fn register(&self, waker: Waker) {
9228    let (replaced, wake) = {
9229      let mut state = self
9230        .state
9231        .lock()
9232        .unwrap_or_else(std::sync::PoisonError::into_inner);
9233      if state.closed {
9234        (Some(waker), None)
9235      } else if state.notified {
9236        state.notified = false;
9237        (state.waker.take(), Some(waker))
9238      } else {
9239        (state.waker.replace(waker), None)
9240      }
9241    };
9242    let _ = catch_unwind_contained(|| drop(replaced));
9243    if let Some(waker) = wake {
9244      wake_waker_contained(waker);
9245    }
9246  }
9247
9248  fn wake(&self) {
9249    let waker = {
9250      let mut state = self
9251        .state
9252        .lock()
9253        .unwrap_or_else(std::sync::PoisonError::into_inner);
9254      if state.closed {
9255        return;
9256      }
9257      if let Some(waker) = state.waker.take() {
9258        Some(waker)
9259      } else {
9260        state.notified = true;
9261        None
9262      }
9263    };
9264    if let Some(waker) = waker {
9265      wake_waker_contained(waker);
9266    }
9267  }
9268
9269  fn close(&self) {
9270    let waker = {
9271      let mut state = self
9272        .state
9273        .lock()
9274        .unwrap_or_else(std::sync::PoisonError::into_inner);
9275      state.closed = true;
9276      state.notified = false;
9277      state.waker.take()
9278    };
9279    let _ = catch_unwind_contained(|| drop(waker));
9280  }
9281
9282  fn is_closed(&self) -> bool {
9283    self
9284      .state
9285      .lock()
9286      .unwrap_or_else(std::sync::PoisonError::into_inner)
9287      .closed
9288  }
9289}
9290
9291/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
9292/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
9293/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
9294/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
9295/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
9296/// detection -- see the interaction notes at the top of this section.
9297#[derive(Default)]
9298struct HostTimerRegistry {
9299  state: Mutex<HostTimerRegistryState>,
9300}
9301
9302#[derive(Default)]
9303struct HostTimerRegistryState {
9304  closed: bool,
9305  timers: FxHashMap<TimerId, HostTimerEntry>,
9306}
9307
9308struct HostTimerEntry {
9309  deadline: Instant,
9310  waker: Waker,
9311  fired: Arc<AtomicBool>,
9312  drivers: Weak<TimerDriverRegistry>,
9313  repoll: Arc<HostTimerRepoll>,
9314}
9315
9316impl HostTimerRegistry {
9317  fn register(
9318    &self,
9319    id: TimerId,
9320    deadline: Instant,
9321    waker: Waker,
9322    fired: &Arc<AtomicBool>,
9323    drivers: &Arc<TimerDriverRegistry>,
9324    repoll: &Arc<HostTimerRepoll>,
9325  ) -> bool {
9326    let replaced = {
9327      let mut state = self
9328        .state
9329        .lock()
9330        .unwrap_or_else(std::sync::PoisonError::into_inner);
9331      if state.closed || fired.load(Ordering::SeqCst) {
9332        fired.store(true, Ordering::SeqCst);
9333        Err(waker)
9334      } else {
9335        Ok(state.timers.insert(
9336          id,
9337          HostTimerEntry {
9338            deadline,
9339            waker,
9340            fired: Arc::clone(fired),
9341            drivers: Arc::downgrade(drivers),
9342            repoll: Arc::clone(repoll),
9343          },
9344        ))
9345      }
9346    };
9347    match replaced {
9348      Ok(Some(entry)) => {
9349        let _ = catch_unwind_contained(|| drop(entry));
9350        true
9351      }
9352      Ok(None) => true,
9353      Err(waker) => {
9354        let _ = catch_unwind_contained(|| drop(waker));
9355        false
9356      }
9357    }
9358  }
9359
9360  fn remove(&self, id: TimerId) {
9361    let removed = self
9362      .state
9363      .lock()
9364      .unwrap_or_else(std::sync::PoisonError::into_inner)
9365      .timers
9366      .remove(&id);
9367    if let Some(entry) = removed {
9368      let _ = catch_unwind_contained(|| drop(entry));
9369    }
9370  }
9371
9372  fn shutdown(&self) {
9373    let timers = {
9374      let mut state = self
9375        .state
9376        .lock()
9377        .unwrap_or_else(std::sync::PoisonError::into_inner);
9378      state.closed = true;
9379      state
9380        .timers
9381        .drain()
9382        .map(|(id, entry)| {
9383          entry.fired.store(true, Ordering::SeqCst);
9384          (id, entry)
9385        })
9386        .collect::<Vec<_>>()
9387    };
9388    for (id, entry) in timers {
9389      entry.repoll.close();
9390      if let Some(drivers) = entry.drivers.upgrade() {
9391        drivers.remove_pending(id);
9392      }
9393      wake_waker_contained(entry.waker);
9394    }
9395  }
9396
9397  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
9398  /// only to pick the diagnostic variant: on a threadless build the park is
9399  /// provably dead either way (the host relay shares the parked thread), but
9400  /// a pending timer means the timer-backed message applies.
9401  fn has_pending(&self) -> bool {
9402    !self
9403      .state
9404      .lock()
9405      .unwrap_or_else(std::sync::PoisonError::into_inner)
9406      .timers
9407      .is_empty()
9408  }
9409
9410  /// Any host timer whose deadline (plus `grace` for host firing lag) is
9411  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
9412  /// timer with zero progress is legitimate. A timer past due by more than
9413  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
9414  /// signature the deadline detection exists to catch, so it must not keep
9415  /// suppressing the panic. Callers pass the armed park deadline as `grace`
9416  /// (the detector's own granularity: firing lag within one window is
9417  /// indistinguishable from an expiry racing the fire).
9418  fn has_live_wait(&self, grace: Duration) -> bool {
9419    let now = Instant::now();
9420    self
9421      .state
9422      .lock()
9423      .unwrap_or_else(std::sync::PoisonError::into_inner)
9424      .timers
9425      .values()
9426      .any(|entry| {
9427        entry
9428          .deadline
9429          .checked_add(grace)
9430          .is_none_or(|deadline| deadline > now)
9431      })
9432  }
9433}
9434
9435/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
9436/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
9437/// arm) both retire the wake-token exactly once.
9438struct HostTimerPendingGuard {
9439  registry: Arc<HostTimerRegistry>,
9440  drivers: Arc<TimerDriverRegistry>,
9441  repoll: Arc<HostTimerRepoll>,
9442  id: TimerId,
9443}
9444
9445impl Drop for HostTimerPendingGuard {
9446  fn drop(&mut self) {
9447    self.repoll.close();
9448    self.registry.remove(self.id);
9449    self.drivers.remove_pending(self.id);
9450  }
9451}
9452
9453/// Runtime-owned timer heap for the MultiThread executor.
9454///
9455/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
9456/// Rayon and therefore cannot consume one of the configured CPU workers while
9457/// a long timer is armed.
9458#[cfg(napi_runtime_os_threads)]
9459struct TimerHeap {
9460  inner: Arc<Mutex<TimerHeapInner>>,
9461  parker: Arc<DriverParker>,
9462  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9463  deadlock_state: Arc<MultiThreadDeadlockState>,
9464  #[cfg(test)]
9465  fail_next_start: AtomicBool,
9466  generation: u64,
9467  executor_id: u64,
9468  thread_name: String,
9469}
9470
9471#[cfg(napi_runtime_os_threads)]
9472#[derive(Default)]
9473struct TimerHeapInner {
9474  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
9475  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
9476  /// one later unbounded cleanup pass.
9477  queue: TimerDeadlineQueue,
9478  entries: FxHashMap<TimerId, HeapTimerEntry>,
9479  /// The parker of the dedicated timekeeper while it is waiting on a timer.
9480  /// `register_timer` unparks it when the earliest deadline moves up, so the
9481  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
9482  timekeeper_parker: Option<Arc<DriverParker>>,
9483  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
9484  /// later registrations must fire immediately (a `Sleep` polled after
9485  /// shutdown resolves early instead of parking a closing runtime forever).
9486  closed: bool,
9487}
9488
9489#[cfg(napi_runtime_os_threads)]
9490struct HeapTimerEntry {
9491  waker: Waker,
9492  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
9493  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
9494  /// even when the wall clock has not reached `deadline` (shutdown
9495  /// drain-fire).
9496  fired: Arc<AtomicBool>,
9497}
9498
9499#[cfg(napi_runtime_os_threads)]
9500#[derive(Default)]
9501struct TimerDeadlineQueue {
9502  nodes: Vec<(Instant, TimerId)>,
9503  positions: FxHashMap<TimerId, usize>,
9504}
9505
9506#[cfg(napi_runtime_os_threads)]
9507impl TimerDeadlineQueue {
9508  #[cfg(test)]
9509  fn len(&self) -> usize {
9510    self.nodes.len()
9511  }
9512
9513  #[cfg(test)]
9514  fn is_empty(&self) -> bool {
9515    self.nodes.is_empty()
9516  }
9517
9518  fn clear(&mut self) {
9519    self.nodes.clear();
9520    self.positions.clear();
9521  }
9522
9523  fn peek(&self) -> Option<(Instant, TimerId)> {
9524    self.nodes.first().copied()
9525  }
9526
9527  fn push(&mut self, deadline: Instant, id: TimerId) {
9528    let index = self.nodes.len();
9529    assert!(
9530      self.positions.insert(id, index).is_none(),
9531      "timer deadline queue received duplicate id {id}"
9532    );
9533    self.nodes.push((deadline, id));
9534    self.sift_up(index);
9535  }
9536
9537  fn pop(&mut self) -> Option<(Instant, TimerId)> {
9538    let first = self.nodes.first().copied()?;
9539    self.positions.remove(&first.1);
9540    let last = self
9541      .nodes
9542      .pop()
9543      .expect("the non-empty timer heap must have a final node");
9544    if !self.nodes.is_empty() {
9545      self.nodes[0] = last;
9546      self.positions.insert(last.1, 0);
9547      self.sift_down(0);
9548    }
9549    Some(first)
9550  }
9551
9552  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9553    let index = self.positions.remove(&id)?;
9554    let removed = self.nodes[index];
9555    let last = self.nodes.pop().expect("an indexed timer node must exist");
9556    if index < self.nodes.len() {
9557      self.nodes[index] = last;
9558      self.positions.insert(last.1, index);
9559      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9560        self.sift_up(index);
9561      } else {
9562        self.sift_down(index);
9563      }
9564    }
9565    Some(removed)
9566  }
9567
9568  fn sift_up(&mut self, mut index: usize) {
9569    while index > 0 {
9570      let parent = (index - 1) / 2;
9571      if self.nodes[parent] <= self.nodes[index] {
9572        break;
9573      }
9574      self.swap(parent, index);
9575      index = parent;
9576    }
9577  }
9578
9579  fn sift_down(&mut self, mut index: usize) {
9580    loop {
9581      let left = index * 2 + 1;
9582      if left >= self.nodes.len() {
9583        return;
9584      }
9585      let right = left + 1;
9586      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9587        right
9588      } else {
9589        left
9590      };
9591      if self.nodes[index] <= self.nodes[child] {
9592        return;
9593      }
9594      self.swap(index, child);
9595      index = child;
9596    }
9597  }
9598
9599  fn swap(&mut self, left: usize, right: usize) {
9600    self.nodes.swap(left, right);
9601    self.positions.insert(self.nodes[left].1, left);
9602    self.positions.insert(self.nodes[right].1, right);
9603  }
9604}
9605
9606#[cfg(napi_runtime_os_threads)]
9607impl TimerHeapInner {
9608  fn next_deadline(&self) -> Option<Instant> {
9609    self.queue.peek().map(|(deadline, _)| deadline)
9610  }
9611}
9612
9613#[cfg(napi_runtime_os_threads)]
9614impl TimerHeap {
9615  fn new(
9616    generation: u64,
9617    executor_id: u64,
9618    thread_name: String,
9619    deadlock_state: Arc<MultiThreadDeadlockState>,
9620  ) -> Self {
9621    Self {
9622      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9623      parker: Arc::new(DriverParker::default()),
9624      thread: Mutex::new(None),
9625      deadlock_state,
9626      #[cfg(test)]
9627      fail_next_start: AtomicBool::new(false),
9628      generation,
9629      executor_id,
9630      thread_name,
9631    }
9632  }
9633
9634  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9635    if self
9636      .inner
9637      .lock()
9638      .unwrap_or_else(std::sync::PoisonError::into_inner)
9639      .closed
9640    {
9641      return Ok(());
9642    }
9643    let current_thread = std::thread::current().id();
9644    loop {
9645      let finished = {
9646        let mut thread = self
9647          .thread
9648          .lock()
9649          .unwrap_or_else(std::sync::PoisonError::into_inner);
9650        match thread.as_ref() {
9651          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9652          Some(handle) if !handle.is_finished() => return Ok(()),
9653          Some(_) => thread.take(),
9654          None => {
9655            // Shutdown can close the heap after the optimistic check above.
9656            // Recheck before publishing another service thread.
9657            if self
9658              .inner
9659              .lock()
9660              .unwrap_or_else(std::sync::PoisonError::into_inner)
9661              .closed
9662            {
9663              return Ok(());
9664            }
9665            #[cfg(test)]
9666            if self.fail_next_start.swap(false, Ordering::SeqCst) {
9667              return Err(RuntimeConfigError(
9668                "injected runtime timer thread creation failure".to_string(),
9669              ));
9670            }
9671
9672            let inner = Arc::clone(&self.inner);
9673            let parker = Arc::clone(&self.parker);
9674            let deadlock_state = Arc::clone(&self.deadlock_state);
9675            let generation = self.generation;
9676            let executor_id = self.executor_id;
9677            let handle = std::thread::Builder::new()
9678              .name(self.thread_name.clone())
9679              .spawn(move || {
9680                // Keep the identity through OS-thread TLS teardown. Unlike
9681                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
9682                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9683                let _generation = RuntimeGenerationGuard::enter(generation);
9684                timer_timekeeper_main(&inner, &parker, &deadlock_state);
9685              })
9686              .map_err(|error| {
9687                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9688              })?;
9689            *thread = Some(handle);
9690            return Ok(());
9691          }
9692        }
9693      };
9694
9695      // A finished timer thread may still be running TLS destructors that
9696      // re-enter timer code. Never hold the handle mutex while waiting for
9697      // those destructors, and never attempt to join this thread from itself.
9698      if let Some(finished) = finished
9699        && finished.thread().id() != current_thread
9700        && let Err(payload) = finished.join()
9701      {
9702        discard_panic_payload(payload);
9703      }
9704      if self
9705        .inner
9706        .lock()
9707        .unwrap_or_else(std::sync::PoisonError::into_inner)
9708        .closed
9709      {
9710        return Ok(());
9711      }
9712    }
9713  }
9714
9715  fn close(&self) -> Vec<Waker> {
9716    let mut inner = self
9717      .inner
9718      .lock()
9719      .unwrap_or_else(std::sync::PoisonError::into_inner);
9720    inner.closed = true;
9721    inner.queue.clear();
9722    inner.timekeeper_parker = None;
9723    inner
9724      .entries
9725      .drain()
9726      .map(|(_, entry)| {
9727        entry.fired.store(true, Ordering::SeqCst);
9728        entry.waker
9729      })
9730      .collect()
9731  }
9732
9733  fn join(&self) {
9734    let handle = self
9735      .thread
9736      .lock()
9737      .unwrap_or_else(std::sync::PoisonError::into_inner)
9738      .take();
9739    if let Some(handle) = handle {
9740      if handle.thread().id() == std::thread::current().id() {
9741        // A custom RawWaker may own the final direct executor Arc and release
9742        // it from this thread. Closing the heap makes the loop exit after the
9743        // callback returns; joining self would deadlock that exit.
9744        drop(handle);
9745        return;
9746      }
9747      if let Err(payload) = handle.join() {
9748        discard_panic_payload(payload);
9749      }
9750    }
9751  }
9752}
9753
9754#[cfg(napi_runtime_os_threads)]
9755impl Drop for TimerHeap {
9756  fn drop(&mut self) {
9757    let mut admission = self.deadlock_state.begin_admission();
9758    let wakers = self.close();
9759    if !wakers.is_empty()
9760      && let Some(admission) = admission.as_mut()
9761    {
9762      admission.mark_published();
9763    }
9764    self.parker.unpark();
9765    for waker in wakers {
9766      wake_waker_contained(waker);
9767    }
9768    drop(admission);
9769    self.join();
9770  }
9771}
9772
9773#[cfg(napi_runtime_os_threads)]
9774fn timer_timekeeper_main(
9775  inner: &Mutex<TimerHeapInner>,
9776  parker: &Arc<DriverParker>,
9777  deadlock_state: &Arc<MultiThreadDeadlockState>,
9778) {
9779  loop {
9780    // Acquire before touching the heap. If a verdict has already closed the
9781    // gate, a due timer remains visible until the verdict's final timer recheck
9782    // causes that guard to reopen.
9783    let mut admission = deadlock_state.begin_admission();
9784    let (due, deadline) = {
9785      let mut inner = inner
9786        .lock()
9787        .unwrap_or_else(std::sync::PoisonError::into_inner);
9788      if inner.closed {
9789        inner.timekeeper_parker = None;
9790        return;
9791      }
9792      let now = Instant::now();
9793      let mut due = Vec::new();
9794      while let Some((deadline, id)) = inner.queue.peek() {
9795        if deadline > now {
9796          break;
9797        }
9798        inner.queue.pop();
9799        if let Some(entry) = inner.entries.remove(&id) {
9800          entry.fired.store(true, Ordering::SeqCst);
9801          due.push(entry.waker);
9802        }
9803      }
9804      let deadline = inner.next_deadline();
9805      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9806      (due, deadline)
9807    };
9808
9809    if !due.is_empty() {
9810      if let Some(admission) = admission.as_mut() {
9811        admission.mark_published();
9812      }
9813      for waker in due {
9814        wake_waker_contained(waker);
9815      }
9816      continue;
9817    }
9818    // Never retain an admission while sleeping: it protects only the
9819    // remove-then-wake publication window.
9820    drop(admission);
9821
9822    match deadline {
9823      Some(deadline) => {
9824        let now = Instant::now();
9825        if deadline > now {
9826          parker.park_timeout(deadline - now);
9827        }
9828      }
9829      None => parker.park(),
9830    }
9831  }
9832}
9833
9834#[cfg(napi_runtime_os_threads)]
9835impl MultiThreadExecutor {
9836  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
9837  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
9838  /// shutdown, and set here immediately when the heap is already closed (the
9839  /// caller re-checks it after registering, so a post-shutdown poll resolves
9840  /// instead of parking forever).
9841  fn register_timer(
9842    self: &Arc<Self>,
9843    id: TimerId,
9844    deadline: Instant,
9845    waker: Waker,
9846    fired: &Arc<AtomicBool>,
9847  ) {
9848    let mut admission = self.begin_deadlock_admission();
9849    #[cfg(test)]
9850    run_timer_registration_admission_test_hook();
9851    // Establish the service thread before publishing the entry. If thread
9852    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
9853    // heap state exists for Drop to miss.
9854    if let Err(error) = self.timers.ensure_started() {
9855      let _ = catch_unwind_contained(|| drop(waker));
9856      panic!("{error}");
9857    }
9858    let (became_earliest, published, discarded_waker) = {
9859      let mut inner = self
9860        .timers
9861        .inner
9862        .lock()
9863        .unwrap_or_else(std::sync::PoisonError::into_inner);
9864      if inner.closed || fired.load(Ordering::SeqCst) {
9865        fired.store(true, Ordering::SeqCst);
9866        (false, false, Some(waker))
9867      } else if let Some(entry) = inner.entries.get_mut(&id) {
9868        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
9869        (
9870          false,
9871          false,
9872          Some(std::mem::replace(&mut entry.waker, waker)),
9873        )
9874      } else {
9875        let previous = inner.next_deadline();
9876        inner.entries.insert(
9877          id,
9878          HeapTimerEntry {
9879            waker,
9880            fired: Arc::clone(fired),
9881          },
9882        );
9883        inner.queue.push(deadline, id);
9884        (
9885          previous.is_none_or(|previous| deadline < previous),
9886          true,
9887          None,
9888        )
9889      }
9890    };
9891    if published && let Some(admission) = admission.as_mut() {
9892      admission.mark_published();
9893    }
9894    if let Some(waker) = discarded_waker {
9895      // RawWaker destruction is user code and may re-enter timer cancellation.
9896      // Keep it outside the heap mutex and contain hostile destructors.
9897      let _ = catch_unwind_contained(|| drop(waker));
9898    }
9899    if became_earliest {
9900      // Re-arm the lazy service thread and any cooperative timer wait.
9901      self.note_earlier_timer();
9902    }
9903  }
9904
9905  /// Best-effort cancel: remove the entry and its indexed heap node.
9906  /// If cancellation changes the earliest deadline, wake every timer-bounded
9907  /// driver so no pool worker remains parked on the stale deadline.
9908  fn cancel_timer(&self, id: TimerId) {
9909    let (removed, earliest_changed) = {
9910      let mut inner = self
9911        .timers
9912        .inner
9913        .lock()
9914        .unwrap_or_else(std::sync::PoisonError::into_inner);
9915      let previous = inner.next_deadline();
9916      let removed = inner.entries.remove(&id);
9917      if removed.is_some() {
9918        let queued = inner.queue.remove(id);
9919        debug_assert!(
9920          queued.is_some(),
9921          "a live timer entry must have one indexed deadline node"
9922        );
9923      }
9924      let current = inner.next_deadline();
9925      (removed, previous != current)
9926    };
9927    if let Some(entry) = removed {
9928      // A RawWaker destructor may cancel another timer. Never run it while
9929      // holding the heap lock.
9930      let _ = catch_unwind_contained(|| drop(entry));
9931    }
9932    if earliest_changed {
9933      self.timers.parker.unpark();
9934      self.parked_drivers.wake_all();
9935    }
9936  }
9937
9938  /// Earliest pending deadline (None when the heap is empty or closed).
9939  fn next_timer_deadline(&self) -> Option<Instant> {
9940    self
9941      .timers
9942      .inner
9943      .lock()
9944      .unwrap_or_else(std::sync::PoisonError::into_inner)
9945      .next_deadline()
9946  }
9947
9948  /// Pop and wake everything due at/before now. Wakers are collected under
9949  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
9950  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
9951  /// the lock, before the wake, so the woken poll observes it.
9952  fn fire_due_timers(&self) {
9953    let mut admission = self.begin_deadlock_admission();
9954    let due: Vec<Waker> = {
9955      let mut inner = self
9956        .timers
9957        .inner
9958        .lock()
9959        .unwrap_or_else(std::sync::PoisonError::into_inner);
9960      let now = Instant::now();
9961      let mut due = Vec::new();
9962      while let Some((deadline, id)) = inner.queue.peek() {
9963        if deadline > now {
9964          break;
9965        }
9966        inner.queue.pop();
9967        if let Some(entry) = inner.entries.remove(&id) {
9968          entry.fired.store(true, Ordering::SeqCst);
9969          due.push(entry.waker);
9970        }
9971      }
9972      due
9973    };
9974    if !due.is_empty()
9975      && let Some(admission) = admission.as_mut()
9976    {
9977      admission.mark_published();
9978    }
9979    for waker in due {
9980      // Wakers may originate outside this executor. Isolate user RawWaker
9981      // implementations so a panic cannot unwind the dedicated timekeeper.
9982      wake_waker_contained(waker);
9983    }
9984  }
9985
9986  /// The earliest pending deadline moved up (or the heap went non-empty).
9987  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
9988  /// whose current park may still reflect the previous deadline.
9989  fn note_earlier_timer(self: &Arc<Self>) {
9990    self.timers.parker.unpark();
9991    self.parked_drivers.wake_one();
9992  }
9993
9994  /// Drain-fire every pending heap timer and poison later registrations.
9995  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
9996  /// sleeps resolve on their next poll; the woken tasks are
9997  /// scheduled through the normal path, so a coordinator awaiting a debounce
9998  /// completes its close sequence instead of parking forever.
9999  fn shutdown_timers(&self) {
10000    let mut admission = self.begin_deadlock_admission();
10001    let wakers = self.timers.close();
10002    if !wakers.is_empty()
10003      && let Some(admission) = admission.as_mut()
10004    {
10005      admission.mark_published();
10006    }
10007    for waker in wakers {
10008      // A user-provided RawWaker may panic. Shutdown is a lifecycle
10009      // transition, so one hostile wake must not leave the controller stuck
10010      // in `Stopping` and prevent every later restart.
10011      wake_waker_contained(waker);
10012    }
10013    self.timers.parker.unpark();
10014  }
10015
10016  fn begin_shutdown(&self) {
10017    let _generation = RuntimeGenerationGuard::enter(self.generation);
10018    // Publish stop before taking the blocking queue. The final deadlock verdict
10019    // holds stop publication while rechecking that queue, so queue -> stop
10020    // would invert its lock order. A concurrent taker either claims while stop
10021    // is still false or observes stop after this publication.
10022    self.stop.begin_shutdown();
10023    let queued = {
10024      let mut queue = self
10025        .blocking_queue
10026        .lock()
10027        .unwrap_or_else(std::sync::PoisonError::into_inner);
10028      // The blocking FIFO mutex closes queue admission. `take_blocking` either
10029      // claimed a job before stop publication or observes stop/closure.
10030      queue.closed = true;
10031      queue.take_all()
10032    };
10033    // Captured values belong to user code and may panic in Drop. Retire each
10034    // rejected closure independently so one destructor cannot strand the
10035    // runtime in `Stopping` or prevent the remaining queued jobs from being
10036    // cancelled.
10037    for queued in queued {
10038      let _ = catch_unwind_contained(|| drop(queued));
10039    }
10040    self.shutdown_timers();
10041  }
10042
10043  /// Non-blocking read of the predicate `wait_until_scheduler_idle` sleeps on.
10044  /// The timer join is deliberately excluded: it is a thread lifetime, not
10045  /// work a host can let finish by yielding.
10046  fn scheduler_work_pending(&self) -> bool {
10047    self.active_drainers.load(Ordering::Acquire) != 0
10048  }
10049
10050  fn wait_until_scheduler_idle(&self) {
10051    self.timers.join();
10052    let mut idle = self
10053      .scheduler_idle_lock
10054      .lock()
10055      .unwrap_or_else(std::sync::PoisonError::into_inner);
10056    // Register BEFORE the first predicate evaluation and stay registered for
10057    // the whole wait. `finish_draining` reads this count under the same mutex,
10058    // so any drainer that retires after this increment is published either
10059    // sees `> 0` and wakes us, or had already decremented `active_drainers`
10060    // before we read the predicate -- in which case we never sleep.
10061    *idle += 1;
10062    while self.active_drainers.load(Ordering::Acquire) != 0 {
10063      idle = self
10064        .scheduler_idle
10065        .wait(idle)
10066        .unwrap_or_else(std::sync::PoisonError::into_inner);
10067    }
10068    *idle -= 1;
10069  }
10070}
10071
10072/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
10073/// (or early on runtime shutdown drain-fire). Dropping it cancels the
10074/// underlying registration -- the `tokio::select!` losing-arm semantics the
10075/// watch coordinator's debounce loop relies on.
10076pub struct Sleep {
10077  deadline: Instant,
10078  inner: SleepInner,
10079}
10080
10081enum SleepInner {
10082  #[cfg(napi_runtime_os_threads)]
10083  Heap(HeapSleep),
10084  Host(HostSleep),
10085}
10086
10087#[cfg(napi_runtime_os_threads)]
10088struct HeapSleep {
10089  id: TimerId,
10090  executor: Weak<MultiThreadExecutor>,
10091  fired: Arc<AtomicBool>,
10092  registered: bool,
10093}
10094
10095struct HostSleep {
10096  id: TimerId,
10097  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
10098  /// registrant. Arming redundant host timers prevents a live but starved
10099  /// environment from owning the sleep's only wake source.
10100  drivers: Arc<TimerDriverRegistry>,
10101  /// Drivers this sleep last armed on. The first responsive fire wakes the
10102  /// sleep; completion cancels every losing arm, including pre-delivery arms
10103  /// whose binding relay retains a cancellation tombstone.
10104  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10105  /// The executor's registry, held so the FIRST poll can arm the wake-token
10106  /// at the same moment `driver.register` arms the host timer.
10107  registry: Arc<HostTimerRegistry>,
10108  fired: Arc<AtomicBool>,
10109  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
10110  /// request a re-poll without retaining or invoking a task waker under the
10111  /// driver-registry locks.
10112  repoll: Arc<HostTimerRepoll>,
10113  /// Retires this sleep's wake-token in the executor's registry exactly once
10114  /// (created at first poll alongside the host registration, taken on
10115  /// completion, dropped with the Sleep on cancellation). `None` before the
10116  /// first poll: an unpolled Sleep holds no wake-token.
10117  pending: Option<HostTimerPendingGuard>,
10118}
10119
10120fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10121  let _ = catch_unwind_contained(|| driver.cancel(id));
10122  let _ = catch_unwind_contained(|| drop(driver));
10123}
10124
10125fn cancel_host_timer_drivers(
10126  id: TimerId,
10127  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10128) {
10129  for (_, driver) in armed.drain() {
10130    cancel_host_timer_driver(id, driver);
10131  }
10132}
10133
10134impl Future for Sleep {
10135  type Output = ();
10136
10137  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10138    let deadline = self.deadline;
10139    let this = self.get_mut();
10140    match &mut this.inner {
10141      #[cfg(napi_runtime_os_threads)]
10142      SleepInner::Heap(heap) => {
10143        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10144          if heap.registered {
10145            // Wall-clock completion may beat the (busy) timekeeper's fire:
10146            // retire the registration so it cannot wake spuriously later.
10147            if let Some(executor) = heap.executor.upgrade() {
10148              executor.cancel_timer(heap.id);
10149            }
10150            heap.registered = false;
10151          }
10152          return Poll::Ready(());
10153        }
10154        let Some(executor) = heap.executor.upgrade() else {
10155          heap.fired.store(true, Ordering::SeqCst);
10156          heap.registered = false;
10157          return Poll::Ready(());
10158        };
10159        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10160        heap.registered = true;
10161        // Registration on a closed (shut-down) heap stores nothing and sets
10162        // `fired` instead: re-check so this poll resolves rather than parking
10163        // a closing runtime forever.
10164        if heap.fired.load(Ordering::SeqCst) {
10165          heap.registered = false;
10166          return Poll::Ready(());
10167        }
10168        Poll::Pending
10169      }
10170      SleepInner::Host(host) => {
10171        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10172          cancel_host_timer_drivers(host.id, &mut host.armed);
10173          host.pending.take();
10174          return Poll::Ready(());
10175        }
10176        // Clone both wakers before publishing either one. If the second clone
10177        // unwinds, destroy the first clone behind its own panic boundary before
10178        // resuming the original panic, so a hostile RawWaker destructor cannot
10179        // turn clone failure into a process-aborting double panic.
10180        let repoll_waker = cx.waker().clone();
10181        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10182          Ok(waker) => waker,
10183          Err(payload) => {
10184            let _ = catch_unwind_contained(|| drop(repoll_waker));
10185            resume_unwind(payload);
10186          }
10187        };
10188        host.repoll.register(repoll_waker);
10189        if !host.registry.register(
10190          host.id,
10191          deadline,
10192          registry_waker,
10193          &host.fired,
10194          &host.drivers,
10195          &host.repoll,
10196        ) {
10197          cancel_host_timer_drivers(host.id, &mut host.armed);
10198          if host.pending.take().is_none() {
10199            host.repoll.close();
10200          }
10201          return Poll::Ready(());
10202        }
10203        if host.pending.is_none() {
10204          // FIRST poll: the registry entry appears at the same moment the
10205          // host timer is armed below -- and only then. An unpolled Sleep has
10206          // no host callback behind it, so counting it at creation would
10207          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
10208          // mislabel the threadless certain diagnostic as timer-backed. The
10209          // certain panic itself no longer consults pending timers for its
10210          // verdict.
10211          host.drivers.register_pending(host.id, &host.repoll);
10212          host.pending = Some(HostTimerPendingGuard {
10213            registry: Arc::clone(&host.registry),
10214            drivers: Arc::clone(&host.drivers),
10215            repoll: Arc::clone(&host.repoll),
10216            id: host.id,
10217          });
10218        }
10219        // Re-select every LIVE driver on every poll. A live but starved
10220        // environment may accept a TSFN call and then stop servicing its
10221        // event loop, so no single host may own the only timer arm.
10222        let current = host.drivers.live();
10223        assert!(
10224          !current.as_slice().is_empty(),
10225          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10226           that registered timer drivers (via \
10227           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10228           one per importing env through `registerTimerHost`) have all been torn down, so this \
10229           `sleep_until` can never be woken."
10230        );
10231        // Publish cancellation ownership for the complete live snapshot before
10232        // invoking any arbitrary callback. An earlier driver may unregister a
10233        // later one and then unwind; both the Sleep and the contained snapshot
10234        // must still own that later driver while the original panic propagates.
10235        // See the implementation notes in this crate's README.
10236        let missing = current
10237          .as_slice()
10238          .iter()
10239          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10240          .count();
10241        host.armed.reserve(missing);
10242        for (current_id, current_driver) in current.as_slice() {
10243          host
10244            .armed
10245            .entry(*current_id)
10246            .or_insert_with(|| Arc::clone(current_driver));
10247        }
10248        for (current_id, _) in current.as_slice() {
10249          let armed_driver = host
10250            .armed
10251            .get(current_id)
10252            .expect("every live timer driver must have cancellation ownership before callbacks");
10253          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10254        }
10255        let current_ids = current
10256          .as_slice()
10257          .iter()
10258          .map(|(current_id, _)| *current_id)
10259          .collect::<FxHashSet<_>>();
10260        let retired = host
10261          .armed
10262          .keys()
10263          .filter(|armed_id| !current_ids.contains(armed_id))
10264          .copied()
10265          .collect::<Vec<_>>();
10266        for retired in retired {
10267          let old_driver = host
10268            .armed
10269            .remove(&retired)
10270            .expect("the retired timer driver must remain armed");
10271          cancel_host_timer_driver(host.id, old_driver);
10272        }
10273        Poll::Pending
10274      }
10275    }
10276  }
10277}
10278
10279impl Drop for Sleep {
10280  fn drop(&mut self) {
10281    match &mut self.inner {
10282      #[cfg(napi_runtime_os_threads)]
10283      SleepInner::Heap(heap) => {
10284        if heap.registered
10285          && let Some(executor) = heap.executor.upgrade()
10286        {
10287          executor.cancel_timer(heap.id);
10288        }
10289      }
10290      SleepInner::Host(host) => {
10291        // Close directly as an unwind backstop even when first-poll setup
10292        // failed before `pending` could install its normal cleanup guard.
10293        host.repoll.close();
10294        cancel_host_timer_drivers(host.id, &mut host.armed);
10295        // `pending` (if still held) drops here and retires the wake-token.
10296      }
10297    }
10298  }
10299}
10300
10301/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
10302/// the executor-owned heap; CurrentThread requires a LIVE host driver
10303/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
10304/// missing driver must never become a silent never-firing debounce).
10305pub fn sleep_until(deadline: Instant) -> Sleep {
10306  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10307}
10308
10309/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
10310/// build executors and driver registries locally and must not touch the
10311/// process-global ones).
10312fn make_sleep(
10313  backend: &RuntimeBackend,
10314  host_drivers: &Arc<TimerDriverRegistry>,
10315  deadline: Instant,
10316) -> Sleep {
10317  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10318  match &backend.executor {
10319    #[cfg(napi_runtime_os_threads)]
10320    RuntimeExecutor::MultiThread(executor) => Sleep {
10321      deadline,
10322      inner: SleepInner::Heap(HeapSleep {
10323        id,
10324        executor: Arc::downgrade(executor),
10325        fired: Arc::new(AtomicBool::new(false)),
10326        registered: false,
10327      }),
10328    },
10329    RuntimeExecutor::CurrentThread(executor) => {
10330      // Fail loud at CREATION when no live driver exists (the
10331      // never-registered case gets its diagnostic at the sleep_until call
10332      // site, not on some later poll). The driver is NOT pinned here: each
10333      // poll re-selects every live registrant, so a driver dying while this
10334      // sleep is in flight is cancelled while the remaining hosts stay armed
10335      // -- and if every driver dies mid-flight, the poll path has its own loud
10336      // panic.
10337      assert!(
10338        host_drivers.has_live_driver(),
10339        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10340         single-thread flavor delegates timers to the host event loop. Install one via \
10341         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10342         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10343      );
10344      // The registry entry is created at FIRST POLL (when `driver.register`
10345      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
10346      // -- nothing was handed to the host loop -- so counting it at creation
10347      // would wrongly veto the threaded deadline verdict and mislabel the
10348      // threadless certain diagnostic as timer-backed. First-poll accounting
10349      // has no false-panic side.
10350      Sleep {
10351        deadline,
10352        inner: SleepInner::Host(HostSleep {
10353          id,
10354          drivers: Arc::clone(host_drivers),
10355          armed: FxHashMap::default(),
10356          registry: Arc::clone(&executor.host_timers),
10357          fired: Arc::new(AtomicBool::new(false)),
10358          repoll: Arc::new(HostTimerRepoll::default()),
10359          pending: None,
10360        }),
10361      }
10362    }
10363  }
10364}
10365
10366#[derive(Clone)]
10367enum RuntimeExecutor {
10368  CurrentThread(Arc<CurrentThreadExecutor>),
10369  #[cfg(napi_runtime_os_threads)]
10370  MultiThread(Arc<MultiThreadExecutor>),
10371}
10372
10373enum WeakRuntimeExecutor {
10374  CurrentThread(Weak<CurrentThreadExecutor>),
10375  #[cfg(napi_runtime_os_threads)]
10376  MultiThread(Weak<MultiThreadExecutor>),
10377}
10378
10379impl WeakRuntimeExecutor {
10380  fn schedule(&self, runnable: Runnable) {
10381    match self {
10382      Self::CurrentThread(executor) => {
10383        if let Some(executor) = executor.upgrade() {
10384          executor.schedule(runnable);
10385        }
10386      }
10387      #[cfg(napi_runtime_os_threads)]
10388      Self::MultiThread(executor) => {
10389        if let Some(executor) = executor.upgrade() {
10390          executor.schedule(runnable);
10391        }
10392      }
10393    }
10394  }
10395}
10396
10397#[derive(Clone)]
10398struct RuntimeBackend {
10399  work: Arc<GenerationWork>,
10400  executor: RuntimeExecutor,
10401}
10402
10403impl RuntimeBackend {
10404  fn new(
10405    options: &RuntimeOptions,
10406    metrics: Arc<RuntimeMetrics>,
10407  ) -> Result<Self, RuntimeConfigError> {
10408    #[cfg(test)]
10409    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10410      return Err(RuntimeConfigError(
10411        "injected async runtime backend creation failure".to_string(),
10412      ));
10413    }
10414
10415    let work = GenerationWork::new();
10416    let stop = Arc::new(GenerationStop::default());
10417    let executor = match options.flavor {
10418      RuntimeFlavor::CurrentThread => {
10419        let executor = CurrentThreadExecutor::with_generation(
10420          metrics,
10421          THREADLESS_BUILD,
10422          options.park_deadline,
10423          work.id,
10424          Arc::clone(&stop),
10425        );
10426        executor.attach_generation_work(&work);
10427        RuntimeExecutor::CurrentThread(Arc::new(executor))
10428      }
10429      RuntimeFlavor::MultiThread => {
10430        #[cfg(napi_runtime_os_threads)]
10431        {
10432          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10433            options, metrics, work.id, stop,
10434          )?))
10435        }
10436        #[cfg(not(napi_runtime_os_threads))]
10437        {
10438          let _ = metrics;
10439          return Err(RuntimeConfigError(
10440            "the multi-thread runtime is unavailable in this threadless WebAssembly build \
10441             (wasm32-wasip1 / wasm32-unknown-unknown); build for wasm32-wasip1-threads to use it"
10442              .to_string(),
10443          ));
10444        }
10445      }
10446    };
10447    Ok(Self { work, executor })
10448  }
10449
10450  #[cfg(test)]
10451  fn from_executor(executor: RuntimeExecutor) -> Self {
10452    let work = GenerationWork::new();
10453    if let RuntimeExecutor::CurrentThread(executor) = &executor {
10454      executor.attach_generation_work(&work);
10455    }
10456    Self { work, executor }
10457  }
10458
10459  fn generation(&self) -> u64 {
10460    self.work.id
10461  }
10462
10463  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10464    match &self.executor {
10465      RuntimeExecutor::CurrentThread(executor) => {
10466        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10467      }
10468      #[cfg(napi_runtime_os_threads)]
10469      RuntimeExecutor::MultiThread(executor) => {
10470        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10471      }
10472    }
10473  }
10474
10475  /// First schedule of a freshly submitted task. This runs on the stack of the
10476  /// caller of `spawn`, never on a waker's; see
10477  /// `CurrentThreadExecutor::schedule_submission`.
10478  fn schedule_submission(&self, runnable: Runnable) {
10479    match &self.executor {
10480      RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10481      #[cfg(napi_runtime_os_threads)]
10482      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10483    }
10484  }
10485
10486  fn spawn_registered_blocking<F, T>(
10487    &self,
10488    function: F,
10489    registration: GenerationWorkGuard,
10490    metrics: &Arc<RuntimeMetrics>,
10491  ) -> JoinHandle<T>
10492  where
10493    F: FnOnce() -> T + Send + 'static,
10494    T: Send + 'static,
10495  {
10496    match &self.executor {
10497      RuntimeExecutor::CurrentThread(executor) => {
10498        let _ = metrics;
10499        executor.schedule_blocking(function, registration)
10500      }
10501      #[cfg(napi_runtime_os_threads)]
10502      RuntimeExecutor::MultiThread(executor) => {
10503        let generation = self.generation();
10504        let _ = metrics;
10505        let function = RegisteredBlockingFunction::new(function, registration, generation);
10506        executor.schedule_blocking_result(move || function.run())
10507      }
10508    }
10509  }
10510
10511  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10512    match &self.executor {
10513      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10514      #[cfg(napi_runtime_os_threads)]
10515      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10516    }
10517  }
10518
10519  fn generation_stop(&self) -> Arc<GenerationStop> {
10520    match &self.executor {
10521      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10522      #[cfg(napi_runtime_os_threads)]
10523      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10524    }
10525  }
10526
10527  fn begin_shutdown(&self) {
10528    let _generation = RuntimeGenerationGuard::enter(self.generation());
10529    self.work.close_and_abort();
10530    match &self.executor {
10531      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10532      #[cfg(napi_runtime_os_threads)]
10533      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10534    }
10535  }
10536
10537  fn wait_until_idle(&self) {
10538    match &self.executor {
10539      RuntimeExecutor::CurrentThread(executor) => {
10540        // A wake in flight across `begin_shutdown` is rejected by the closed
10541        // queue and parked in `queue.rejected` with its guard still held, and
10542        // a failure transition racing `begin_shutdown` from a waker's stack
10543        // moves the queued guards there too; retire them here, on this stack,
10544        // until the generation is idle.
10545        self.work.wait_until_idle_retiring(
10546          || executor.has_rejected_work(),
10547          || executor.retire_rejected_work(),
10548        );
10549        executor.wait_until_scheduler_idle();
10550      }
10551      #[cfg(napi_runtime_os_threads)]
10552      RuntimeExecutor::MultiThread(executor) => {
10553        self.work.wait_until_idle();
10554        executor.wait_until_scheduler_idle();
10555      }
10556    }
10557  }
10558
10559  /// Non-blocking counterpart of `wait_until_idle`: whether that call would
10560  /// have to wait for anything. Same sources, read without sleeping --
10561  /// registered async/blocking work, work the executor still owes a
10562  /// cancellation, and an in-flight drain or host turn.
10563  ///
10564  /// Joining the worker threads (phase 2's `wait_for_all_workers`) is NOT a
10565  /// source here: a Rayon worker exits when the last backend handle is
10566  /// dropped, which only phase 2 does, so `WorkerLifecycle::remaining` is
10567  /// non-zero for the whole window between the phases by construction and
10568  /// would make this poll never answer `false`. That join waits on thread
10569  /// teardown, never on user work, so yielding to the host cannot help it.
10570  fn work_pending(&self) -> bool {
10571    if self.work.has_active_work() {
10572      return true;
10573    }
10574    match &self.executor {
10575      RuntimeExecutor::CurrentThread(executor) => {
10576        executor.has_rejected_work() || executor.scheduler_work_pending()
10577      }
10578      #[cfg(napi_runtime_os_threads)]
10579      RuntimeExecutor::MultiThread(executor) => executor.scheduler_work_pending(),
10580    }
10581  }
10582
10583  #[cfg(napi_runtime_os_threads)]
10584  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10585    match &self.executor {
10586      RuntimeExecutor::CurrentThread(_) => None,
10587      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10588    }
10589  }
10590
10591  fn stop_identity(&self) -> RuntimeStopIdentity {
10592    RuntimeStopIdentity {
10593      generation: self.generation(),
10594      #[cfg(napi_runtime_os_threads)]
10595      executor_id: match &self.executor {
10596        RuntimeExecutor::CurrentThread(_) => None,
10597        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10598      },
10599    }
10600  }
10601
10602  fn is_current(&self) -> bool {
10603    self.stop_identity().is_current()
10604  }
10605}
10606
10607#[derive(Clone, Copy)]
10608struct RuntimeStopIdentity {
10609  generation: u64,
10610  #[cfg(napi_runtime_os_threads)]
10611  executor_id: Option<u64>,
10612}
10613
10614impl RuntimeStopIdentity {
10615  fn is_current(self) -> bool {
10616    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10617      return true;
10618    }
10619    #[cfg(napi_runtime_os_threads)]
10620    if let Some(executor_id) = self.executor_id
10621      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10622        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10623    {
10624      return true;
10625    }
10626    false
10627  }
10628}
10629
10630enum RuntimeLifecycle {
10631  Initial,
10632  StoppedBeforeFirstUse,
10633  Running(RuntimeBackend),
10634  Stopping(RuntimeStopIdentity),
10635  StoppingWithoutBackend(ZeroBackendShutdownTarget),
10636  Stopped,
10637}
10638
10639#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10640enum ZeroBackendShutdownTarget {
10641  StoppedBeforeFirstUse,
10642  Stopped,
10643}
10644
10645impl ZeroBackendShutdownTarget {
10646  fn into_lifecycle(self) -> RuntimeLifecycle {
10647    match self {
10648      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10649      Self::Stopped => RuntimeLifecycle::Stopped,
10650    }
10651  }
10652}
10653
10654struct RuntimeState {
10655  options: RuntimeOptions,
10656  lifecycle: RuntimeLifecycle,
10657  rejected_drops: usize,
10658  /// Handoff between the two shutdown phases, `None` unless a
10659  /// `begin_shutdown` is outstanding. `RuntimeLifecycle::Stopping` carries no
10660  /// backend, so the single-call `shutdown` keeps it on its own stack; the
10661  /// split needs somewhere to park it across the host's yield window.
10662  ///
10663  /// Scoped to the generation the phase 1 was opened against: a successful
10664  /// `start` publishes a fresh one and drops it
10665  /// (`forget_shutdown_handoff`).
10666  ///
10667  /// A `ShutdownDrain::Backend` handoff is taken: whoever finds it takes it,
10668  /// drains it and publishes the stop; everybody else answers off the
10669  /// lifecycle. A `ShutdownDrain::ZeroBackend` handoff is not -- there is no
10670  /// backend to own. Nobody takes it. Every `finish_shutdown` that finds it
10671  /// waits on `rejected_drops`, and the one that observes zero clears the
10672  /// slot together with the publication, under one lock hold. Either way the
10673  /// handoff carries no owner and no credit -- see `phase_ever_opened`.
10674  ///
10675  /// The slot never goes empty while a stop is still owed. For `Backend` the
10676  /// taker swaps the handoff for `ShutdownDrain::Joining`, which stays until
10677  /// `Stopped` is published under this same lock. For `ZeroBackend` the
10678  /// handoff itself stays, untouched, until the target lifecycle is published
10679  /// under that lock. That keeps the non-blocking surfaces
10680  /// (`begin_shutdown`'s re-entry arm, `runtime_work_pending`) truthful for
10681  /// the whole wait instead of reading like a runtime with nothing pending.
10682  draining: Option<ShutdownDrain>,
10683  /// Sticky: `true` once any `begin_shutdown` has OPENED a phase (any arm that
10684  /// parks a handoff, `Backend` or `Settled`). Never cleared -- not by a
10685  /// finish, not by a `start`, not by a restart.
10686  ///
10687  /// It answers the one question `finish_shutdown` cannot answer from
10688  /// `draining` and the lifecycle alone. An empty handoff slot on a runtime
10689  /// that has never announced a stop is a host bug (phase 2 called on its
10690  /// own); an empty slot on a runtime that has announced one only means
10691  /// somebody else already completed, dropped or superseded it, which phase 2
10692  /// reports as `Ok(())`.
10693  ///
10694  /// A counter was tried here and is wrong by construction: the credit is
10695  /// spent by whichever thread performs the join, NOT by the thread that
10696  /// opened the phase. A concurrent in-protocol `finish_shutdown` therefore
10697  /// pairs off a single-call `shutdown`'s own phase, and that `shutdown` is
10698  /// then told its stop was never started -- 8.2% of the time on
10699  /// CurrentThread, after it had already published the stop. `(begin, start)`
10700  /// pairs leak credits on top of that, turning later orphan finishes into
10701  /// `Ok`. A bool can neither race nor leak.
10702  phase_ever_opened: bool,
10703}
10704
10705impl RuntimeState {
10706  /// Park an accepted phase 1's handoff and record that a stop has been
10707  /// announced. Both halves are set under the one lock every opening arm
10708  /// already holds, so no phase can be open without the flag.
10709  fn open_phase_one(&mut self, drain: ShutdownDrain) {
10710    self.phase_ever_opened = true;
10711    self.draining = Some(drain);
10712  }
10713
10714  /// Drop any parked handoff. Called by every path that publishes a
10715  /// generation (`start`, and `backend_locked`'s lazy first backend): a fresh
10716  /// generation owes nothing to a handoff opened against an earlier one.
10717  ///
10718  /// Dropping `draining` here is what keeps a stale handoff from answering
10719  /// for the new generation -- a surviving `Settled` would make the next
10720  /// `begin_shutdown` return from its outstanding-handoff arm without
10721  /// stopping anything, let `finish_shutdown` consume it and report `Ok(())`,
10722  /// and pin `runtime_work_pending` to `false`, so a `shutdown` would claim
10723  /// success while this generation kept running, kept accepting work and kept
10724  /// its MultiThread workers unjoined.
10725  ///
10726  /// It does NOT clear `phase_ever_opened`. Dropping the handoff answers the
10727  /// stale-handoff question completely; whether phase 2 may be called at all
10728  /// is a different question, and a `start` does not turn an outstanding phase
10729  /// 2 into a host bug -- it turns it into a no-op.
10730  fn forget_shutdown_handoff(&mut self) {
10731    // Only a settled handoff can ever reach here: `ShutdownDrain::Backend` is
10732    // parked together with the published `Stopping` and `Joining` replaces it
10733    // until `Stopped` is published, and every `start` arm waits out
10734    // `Stopping` instead of publishing over it. `ZeroBackend` is the same
10735    // argument one lifecycle over: it is parked together with the published
10736    // `StoppingWithoutBackend`, every `start` arm waits that out, and
10737    // `backend_locked` refuses to build a backend under it.
10738    debug_assert!(
10739      !matches!(
10740        self.draining,
10741        Some(ShutdownDrain::Backend(_) | ShutdownDrain::Joining(_) | ShutdownDrain::ZeroBackend(_))
10742      ),
10743      "a start must never publish over an outstanding backend drain"
10744    );
10745    self.draining = None;
10746  }
10747}
10748
10749/// What `RuntimeController::finish_shutdown` still owes an outstanding
10750/// `begin_shutdown`.
10751enum ShutdownDrain {
10752  /// `Stopping(identity)` is published and this backend is waiting to be
10753  /// drained and joined. Nobody owns it yet, so it is still up for grabs.
10754  Backend(RuntimeBackend),
10755  /// A `finish_shutdown` has TAKEN the handoff above and is inside the join.
10756  /// The marker stays until that thread publishes `Stopped`: the lifecycle
10757  /// reads `Stopping` for the whole window, so an empty slot would make a
10758  /// re-entering phase 1 park on `lifecycle_changed` and
10759  /// `runtime_work_pending` answer `false` about work the finisher is
10760  /// demonstrably still waiting for.
10761  ///
10762  /// The backend rides along only for the FIRST half of that window
10763  /// (`wait_until_idle`), which is the half that can still report work. The
10764  /// drainer clears it to `None` before releasing its own last reference,
10765  /// because that release is what drops the executor and lets the workers
10766  /// exit -- a marker that kept a clone alive would wedge the worker join
10767  /// forever (observed: `wait_for_all_workers` never returning). By then
10768  /// `wait_until_idle` has returned, so `None` is the truthful verdict for
10769  /// the remaining half.
10770  ///
10771  /// It is NOT up for grabs -- a second `finish_shutdown` that finds it waits
10772  /// for the publication and reports it, exactly as it does for an empty slot
10773  /// on a runtime that has announced a stop.
10774  Joining(Option<RuntimeBackend>),
10775  /// Phase 1 completed a zero-backend transition (`Initial`, or an already
10776  /// stopped runtime): phase 2 has nothing left to drain.
10777  Settled,
10778  /// Phase 1 announced a zero-backend stop (`Initial`, or an already stopped
10779  /// runtime) that still owes the retirement of one or more
10780  /// rejected-submission destructors before `target` may be published.
10781  ///
10782  /// Those destructors are arbitrary user `Drop` code running on the
10783  /// REJECTING thread, so waiting for them is exactly what phase 1 must not
10784  /// do: on the threaded WASI artifact that thread can be waiting for a
10785  /// JavaScript turn the phase-1 caller can only give by returning. The
10786  /// lifecycle reads `StoppingWithoutBackend(target)` for the whole window,
10787  /// exactly as `Stopping` + `Backend` do for a generation with a backend, and
10788  /// phase 2 does the wait and publishes the target.
10789  ///
10790  /// It is NOT taken out of the slot the way `Backend` is -- there is nothing
10791  /// to own -- so every `finish_shutdown` that finds it re-reads the slot
10792  /// after each wake and only the one that observes `rejected_drops == 0`
10793  /// under the published target publishes it.
10794  ZeroBackend(ZeroBackendShutdownTarget),
10795}
10796
10797#[cfg(test)]
10798thread_local! {
10799  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10800    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10801  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10802    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10803  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10804    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10805  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10806    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10807  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10808    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10809  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10810    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10811  static AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK:
10812    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10813  static BETWEEN_SHUTDOWN_PHASES_TEST_HOOK:
10814    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10815  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10816    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10817}
10818
10819#[cfg(test)]
10820fn run_before_runtime_submission_lock_test_hook() {
10821  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10822  {
10823    hook();
10824  }
10825}
10826
10827#[cfg(test)]
10828fn run_after_async_work_registration_test_hook() {
10829  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10830  {
10831    hook();
10832  }
10833}
10834
10835#[cfg(test)]
10836fn run_after_blocking_work_registration_test_hook() {
10837  if let Some(hook) =
10838    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10839  {
10840    hook();
10841  }
10842}
10843
10844#[cfg(test)]
10845fn run_after_current_thread_host_admission_test_hook() {
10846  if let Some(hook) =
10847    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10848  {
10849    hook();
10850  }
10851}
10852
10853#[cfg(test)]
10854fn run_before_initial_start_wait_test_hook() {
10855  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10856    hook();
10857  }
10858}
10859
10860#[cfg(test)]
10861fn run_after_generation_stop_publication_test_hook() {
10862  if let Some(hook) =
10863    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10864  {
10865    hook();
10866  }
10867}
10868
10869/// Runs on the thread that TOOK the outstanding phase 1, after the handoff is
10870/// out of the state and before it waits: anything a test starts from here
10871/// provably arrives at the handshake second.
10872#[cfg(test)]
10873fn run_after_shutdown_drain_taken_test_hook() {
10874  if let Some(hook) = AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10875    hook();
10876  }
10877}
10878
10879/// Runs inside the single-call `shutdown`, in the window between its two
10880/// halves where it holds no lock: this is exactly where a racing `start` can
10881/// land and drop the handoff phase 1 parked.
10882#[cfg(test)]
10883fn run_between_shutdown_phases_test_hook() {
10884  if let Some(hook) = BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10885    hook();
10886  }
10887}
10888
10889thread_local! {
10890  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10891    const { std::cell::Cell::new(0) };
10892}
10893
10894struct RejectedSubmissionDropContext;
10895
10896impl RejectedSubmissionDropContext {
10897  fn enter() -> Self {
10898    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10899    Self
10900  }
10901
10902  fn is_current() -> bool {
10903    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10904  }
10905}
10906
10907impl Drop for RejectedSubmissionDropContext {
10908  fn drop(&mut self) {
10909    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
10910      depth.set(
10911        depth
10912          .get()
10913          .checked_sub(1)
10914          .expect("rejected submission drop depth underflow"),
10915      );
10916    });
10917  }
10918}
10919
10920struct RuntimeController {
10921  state: Mutex<RuntimeState>,
10922  lifecycle_changed: Condvar,
10923  metrics: Arc<RuntimeMetrics>,
10924  // Lock-free mirror of `state.lifecycle`'s backend, read by `fast_register`
10925  // without the `state` lock. Invariant: store `Some(b)` on every transition
10926  // INTO `Running(b)` and `None` on every transition OUT -- exactly 3 sites
10927  // today (lazy-create, start, shutdown). Any new Running-boundary write MUST
10928  // mirror here, or `fast_register` routes work onto a superseded backend.
10929  current_backend: ArcSwapOption<RuntimeBackend>,
10930}
10931
10932struct RejectedSubmissionGuard<'a> {
10933  controller: &'a RuntimeController,
10934}
10935
10936impl Drop for RejectedSubmissionGuard<'_> {
10937  fn drop(&mut self) {
10938    let mut state = self
10939      .controller
10940      .state
10941      .lock()
10942      .unwrap_or_else(std::sync::PoisonError::into_inner);
10943    state.rejected_drops = state
10944      .rejected_drops
10945      .checked_sub(1)
10946      .expect("rejected submission drop count underflow");
10947    self.controller.lifecycle_changed.notify_all();
10948  }
10949}
10950
10951impl RuntimeController {
10952  fn new() -> Self {
10953    let options = RuntimeOptions::default()
10954      .validate()
10955      .expect("default async runtime options must be valid");
10956    Self {
10957      state: Mutex::new(RuntimeState {
10958        options,
10959        lifecycle: RuntimeLifecycle::Initial,
10960        rejected_drops: 0,
10961        draining: None,
10962        phase_ever_opened: false,
10963      }),
10964      lifecycle_changed: Condvar::new(),
10965      metrics: Arc::new(RuntimeMetrics::default()),
10966      current_backend: ArcSwapOption::empty(),
10967    }
10968  }
10969
10970  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10971    let options = options.validate()?;
10972    let mut state = self
10973      .state
10974      .lock()
10975      .unwrap_or_else(std::sync::PoisonError::into_inner);
10976    Self::ensure_configuration_mutable(&state)?;
10977    state.options = options;
10978    Ok(())
10979  }
10980
10981  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10982    self.configure_partial_inner(patch, |_| {})
10983  }
10984
10985  fn configure_partial_inner(
10986    &self,
10987    patch: RuntimeOptionsPatch,
10988    after_merge: impl FnOnce(&RuntimeOptions),
10989  ) -> Result<(), RuntimeConfigError> {
10990    let mut state = self
10991      .state
10992      .lock()
10993      .unwrap_or_else(std::sync::PoisonError::into_inner);
10994    Self::ensure_configuration_mutable(&state)?;
10995
10996    let mut options = state.options.clone();
10997    patch.apply_to(&mut options);
10998    after_merge(&options);
10999    let options = options.validate()?;
11000    state.options = options;
11001    Ok(())
11002  }
11003
11004  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11005    if matches!(
11006      &state.lifecycle,
11007      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
11008    ) {
11009      return Ok(());
11010    }
11011    Err(RuntimeConfigError(
11012      "the async runtime configuration is frozen; configure it before the first async call"
11013        .to_string(),
11014    ))
11015  }
11016
11017  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
11018    let Some(active_generation) = active_runtime_generation() else {
11019      return Ok(());
11020    };
11021    let lifecycle_generation = match &state.lifecycle {
11022      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11023      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11024      RuntimeLifecycle::Initial
11025      | RuntimeLifecycle::StoppedBeforeFirstUse
11026      | RuntimeLifecycle::StoppingWithoutBackend(_)
11027      | RuntimeLifecycle::Stopped => None,
11028    };
11029    if lifecycle_generation == Some(active_generation) {
11030      return Ok(());
11031    }
11032    Err(RuntimeConfigError(
11033      "cannot access or control the async runtime from work owned by a retired generation"
11034        .to_string(),
11035    ))
11036  }
11037
11038  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
11039    Self::ensure_active_generation_current(state)?;
11040    match &state.lifecycle {
11041      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
11042      RuntimeLifecycle::StoppedBeforeFirstUse
11043      | RuntimeLifecycle::Stopping(_)
11044      | RuntimeLifecycle::StoppingWithoutBackend(_)
11045      | RuntimeLifecycle::Stopped => {
11046        return Err(RuntimeConfigError(
11047          "the async runtime is stopped; call start before submitting work".to_string(),
11048        ));
11049      }
11050      RuntimeLifecycle::Initial => {}
11051    }
11052    if state.rejected_drops != 0 {
11053      return Err(RuntimeConfigError(
11054        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
11055      ));
11056    }
11057    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11058    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
11059    state.forget_shutdown_handoff();
11060    self.current_backend.store(Some(Arc::new(backend.clone())));
11061    Ok(backend)
11062  }
11063
11064  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
11065    let mut state = self
11066      .state
11067      .lock()
11068      .unwrap_or_else(std::sync::PoisonError::into_inner);
11069    self.backend_locked(&mut state)
11070  }
11071
11072  fn backend(&self) -> RuntimeBackend {
11073    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
11074  }
11075
11076  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
11077    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
11078      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
11079      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
11080      RuntimeLifecycle::Initial
11081      | RuntimeLifecycle::StoppedBeforeFirstUse
11082      | RuntimeLifecycle::StoppingWithoutBackend(_)
11083      | RuntimeLifecycle::Stopped => None,
11084    });
11085    state.rejected_drops += 1;
11086    generation
11087  }
11088
11089  /// Lock-free spawn fast path. Returns Some((backend, registration)) when the
11090  /// runtime is Running, the caller's generation is current, and registration
11091  /// succeeds -- all without taking the outer `state` lock. Returns None (fall
11092  /// to the locked slow path) for any miss: no current backend, retired
11093  /// generation, or a closed generation. Staleness is safe: see the safety
11094  /// invariant -- try_register_* re-checks `closed`, and wait_until_idle already
11095  /// gates on any registration that slips in during shutdown.
11096  #[inline]
11097  fn fast_register<R>(
11098    &self,
11099    register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
11100  ) -> Option<(Arc<RuntimeBackend>, R)> {
11101    let guard = self.current_backend.load();
11102    let backend = guard.as_ref()?; // None => Initial/Stopping/Stopped => slow path
11103    if let Some(active) = active_runtime_generation()
11104      && active != backend.generation()
11105    {
11106      return None; // retired generation => slow path yields exact error
11107    }
11108    let registration = register(&backend.work)?; // closed => None => slow path rejects
11109    // Hand back the already-loaded Arc (one refcount bump) rather than deep-
11110    // cloning `RuntimeBackend`'s two inner Arcs; both callers only borrow it.
11111    Some((Arc::clone(backend), registration))
11112  }
11113
11114  fn try_spawn_tracked<F, T>(
11115    &self,
11116    future: F,
11117  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11118  where
11119    F: Future<Output = T> + Send + 'static,
11120    T: Send + 'static,
11121  {
11122    #[cfg(test)]
11123    run_before_runtime_submission_lock_test_hook();
11124
11125    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
11126      #[cfg(test)]
11127      run_after_async_work_registration_test_hook();
11128      return Ok(spawn_registered(
11129        &backend,
11130        Arc::clone(&self.metrics),
11131        future,
11132        registration,
11133      ));
11134    }
11135
11136    let (backend, registration) = {
11137      let mut state = self
11138        .state
11139        .lock()
11140        .unwrap_or_else(std::sync::PoisonError::into_inner);
11141      let backend = match self.backend_locked(&mut state) {
11142        Ok(backend) => backend,
11143        Err(error) => {
11144          let generation = Self::begin_rejected_drop(&mut state);
11145          return Err((error, future, generation));
11146        }
11147      };
11148      let Some(registration) = backend.work.try_register_async() else {
11149        let generation = Self::begin_rejected_drop(&mut state);
11150        return Err((
11151          RuntimeConfigError(
11152            "the async runtime is stopped; call start before submitting work".to_string(),
11153          ),
11154          future,
11155          generation,
11156        ));
11157      };
11158      (backend, registration)
11159    };
11160    #[cfg(test)]
11161    run_after_async_work_registration_test_hook();
11162    Ok(spawn_registered(
11163      &backend,
11164      Arc::clone(&self.metrics),
11165      future,
11166      registration,
11167    ))
11168  }
11169
11170  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11171  where
11172    F: Future<Output = T> + Send + 'static,
11173    T: Send + 'static,
11174  {
11175    match self.try_spawn_tracked(future) {
11176      Ok(handle) => Ok(handle),
11177      Err((error, future, _generation)) => {
11178        drop(RejectedSubmissionGuard { controller: self });
11179        Err((error, future))
11180      }
11181    }
11182  }
11183
11184  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
11185  where
11186    F: Future<Output = T> + Send + 'static,
11187    T: Send + 'static,
11188  {
11189    match self.try_spawn_tracked(future) {
11190      Ok(handle) => handle,
11191      Err((error, future, generation)) => {
11192        let _rejection = RejectedSubmissionGuard { controller: self };
11193        let _drop_context = RejectedSubmissionDropContext::enter();
11194        let _generation = generation.map(RuntimeGenerationGuard::enter);
11195        let _ = catch_unwind_contained(|| drop(future));
11196        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11197          message: error.to_string(),
11198        }))))
11199      }
11200    }
11201  }
11202
11203  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
11204  where
11205    F: Future<Output = ()> + Send + 'static,
11206  {
11207    match self.try_spawn(future) {
11208      Ok(handle) => {
11209        handle.detach();
11210        Ok(())
11211      }
11212      Err((_, future)) => Err(future),
11213    }
11214  }
11215
11216  fn try_spawn_blocking_tracked<F, T>(
11217    &self,
11218    function: F,
11219  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
11220  where
11221    F: FnOnce() -> T + Send + 'static,
11222    T: Send + 'static,
11223  {
11224    #[cfg(test)]
11225    run_before_runtime_submission_lock_test_hook();
11226
11227    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
11228      #[cfg(test)]
11229      run_after_blocking_work_registration_test_hook();
11230      return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
11231    }
11232
11233    let (backend, registration) = {
11234      let mut state = self
11235        .state
11236        .lock()
11237        .unwrap_or_else(std::sync::PoisonError::into_inner);
11238      let backend = match self.backend_locked(&mut state) {
11239        Ok(backend) => backend,
11240        Err(error) => {
11241          let generation = Self::begin_rejected_drop(&mut state);
11242          return Err((error, function, generation));
11243        }
11244      };
11245      let Some(registration) = backend.work.try_register_work() else {
11246        let generation = Self::begin_rejected_drop(&mut state);
11247        return Err((
11248          RuntimeConfigError(
11249            "the async runtime is stopped; call start before submitting work".to_string(),
11250          ),
11251          function,
11252          generation,
11253        ));
11254      };
11255      (backend, registration)
11256    };
11257    #[cfg(test)]
11258    run_after_blocking_work_registration_test_hook();
11259    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11260  }
11261
11262  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11263  where
11264    F: FnOnce() -> T + Send + 'static,
11265    T: Send + 'static,
11266  {
11267    match self.try_spawn_blocking_tracked(function) {
11268      Ok(handle) => Ok(handle),
11269      Err((error, function, _generation)) => {
11270        drop(RejectedSubmissionGuard { controller: self });
11271        Err((error, function))
11272      }
11273    }
11274  }
11275
11276  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11277  where
11278    F: FnOnce() -> T + Send + 'static,
11279    T: Send + 'static,
11280  {
11281    match self.try_spawn_blocking_tracked(function) {
11282      Ok(handle) => handle,
11283      Err((error, function, generation)) => {
11284        let _rejection = RejectedSubmissionGuard { controller: self };
11285        let _drop_context = RejectedSubmissionDropContext::enter();
11286        let _generation = generation.map(RuntimeGenerationGuard::enter);
11287        let _ = catch_unwind_contained(|| drop(function));
11288        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11289          message: error.to_string(),
11290        }))))
11291      }
11292    }
11293  }
11294
11295  #[cfg(test)]
11296  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11297    self
11298      .try_block_on(future)
11299      .unwrap_or_else(|error| panic!("{error}"))
11300  }
11301
11302  fn try_block_on_scope(
11303    &self,
11304  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11305    let mut state = self
11306      .state
11307      .lock()
11308      .unwrap_or_else(std::sync::PoisonError::into_inner);
11309    let backend = match self.backend_locked(&mut state) {
11310      Ok(backend) => backend,
11311      Err(error) => {
11312        let generation = Self::begin_rejected_drop(&mut state);
11313        return Err((error, generation));
11314      }
11315    };
11316    let Some(registration) = backend.work.try_register_work() else {
11317      let generation = Self::begin_rejected_drop(&mut state);
11318      return Err((
11319        RuntimeConfigError(
11320          "the async runtime is stopped; call start before submitting work".to_string(),
11321        ),
11322        generation,
11323      ));
11324    };
11325    Ok((backend, registration))
11326  }
11327
11328  fn try_block_on(
11329    &self,
11330    future: Pin<&mut dyn Future<Output = ()>>,
11331  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11332    let (backend, registration) = match self.try_block_on_scope() {
11333      Ok(scope) => scope,
11334      Err((error, _generation)) => {
11335        drop(RejectedSubmissionGuard { controller: self });
11336        return Err(error);
11337      }
11338    };
11339    let _registration = registration;
11340    let _generation = RuntimeGenerationGuard::enter(backend.generation());
11341    Ok(backend.block_on(future))
11342  }
11343
11344  fn start(&self) -> Result<(), RuntimeConfigError> {
11345    let mut state = self
11346      .state
11347      .lock()
11348      .unwrap_or_else(std::sync::PoisonError::into_inner);
11349    loop {
11350      Self::ensure_active_generation_current(&state)?;
11351      match &state.lifecycle {
11352        // napi starts the runtime while the addon is still being registered.
11353        // Keep the first backend lazy so configureAsyncRuntime remains usable
11354        // until the first async binding call.
11355        RuntimeLifecycle::Initial => {
11356          if state.rejected_drops == 0 {
11357            // A started runtime owes nothing to a handoff parked against an
11358            // earlier generation. The stop that handoff announced still
11359            // happened, so `phase_ever_opened` stays set and a phase 2 that
11360            // arrives afterwards is a no-op instead of an error.
11361            state.forget_shutdown_handoff();
11362            return Ok(());
11363          }
11364          if RejectedSubmissionDropContext::is_current() {
11365            return Err(RuntimeConfigError(
11366              "cannot start the async runtime while a rejected submission is being destroyed"
11367                .to_string(),
11368            ));
11369          }
11370          #[cfg(test)]
11371          run_before_initial_start_wait_test_hook();
11372          state = self
11373            .lifecycle_changed
11374            .wait(state)
11375            .unwrap_or_else(std::sync::PoisonError::into_inner);
11376        }
11377        RuntimeLifecycle::Running(_) => {
11378          state.forget_shutdown_handoff();
11379          return Ok(());
11380        }
11381        RuntimeLifecycle::Stopping(identity) => {
11382          if identity.is_current() {
11383            return Err(RuntimeConfigError(
11384              "cannot restart the async runtime from work in the generation being stopped"
11385                .to_string(),
11386            ));
11387          }
11388          state = self
11389            .lifecycle_changed
11390            .wait(state)
11391            .unwrap_or_else(std::sync::PoisonError::into_inner);
11392        }
11393        RuntimeLifecycle::StoppingWithoutBackend(_) => {
11394          if RejectedSubmissionDropContext::is_current() {
11395            return Err(RuntimeConfigError(
11396              "cannot restart the async runtime while a rejected submission is being destroyed"
11397                .to_string(),
11398            ));
11399          }
11400          state = self
11401            .lifecycle_changed
11402            .wait(state)
11403            .unwrap_or_else(std::sync::PoisonError::into_inner);
11404        }
11405        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11406          if state.rejected_drops == 0 {
11407            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11408              state.lifecycle = RuntimeLifecycle::Initial;
11409              state.forget_shutdown_handoff();
11410              return Ok(());
11411            }
11412            break;
11413          }
11414          if RejectedSubmissionDropContext::is_current() {
11415            return Err(RuntimeConfigError(
11416              "cannot restart the async runtime while a rejected submission is being destroyed"
11417                .to_string(),
11418            ));
11419          }
11420          state = self
11421            .lifecycle_changed
11422            .wait(state)
11423            .unwrap_or_else(std::sync::PoisonError::into_inner);
11424        }
11425      }
11426    }
11427    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11428    self.current_backend.store(Some(Arc::new(backend.clone())));
11429    state.lifecycle = RuntimeLifecycle::Running(backend);
11430    state.forget_shutdown_handoff();
11431    Ok(())
11432  }
11433
11434  fn options(&self) -> RuntimeOptions {
11435    self
11436      .state
11437      .lock()
11438      .unwrap_or_else(std::sync::PoisonError::into_inner)
11439      .options
11440      .clone()
11441  }
11442
11443  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11444    let state = self
11445      .state
11446      .lock()
11447      .unwrap_or_else(std::sync::PoisonError::into_inner);
11448    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11449      return None;
11450    };
11451    match &backend.executor {
11452      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11453      #[cfg(napi_runtime_os_threads)]
11454      RuntimeExecutor::MultiThread(_) => None,
11455    }
11456  }
11457
11458  fn request_current_thread_drain(&self) {
11459    if let Some(executor) = self.running_current_thread_executor() {
11460      executor.request_drain_if_queued();
11461    }
11462  }
11463
11464  fn retire_rejected_current_thread_work(&self) {
11465    if let Some(executor) = self.running_current_thread_executor() {
11466      executor.retire_rejected_work_off_waker_stack();
11467    }
11468  }
11469
11470  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11471    let executor = {
11472      let state = self
11473        .state
11474        .lock()
11475        .unwrap_or_else(std::sync::PoisonError::into_inner);
11476      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11477        return;
11478      };
11479      match &backend.executor {
11480        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11481        #[cfg(napi_runtime_os_threads)]
11482        RuntimeExecutor::MultiThread(_) => return,
11483      }
11484    };
11485    executor.cancel_host_dispatch(failure);
11486  }
11487
11488  #[cfg(test)]
11489  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11490    let executor = {
11491      let state = self
11492        .state
11493        .lock()
11494        .unwrap_or_else(std::sync::PoisonError::into_inner);
11495      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11496        return;
11497      };
11498      match &backend.executor {
11499        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11500        #[cfg(napi_runtime_os_threads)]
11501        RuntimeExecutor::MultiThread(_) => return,
11502      }
11503    };
11504    executor.cancel_host_dispatch_for_test(dispatch);
11505  }
11506
11507  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11508    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11509    self.drive_current_thread_dispatch_with_lease(dispatch, true)
11510  }
11511
11512  fn drive_current_thread_dispatch_with_lease(
11513    &self,
11514    dispatch: u64,
11515    mark_serviced: bool,
11516  ) -> Option<CurrentThreadTaskCallbackLease> {
11517    let mut host_turn = {
11518      let state = self
11519        .state
11520        .lock()
11521        .unwrap_or_else(std::sync::PoisonError::into_inner);
11522      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11523        return None;
11524      };
11525      let executor = match &backend.executor {
11526        RuntimeExecutor::CurrentThread(executor) => executor,
11527        #[cfg(napi_runtime_os_threads)]
11528        RuntimeExecutor::MultiThread(_) => return None,
11529      };
11530      let host_turn = executor.try_admit_host_turn(dispatch)?;
11531      if mark_serviced {
11532        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11533      }
11534      host_turn
11535    };
11536    #[cfg(test)]
11537    run_after_current_thread_host_admission_test_hook();
11538    host_turn.drive();
11539    Some(CurrentThreadTaskCallbackLease::new(host_turn))
11540  }
11541
11542  #[cfg(test)]
11543  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11544    self
11545      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11546      .is_some()
11547  }
11548
11549  /// Phase 1 of the two-phase shutdown: publish `Stopping`, close admission,
11550  /// abort registered tasks, drop queued work, drain-fire timers and wake
11551  /// parkers -- then return WITHOUT waiting for work that is already running.
11552  ///
11553  /// `Ok(true)` = the announced stop still owes a wait, so the caller should
11554  /// let its host loop turn and poll [`Self::runtime_work_pending`] before
11555  /// phase 2. That is registered work of a generation with a backend, and --
11556  /// on a zero-backend stop -- a rejected submission still being destroyed on
11557  /// another thread, which phase 1 parks as `ShutdownDrain::ZeroBackend`
11558  /// rather than wait out. `Ok(false)` = there is nothing left and
11559  /// `finish_shutdown` will not block.
11560  ///
11561  /// This exists for a host that cannot yield inside a single-call
11562  /// `shutdown`: the threaded WASI artifact enters shutdown from the JS
11563  /// thread, so a blocking closure that still needs a JS turn could never get
11564  /// one while that thread sat in `wait_until_idle`.
11565  ///
11566  /// A `begin_shutdown` while one is outstanding opens no second phase and
11567  /// never waits: it answers with the current work-pending verdict, so the
11568  /// thread that owns the shutdown may re-enter it from a host turn. That
11569  /// holds for the whole of an outstanding stop, including the window in
11570  /// which another thread is already inside the join -- the handoff slot
11571  /// carries `ShutdownDrain::Joining` there rather than going empty, so this
11572  /// call answers from it instead of parking on the published `Stopping`.
11573  /// Phase 1s and phase 2s do not have to balance -- `finish_shutdown`
11574  /// completes the most recently announced stop and is idempotent.
11575  ///
11576  /// `start()` between the phases is NOT allowed -- it waits in `Stopping`
11577  /// until phase 2 publishes `Stopped`, and is rejected outright when it comes
11578  /// from work in the generation being stopped. Admission needs no guard of
11579  /// its own: `backend_locked` already rejects every submission under
11580  /// `Stopping`. The same holds for a zero-backend stop that still owes a
11581  /// rejected destruction: it publishes `StoppingWithoutBackend` for the whole
11582  /// window, and `start` waits there until phase 2 publishes the target. Only
11583  /// a zero-backend phase 1 that settled outright (`ShutdownDrain::Settled`,
11584  /// nothing published to wait in) lets a `start` through -- it drops the
11585  /// handoff, and the `finish_shutdown` that follows finds the fresh
11586  /// generation running and is a no-op `Ok(())`.
11587  fn begin_shutdown(&self) -> Result<bool, RuntimeConfigError> {
11588    let backend = {
11589      let mut state = self
11590        .state
11591        .lock()
11592        .unwrap_or_else(std::sync::PoisonError::into_inner);
11593      // Before the outstanding-handoff arm, not only inside the loop below:
11594      // that arm returns early, and work of a RETIRED generation must be told
11595      // so rather than be handed the current stop's verdict. The window it
11596      // covers is the whole of an outstanding stop, join included, so the
11597      // guard cannot sit behind it.
11598      Self::ensure_active_generation_current(&state)?;
11599      if let Some(drain) = &state.draining {
11600        let outstanding = match drain {
11601          // `Joining` answers exactly like `Backend`: the stop it carries has
11602          // been announced and is being completed right now, so this call has
11603          // nothing to open and nothing to wait for -- only a verdict to give.
11604          ShutdownDrain::Backend(backend) => Some(backend.clone()),
11605          ShutdownDrain::Joining(backend) => backend.clone(),
11606          // A zero-backend stop that still owes a rejected destruction. The
11607          // verdict lives in `state`, not in a backend, so it is answered
11608          // here -- under this lock, before the `drop(state)` below -- and,
11609          // like every other arm of this re-entry, it never waits. The
11610          // drop-context error keeps the surface identical to the
11611          // `StoppingWithoutBackend` and `Stopped` arms below: a destructor
11612          // can never drive the shutdown of the state it is blocking.
11613          ShutdownDrain::ZeroBackend(_) => {
11614            if RejectedSubmissionDropContext::is_current() {
11615              return Err(RuntimeConfigError(
11616                "cannot shut down the async runtime while a rejected submission is being destroyed"
11617                  .to_string(),
11618              ));
11619            }
11620            return Ok(state.rejected_drops != 0);
11621          }
11622          ShutdownDrain::Settled => None,
11623        };
11624        // A second phase 1 opens nothing: the stop this handoff carries has
11625        // already been announced, and one `finish_shutdown` completes it for
11626        // every caller. Answering from the handoff can never skip stopping a
11627        // live generation: the handoff belongs to the generation it was opened
11628        // against, and `forget_shutdown_handoff` drops it at every `start`.
11629        //
11630        // Answer off the lifecycle lock: probing a backend takes the
11631        // generation and executor locks, and the same re-entry must not
11632        // nest them under this one.
11633        drop(state);
11634        return Ok(outstanding.is_some_and(|backend| backend.work_pending()));
11635      }
11636      loop {
11637        Self::ensure_active_generation_current(&state)?;
11638        match &state.lifecycle {
11639          RuntimeLifecycle::Initial => {
11640            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11641              return Err(RuntimeConfigError(
11642                "cannot shut down the async runtime while a rejected submission is being destroyed"
11643                  .to_string(),
11644              ));
11645            }
11646            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11647            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11648            if state.rejected_drops != 0 {
11649              // Phase 1 NEVER waits. The outstanding retirement is another
11650              // thread's user `Drop`, and that thread may be waiting for a
11651              // host turn this one can only give by returning. Park the
11652              // announced stop for phase 2 and report it as pending work, so
11653              // the host keeps turning its loop -- the turn that destructor
11654              // may be waiting for.
11655              state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11656              self.lifecycle_changed.notify_all();
11657              return Ok(true);
11658            }
11659            state.lifecycle = target.into_lifecycle();
11660            state.open_phase_one(ShutdownDrain::Settled);
11661            self.lifecycle_changed.notify_all();
11662            return Ok(false);
11663          }
11664          RuntimeLifecycle::Running(backend) => {
11665            if backend.is_current() {
11666              return Err(RuntimeConfigError(
11667                "cannot shut down the async runtime from work running on that runtime".to_string(),
11668              ));
11669            }
11670            let identity = backend.stop_identity();
11671            let stop = backend.generation_stop();
11672            // Lock order is lifecycle -> stop publication. Publish before
11673            // mutating the lifecycle so a gated verdict that acquired the
11674            // publication mutex first wins while this state remains Running;
11675            // otherwise the verdict must observe the published stop.
11676            let stop_publication = stop.publication_guard();
11677            stop_publication.publish_stopping();
11678            let RuntimeLifecycle::Running(backend) =
11679              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11680            else {
11681              unreachable!();
11682            };
11683            self.current_backend.store(None);
11684            // Park the backend for phase 2. `Stopping` carries only an
11685            // identity, so without this slot the backend would live on this
11686            // stack and phase 1 could not return.
11687            state.open_phase_one(ShutdownDrain::Backend(backend.clone()));
11688            // Release before aborting tasks or running any user destruction.
11689            // Neither path may retain the publication mutex across scheduler
11690            // admission, lifecycle reentry, or a panic boundary.
11691            drop(stop_publication);
11692            break backend;
11693          }
11694          RuntimeLifecycle::Stopping(identity) => {
11695            if identity.is_current() {
11696              return Err(RuntimeConfigError(
11697                "cannot wait for async runtime shutdown from work in the generation being stopped"
11698                  .to_string(),
11699              ));
11700            }
11701            state = self
11702              .lifecycle_changed
11703              .wait(state)
11704              .unwrap_or_else(std::sync::PoisonError::into_inner);
11705          }
11706          RuntimeLifecycle::StoppingWithoutBackend(_) => {
11707            if RejectedSubmissionDropContext::is_current() {
11708              return Err(RuntimeConfigError(
11709                "cannot shut down the async runtime while a rejected submission is being destroyed"
11710                  .to_string(),
11711              ));
11712            }
11713            state = self
11714              .lifecycle_changed
11715              .wait(state)
11716              .unwrap_or_else(std::sync::PoisonError::into_inner);
11717          }
11718          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11719            if state.rejected_drops == 0 {
11720              state.open_phase_one(ShutdownDrain::Settled);
11721              return Ok(false);
11722            }
11723            if RejectedSubmissionDropContext::is_current() {
11724              return Err(RuntimeConfigError(
11725                "cannot shut down the async runtime while a rejected submission is being destroyed"
11726                  .to_string(),
11727              ));
11728            }
11729            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11730              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11731            } else {
11732              ZeroBackendShutdownTarget::Stopped
11733            };
11734            // Same rule as the `Initial` arm: announce the stop, hand the
11735            // wait to phase 2, and return without waiting on another thread's
11736            // destructor.
11737            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11738            state.open_phase_one(ShutdownDrain::ZeroBackend(target));
11739            self.lifecycle_changed.notify_all();
11740            return Ok(true);
11741          }
11742        }
11743      }
11744    };
11745
11746    #[cfg(test)]
11747    run_after_generation_stop_publication_test_hook();
11748    backend.begin_shutdown();
11749    Ok(backend.work_pending())
11750  }
11751
11752  /// Non-blocking poll for the window between the phases: `false` means
11753  /// `finish_shutdown` will not have to wait for user work. Reads the running
11754  /// generation when no phase 1 is outstanding, so a host may also use it to
11755  /// decide whether a shutdown would block at all.
11756  ///
11757  /// It stays truthful while a `finish_shutdown` is inside the join: that
11758  /// thread leaves `ShutdownDrain::Joining` in the slot, so this poll reports
11759  /// the very work the join is waiting for instead of `false`.
11760  fn runtime_work_pending(&self) -> bool {
11761    let backend = {
11762      let state = self
11763        .state
11764        .lock()
11765        .unwrap_or_else(std::sync::PoisonError::into_inner);
11766      match (&state.draining, &state.lifecycle) {
11767        // `Joining` is the in-flight drain: while it still carries the
11768        // backend, report the work the finisher is waiting for, not `false`.
11769        (Some(ShutdownDrain::Backend(backend) | ShutdownDrain::Joining(Some(backend))), _) => {
11770          backend.clone()
11771        }
11772        // A zero-backend stop that still owes a rejected destruction: that
11773        // destruction IS the pending work, and reporting it is what makes the
11774        // host keep turning its event loop -- the turn the destructor may be
11775        // waiting for. `false` here would send the loader straight into the
11776        // phase-2 join in the same synchronous turn.
11777        (Some(ShutdownDrain::ZeroBackend(_)), _) => return state.rejected_drops != 0,
11778        (None, RuntimeLifecycle::Running(backend)) => backend.clone(),
11779        // `Settled` is a handoff for a generation that had no backend, and
11780        // `forget_shutdown_handoff` drops it at every `start`, so it never
11781        // shadows a running generation. A backend-less `Joining` is a drain
11782        // past `wait_until_idle`: no user work can be left to report.
11783        (Some(ShutdownDrain::Joining(None) | ShutdownDrain::Settled), _) | (None, _) => {
11784          return false;
11785        }
11786      }
11787    };
11788    backend.work_pending()
11789  }
11790
11791  /// Phase 2 of the two-phase shutdown: wait for the generation to go idle,
11792  /// join the workers and publish `Stopped`. Everything after
11793  /// `begin_shutdown`, in the same order the single call runs it.
11794  ///
11795  /// It completes the most recently announced stop, and it is IDEMPOTENT. It
11796  /// owns no phase and counts none: whoever finds a `ShutdownDrain::Backend`
11797  /// handoff takes it, swaps it for `ShutdownDrain::Joining`, drains it and
11798  /// publishes the stop while retiring that marker, and every other caller
11799  /// answers off the lifecycle -- waiting out a join that is still in flight,
11800  /// then reporting `Ok(())` for the stop that was delivered. A
11801  /// `ShutdownDrain::ZeroBackend` handoff is never taken and never swapped:
11802  /// every caller that finds it waits on the outstanding rejected
11803  /// destruction, the one that observes it retired publishes the target and
11804  /// clears the slot, and the rest wake to that published stop and report it
11805  /// the same way. Phase 1s and phase 2s therefore do not have to balance,
11806  /// and two threads completing the same shutdown both get `Ok(())` instead
11807  /// of one of them being told its own stop never happened.
11808  ///
11809  /// It never stops a generation it did not begin. A `start` between the
11810  /// phases -- or any later restart -- drops a handoff that published nothing
11811  /// to wait in (`ShutdownDrain::Settled`) and publishes a fresh generation; a
11812  /// `finish_shutdown` that arrives afterwards with nothing outstanding is a
11813  /// no-op `Ok(())` and does NOT adopt that generation. A `ZeroBackend`
11814  /// handoff is never dropped that way: it publishes `StoppingWithoutBackend`
11815  /// for its whole window, so a `start` waits there until phase 2 publishes
11816  /// the target.
11817  ///
11818  /// The only missing-phase error left is phase 2 on a runtime that has never
11819  /// begun a shutdown at all (`phase_ever_opened == false`): the host called
11820  /// the second half without ever calling the first. Like `shutdown`, it also
11821  /// rejects a caller that is work of the generation being stopped, which could
11822  /// only wait for itself.
11823  fn finish_shutdown(&self) -> Result<(), RuntimeConfigError> {
11824    const STOPPING_WAIT_ERROR: &str =
11825      "cannot wait for async runtime shutdown from work in the generation being stopped";
11826    const REJECTED_DROP_ERROR: &str =
11827      "cannot shut down the async runtime while a rejected submission is being destroyed";
11828    const NO_PHASE_ERROR: &str =
11829      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown";
11830
11831    let backend = {
11832      let mut state = self
11833        .state
11834        .lock()
11835        .unwrap_or_else(std::sync::PoisonError::into_inner);
11836      // Every arm but `Backend` leaves this function; only that one falls
11837      // through to the take below.
11838      match &state.draining {
11839        Some(ShutdownDrain::Settled) => {
11840          // Phase 1 completed a zero-backend transition; nothing to drain.
11841          state.draining = None;
11842          return Ok(());
11843        }
11844        Some(ShutdownDrain::Backend(backend)) => {
11845          if backend.is_current() {
11846            return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11847          }
11848        }
11849        // Phase 1 announced a zero-backend stop that still owed the
11850        // retirement of a rejected submission's destructor. This is the wait
11851        // phase 1 refused to do. The marker is NOT taken -- there is nothing
11852        // to own -- so the slot is re-read after every wake: only the caller
11853        // that observes `rejected_drops == 0` while the slot still carries
11854        // this target publishes it, and a second finisher that wakes to an
11855        // already-published stop reports it like any other completed phase 2.
11856        Some(ShutdownDrain::ZeroBackend(_)) => loop {
11857          let target = match (&state.draining, &state.lifecycle) {
11858            (
11859              Some(ShutdownDrain::ZeroBackend(parked)),
11860              RuntimeLifecycle::StoppingWithoutBackend(published),
11861            ) if published == parked => *parked,
11862            // Another finisher published it, or a `start` superseded it.
11863            // Either way the stop this call was asked to complete has been
11864            // delivered.
11865            _ => return Ok(()),
11866          };
11867          if state.rejected_drops == 0 {
11868            // Retire the marker and publish the stop under the ONE lock
11869            // acquisition, exactly as the backend path does below: no
11870            // observer may ever see `StoppingWithoutBackend` without a drain,
11871            // or the terminal lifecycle with one.
11872            state.draining = None;
11873            state.lifecycle = target.into_lifecycle();
11874            self.lifecycle_changed.notify_all();
11875            return Ok(());
11876          }
11877          if RejectedSubmissionDropContext::is_current() {
11878            return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11879          }
11880          // `rejected_drops` can flicker 1 -> 0 -> 1 across the host's turns:
11881          // a fresh submission under `StoppingWithoutBackend` is rejected and
11882          // increments it again. Re-observe, exactly as the backend path's
11883          // tail wait does.
11884          state = self
11885            .lifecycle_changed
11886            .wait(state)
11887            .unwrap_or_else(std::sync::PoisonError::into_inner);
11888        },
11889        // `Joining`: another caller took the handoff and is inside the join.
11890        // Same answer as an empty slot on a runtime that announced a stop --
11891        // wait out the publication and report it. `phase_ever_opened` is
11892        // necessarily set here, so the orphan check below never fires.
11893        Some(ShutdownDrain::Joining(_)) | None => {
11894          if !state.phase_ever_opened {
11895            // No shutdown has ever been announced on this runtime, so there is
11896            // nothing for this call to complete: the host called the second
11897            // half without the first.
11898            return Err(RuntimeConfigError(NO_PHASE_ERROR.to_string()));
11899          }
11900          // A stop WAS announced. The slot holds an in-flight drain, or is
11901          // empty because a `start` dropped the handoff or the joining thread
11902          // has already published and retired its marker. Wait out a
11903          // join still in flight and report its publication -- a concurrent
11904          // `shutdown` waits in exactly this place today. Waiting inside this
11905          // arm, instead of re-entering the match, also keeps this caller from
11906          // adopting a phase 1 that was opened after it woke.
11907          loop {
11908            match &state.lifecycle {
11909              RuntimeLifecycle::Stopping(identity) => {
11910                if identity.is_current() {
11911                  return Err(RuntimeConfigError(STOPPING_WAIT_ERROR.to_string()));
11912                }
11913              }
11914              RuntimeLifecycle::StoppingWithoutBackend(_) => {
11915                if RejectedSubmissionDropContext::is_current() {
11916                  return Err(RuntimeConfigError(REJECTED_DROP_ERROR.to_string()));
11917                }
11918              }
11919              // Stopped/StoppedBeforeFirstUse: the publication an announced
11920              // stop was owed has happened, whichever thread performed the
11921              // join. Initial/Running: a `start` superseded that generation --
11922              // this call must report the stop, not stop the new generation.
11923              RuntimeLifecycle::StoppedBeforeFirstUse
11924              | RuntimeLifecycle::Stopped
11925              | RuntimeLifecycle::Initial
11926              | RuntimeLifecycle::Running(_) => return Ok(()),
11927            }
11928            state = self
11929              .lifecycle_changed
11930              .wait(state)
11931              .unwrap_or_else(std::sync::PoisonError::into_inner);
11932          }
11933        }
11934      }
11935      // Take the backend so this thread owns the drain and the slot can
11936      // never be taken twice -- but leave an explicit in-flight marker behind
11937      // instead of emptying the slot, so the non-blocking surfaces keep
11938      // telling the truth until `Stopped` is published below. Nothing can
11939      // overwrite it in between: `open_phase_one` is only reached with an
11940      // empty slot, and every `start` arm waits out the `Stopping` this
11941      // marker is published alongside.
11942      let Some(ShutdownDrain::Backend(backend)) = state.draining.take() else {
11943        unreachable!();
11944      };
11945      state.draining = Some(ShutdownDrain::Joining(Some(backend.clone())));
11946      backend
11947    };
11948
11949    #[cfg(test)]
11950    run_after_shutdown_drain_taken_test_hook();
11951    backend.wait_until_idle();
11952
11953    #[cfg(napi_runtime_os_threads)]
11954    let worker_lifecycle = backend.worker_lifecycle();
11955    // Hand the backend back out of the marker BEFORE dropping this thread's
11956    // own reference: `drop(backend)` below has to be the LAST one, because
11957    // releasing the executor is what lets the workers exit and what
11958    // `wait_for_all_workers` then joins. The marker itself stays -- phase 1
11959    // and the poll must keep answering without waiting for the rest of the
11960    // join -- it just stops carrying a backend, which `wait_until_idle`
11961    // having returned already makes the truthful answer.
11962    {
11963      let mut state = self
11964        .state
11965        .lock()
11966        .unwrap_or_else(std::sync::PoisonError::into_inner);
11967      debug_assert!(
11968        matches!(state.draining, Some(ShutdownDrain::Joining(Some(_)))),
11969        "nothing may disturb an in-flight drain marker before its publication"
11970      );
11971      state.draining = Some(ShutdownDrain::Joining(None));
11972    }
11973    drop(backend);
11974
11975    #[cfg(napi_runtime_os_threads)]
11976    if let Some(worker_lifecycle) = worker_lifecycle {
11977      worker_lifecycle.wait_for_all_workers();
11978    }
11979
11980    let mut state = self
11981      .state
11982      .lock()
11983      .unwrap_or_else(std::sync::PoisonError::into_inner);
11984    while state.rejected_drops != 0 {
11985      state = self
11986        .lifecycle_changed
11987        .wait(state)
11988        .unwrap_or_else(std::sync::PoisonError::into_inner);
11989    }
11990    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
11991    debug_assert!(
11992      matches!(state.draining, Some(ShutdownDrain::Joining(None))),
11993      "nothing may retire an in-flight drain marker but its own publication"
11994    );
11995    // Retire the marker and publish the stop under the ONE lock acquisition:
11996    // no observer may ever see `Stopping` without a drain, or `Stopped` with
11997    // one.
11998    state.draining = None;
11999    state.lifecycle = RuntimeLifecycle::Stopped;
12000    self.lifecycle_changed.notify_all();
12001    Ok(())
12002  }
12003
12004  /// Single-call shutdown: the two phases back to back. Ordering and
12005  /// semantics are exactly what this function has always done -- phase 1 ends
12006  /// at `RuntimeBackend::begin_shutdown` and phase 2 resumes at
12007  /// `wait_until_idle`, with nothing in between.
12008  ///
12009  /// It takes the lifecycle lock twice, so a `start` (or another shutdown) can
12010  /// land between the halves. Phase 2 being idempotent is what makes that
12011  /// window free: whether the stop this call announced is published by this
12012  /// thread, by a concurrent `finish_shutdown` that took the handoff first, or
12013  /// superseded by a racing `start`, this call reports `Ok(())` for the stop it
12014  /// performed -- it never has to be told that its own shutdown was never
12015  /// started.
12016  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
12017    self.begin_shutdown()?;
12018    #[cfg(test)]
12019    run_between_shutdown_phases_test_hook();
12020    self.finish_shutdown()
12021  }
12022}
12023
12024static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
12025
12026pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
12027  RUNTIME.configure(options)
12028}
12029
12030pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
12031  RUNTIME.configure_partial(patch)
12032}
12033
12034pub fn configured_options() -> RuntimeOptions {
12035  RUNTIME.options()
12036}
12037
12038pub fn is_multi_threaded() -> bool {
12039  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
12040}
12041
12042fn spawn_registered<F, T>(
12043  backend: &RuntimeBackend,
12044  metrics: Arc<RuntimeMetrics>,
12045  future: F,
12046  registration: (AbortRegistration, GenerationWorkGuard),
12047) -> JoinHandle<T>
12048where
12049  F: Future<Output = T> + Send + 'static,
12050  T: Send + 'static,
12051{
12052  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
12053  let generation = backend.generation();
12054  let (abort_registration, work_registration) = registration;
12055  #[cfg(napi_runtime_os_threads)]
12056  let dependency = Arc::new(TaskDependency::new_task(generation));
12057  #[cfg(napi_runtime_os_threads)]
12058  let task_dependency = Arc::clone(&dependency);
12059  let wrapped = async move {
12060    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
12061    let mut abortable = std::pin::pin!(abortable);
12062    let polled = futures::future::poll_fn(|cx| {
12063      #[cfg(napi_runtime_os_threads)]
12064      task_dependency.begin_poll();
12065      #[cfg(napi_runtime_os_threads)]
12066      let _dependency = TaskDependencyGuard::enter(&task_dependency);
12067      abortable.as_mut().poll(cx)
12068    });
12069    match AssertUnwindSafe(polled).catch_unwind().await {
12070      Ok(Ok(output)) => {
12071        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
12072        Ok(ContainedTaskOutput::new(output, generation))
12073      }
12074      Ok(Err(_)) => Err(JoinError {
12075        message: "async runtime stopped before the task completed".to_string(),
12076      }),
12077      Err(panic) => {
12078        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
12079        Err(panic_payload_to_join_error(panic))
12080      }
12081    }
12082  };
12083  let scheduler = backend.downgrade_executor();
12084  // `async-task` aborts the process if its future's destructor unwinds. Keep
12085  // the entire task generator behind our manually-dropped containment wrapper,
12086  // including the never-polled case where `future` is still a generator field.
12087  let (runnable, task) = async_task::spawn(
12088    RegisteredTaskFuture::new(wrapped, work_registration, generation),
12089    move |runnable| {
12090      scheduler.schedule(runnable);
12091    },
12092  );
12093  backend.schedule_submission(runnable);
12094  JoinHandle(JoinHandleInner::Task {
12095    task: task.fallible(),
12096    awaiter: None,
12097    #[cfg(napi_runtime_os_threads)]
12098    dependency,
12099  })
12100}
12101
12102pub fn spawn<F, T>(future: F) -> JoinHandle<T>
12103where
12104  F: Future<Output = T> + Send + 'static,
12105  T: Send + 'static,
12106{
12107  RUNTIME.spawn(future)
12108}
12109
12110/// Submit a future without consuming it when the runtime is not accepting work.
12111///
12112/// Lifecycle owners can retain the returned future and retry it after
12113/// [`start`] completes a runtime restart.
12114///
12115/// **On a wasm target, prefer this over [`spawn`].** Building the MultiThread
12116/// pool is lazy and fail-loud: the first async call is what calls
12117/// `wasi.thread-spawn`, and whether that works is a JavaScript fact the crate
12118/// cannot observe up front. Here the failure is an `Err` you can act on; the
12119/// infallible helpers turn it into a panic, which `panic = "abort"` then turns
12120/// into a dead instance.
12121pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12122where
12123  F: Future<Output = T> + Send + 'static,
12124  T: Send + 'static,
12125{
12126  RUNTIME.try_spawn(future)
12127}
12128
12129pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
12130where
12131  F: Future<Output = ()> + Send + 'static,
12132{
12133  RUNTIME.try_spawn_detached(future)
12134}
12135
12136pub fn spawn_detached<F>(future: F)
12137where
12138  F: Future<Output = ()> + Send + 'static,
12139{
12140  spawn(future).detach();
12141}
12142
12143pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
12144where
12145  F: FnOnce() -> T + Send + 'static,
12146  T: Send + 'static,
12147{
12148  RUNTIME.spawn_blocking(function)
12149}
12150
12151/// Submit blocking work without consuming it when the runtime is not accepting work.
12152///
12153/// Lifecycle owners can retain the returned closure and retry it after [`start`]
12154/// completes a runtime restart.
12155///
12156/// **On a wasm target, prefer this over [`spawn_blocking`]** -- see
12157/// [`try_spawn`] for why.
12158pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
12159where
12160  F: FnOnce() -> T + Send + 'static,
12161  T: Send + 'static,
12162{
12163  RUNTIME.try_spawn_blocking(function)
12164}
12165
12166fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
12167  let (backend, registration) = match controller.try_block_on_scope() {
12168    Ok(scope) => scope,
12169    Err((error, generation)) => {
12170      let _rejection = RejectedSubmissionGuard { controller };
12171      let _drop_context = RejectedSubmissionDropContext::enter();
12172      let _generation = generation.map(RuntimeGenerationGuard::enter);
12173      let _ = catch_unwind_contained(|| drop(future));
12174      panic!("{error}");
12175    }
12176  };
12177  let generation = backend.generation();
12178  let _registration = registration;
12179  let _generation = RuntimeGenerationGuard::enter(generation);
12180  let mut output = None;
12181  {
12182    let future = ContainedFuture::new(future, generation);
12183    let mut erased = std::pin::pin!(async {
12184      output = Some(future.await);
12185    });
12186    let _ = backend.block_on(erased.as_mut());
12187    // `erased` is destroyed before this scope exits, while the controller's
12188    // generation work and identity guards are live. `ContainedFuture` keeps a
12189    // poll panic from becoming a process-aborting double panic if the owned
12190    // future's destructor also unwinds.
12191  }
12192  output.expect("async runtime returned before the future completed")
12193}
12194
12195pub fn block_on<F: Future>(future: F) -> F::Output {
12196  block_on_with_controller(&RUNTIME, future)
12197}
12198
12199pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
12200  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
12201}
12202
12203/// Drive a borrowed future without consuming it when admission or shutdown fails.
12204///
12205/// **On a wasm target, prefer this over [`block_on`] / [`block_on_dyn`]** --
12206/// see [`try_spawn`] for why.
12207///
12208/// Under [`RuntimeFlavor::MultiThread`] this parks the CALLING thread on
12209/// `memory.atomic.wait32`, which a browser main thread is forbidden to do
12210/// (emnapi answers `napi_would_deadlock` there). The threaded wasm artifact is
12211/// a Node / WebContainer target today; inside a Worker it is fine.
12212pub fn try_block_on_dyn(
12213  future: Pin<&mut dyn Future<Output = ()>>,
12214) -> Result<(), RuntimeConfigError> {
12215  match RUNTIME.try_block_on(future)? {
12216    BlockOnOutcome::Completed => Ok(()),
12217    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
12218      "the async runtime stopped before block_on completed its future".to_string(),
12219    )),
12220  }
12221}
12222
12223pub fn start() -> Result<(), RuntimeConfigError> {
12224  RUNTIME.start()
12225}
12226
12227/// Shut the async runtime down: stop accepting work, cancel what is queued,
12228/// wait for what is running, join the workers.
12229///
12230/// Equivalent to [`begin_shutdown`] followed by [`finish_shutdown`], and
12231/// unchanged in ordering and semantics. It reports the shutdown it performed
12232/// even when another thread completes that shutdown, or restarts the runtime,
12233/// between the two halves.
12234pub fn shutdown() -> Result<(), RuntimeConfigError> {
12235  RUNTIME.shutdown()
12236}
12237
12238/// Phase 1 of a two-phase shutdown. Publishes the stop, closes admission,
12239/// aborts registered tasks, drops queued work, drain-fires timers and wakes
12240/// parkers -- and never waits. Never, including for a rejected submission
12241/// whose destructor is still running on another thread: that wait belongs to
12242/// [`finish_shutdown`] too.
12243///
12244/// `Ok(true)` means the announced stop still owes a wait -- registered work
12245/// that is still live, or a rejected submission still being destroyed: let
12246/// the host loop turn, poll [`runtime_work_pending`] until it answers `false`
12247/// (or a bound expires), then call [`finish_shutdown`]. `Ok(false)` means
12248/// [`finish_shutdown`] has nothing to wait for.
12249///
12250/// For a host that cannot yield inside [`shutdown`] -- on the threaded WASI
12251/// artifact the call comes from the JS thread, which is also the only thread
12252/// that can give a running blocking closure the turn it is waiting for.
12253///
12254/// Submissions are rejected between the phases, and [`start`] must not be
12255/// called there: it waits for phase 2 to publish the stop. A second
12256/// `begin_shutdown` before [`finish_shutdown`] neither waits nor starts a
12257/// second shutdown; it just re-reports [`runtime_work_pending`]. Calls of the
12258/// two phases do not have to balance: [`finish_shutdown`] completes the most
12259/// recently announced stop and is idempotent.
12260pub fn begin_shutdown() -> Result<bool, RuntimeConfigError> {
12261  RUNTIME.begin_shutdown()
12262}
12263
12264/// Non-blocking poll for the window between [`begin_shutdown`] and
12265/// [`finish_shutdown`]: `false` means phase 2 will not wait for user work.
12266///
12267/// Answers for the running generation when no shutdown is outstanding.
12268pub fn runtime_work_pending() -> bool {
12269  RUNTIME.runtime_work_pending()
12270}
12271
12272/// Phase 2 of a two-phase shutdown: wait for the generation to go idle, join
12273/// the workers and publish the stop.
12274///
12275/// Completes the most recently announced stop, whichever thread performs the
12276/// join, and is idempotent: extra calls, and calls that race another thread
12277/// completing the same shutdown, answer `Ok(())`. A call that arrives after a
12278/// [`start`] finds nothing outstanding and is a no-op -- it never stops the
12279/// generation that restart published. It errors only when no [`begin_shutdown`]
12280/// has EVER been called on this runtime, and when called from work of the
12281/// generation being stopped. Calling it while [`runtime_work_pending`] still
12282/// answers `true` is allowed -- it then blocks, which is what [`shutdown`]
12283/// does.
12284pub fn finish_shutdown() -> Result<(), RuntimeConfigError> {
12285  RUNTIME.finish_shutdown()
12286}
12287
12288fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
12289  for failure in completion.failed_dispatches {
12290    RUNTIME.cancel_current_thread_dispatch(failure);
12291  }
12292  if completion.redispatch {
12293    RUNTIME.request_current_thread_drain();
12294  }
12295  // Every host entry point lands here (delivery acknowledgement or failure,
12296  // driver unregistration, explicit dispatch cancellation). Work a
12297  // waker-stack failure transition moved aside is cancelled on this clean
12298  // stack even when the completion itself carried nothing, e.g. an env
12299  // cleanup hook unregistering a driver whose deliveries all failed already.
12300  RUNTIME.retire_rejected_current_thread_work();
12301}
12302
12303/// Acknowledge that one exact host delivery callback returned the successful
12304/// claim result from [`drive_current_thread_tasks`].
12305///
12306/// This releases the host's single-flight slot and submits at most its latest
12307/// still-needed pending delivery. The registry verifies that this exact
12308/// delivery was claimed; a forged success for an unclaimed delivery is treated
12309/// as failure. A stale acknowledgement from an unregistered host or retired
12310/// attempt is a no-op.
12311pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12312  complete_current_thread_task_delivery(
12313    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
12314  );
12315}
12316
12317/// Report that one exact host delivery callback failed.
12318///
12319/// Recovery begins only after no other live host attempt can still service the
12320/// same executor dispatch. The registration and delivery identities prevent a
12321/// stale failure from affecting another host or a re-registered generation.
12322pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
12323  complete_current_thread_task_delivery(
12324    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
12325  );
12326}
12327
12328/// Cancel an accepted CurrentThread host delivery that failed before it could
12329/// schedule a fresh turn.
12330///
12331/// `delivery` is the opaque registration-scoped driver capability,
12332/// not the executor's shared dispatch capability. Repeated or stale failures
12333/// are no-ops.
12334pub fn cancel_current_thread_task_dispatch(delivery: u64) {
12335  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
12336}
12337
12338/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
12339///
12340/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
12341/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
12342/// Returns a callback lease only when this exact delivery was claimed, admitted,
12343/// and driven. It returns `None` for stale/unregistered deliveries, before
12344/// backend creation, and on MultiThread. The host must retain the lease through
12345/// delivery acknowledgement/failure and callback payload destruction.
12346pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
12347  RUNTIME.drive_current_thread_tasks(delivery)
12348}
12349
12350pub fn reset_metrics() {
12351  RUNTIME.metrics.reset();
12352}
12353
12354pub fn metrics() -> RuntimeMetricsSnapshot {
12355  let options = RUNTIME.options();
12356  RUNTIME.metrics.snapshot(&options)
12357}
12358
12359#[cfg(test)]
12360// The wasm lanes gate out whole classes of tests (see the three rules below),
12361// which strands their helper fns, statics and driver registries. That is the
12362// point of the gates, not a defect, and 100+ dead-code warnings would bury a
12363// real one in the `cargo check --tests` guard CI runs on both WASI triples.
12364#[cfg_attr(target_family = "wasm", allow(dead_code))]
12365mod tests {
12366  use super::*;
12367
12368  // Three gates in here do NOT mean "can this build create OS threads?", so
12369  // the `napi_runtime_os_threads` rewrite deliberately does not reach them.
12370  // Each was verified by running the suite on `wasm32-wasip1-threads`:
12371  //
12372  // * `#[cfg(not(target_family = "wasm"))]` on a test that re-execs itself
12373  //   through `std::process::Command`: `std::env::current_exe()` is
12374  //   `Unsupported` on WASI (22 tests).
12375  // * `#[cfg(not(target_family = "wasm"))]` on a test that asserts the
12376  //   DEFAULT flavor is MultiThread: every wasm target keeps CurrentThread by
12377  //   default, which `RuntimeOptions::default` still decides with
12378  //   `cfg!(target_family = "wasm")` (2 tests).
12379  // * `panic = "unwind"` on a test that drives a panic through
12380  //   `catch_unwind`: both WASI targets are `panic = "abort"`, so every
12381  //   containment path this crate has is inert there and the panic takes the
12382  //   process down instead of becoming a `JoinError` (43 tests). The
12383  //   containment code itself still compiles and runs; only the tests that
12384  //   PROVE containment need an unwinding target.
12385  //
12386  // `napi_runtime_os_threads` itself is also the right gate for a test whose
12387  // HARNESS spawns a `std::thread` even though the code under test does not:
12388  // threadless WASI answers `thread_spawn` with `Os { code: 58, Unsupported }`
12389  // (13 tests).
12390
12391  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12392  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12393    LazyLock::new(Mutex::default);
12394  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12395  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12396  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12397  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12398    LazyLock::new(Mutex::default);
12399  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12400  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12401    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12402  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12403    LazyLock::new(Mutex::default);
12404  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
12405  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
12406    CurrentThreadTaskDriverRegistry,
12407  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12408  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12409  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
12410  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12411    LazyLock::new(Mutex::default);
12412  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12413    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12414  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12415    LazyLock::new(Mutex::default);
12416  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
12417  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12418    LazyLock::new(Mutex::default);
12419  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12420    LazyLock::new(Mutex::default);
12421  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
12422    CurrentThreadTaskDriverRegistry,
12423  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12424  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
12425    LazyLock::new(Mutex::default);
12426  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12427  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12428    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12429  static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12430    CurrentThreadTaskDriverRegistry,
12431  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12432  static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
12433    CurrentThreadTaskDriverRegistry,
12434  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12435  static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12436    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12437  static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12438    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12439  static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12440    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12441  static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12442    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12443  static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12444    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12445  static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
12446    CurrentThreadTaskDriverRegistry,
12447  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12448  static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
12449    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
12450  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
12451
12452  thread_local! {
12453    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
12454      const { std::cell::Cell::new(0) };
12455    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
12456      const { std::cell::Cell::new(0) };
12457  }
12458
12459  #[cfg(napi_runtime_os_threads)]
12460  struct StopPollProbe {
12461    polls: Arc<AtomicUsize>,
12462    first_poll: Option<std::sync::mpsc::Sender<()>>,
12463  }
12464
12465  #[cfg(napi_runtime_os_threads)]
12466  impl Future for StopPollProbe {
12467    type Output = ();
12468
12469    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12470      self.polls.fetch_add(1, Ordering::SeqCst);
12471      if let Some(first_poll) = self.first_poll.take() {
12472        first_poll.send(()).unwrap();
12473      }
12474      Poll::Pending
12475    }
12476  }
12477
12478  #[cfg(napi_runtime_os_threads)]
12479  struct PanickingJoinAwaiter {
12480    wakes: Arc<AtomicUsize>,
12481    drops: Arc<AtomicUsize>,
12482  }
12483
12484  #[cfg(napi_runtime_os_threads)]
12485  impl std::task::Wake for PanickingJoinAwaiter {
12486    fn wake(self: Arc<Self>) {
12487      self.wakes.fetch_add(1, Ordering::SeqCst);
12488      panic!("intentional JoinHandle awaiter wake panic");
12489    }
12490
12491    fn wake_by_ref(self: &Arc<Self>) {
12492      self.wakes.fetch_add(1, Ordering::SeqCst);
12493      panic!("intentional JoinHandle awaiter wake-by-ref panic");
12494    }
12495  }
12496
12497  #[cfg(napi_runtime_os_threads)]
12498  impl Drop for PanickingJoinAwaiter {
12499    fn drop(&mut self) {
12500      self.drops.fetch_add(1, Ordering::SeqCst);
12501      panic!("intentional JoinHandle awaiter destructor panic");
12502    }
12503  }
12504
12505  #[cfg(napi_runtime_os_threads)]
12506  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
12507    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
12508  }
12509
12510  #[cfg(napi_runtime_os_threads)]
12511  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
12512  enum JoinAwaiterWakerEventKind {
12513    Clone,
12514    Wake,
12515    WakeByRef,
12516    Drop,
12517  }
12518
12519  #[cfg(napi_runtime_os_threads)]
12520  struct JoinAwaiterWakerState {
12521    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
12522  }
12523
12524  #[cfg(napi_runtime_os_threads)]
12525  impl JoinAwaiterWakerState {
12526    fn record(&self, kind: JoinAwaiterWakerEventKind) {
12527      self
12528        .events
12529        .lock()
12530        .unwrap_or_else(std::sync::PoisonError::into_inner)
12531        .push((kind, active_runtime_generation()));
12532    }
12533
12534    fn clear(&self) {
12535      self
12536        .events
12537        .lock()
12538        .unwrap_or_else(std::sync::PoisonError::into_inner)
12539        .clear();
12540    }
12541
12542    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
12543      self
12544        .events
12545        .lock()
12546        .unwrap_or_else(std::sync::PoisonError::into_inner)
12547        .clone()
12548    }
12549  }
12550
12551  #[cfg(napi_runtime_os_threads)]
12552  struct JoinAwaiterWakerToken {
12553    state: Arc<JoinAwaiterWakerState>,
12554  }
12555
12556  #[cfg(napi_runtime_os_threads)]
12557  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
12558    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12559    token.state.record(JoinAwaiterWakerEventKind::Clone);
12560    let clone = Box::new(JoinAwaiterWakerToken {
12561      state: Arc::clone(&token.state),
12562    });
12563    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
12564  }
12565
12566  #[cfg(napi_runtime_os_threads)]
12567  unsafe fn wake_join_awaiter_waker(data: *const ()) {
12568    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12569    token.state.record(JoinAwaiterWakerEventKind::Wake);
12570  }
12571
12572  #[cfg(napi_runtime_os_threads)]
12573  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
12574    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
12575    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
12576  }
12577
12578  #[cfg(napi_runtime_os_threads)]
12579  unsafe fn drop_join_awaiter_waker(data: *const ()) {
12580    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
12581    token.state.record(JoinAwaiterWakerEventKind::Drop);
12582  }
12583
12584  #[cfg(napi_runtime_os_threads)]
12585  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
12586    clone_join_awaiter_waker,
12587    wake_join_awaiter_waker,
12588    wake_join_awaiter_waker_by_ref,
12589    drop_join_awaiter_waker,
12590  );
12591
12592  #[cfg(napi_runtime_os_threads)]
12593  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
12594    let token = Box::new(JoinAwaiterWakerToken { state });
12595    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
12596    unsafe { Waker::from_raw(raw) }
12597  }
12598
12599  #[cfg(napi_runtime_os_threads)]
12600  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
12601    wait_until("the block_on driver to sleep", || {
12602      stop
12603        .parkers
12604        .lock()
12605        .unwrap_or_else(std::sync::PoisonError::into_inner)
12606        .iter()
12607        .filter_map(Weak::upgrade)
12608        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12609    });
12610  }
12611
12612  #[cfg(panic = "unwind")]
12613  #[test]
12614  fn unique_ids_fail_before_wraparound() {
12615    let counter = AtomicU64::new(u64::MAX - 1);
12616    assert_eq!(
12617      next_unique_id(&counter, "test id space exhausted"),
12618      u64::MAX - 1
12619    );
12620
12621    let exhausted = catch_unwind(AssertUnwindSafe(|| {
12622      next_unique_id(&counter, "test id space exhausted");
12623    }));
12624    assert!(exhausted.is_err());
12625    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
12626  }
12627
12628  #[cfg(panic = "unwind")]
12629  #[test]
12630  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
12631    let work = GenerationWork::new();
12632    {
12633      let mut state = work.state.lock().unwrap();
12634      state.next_task_id = u64::MAX;
12635    }
12636
12637    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
12638    assert!(exhausted.is_err());
12639
12640    let state = work.state.lock().unwrap();
12641    assert_eq!(state.next_task_id, u64::MAX);
12642    assert_eq!(state.active, 0);
12643    assert!(state.abort_handles.is_empty());
12644  }
12645
12646  #[cfg(panic = "unwind")]
12647  #[test]
12648  fn caught_panic_payload_destructor_runs() {
12649    struct DropSignal(Arc<AtomicBool>);
12650
12651    impl Drop for DropSignal {
12652      fn drop(&mut self) {
12653        self.0.store(true, Ordering::SeqCst);
12654      }
12655    }
12656
12657    let dropped = Arc::new(AtomicBool::new(false));
12658    let payload = DropSignal(Arc::clone(&dropped));
12659
12660    assert!(
12661      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
12662      "the original panic must remain contained"
12663    );
12664    assert!(
12665      dropped.load(Ordering::SeqCst),
12666      "an ordinary caught panic payload must be destroyed"
12667    );
12668  }
12669
12670  #[cfg(panic = "unwind")]
12671  #[test]
12672  fn hostile_caught_panic_payload_destructor_remains_contained() {
12673    struct PanicOnDrop(Arc<AtomicBool>);
12674
12675    impl Drop for PanicOnDrop {
12676      fn drop(&mut self) {
12677        self.0.store(true, Ordering::SeqCst);
12678        panic!("caught panic payload destructor escaped");
12679      }
12680    }
12681
12682    let dropped = Arc::new(AtomicBool::new(false));
12683    let payload = PanicOnDrop(Arc::clone(&dropped));
12684    let result = catch_unwind(AssertUnwindSafe(|| {
12685      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12686    }));
12687
12688    assert!(
12689      result.is_ok(),
12690      "the payload destructor's nested panic must remain contained"
12691    );
12692    assert!(
12693      result.unwrap().is_none(),
12694      "the original panic must remain contained"
12695    );
12696    assert!(
12697      dropped.load(Ordering::SeqCst),
12698      "the hostile payload destructor must be attempted"
12699    );
12700  }
12701
12702  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12703    CurrentThreadHostDispatchResult::Accepted
12704  }
12705
12706  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12707    CurrentThreadHostDispatchResult::Unavailable
12708  }
12709
12710  fn count_current_thread_recovery_host_dispatch(
12711    _dispatch: u64,
12712  ) -> CurrentThreadHostDispatchResult {
12713    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12714    CurrentThreadHostDispatchResult::Accepted
12715  }
12716
12717  fn reset_current_thread_recovery_host_dispatches() {
12718    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12719  }
12720
12721  fn current_thread_recovery_host_dispatches() -> usize {
12722    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12723  }
12724
12725  fn accept_only_first_current_thread_host_dispatch(
12726    _dispatch: u64,
12727  ) -> CurrentThreadHostDispatchResult {
12728    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12729      let previous = dispatches.get();
12730      dispatches.set(previous + 1);
12731      previous == 0
12732    }) {
12733      CurrentThreadHostDispatchResult::Accepted
12734    } else {
12735      CurrentThreadHostDispatchResult::Unavailable
12736    }
12737  }
12738
12739  fn reset_current_thread_one_shot_host_dispatches() {
12740    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12741  }
12742
12743  fn current_thread_one_shot_host_dispatches() -> usize {
12744    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12745  }
12746
12747  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12748    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12749    CurrentThreadHostDispatchResult::Accepted
12750  }
12751
12752  fn count_current_thread_blocking_budget_host_dispatch(
12753    _dispatch: u64,
12754  ) -> CurrentThreadHostDispatchResult {
12755    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12756    CurrentThreadHostDispatchResult::Accepted
12757  }
12758
12759  fn count_current_thread_admission_host_dispatch(
12760    _dispatch: u64,
12761  ) -> CurrentThreadHostDispatchResult {
12762    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12763    CurrentThreadHostDispatchResult::Accepted
12764  }
12765
12766  fn count_current_thread_interleaved_recovery_host_dispatch(
12767    _dispatch: u64,
12768  ) -> CurrentThreadHostDispatchResult {
12769    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12770    CurrentThreadHostDispatchResult::Accepted
12771  }
12772
12773  fn dispatch_late_registration_current_thread_tasks(
12774    dispatch: u64,
12775  ) -> CurrentThreadHostDispatchResult {
12776    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12777  }
12778
12779  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12780    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12781      .lock()
12782      .unwrap()
12783      .push(dispatch);
12784    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
12785  }
12786
12787  fn dispatch_authoritative_panic_current_thread_tasks(
12788    dispatch: u64,
12789  ) -> CurrentThreadHostDispatchResult {
12790    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12791      .lock()
12792      .unwrap()
12793      .push(dispatch);
12794    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
12795      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
12796      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
12797      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
12798    }
12799  }
12800
12801  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12802    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12803  }
12804
12805  fn dispatch_waker_stack_failure_current_thread_tasks(
12806    dispatch: u64,
12807  ) -> CurrentThreadHostDispatchResult {
12808    CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12809  }
12810
12811  fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
12812    dispatch: u64,
12813  ) -> CurrentThreadHostDispatchResult {
12814    CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12815  }
12816
12817  fn dispatch_submission_failure_current_thread_tasks(
12818    dispatch: u64,
12819  ) -> CurrentThreadHostDispatchResult {
12820    CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12821  }
12822
12823  fn dispatch_retirement_turn_current_thread_tasks(
12824    dispatch: u64,
12825  ) -> CurrentThreadHostDispatchResult {
12826    CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
12827  }
12828
12829  fn dispatch_nested_retirement_current_thread_tasks(
12830    dispatch: u64,
12831  ) -> CurrentThreadHostDispatchResult {
12832    CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
12833  }
12834
12835  fn dispatch_sustained_producer_current_thread_tasks(
12836    dispatch: u64,
12837  ) -> CurrentThreadHostDispatchResult {
12838    CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
12839  }
12840
12841  fn dispatch_retirement_retry_current_thread_tasks(
12842    dispatch: u64,
12843  ) -> CurrentThreadHostDispatchResult {
12844    CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
12845  }
12846
12847  fn dispatch_final_retirement_offer_current_thread_tasks(
12848    dispatch: u64,
12849  ) -> CurrentThreadHostDispatchResult {
12850    CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
12851  }
12852
12853  fn dispatch_inline_final_offer_current_thread_tasks(
12854    dispatch: u64,
12855  ) -> CurrentThreadHostDispatchResult {
12856    CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
12857  }
12858
12859  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
12860    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12861      .lock()
12862      .unwrap()
12863      .push(dispatch);
12864    assert!(
12865      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12866      "injected CurrentThread host dispatch panic"
12867    );
12868    CurrentThreadHostDispatchResult::Accepted
12869  }
12870
12871  fn panic_once_current_thread_armed_republish_host_dispatch(
12872    dispatch: u64,
12873  ) -> CurrentThreadHostDispatchResult {
12874    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12875      .lock()
12876      .unwrap()
12877      .push(dispatch);
12878    assert!(
12879      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12880      "injected armed CurrentThread host dispatch panic"
12881    );
12882    CurrentThreadHostDispatchResult::Accepted
12883  }
12884
12885  fn record_current_thread_republish_rearm_dispatch(
12886    dispatch: u64,
12887  ) -> CurrentThreadHostDispatchResult {
12888    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12889      .lock()
12890      .unwrap()
12891      .push(dispatch);
12892    CurrentThreadHostDispatchResult::Accepted
12893  }
12894
12895  fn reject_initial_current_thread_publication_once(
12896    dispatch: u64,
12897  ) -> CurrentThreadHostDispatchResult {
12898    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12899      .lock()
12900      .unwrap();
12901    dispatches.push(dispatch);
12902    if dispatches.len() == 1 {
12903      CurrentThreadHostDispatchResult::Unavailable
12904    } else {
12905      CurrentThreadHostDispatchResult::Accepted
12906    }
12907  }
12908
12909  fn reject_current_thread_replacement_publication_once(
12910    dispatch: u64,
12911  ) -> CurrentThreadHostDispatchResult {
12912    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12913      .lock()
12914      .unwrap();
12915    dispatches.push(dispatch);
12916    if dispatches.len() == 2 {
12917      CurrentThreadHostDispatchResult::Unavailable
12918    } else {
12919      CurrentThreadHostDispatchResult::Accepted
12920    }
12921  }
12922
12923  fn fail_current_thread_replacement_publication_once(
12924    dispatch: u64,
12925  ) -> CurrentThreadHostDispatchResult {
12926    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12927      .lock()
12928      .unwrap()
12929      .push(dispatch);
12930    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
12931  }
12932
12933  fn count_current_thread_shutdown_republish_host_dispatch(
12934    _dispatch: u64,
12935  ) -> CurrentThreadHostDispatchResult {
12936    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12937    CurrentThreadHostDispatchResult::Accepted
12938  }
12939
12940  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12941    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12942    CurrentThreadHostDispatchResult::Accepted
12943  }
12944
12945  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
12946    let pending = executor.dispatch_pending.load(Ordering::Acquire);
12947    if pending != 0 {
12948      return pending;
12949    }
12950    let dispatch = next_current_thread_dispatch_id();
12951    match executor.dispatch_pending.compare_exchange(
12952      0,
12953      dispatch,
12954      Ordering::AcqRel,
12955      Ordering::Acquire,
12956    ) {
12957      Ok(_) => dispatch,
12958      Err(pending) => pending,
12959    }
12960  }
12961
12962  fn current_thread_dispatch_failure(
12963    dispatch: u64,
12964    epoch: u64,
12965  ) -> CurrentThreadTaskDispatchFailure {
12966    CurrentThreadTaskDispatchFailure { dispatch, epoch }
12967  }
12968
12969  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
12970    let executor = controller
12971      .running_current_thread_executor()
12972      .expect("the test controller must have a running CurrentThread executor");
12973    current_thread_dispatch(&executor)
12974  }
12975
12976  #[test]
12977  fn current_thread_executor_drives_spawned_tasks() {
12978    let metrics = Arc::new(RuntimeMetrics::default());
12979    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
12980    let scheduler = Arc::clone(&executor);
12981    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
12982      scheduler.schedule(runnable);
12983    });
12984
12985    executor.schedule(runnable);
12986
12987    let mut output = None;
12988    {
12989      let mut future = std::pin::pin!(async {
12990        output = Some(task.await);
12991      });
12992      executor.block_on(future.as_mut());
12993    }
12994    assert_eq!(output, Some(42));
12995    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
12996    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
12997  }
12998
12999  #[cfg(napi_runtime_os_threads)]
13000  #[test]
13001  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
13002    use std::sync::mpsc;
13003
13004    let executor = Arc::new(CurrentThreadExecutor::with_detection(
13005      Arc::new(RuntimeMetrics::default()),
13006      false,
13007      None,
13008    ));
13009    let polls = Arc::new(AtomicUsize::new(0));
13010    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13011    let (outcome_tx, outcome_rx) = mpsc::channel();
13012    let driver_executor = Arc::clone(&executor);
13013    let driver_polls = Arc::clone(&polls);
13014    let driver = std::thread::spawn(move || {
13015      let mut future = std::pin::pin!(StopPollProbe {
13016        polls: driver_polls,
13017        first_poll: Some(first_poll_tx)
13018      });
13019      outcome_tx
13020        .send(driver_executor.block_on(future.as_mut()))
13021        .unwrap();
13022    });
13023
13024    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13025    wait_for_generation_parker_to_sleep(&executor.stop);
13026    executor.stop.begin_shutdown();
13027
13028    assert_eq!(
13029      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13030      BlockOnOutcome::Stopped
13031    );
13032    driver.join().unwrap();
13033    assert_eq!(
13034      polls.load(Ordering::SeqCst),
13035      1,
13036      "a shutdown wake must be checked before the next CurrentThread user poll"
13037    );
13038  }
13039
13040  #[cfg(napi_runtime_os_threads)]
13041  #[test]
13042  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
13043    use std::sync::mpsc;
13044
13045    let metrics = Arc::new(RuntimeMetrics::default());
13046    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13047      Arc::clone(&metrics),
13048      accept_current_thread_host_dispatch,
13049    ));
13050    let polls = Arc::new(AtomicUsize::new(0));
13051    let task_polls = Arc::clone(&polls);
13052    let (runnable, task) = async_task::spawn(
13053      async move {
13054        task_polls.fetch_add(1, Ordering::SeqCst);
13055      },
13056      |_| {},
13057    );
13058    executor.schedule(runnable);
13059    let dispatch = current_thread_dispatch(&executor);
13060
13061    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
13062    let (release_claim_tx, release_claim_rx) = mpsc::channel();
13063    let driver_executor = Arc::clone(&executor);
13064    let driver = std::thread::spawn(move || {
13065      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
13066        *slot.borrow_mut() = Some(Box::new(move || {
13067          claim_locked_tx.send(()).unwrap();
13068          release_claim_rx.recv().unwrap();
13069        }));
13070      });
13071      driver_executor.drive_host_turn_for_dispatch(dispatch);
13072    });
13073    claim_locked_rx
13074      .recv_timeout(Duration::from_secs(2))
13075      .expect("the admitted host turn must pause before its runnable claim");
13076
13077    executor.stop.publish_stopping();
13078    release_claim_tx.send(()).unwrap();
13079    driver.join().unwrap();
13080
13081    assert_eq!(
13082      polls.load(Ordering::SeqCst),
13083      0,
13084      "stop publication must outrank a later runnable claim by an already-admitted host turn"
13085    );
13086    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13087
13088    executor.begin_shutdown();
13089    assert!(
13090      futures::executor::block_on(task.fallible()).is_none(),
13091      "shutdown must cancel the runnable rejected by the stopped claim"
13092    );
13093    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13094  }
13095
13096  #[cfg(napi_runtime_os_threads)]
13097  #[test]
13098  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
13099    use std::sync::mpsc;
13100
13101    let metrics = Arc::new(RuntimeMetrics::default());
13102    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13103      Arc::clone(&metrics),
13104      accept_current_thread_host_dispatch,
13105    ));
13106    let polls = Arc::new(AtomicUsize::new(0));
13107    let task_polls = Arc::clone(&polls);
13108    let (runnable, task) = async_task::spawn(
13109      async move {
13110        task_polls.fetch_add(1, Ordering::SeqCst);
13111      },
13112      |_| {},
13113    );
13114    executor.schedule(runnable);
13115
13116    let queue = executor
13117      .queue
13118      .lock()
13119      .unwrap_or_else(std::sync::PoisonError::into_inner);
13120    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
13121    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
13122    let cancelling_executor = Arc::clone(&executor);
13123    let cancellation = std::thread::spawn(move || {
13124      let scheduler = cancelling_executor
13125        .scheduler_idle_lock
13126        .lock()
13127        .unwrap_or_else(std::sync::PoisonError::into_inner);
13128      let cancelling = cancelling_executor
13129        .begin_failed_dispatch_cancellation()
13130        .expect("the test must publish terminal dispatch cancellation");
13131      drop(scheduler);
13132      cancellation_published_tx.send(()).unwrap();
13133      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
13134      cancellation_done_tx.send(()).unwrap();
13135    });
13136    cancellation_published_rx
13137      .recv_timeout(Duration::from_secs(2))
13138      .expect("terminal dispatch cancellation must publish before queue draining");
13139
13140    let claimant_executor = Arc::clone(&executor);
13141    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
13142    assert!(
13143      !claimant.join().unwrap(),
13144      "a runnable claim after terminal cancellation publication must be rejected"
13145    );
13146    assert_eq!(polls.load(Ordering::SeqCst), 0);
13147    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13148    assert!(
13149      matches!(
13150        cancellation_done_rx.try_recv(),
13151        Err(mpsc::TryRecvError::Empty)
13152      ),
13153      "terminal cancellation must still be waiting to drain the queued runnable"
13154    );
13155
13156    drop(queue);
13157    cancellation_done_rx
13158      .recv_timeout(Duration::from_secs(2))
13159      .expect("terminal cancellation must drain the rejected runnable");
13160    cancellation.join().unwrap();
13161    assert!(
13162      futures::executor::block_on(task.fallible()).is_none(),
13163      "terminal cancellation must cancel the runnable it prevented from starting"
13164    );
13165    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13166  }
13167
13168  #[cfg(napi_runtime_os_threads)]
13169  #[test]
13170  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
13171    use std::sync::mpsc;
13172
13173    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
13174    let polls = Arc::new(AtomicUsize::new(0));
13175    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13176    let (outcome_tx, outcome_rx) = mpsc::channel();
13177    let driver_executor = Arc::clone(&executor);
13178    let driver_polls = Arc::clone(&polls);
13179    let driver = std::thread::spawn(move || {
13180      let mut future = std::pin::pin!(StopPollProbe {
13181        polls: driver_polls,
13182        first_poll: Some(first_poll_tx)
13183      });
13184      outcome_tx
13185        .send(driver_executor.block_on(future.as_mut()))
13186        .unwrap();
13187    });
13188
13189    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13190    wait_for_generation_parker_to_sleep(&executor.stop);
13191    executor.stop.begin_shutdown();
13192
13193    assert_eq!(
13194      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13195      BlockOnOutcome::Stopped
13196    );
13197    driver.join().unwrap();
13198    assert_eq!(
13199      polls.load(Ordering::SeqCst),
13200      1,
13201      "a shutdown wake must be checked before the next parking block_on user poll"
13202    );
13203  }
13204
13205  #[cfg(napi_runtime_os_threads)]
13206  #[test]
13207  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
13208    use std::sync::mpsc;
13209
13210    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
13211    let polls = Arc::new(AtomicUsize::new(0));
13212    let (first_poll_tx, first_poll_rx) = mpsc::channel();
13213    let (outcome_tx, outcome_rx) = mpsc::channel();
13214    let driver_executor = Arc::clone(&executor);
13215    let driver_polls = Arc::clone(&polls);
13216    let driver = std::thread::spawn(move || {
13217      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
13218      let mut future = std::pin::pin!(StopPollProbe {
13219        polls: driver_polls,
13220        first_poll: Some(first_poll_tx)
13221      });
13222      outcome_tx
13223        .send(driver_executor.block_on(future.as_mut()))
13224        .unwrap();
13225    });
13226
13227    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13228    wait_for_generation_parker_to_sleep(&executor.stop);
13229    executor.stop.begin_shutdown();
13230
13231    assert_eq!(
13232      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13233      BlockOnOutcome::Stopped
13234    );
13235    driver.join().unwrap();
13236    assert_eq!(
13237      polls.load(Ordering::SeqCst),
13238      1,
13239      "a shutdown wake must be checked before the next cooperative user poll"
13240    );
13241  }
13242
13243  #[test]
13244  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
13245    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13246      Arc::new(RuntimeMetrics::default()),
13247      accept_current_thread_host_dispatch,
13248    ));
13249    let pending = u64::MAX - 1;
13250    executor.dispatch_pending.store(pending, Ordering::Release);
13251    let mut allocations = 0;
13252
13253    executor.request_drain_with(|| {
13254      allocations += 1;
13255      u64::MAX
13256    });
13257
13258    assert_eq!(allocations, 0);
13259    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
13260  }
13261
13262  #[cfg(napi_runtime_os_threads)]
13263  #[test]
13264  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
13265    use std::sync::mpsc;
13266
13267    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13268      Arc::new(RuntimeMetrics::default()),
13269      accept_current_thread_host_dispatch,
13270    ));
13271    let dispatch = current_thread_dispatch(&executor);
13272    let scheduler = executor
13273      .scheduler_idle_lock
13274      .lock()
13275      .unwrap_or_else(std::sync::PoisonError::into_inner);
13276    let (claiming_tx, claiming_rx) = mpsc::channel();
13277    let (claimed_tx, claimed_rx) = mpsc::channel();
13278    let claiming_executor = Arc::clone(&executor);
13279    let claimant = std::thread::spawn(move || {
13280      claiming_tx.send(()).unwrap();
13281      claimed_tx
13282        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
13283        .unwrap();
13284    });
13285    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13286
13287    executor.stop.begin_shutdown();
13288    drop(scheduler);
13289
13290    assert!(
13291      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
13292      "an old-generation dispatch must not claim a host turn after stop becomes visible"
13293    );
13294    claimant.join().unwrap();
13295    assert_eq!(
13296      executor.dispatch_pending.load(Ordering::Acquire),
13297      dispatch,
13298      "rejecting the stopped claim must not consume its stale capability"
13299    );
13300  }
13301
13302  #[cfg(napi_runtime_os_threads)]
13303  #[test]
13304  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
13305    use std::sync::mpsc;
13306
13307    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13308    let metrics = Arc::new(RuntimeMetrics::default());
13309    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13310      Arc::clone(&metrics),
13311      count_current_thread_stopped_host_dispatch,
13312    ));
13313    let scheduler = executor
13314      .scheduler_idle_lock
13315      .lock()
13316      .unwrap_or_else(std::sync::PoisonError::into_inner);
13317    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13318    let scheduling_executor = Arc::clone(&executor);
13319    let scheduling = std::thread::spawn(move || {
13320      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13321        *slot.borrow_mut() = Some(Box::new(move || {
13322          publication_blocked_tx.send(()).unwrap();
13323        }));
13324      });
13325      let scheduler = Arc::clone(&scheduling_executor);
13326      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13327        scheduler.schedule(runnable);
13328      });
13329      task.detach();
13330      scheduling_executor.schedule(runnable);
13331    });
13332
13333    publication_blocked_rx
13334      .recv_timeout(Duration::from_secs(2))
13335      .expect("dispatch publication must block behind the scheduler-idle mutex");
13336    executor.begin_shutdown();
13337    drop(scheduler);
13338    scheduling.join().unwrap();
13339    executor.wait_until_scheduler_idle();
13340
13341    assert_eq!(
13342      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
13343      0,
13344      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
13345    );
13346    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13347    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13348  }
13349
13350  #[cfg(napi_runtime_os_threads)]
13351  #[test]
13352  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
13353    use std::{sync::mpsc, time::Duration};
13354
13355    let metrics = Arc::new(RuntimeMetrics::default());
13356    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13357      Arc::clone(&metrics),
13358      accept_current_thread_host_dispatch,
13359    ));
13360    let scheduler = Arc::clone(&executor);
13361    let completed = Arc::new(AtomicBool::new(false));
13362    let completed_task = Arc::clone(&completed);
13363    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
13364    let shared = async move {
13365      wake_rx.await.expect("wake sender dropped");
13366    }
13367    .boxed()
13368    .shared();
13369    let (runnable, task) = async_task::spawn(
13370      async move {
13371        shared.await;
13372        completed_task.store(true, Ordering::SeqCst);
13373      },
13374      move |runnable| scheduler.schedule(runnable),
13375    );
13376
13377    executor.schedule(runnable);
13378    assert!(
13379      !completed.load(Ordering::SeqCst),
13380      "accepted host dispatch must defer the first poll"
13381    );
13382    executor.drive_host_turn();
13383
13384    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
13385    std::thread::spawn(move || {
13386      wake_tx.send(()).expect("shared wake receiver dropped");
13387      send_returned_tx.send(()).unwrap();
13388    });
13389    send_returned_rx
13390      .recv_timeout(Duration::from_secs(1))
13391      .expect("Shared wake re-entered its own waker mutex through inline polling");
13392    assert!(
13393      !completed.load(Ordering::SeqCst),
13394      "the wake must remain queued until a fresh host turn"
13395    );
13396
13397    executor.drive_host_turn();
13398    futures::executor::block_on(task);
13399    assert!(completed.load(Ordering::SeqCst));
13400    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13401  }
13402
13403  #[cfg(napi_runtime_os_threads)]
13404  #[test]
13405  fn current_thread_hostless_wake_is_enqueue_only() {
13406    use std::{sync::mpsc, time::Duration};
13407
13408    struct LockOnSecondPoll {
13409      gate: Arc<Mutex<()>>,
13410      first: bool,
13411      waker: Option<mpsc::Sender<Waker>>,
13412      completed: Arc<AtomicBool>,
13413    }
13414
13415    impl Future for LockOnSecondPoll {
13416      type Output = ();
13417
13418      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
13419        if self.first {
13420          self.first = false;
13421          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
13422          return Poll::Pending;
13423        }
13424        drop(self.gate.lock().unwrap());
13425        self.completed.store(true, Ordering::SeqCst);
13426        Poll::Ready(())
13427      }
13428    }
13429
13430    let metrics = Arc::new(RuntimeMetrics::default());
13431    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
13432    let gate = Arc::new(Mutex::new(()));
13433    let completed = Arc::new(AtomicBool::new(false));
13434    let (waker_tx, waker_rx) = mpsc::channel();
13435    let scheduler = Arc::clone(&executor);
13436    let (runnable, task) = async_task::spawn(
13437      LockOnSecondPoll {
13438        gate: Arc::clone(&gate),
13439        first: true,
13440        waker: Some(waker_tx),
13441        completed: Arc::clone(&completed),
13442      },
13443      move |runnable| scheduler.schedule(runnable),
13444    );
13445    executor.schedule(runnable);
13446    executor.drive_host_turn();
13447    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
13448
13449    let (returned_tx, returned_rx) = mpsc::channel();
13450    let wake_gate = Arc::clone(&gate);
13451    let waking = std::thread::spawn(move || {
13452      let _held = wake_gate.lock().unwrap();
13453      waker.wake();
13454      returned_tx.send(()).unwrap();
13455    });
13456    returned_rx
13457      .recv_timeout(Duration::from_secs(1))
13458      .expect("hostless wake must enqueue without polling under the caller's lock");
13459    waking.join().unwrap();
13460    assert!(!completed.load(Ordering::SeqCst));
13461
13462    executor.drive_host_turn();
13463    futures::executor::block_on(task);
13464    assert!(completed.load(Ordering::SeqCst));
13465  }
13466
13467  #[cfg(napi_runtime_os_threads)]
13468  #[test]
13469  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
13470    use std::sync::mpsc;
13471
13472    let controller = Arc::new(current_thread_controller(
13473      "current-thread-hostless-late-work",
13474    ));
13475    let backend = controller.backend();
13476    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
13477      panic!("configured CurrentThread backend must create a CurrentThread executor");
13478    };
13479    let executor = Arc::clone(executor);
13480    drop(backend);
13481
13482    let (value_tx, value_rx) = oneshot::channel();
13483    let (done_tx, done_rx) = mpsc::channel();
13484    let block_on_controller = Arc::clone(&controller);
13485    let runner = std::thread::spawn(move || {
13486      let mut output = None;
13487      let outcome = {
13488        let mut future = std::pin::pin!(async {
13489          output = Some(value_rx.await.expect("scheduled sender must deliver"));
13490        });
13491        block_on_controller.block_on(future.as_mut())
13492      };
13493      done_tx.send((outcome, output)).unwrap();
13494    });
13495
13496    wait_until(
13497      "the hostless CurrentThread block_on driver to sleep",
13498      || {
13499        executor
13500          .stop
13501          .parkers
13502          .lock()
13503          .unwrap_or_else(std::sync::PoisonError::into_inner)
13504          .iter()
13505          .filter_map(Weak::upgrade)
13506          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13507      },
13508    );
13509
13510    controller
13511      .try_spawn_detached(async move {
13512        value_tx
13513          .send(37usize)
13514          .expect("block_on receiver must remain alive");
13515      })
13516      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
13517
13518    let (outcome, output) = done_rx
13519      .recv_timeout(Duration::from_secs(2))
13520      .expect("queue publication must wake the sleeping explicit driver");
13521    assert_eq!(outcome, BlockOnOutcome::Completed);
13522    assert_eq!(output, Some(37));
13523    runner.join().unwrap();
13524    controller.shutdown().unwrap();
13525  }
13526
13527  #[cfg(napi_runtime_os_threads)]
13528  #[test]
13529  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
13530    use std::sync::mpsc;
13531
13532    struct BlockingPoll {
13533      entered: Option<mpsc::Sender<()>>,
13534      release: mpsc::Receiver<()>,
13535    }
13536
13537    impl Future for BlockingPoll {
13538      type Output = ();
13539
13540      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
13541        self.entered.take().unwrap().send(()).unwrap();
13542        self.release.recv().unwrap();
13543        Poll::Ready(())
13544      }
13545    }
13546
13547    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13548      Arc::new(RuntimeMetrics::default()),
13549      reject_current_thread_host_dispatch,
13550    ));
13551    let (value_tx, value_rx) = oneshot::channel();
13552    let (older_done_tx, older_done_rx) = mpsc::channel();
13553    let older_executor = Arc::clone(&executor);
13554    let older = std::thread::spawn(move || {
13555      let mut value = None;
13556      {
13557        let mut future = std::pin::pin!(async {
13558          value = Some(value_rx.await.unwrap());
13559        });
13560        assert_eq!(
13561          older_executor.block_on(future.as_mut()),
13562          BlockOnOutcome::Completed
13563        );
13564      }
13565      older_done_tx.send(value.unwrap()).unwrap();
13566    });
13567    wait_until("the older CurrentThread driver to sleep", || {
13568      executor
13569        .stop
13570        .parkers
13571        .lock()
13572        .unwrap_or_else(std::sync::PoisonError::into_inner)
13573        .iter()
13574        .filter_map(Weak::upgrade)
13575        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
13576    });
13577
13578    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
13579    let (release_newer_tx, release_newer_rx) = mpsc::channel();
13580    let newer_executor = Arc::clone(&executor);
13581    let newer = std::thread::spawn(move || {
13582      let mut future = std::pin::pin!(BlockingPoll {
13583        entered: Some(newer_entered_tx),
13584        release: release_newer_rx
13585      });
13586      assert_eq!(
13587        newer_executor.block_on(future.as_mut()),
13588        BlockOnOutcome::Completed
13589      );
13590    });
13591    newer_entered_rx
13592      .recv_timeout(Duration::from_secs(1))
13593      .expect("the newer driver must be blocked inside its poll");
13594
13595    let scheduler = Arc::clone(&executor);
13596    let (runnable, task) = async_task::spawn(
13597      async move {
13598        value_tx.send(41usize).unwrap();
13599      },
13600      move |runnable| scheduler.schedule(runnable),
13601    );
13602    task.detach();
13603    executor.schedule(runnable);
13604
13605    assert_eq!(
13606      older_done_rx
13607        .recv_timeout(Duration::from_secs(2))
13608        .expect("queue publication must also wake the older sleeping driver"),
13609      41
13610    );
13611    release_newer_tx.send(()).unwrap();
13612    older.join().unwrap();
13613    newer.join().unwrap();
13614  }
13615
13616  #[test]
13617  fn current_thread_shutdown_cancels_host_queued_task() {
13618    use std::sync::mpsc;
13619
13620    struct DropSignal(mpsc::Sender<()>);
13621
13622    impl Drop for DropSignal {
13623      fn drop(&mut self) {
13624        self.0.send(()).unwrap();
13625      }
13626    }
13627
13628    let metrics = Arc::new(RuntimeMetrics::default());
13629    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13630      Arc::clone(&metrics),
13631      accept_current_thread_host_dispatch,
13632    ));
13633    let scheduler = Arc::clone(&executor);
13634    let (dropped_tx, dropped_rx) = mpsc::channel();
13635    let signal = DropSignal(dropped_tx);
13636    let (runnable, task) = async_task::spawn(
13637      async move {
13638        let _signal = signal;
13639        futures::future::pending::<()>().await;
13640      },
13641      move |runnable| scheduler.schedule(runnable),
13642    );
13643
13644    executor.schedule(runnable);
13645    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13646    executor.begin_shutdown();
13647
13648    dropped_rx
13649      .recv_timeout(Duration::from_secs(1))
13650      .expect("shutdown must drop a task queued behind a host turn");
13651    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13652    assert!(
13653      futures::executor::block_on(task.fallible()).is_none(),
13654      "the queued task handle must resolve as cancelled"
13655    );
13656  }
13657
13658  #[test]
13659  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
13660    reset_current_thread_recovery_host_dispatches();
13661    let metrics = Arc::new(RuntimeMetrics::default());
13662    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13663      Arc::clone(&metrics),
13664      count_current_thread_recovery_host_dispatch,
13665    ));
13666    let scheduler = Arc::clone(&executor);
13667    let completed = Arc::new(AtomicBool::new(false));
13668    let completed_task = Arc::clone(&completed);
13669    let (runnable, task) = async_task::spawn(
13670      async move {
13671        completed_task.store(true, Ordering::SeqCst);
13672      },
13673      move |runnable| scheduler.schedule(runnable),
13674    );
13675
13676    executor.schedule(runnable);
13677    assert_eq!(current_thread_recovery_host_dispatches(), 1);
13678    let lost_dispatch = current_thread_dispatch(&executor);
13679
13680    // Model another host registering while the first host still owns an
13681    // accepted delivery. The executor capability stays stable; the host
13682    // registry gives the new registration a distinct delivery token.
13683    executor.request_drain_if_queued();
13684    assert_eq!(
13685      current_thread_recovery_host_dispatches(),
13686      2,
13687      "the newly registered host must receive the pending executor dispatch"
13688    );
13689    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13690    executor.drive_host_turn_for_dispatch(lost_dispatch);
13691    futures::executor::block_on(task);
13692    assert!(completed.load(Ordering::SeqCst));
13693    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13694
13695    let second_completed = Arc::new(AtomicBool::new(false));
13696    let second_completed_task = Arc::clone(&second_completed);
13697    let second_scheduler = Arc::clone(&executor);
13698    let (second_runnable, second_task) = async_task::spawn(
13699      async move {
13700        second_completed_task.store(true, Ordering::SeqCst);
13701      },
13702      move |runnable| second_scheduler.schedule(runnable),
13703    );
13704    executor.schedule(second_runnable);
13705    assert_eq!(
13706      current_thread_recovery_host_dispatches(),
13707      3,
13708      "work queued after the stale callback must receive a new dispatch"
13709    );
13710    let third_dispatch = current_thread_dispatch(&executor);
13711
13712    // The already-consumed capability remains stale relative to dispatch 2 and
13713    // cannot service its work.
13714    executor.drive_host_turn_for_dispatch(lost_dispatch);
13715    assert!(!second_completed.load(Ordering::SeqCst));
13716    executor.drive_host_turn_for_dispatch(third_dispatch);
13717    futures::executor::block_on(second_task);
13718    assert!(second_completed.load(Ordering::SeqCst));
13719
13720    let third_scheduler = Arc::clone(&executor);
13721    let (third_runnable, third_task) =
13722      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13723    executor.schedule(third_runnable);
13724    assert_eq!(
13725      current_thread_recovery_host_dispatches(),
13726      4,
13727      "a stale callback must not leave the dispatch latch stuck"
13728    );
13729    executor.drive_host_turn();
13730    futures::executor::block_on(third_task);
13731  }
13732
13733  #[test]
13734  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13735    let metrics = Arc::new(RuntimeMetrics::default());
13736    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13737      Arc::clone(&metrics),
13738      accept_current_thread_host_dispatch,
13739    ));
13740    let scheduler = Arc::clone(&executor);
13741    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13742      scheduler.schedule(runnable)
13743    });
13744
13745    executor.schedule(runnable);
13746    let failed_dispatch = current_thread_dispatch(&executor);
13747    executor.cancel_host_dispatch_for_test(failed_dispatch);
13748    let recovery_dispatch = current_thread_dispatch(&executor);
13749    assert_eq!(
13750      executor.recovery_dispatch.load(Ordering::Acquire),
13751      recovery_dispatch
13752    );
13753
13754    executor.request_drain_if_queued();
13755    let new_host_dispatch = current_thread_dispatch(&executor);
13756    assert_eq!(new_host_dispatch, recovery_dispatch);
13757    assert_eq!(
13758      executor.recovery_dispatch.load(Ordering::Acquire),
13759      recovery_dispatch
13760    );
13761    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13762
13763    executor.drive_host_turn_for_dispatch(new_host_dispatch);
13764    assert_eq!(futures::executor::block_on(task), 17);
13765  }
13766
13767  #[cfg(napi_runtime_os_threads)]
13768  #[test]
13769  fn current_thread_host_admission_serializes_dispatch_publication() {
13770    use std::sync::mpsc;
13771
13772    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13773    let metrics = Arc::new(RuntimeMetrics::default());
13774    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13775      Arc::clone(&metrics),
13776      count_current_thread_admission_host_dispatch,
13777    ));
13778    let first_scheduler = Arc::clone(&executor);
13779    let (first_runnable, first_task) =
13780      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13781    first_task.detach();
13782    executor.schedule(first_runnable);
13783    let dispatch = current_thread_dispatch(&executor);
13784    assert_eq!(
13785      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13786      1
13787    );
13788
13789    let (claimed_tx, claimed_rx) = mpsc::channel();
13790    let (release_tx, release_rx) = mpsc::channel();
13791    let driver_executor = Arc::clone(&executor);
13792    let driver = std::thread::spawn(move || {
13793      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
13794        *slot.borrow_mut() = Some(Box::new(move || {
13795          claimed_tx.send(()).unwrap();
13796          release_rx.recv().unwrap();
13797        }));
13798      });
13799      driver_executor.drive_host_turn_for_dispatch(dispatch);
13800    });
13801    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13802
13803    let second_ran = Arc::new(AtomicBool::new(false));
13804    let second_ran_task = Arc::clone(&second_ran);
13805    let scheduling_executor = Arc::clone(&executor);
13806    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13807    let (scheduled_tx, scheduled_rx) = mpsc::channel();
13808    let scheduling = std::thread::spawn(move || {
13809      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13810        *slot.borrow_mut() = Some(Box::new(move || {
13811          publication_blocked_tx.send(()).unwrap();
13812        }));
13813      });
13814      let scheduler = Arc::clone(&scheduling_executor);
13815      let (runnable, task) = async_task::spawn(
13816        async move {
13817          second_ran_task.store(true, Ordering::SeqCst);
13818        },
13819        move |runnable| scheduler.schedule(runnable),
13820      );
13821      task.detach();
13822      scheduling_executor.schedule(runnable);
13823      scheduled_tx.send(()).unwrap();
13824    });
13825
13826    publication_blocked_rx
13827      .recv_timeout(Duration::from_secs(2))
13828      .unwrap();
13829    assert!(
13830      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
13831      "dispatch publication must wait until host admission publishes its draining role"
13832    );
13833    assert_eq!(
13834      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13835      1,
13836      "the admission gap must not publish a duplicate host dispatch"
13837    );
13838
13839    release_tx.send(()).unwrap();
13840    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13841    scheduling.join().unwrap();
13842    driver.join().unwrap();
13843    if !second_ran.load(Ordering::SeqCst) {
13844      executor.drive_host_turn();
13845    }
13846    assert!(second_ran.load(Ordering::SeqCst));
13847    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13848  }
13849
13850  #[test]
13851  fn current_thread_host_turn_yields_after_runnable_budget() {
13852    struct SelfWakingFuture {
13853      remaining: usize,
13854    }
13855
13856    impl Future for SelfWakingFuture {
13857      type Output = ();
13858
13859      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13860        if self.remaining == 0 {
13861          return Poll::Ready(());
13862        }
13863        self.remaining -= 1;
13864        cx.waker().wake_by_ref();
13865        Poll::Pending
13866      }
13867    }
13868
13869    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13870    let metrics = Arc::new(RuntimeMetrics::default());
13871    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13872      Arc::clone(&metrics),
13873      count_current_thread_budget_host_dispatch,
13874    ));
13875    let scheduler = Arc::clone(&executor);
13876    let (runnable, task) = async_task::spawn(
13877      SelfWakingFuture {
13878        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13879      },
13880      move |runnable| scheduler.schedule(runnable),
13881    );
13882
13883    executor.schedule(runnable);
13884    executor.drive_host_turn();
13885
13886    assert_eq!(
13887      metrics.runnable_polls.load(Ordering::Relaxed),
13888      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
13889    );
13890    assert_eq!(
13891      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13892      2,
13893      "remaining hot work must continue through a fresh host turn"
13894    );
13895
13896    executor.drive_host_turn();
13897    futures::executor::block_on(task);
13898    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13899  }
13900
13901  #[cfg(napi_runtime_os_threads)]
13902  #[test]
13903  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
13904    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
13905
13906    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13907    let metrics = Arc::new(RuntimeMetrics::default());
13908    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13909      Arc::clone(&metrics),
13910      count_current_thread_blocking_budget_host_dispatch,
13911    ));
13912    let work = GenerationWork::new();
13913    let observed = Arc::new(Mutex::new(Vec::new()));
13914
13915    let task_executor = Arc::clone(&executor);
13916    let wake_executor = Arc::clone(&executor);
13917    let producer_executor = Arc::clone(&executor);
13918    let producer_work = Arc::clone(&work);
13919    let producer_observed = Arc::clone(&observed);
13920    let outer_registration = work
13921      .try_register_work()
13922      .expect("the outer blocking job must be accepted");
13923    let (runnable, task) = async_task::spawn(
13924      async move {
13925        let queued = task_executor
13926          .schedule_blocking(
13927            move || {
13928              std::thread::spawn(move || {
13929                (0..QUEUED_BLOCKING_JOBS)
13930                  .map(|index| {
13931                    let registration = producer_work
13932                      .try_register_work()
13933                      .expect("queued blocking work must be accepted");
13934                    let observed = Arc::clone(&producer_observed);
13935                    producer_executor.schedule_blocking(
13936                      move || {
13937                        observed
13938                          .lock()
13939                          .unwrap_or_else(std::sync::PoisonError::into_inner)
13940                          .push(index);
13941                      },
13942                      registration,
13943                    )
13944                  })
13945                  .collect::<Vec<_>>()
13946              })
13947              .join()
13948              .expect("the blocking producer must finish")
13949            },
13950            outer_registration,
13951          )
13952          .await
13953          .expect("the inline blocking job must finish");
13954        for handle in queued {
13955          handle.await.expect("queued blocking work must finish");
13956        }
13957      },
13958      move |runnable| wake_executor.schedule(runnable),
13959    );
13960
13961    executor.schedule(runnable);
13962    executor.drive_host_turn();
13963
13964    let first_turn = observed
13965      .lock()
13966      .unwrap_or_else(std::sync::PoisonError::into_inner)
13967      .clone();
13968    assert!(
13969      !first_turn.is_empty(),
13970      "the first host turn must service queued blocking work"
13971    );
13972    assert!(
13973      first_turn.len() < QUEUED_BLOCKING_JOBS,
13974      "inline lane release must not drain the entire blocking FIFO in one host turn"
13975    );
13976    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
13977    assert_eq!(
13978      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13979      2,
13980      "remaining blocking work must continue through a fresh host turn"
13981    );
13982
13983    let mut continuation_turns = 0;
13984    while observed
13985      .lock()
13986      .unwrap_or_else(std::sync::PoisonError::into_inner)
13987      .len()
13988      < QUEUED_BLOCKING_JOBS
13989    {
13990      continuation_turns += 1;
13991      assert!(
13992        continuation_turns <= QUEUED_BLOCKING_JOBS,
13993        "queued blocking work failed to make bounded host-turn progress"
13994      );
13995      executor.drive_host_turn();
13996    }
13997    executor.drive_host_turn();
13998    futures::executor::block_on(task);
13999
14000    assert_eq!(
14001      *observed
14002        .lock()
14003        .unwrap_or_else(std::sync::PoisonError::into_inner),
14004      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
14005      "blocking jobs must preserve FIFO order across host turns"
14006    );
14007    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14008  }
14009
14010  #[cfg(napi_runtime_os_threads)]
14011  #[test]
14012  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
14013    use std::sync::mpsc;
14014
14015    struct SelfWakingFuture {
14016      remaining: usize,
14017    }
14018
14019    impl Future for SelfWakingFuture {
14020      type Output = ();
14021
14022      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
14023        if self.remaining == 0 {
14024          return Poll::Ready(());
14025        }
14026        self.remaining -= 1;
14027        cx.waker().wake_by_ref();
14028        Poll::Pending
14029      }
14030    }
14031
14032    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
14033    let old_generation = controller.backend().generation();
14034    controller
14035      .try_spawn_detached(SelfWakingFuture {
14036        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
14037      })
14038      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
14039    let dispatch = controller_current_thread_dispatch(&controller);
14040
14041    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14042    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14043    let driver_controller = Arc::clone(&controller);
14044    let driver = std::thread::spawn(move || {
14045      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14046        *slot.borrow_mut() = Some(Box::new(move || {
14047          dispatch_entered_tx.send(()).unwrap();
14048          release_dispatch_rx.recv().unwrap();
14049        }));
14050      });
14051      driver_controller.drive_current_thread_dispatch(dispatch, false);
14052    });
14053    dispatch_entered_rx
14054      .recv_timeout(Duration::from_secs(2))
14055      .expect("the bounded host turn must begin publishing its continuation");
14056
14057    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14058    let shutdown_controller = Arc::clone(&controller);
14059    let shutdown = std::thread::spawn(move || {
14060      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14061    });
14062    wait_until("CurrentThread shutdown to leave Running", || {
14063      let state = controller
14064        .state
14065        .lock()
14066        .unwrap_or_else(std::sync::PoisonError::into_inner);
14067      !matches!(
14068        &state.lifecycle,
14069        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14070      )
14071    });
14072
14073    let (restart_tx, restart_rx) = mpsc::channel();
14074    let restart_controller = Arc::clone(&controller);
14075    let restart = std::thread::spawn(move || {
14076      let result = restart_controller
14077        .start()
14078        .map(|()| restart_controller.backend().generation());
14079      restart_tx.send(result).unwrap();
14080    });
14081
14082    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14083    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14084    let shutdown_overlapped = early_shutdown.is_some();
14085    let restart_overlapped = early_restart.is_some();
14086    release_dispatch_tx.send(()).unwrap();
14087    driver.join().unwrap();
14088
14089    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14090      shutdown_rx
14091        .recv_timeout(Duration::from_secs(2))
14092        .expect("shutdown must finish after continuation publication retires")
14093    });
14094    shutdown_result.unwrap();
14095    let new_generation = early_restart.unwrap_or_else(|| {
14096      restart_rx
14097        .recv_timeout(Duration::from_secs(2))
14098        .expect("restart must follow continuation publication and shutdown")
14099    });
14100    let new_generation = new_generation.unwrap();
14101    shutdown.join().unwrap();
14102    restart.join().unwrap();
14103
14104    assert!(
14105      !shutdown_overlapped,
14106      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
14107    );
14108    assert!(
14109      !restart_overlapped,
14110      "restart must not overlap an old-generation continuation dispatch"
14111    );
14112    assert_ne!(new_generation, old_generation);
14113    controller.shutdown().unwrap();
14114  }
14115
14116  #[cfg(napi_runtime_os_threads)]
14117  #[test]
14118  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
14119    use std::sync::mpsc;
14120
14121    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
14122    let old_generation = controller.backend().generation();
14123    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
14124    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
14125    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
14126    let submit_controller = Arc::clone(&controller);
14127    let reentrant_controller = Arc::clone(&controller);
14128    let submitter = std::thread::spawn(move || {
14129      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14130        *slot.borrow_mut() = Some(Box::new(move || {
14131          reentrant_shutdown_tx
14132            .send(reentrant_controller.shutdown())
14133            .unwrap();
14134          dispatch_entered_tx.send(()).unwrap();
14135          release_dispatch_rx.recv().unwrap();
14136        }));
14137      });
14138      submit_controller
14139        .try_spawn_detached(std::future::pending::<()>())
14140        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
14141    });
14142
14143    let reentrant_shutdown = reentrant_shutdown_rx
14144      .recv_timeout(Duration::from_secs(2))
14145      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
14146    dispatch_entered_rx
14147      .recv_timeout(Duration::from_secs(2))
14148      .expect("the initial host dispatch publication must reach the injected pause");
14149
14150    let (shutdown_tx, shutdown_rx) = mpsc::channel();
14151    let shutdown_controller = Arc::clone(&controller);
14152    let shutdown = std::thread::spawn(move || {
14153      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
14154    });
14155    wait_until("CurrentThread shutdown to leave Running", || {
14156      let state = controller
14157        .state
14158        .lock()
14159        .unwrap_or_else(std::sync::PoisonError::into_inner);
14160      !matches!(
14161        &state.lifecycle,
14162        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
14163      )
14164    });
14165
14166    let (restart_tx, restart_rx) = mpsc::channel();
14167    let restart_controller = Arc::clone(&controller);
14168    let restart = std::thread::spawn(move || {
14169      let result = restart_controller
14170        .start()
14171        .map(|()| restart_controller.backend().generation());
14172      restart_tx.send(result).unwrap();
14173    });
14174
14175    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
14176    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
14177    let shutdown_overlapped = early_shutdown.is_some();
14178    let restart_overlapped = early_restart.is_some();
14179    release_dispatch_tx.send(()).unwrap();
14180    submitter.join().unwrap();
14181
14182    let shutdown_result = early_shutdown.unwrap_or_else(|| {
14183      shutdown_rx
14184        .recv_timeout(Duration::from_secs(2))
14185        .expect("shutdown must finish after initial dispatch publication retires")
14186    });
14187    shutdown_result.unwrap();
14188    let new_generation = early_restart.unwrap_or_else(|| {
14189      restart_rx
14190        .recv_timeout(Duration::from_secs(2))
14191        .expect("restart must follow initial dispatch publication and shutdown")
14192    });
14193    let new_generation = new_generation.unwrap();
14194    shutdown.join().unwrap();
14195    restart.join().unwrap();
14196
14197    let reentrant_error = reentrant_shutdown
14198      .expect_err("dispatch publication must be generation-scoped scheduler work");
14199    assert!(
14200      reentrant_error
14201        .to_string()
14202        .contains("work running on that runtime")
14203    );
14204    assert!(
14205      !shutdown_overlapped,
14206      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
14207    );
14208    assert!(
14209      !restart_overlapped,
14210      "restart must not overlap an old-generation host dispatch call"
14211    );
14212    assert_ne!(new_generation, old_generation);
14213    controller.shutdown().unwrap();
14214  }
14215
14216  #[cfg(panic = "unwind")]
14217  #[test]
14218  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
14219    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14220      .lock()
14221      .unwrap()
14222      .clear();
14223    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14224    let metrics = Arc::new(RuntimeMetrics::default());
14225    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14226      Arc::clone(&metrics),
14227      panic_once_current_thread_host_dispatch,
14228    ));
14229    let scheduler = Arc::clone(&executor);
14230    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
14231      scheduler.schedule(runnable)
14232    });
14233
14234    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14235    assert!(
14236      publication.is_err(),
14237      "the injected host dispatch panic must unwind"
14238    );
14239    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14240    {
14241      let scheduler = executor
14242        .scheduler_idle_lock
14243        .lock()
14244        .unwrap_or_else(std::sync::PoisonError::into_inner);
14245      assert_eq!(scheduler.active_dispatch_calls, 0);
14246      assert!(scheduler.dispatch_publications.is_empty());
14247    }
14248
14249    executor.request_drain_if_queued();
14250
14251    assert_ne!(dispatch, 0);
14252    assert_eq!(
14253      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
14254        .lock()
14255        .unwrap(),
14256      vec![dispatch, dispatch],
14257      "a later host registration must retry the same pending capability"
14258    );
14259    {
14260      let scheduler = executor
14261        .scheduler_idle_lock
14262        .lock()
14263        .unwrap_or_else(std::sync::PoisonError::into_inner);
14264      assert_eq!(scheduler.active_dispatch_calls, 0);
14265      assert!(scheduler.dispatch_publications.is_empty());
14266    }
14267    executor.drive_host_turn_for_dispatch(dispatch);
14268    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
14269    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14270  }
14271
14272  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14273  #[test]
14274  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
14275    use std::sync::mpsc;
14276
14277    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14278      .lock()
14279      .unwrap()
14280      .clear();
14281    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
14282    let metrics = Arc::new(RuntimeMetrics::default());
14283    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14284      Arc::clone(&metrics),
14285      panic_once_current_thread_armed_republish_host_dispatch,
14286    ));
14287    let scheduler = Arc::clone(&executor);
14288    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
14289      scheduler.schedule(runnable)
14290    });
14291
14292    let (called_tx, called_rx) = mpsc::channel();
14293    let (release_tx, release_rx) = mpsc::channel();
14294    let publishing_executor = Arc::clone(&executor);
14295    let publication = std::thread::spawn(move || {
14296      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14297        *slot.borrow_mut() = Some(Box::new(move || {
14298          called_tx.send(()).unwrap();
14299          release_rx.recv().unwrap();
14300        }));
14301      });
14302      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14303    });
14304    called_rx
14305      .recv_timeout(Duration::from_secs(2))
14306      .expect("the first publication must pause before calling the host dispatcher");
14307
14308    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14309    executor.request_drain_if_queued();
14310    let publication_state = {
14311      let scheduler = executor
14312        .scheduler_idle_lock
14313        .lock()
14314        .unwrap_or_else(std::sync::PoisonError::into_inner);
14315      (
14316        scheduler.active_dispatch_calls,
14317        scheduler.dispatch_publications.len(),
14318        scheduler.dispatch_publications[0].dispatch,
14319        scheduler.dispatch_publications[0].republish_requested,
14320      )
14321    };
14322    release_tx.send(()).unwrap();
14323
14324    let publication = publication.join().unwrap();
14325    assert!(
14326      publication.is_err(),
14327      "the original host-dispatch panic must resume"
14328    );
14329    assert_ne!(dispatch, 0);
14330    assert_eq!(publication_state, (1, 1, dispatch, true));
14331    assert_eq!(
14332      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
14333        .lock()
14334        .unwrap(),
14335      vec![dispatch, dispatch],
14336      "the armed request must retry automatically before the original panic resumes"
14337    );
14338    {
14339      let scheduler = executor
14340        .scheduler_idle_lock
14341        .lock()
14342        .unwrap_or_else(std::sync::PoisonError::into_inner);
14343      assert_eq!(scheduler.active_dispatch_calls, 0);
14344      assert!(scheduler.dispatch_publications.is_empty());
14345    }
14346    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14347    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14348
14349    executor.drive_host_turn_for_dispatch(dispatch);
14350    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
14351    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14352  }
14353
14354  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
14355  #[test]
14356  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
14357    use std::sync::mpsc;
14358
14359    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14360      .lock()
14361      .unwrap()
14362      .clear();
14363    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
14364    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14365    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
14366    {
14367      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14368        .state
14369        .lock()
14370        .unwrap_or_else(std::sync::PoisonError::into_inner);
14371      assert!(state.entries.is_empty());
14372      assert!(state.dispatches.is_empty());
14373    }
14374    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14375      .register(Arc::new(FailDuringAuthoritativePanicPublication));
14376    let metrics = Arc::new(RuntimeMetrics::default());
14377    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14378      Arc::clone(&metrics),
14379      dispatch_authoritative_panic_current_thread_tasks,
14380      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
14381    ));
14382    let scheduler = Arc::clone(&executor);
14383    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
14384      scheduler.schedule(runnable)
14385    });
14386
14387    let (called_tx, called_rx) = mpsc::channel();
14388    let (release_tx, release_rx) = mpsc::channel();
14389    let publishing_executor = Arc::clone(&executor);
14390    let publication = std::thread::spawn(move || {
14391      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
14392        *slot.borrow_mut() = Some(Box::new(move || {
14393          called_tx.send(()).unwrap();
14394          release_rx.recv().unwrap();
14395        }));
14396      });
14397      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
14398    });
14399    called_rx
14400      .recv_timeout(Duration::from_secs(2))
14401      .expect("the first publication must pause before raising its panic");
14402
14403    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14404    executor.request_drain_if_queued();
14405    release_tx.send(()).unwrap();
14406
14407    let payload = publication
14408      .join()
14409      .unwrap()
14410      .expect_err("the first host-dispatch panic must resume");
14411    assert!(
14412      payload.is::<FirstHostileCurrentThreadPanic>(),
14413      "the first panic must remain authoritative over nested recovery"
14414    );
14415    assert_eq!(
14416      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14417      1,
14418      "the later hostile payload must be discarded under containment"
14419    );
14420    assert_eq!(
14421      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14422      0,
14423      "the authoritative payload must remain owned by the resumed unwind"
14424    );
14425    discard_panic_payload(payload);
14426    assert_eq!(
14427      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
14428      1
14429    );
14430
14431    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14432      .lock()
14433      .unwrap()
14434      .clone();
14435    assert_eq!(dispatches.len(), 3);
14436    let replacement_dispatch = dispatches[2];
14437    assert_eq!(dispatches[0], failed_dispatch);
14438    assert_eq!(dispatches[1], failed_dispatch);
14439    assert_ne!(replacement_dispatch, failed_dispatch);
14440    assert_eq!(
14441      executor.dispatch_pending.load(Ordering::Acquire),
14442      replacement_dispatch
14443    );
14444    assert_eq!(
14445      executor.recovery_dispatch.load(Ordering::Acquire),
14446      replacement_dispatch
14447    );
14448    {
14449      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14450        .state
14451        .lock()
14452        .unwrap_or_else(std::sync::PoisonError::into_inner);
14453      assert!(
14454        !state.dispatches.contains_key(&failed_dispatch),
14455        "nested recovery unwind must retire the exact failed epoch"
14456      );
14457      assert!(!state.dispatches.contains_key(&replacement_dispatch));
14458    }
14459
14460    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
14461    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14462    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14463      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14464    executor.request_drain_if_queued();
14465
14466    assert_eq!(
14467      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
14468        .lock()
14469        .unwrap(),
14470      vec![
14471        failed_dispatch,
14472        failed_dispatch,
14473        replacement_dispatch,
14474        replacement_dispatch
14475      ],
14476      "retry must retain the replacement reserved before the nested panic"
14477    );
14478    let delivery = healthy.deliveries()[0];
14479    assert_eq!(
14480      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
14481      Some(replacement_dispatch)
14482    );
14483    let mut host_turn = executor
14484      .try_admit_host_turn(replacement_dispatch)
14485      .expect("the retried replacement delivery must be admitted");
14486    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14487    host_turn.drive();
14488    assert!(
14489      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14490        .finish_delivery(delivery, false)
14491        .failed_dispatches
14492        .is_empty()
14493    );
14494    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
14495    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14496
14497    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
14498    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
14499      .state
14500      .lock()
14501      .unwrap_or_else(std::sync::PoisonError::into_inner);
14502    assert!(state.entries.is_empty());
14503    assert!(state.dispatches.is_empty());
14504  }
14505
14506  #[cfg(napi_runtime_os_threads)]
14507  #[test]
14508  fn current_thread_shutdown_suppresses_queued_republish() {
14509    use std::sync::mpsc;
14510
14511    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14512    let metrics = Arc::new(RuntimeMetrics::default());
14513    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14514      Arc::clone(&metrics),
14515      count_current_thread_shutdown_republish_host_dispatch,
14516    ));
14517    let scheduler = Arc::clone(&executor);
14518    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14519      scheduler.schedule(runnable)
14520    });
14521
14522    let (returned_tx, returned_rx) = mpsc::channel();
14523    let (release_tx, release_rx) = mpsc::channel();
14524    let publishing_executor = Arc::clone(&executor);
14525    let publication = std::thread::spawn(move || {
14526      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14527        *slot.borrow_mut() = Some(Box::new(move || {
14528          returned_tx.send(()).unwrap();
14529          release_rx.recv().unwrap();
14530        }));
14531      });
14532      publishing_executor.schedule(runnable);
14533    });
14534    returned_rx
14535      .recv_timeout(Duration::from_secs(2))
14536      .expect("the initial publication must reach its completion barrier");
14537
14538    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14539    executor.request_drain_if_queued();
14540    let publication_state = {
14541      let scheduler = executor
14542        .scheduler_idle_lock
14543        .lock()
14544        .unwrap_or_else(std::sync::PoisonError::into_inner);
14545      (
14546        scheduler.active_dispatch_calls,
14547        scheduler.dispatch_publications.len(),
14548        scheduler.dispatch_publications[0].dispatch,
14549        scheduler.dispatch_publications[0].republish_requested,
14550      )
14551    };
14552    executor.begin_shutdown();
14553    release_tx.send(()).unwrap();
14554    publication.join().unwrap();
14555    executor.wait_until_scheduler_idle();
14556
14557    assert_ne!(dispatch, 0);
14558    assert_eq!(publication_state, (1, 1, dispatch, true));
14559    assert_eq!(
14560      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
14561      1
14562    );
14563    {
14564      let scheduler = executor
14565        .scheduler_idle_lock
14566        .lock()
14567        .unwrap_or_else(std::sync::PoisonError::into_inner);
14568      assert_eq!(scheduler.active_dispatch_calls, 0);
14569      assert!(scheduler.dispatch_publications.is_empty());
14570    }
14571    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14572    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14573    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14574    assert_eq!(futures::executor::block_on(task.fallible()), None);
14575  }
14576
14577  #[cfg(napi_runtime_os_threads)]
14578  #[test]
14579  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
14580    use std::sync::mpsc;
14581
14582    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
14583      .lock()
14584      .unwrap()
14585      .clear();
14586    let metrics = Arc::new(RuntimeMetrics::default());
14587    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14588      Arc::clone(&metrics),
14589      record_current_thread_republish_rearm_dispatch,
14590    ));
14591    let scheduler = Arc::clone(&executor);
14592    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
14593      scheduler.schedule(runnable)
14594    });
14595
14596    let (first_returned_tx, first_returned_rx) = mpsc::channel();
14597    let (release_first_tx, release_first_rx) = mpsc::channel();
14598    let (second_returned_tx, second_returned_rx) = mpsc::channel();
14599    let (release_second_tx, release_second_rx) = mpsc::channel();
14600    let publishing_executor = Arc::clone(&executor);
14601    let publication = std::thread::spawn(move || {
14602      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14603        *slot.borrow_mut() = Some(Box::new(move || {
14604          first_returned_tx.send(()).unwrap();
14605          release_first_rx.recv().unwrap();
14606          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14607            *slot.borrow_mut() = Some(Box::new(move || {
14608              second_returned_tx.send(()).unwrap();
14609              release_second_rx.recv().unwrap();
14610            }));
14611          });
14612        }));
14613      });
14614      publishing_executor.schedule(runnable);
14615    });
14616    first_returned_rx
14617      .recv_timeout(Duration::from_secs(2))
14618      .expect("the first publication must reach its completion barrier");
14619
14620    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14621    executor.request_drain_if_queued();
14622    executor.request_drain_if_queued();
14623    let first_publication_state = {
14624      let scheduler = executor
14625        .scheduler_idle_lock
14626        .lock()
14627        .unwrap_or_else(std::sync::PoisonError::into_inner);
14628      (
14629        scheduler.active_dispatch_calls,
14630        scheduler.dispatch_publications.len(),
14631        scheduler.dispatch_publications[0].dispatch,
14632        scheduler.dispatch_publications[0].republish_requested,
14633      )
14634    };
14635    release_first_tx.send(()).unwrap();
14636    second_returned_rx
14637      .recv_timeout(Duration::from_secs(2))
14638      .expect("the second publication must reach its completion barrier");
14639
14640    executor.request_drain_if_queued();
14641    executor.request_drain_if_queued();
14642    let second_publication_state = {
14643      let scheduler = executor
14644        .scheduler_idle_lock
14645        .lock()
14646        .unwrap_or_else(std::sync::PoisonError::into_inner);
14647      (
14648        scheduler.active_dispatch_calls,
14649        scheduler.dispatch_publications.len(),
14650        scheduler.dispatch_publications[0].dispatch,
14651        scheduler.dispatch_publications[0].republish_requested,
14652      )
14653    };
14654    release_second_tx.send(()).unwrap();
14655    publication.join().unwrap();
14656
14657    assert_ne!(dispatch, 0);
14658    assert_eq!(first_publication_state, (1, 1, dispatch, true));
14659    assert_eq!(second_publication_state, (1, 1, dispatch, true));
14660    assert_eq!(
14661      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
14662      vec![dispatch, dispatch, dispatch],
14663      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
14664    );
14665    {
14666      let scheduler = executor
14667        .scheduler_idle_lock
14668        .lock()
14669        .unwrap_or_else(std::sync::PoisonError::into_inner);
14670      assert_eq!(scheduler.active_dispatch_calls, 0);
14671      assert!(scheduler.dispatch_publications.is_empty());
14672    }
14673    executor.drive_host_turn_for_dispatch(dispatch);
14674    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
14675    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14676  }
14677
14678  #[cfg(napi_runtime_os_threads)]
14679  #[test]
14680  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
14681    use std::sync::mpsc;
14682
14683    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14684      .lock()
14685      .unwrap()
14686      .clear();
14687    let metrics = Arc::new(RuntimeMetrics::default());
14688    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14689      Arc::clone(&metrics),
14690      reject_initial_current_thread_publication_once,
14691    ));
14692    let scheduler = Arc::clone(&executor);
14693    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14694      scheduler.schedule(runnable)
14695    });
14696
14697    let (returned_tx, returned_rx) = mpsc::channel();
14698    let (release_tx, release_rx) = mpsc::channel();
14699    let publishing_executor = Arc::clone(&executor);
14700    let publication = std::thread::spawn(move || {
14701      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14702        *slot.borrow_mut() = Some(Box::new(move || {
14703          returned_tx.send(()).unwrap();
14704          release_rx.recv().unwrap();
14705        }));
14706      });
14707      publishing_executor.schedule(runnable);
14708    });
14709    returned_rx
14710      .recv_timeout(Duration::from_secs(2))
14711      .expect("the unavailable initial publication must reach its completion barrier");
14712
14713    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14714    executor.request_drain_if_queued();
14715    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14716      .lock()
14717      .unwrap()
14718      .len();
14719
14720    release_tx.send(()).unwrap();
14721    publication.join().unwrap();
14722
14723    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14724      .lock()
14725      .unwrap()
14726      .clone();
14727    assert_ne!(dispatch, 0);
14728    assert_eq!(
14729      attempts_while_unavailable_is_paused, 1,
14730      "a racing host request must join the in-flight publication instead of retiring it independently"
14731    );
14732    assert_eq!(
14733      dispatches,
14734      vec![dispatch, dispatch],
14735      "the publication owner must retry the same internal capability after the new host joins"
14736    );
14737    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14738    executor.drive_host_turn_for_dispatch(dispatch);
14739    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14740    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14741  }
14742
14743  #[cfg(napi_runtime_os_threads)]
14744  #[test]
14745  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14746    use std::sync::mpsc;
14747
14748    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14749      .lock()
14750      .unwrap()
14751      .clear();
14752    let metrics = Arc::new(RuntimeMetrics::default());
14753    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14754      Arc::clone(&metrics),
14755      reject_current_thread_replacement_publication_once,
14756    ));
14757    let scheduler = Arc::clone(&executor);
14758    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14759      scheduler.schedule(runnable)
14760    });
14761    executor.schedule(runnable);
14762    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14763    assert_ne!(failed_dispatch, 0);
14764
14765    let (returned_tx, returned_rx) = mpsc::channel();
14766    let (release_tx, release_rx) = mpsc::channel();
14767    let cancelling_executor = Arc::clone(&executor);
14768    let cancellation = std::thread::spawn(move || {
14769      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14770        *slot.borrow_mut() = Some(Box::new(move || {
14771          returned_tx.send(()).unwrap();
14772          release_rx.recv().unwrap();
14773        }));
14774      });
14775      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14776    });
14777    returned_rx
14778      .recv_timeout(Duration::from_secs(2))
14779      .expect("the unavailable replacement publication must reach its completion barrier");
14780
14781    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14782    executor.request_drain_if_queued();
14783    let attempts_while_unavailable_is_paused =
14784      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14785        .lock()
14786        .unwrap()
14787        .len();
14788
14789    release_tx.send(()).unwrap();
14790    cancellation.join().unwrap();
14791
14792    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14793      .lock()
14794      .unwrap()
14795      .clone();
14796    assert_ne!(replacement_dispatch, 0);
14797    assert_ne!(replacement_dispatch, failed_dispatch);
14798    assert_eq!(
14799      attempts_while_unavailable_is_paused, 2,
14800      "a racing host request must join the in-flight replacement publication"
14801    );
14802    assert_eq!(
14803      dispatches,
14804      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14805      "republication must reuse the sole replacement capability instead of allocating another"
14806    );
14807    assert_eq!(
14808      executor.dispatch_pending.load(Ordering::Acquire),
14809      replacement_dispatch
14810    );
14811    assert_eq!(
14812      executor.recovery_dispatch.load(Ordering::Acquire),
14813      replacement_dispatch
14814    );
14815    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14816
14817    executor.drive_host_turn_for_dispatch(replacement_dispatch);
14818    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
14819    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14820    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14821  }
14822
14823  #[cfg(napi_runtime_os_threads)]
14824  #[test]
14825  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
14826    use std::sync::mpsc;
14827
14828    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14829      .lock()
14830      .unwrap()
14831      .clear();
14832    {
14833      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14834        .state
14835        .lock()
14836        .unwrap_or_else(std::sync::PoisonError::into_inner);
14837      assert!(state.entries.is_empty());
14838      assert!(state.dispatches.is_empty());
14839    }
14840    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14841    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14842      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14843    let metrics = Arc::new(RuntimeMetrics::default());
14844    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14845      Arc::clone(&metrics),
14846      fail_current_thread_replacement_publication_once,
14847      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
14848    ));
14849    let scheduler = Arc::clone(&executor);
14850    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
14851      scheduler.schedule(runnable)
14852    });
14853    executor.schedule(runnable);
14854    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14855    assert_ne!(failed_dispatch, 0);
14856    let initial_delivery = initial.deliveries()[0];
14857    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14858      .finish_delivery(initial_delivery, true);
14859    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
14860      panic!("the initial accepted delivery must report one exact failure");
14861    };
14862    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
14863    let failed_dispatch_failure = *failed_dispatch_failure;
14864    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
14865    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14866      .register(Arc::new(FailDuringReplacementRepublishPublication));
14867
14868    let (returned_tx, returned_rx) = mpsc::channel();
14869    let (release_tx, release_rx) = mpsc::channel();
14870    let cancelling_executor = Arc::clone(&executor);
14871    let cancellation = std::thread::spawn(move || {
14872      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14873        *slot.borrow_mut() = Some(Box::new(move || {
14874          returned_tx.send(()).unwrap();
14875          release_rx.recv().unwrap();
14876        }));
14877      });
14878      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
14879    });
14880    returned_rx
14881      .recv_timeout(Duration::from_secs(2))
14882      .expect("the failed replacement publication must reach its completion barrier");
14883
14884    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14885    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14886    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14887      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14888    executor.request_drain_if_queued();
14889    let publication_state = {
14890      let scheduler = executor
14891        .scheduler_idle_lock
14892        .lock()
14893        .unwrap_or_else(std::sync::PoisonError::into_inner);
14894      (
14895        scheduler.active_dispatch_calls,
14896        scheduler.dispatch_publications.len(),
14897        scheduler.dispatch_publications[0].dispatch,
14898        scheduler.dispatch_publications[0].republish_requested,
14899      )
14900    };
14901    release_tx.send(()).unwrap();
14902    cancellation.join().unwrap();
14903
14904    assert_ne!(replacement_dispatch, 0);
14905    assert_ne!(replacement_dispatch, failed_dispatch);
14906    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
14907    assert_eq!(
14908      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14909        .lock()
14910        .unwrap(),
14911      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14912      "the newly armed host request must receive the exact replacement before cancellation"
14913    );
14914    assert_eq!(
14915      executor.dispatch_pending.load(Ordering::Acquire),
14916      replacement_dispatch
14917    );
14918    assert_eq!(
14919      executor.recovery_dispatch.load(Ordering::Acquire),
14920      replacement_dispatch
14921    );
14922    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14923
14924    let delivery = healthy.deliveries()[0];
14925    assert_eq!(delivery.dispatch, replacement_dispatch);
14926    assert_eq!(
14927      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14928        .claim_delivery(delivery.capability()),
14929      Some(replacement_dispatch)
14930    );
14931    let mut host_turn = executor
14932      .try_admit_host_turn(replacement_dispatch)
14933      .expect("the rearmed replacement delivery must be admitted");
14934    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14935    host_turn.drive();
14936    assert!(
14937      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14938        .finish_delivery(delivery, false)
14939        .failed_dispatches
14940        .is_empty()
14941    );
14942    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
14943    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14944    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14945
14946    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
14947    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
14948    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14949      .state
14950      .lock()
14951      .unwrap_or_else(std::sync::PoisonError::into_inner);
14952    assert!(state.entries.is_empty());
14953    assert!(state.dispatches.is_empty());
14954  }
14955
14956  #[cfg(napi_runtime_os_threads)]
14957  #[test]
14958  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
14959    use std::sync::mpsc;
14960
14961    {
14962      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14963        .state
14964        .lock()
14965        .unwrap_or_else(std::sync::PoisonError::into_inner);
14966      assert!(state.entries.is_empty());
14967      assert!(state.dispatches.is_empty());
14968    }
14969    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14970    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14971      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14972    let metrics = Arc::new(RuntimeMetrics::default());
14973    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14974      Arc::clone(&metrics),
14975      dispatch_stale_failure_current_thread_tasks,
14976      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
14977    ));
14978    let scheduler = Arc::clone(&executor);
14979    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
14980      scheduler.schedule(runnable)
14981    });
14982    executor.schedule(runnable);
14983
14984    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14985    let initial_delivery = initial.deliveries()[0];
14986    let delayed_completion =
14987      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
14988    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
14989      panic!("the initial accepted delivery must report one exact failure");
14990    };
14991    assert_eq!(
14992      *delayed_failure,
14993      current_thread_dispatch_failure(dispatch, 0)
14994    );
14995    let delayed_failure = *delayed_failure;
14996
14997    let (returned_tx, returned_rx) = mpsc::channel();
14998    let (release_tx, release_rx) = mpsc::channel();
14999    let publishing_executor = Arc::clone(&executor);
15000    let publication = std::thread::spawn(move || {
15001      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
15002        *slot.borrow_mut() = Some(Box::new(move || {
15003          returned_tx.send(()).unwrap();
15004          release_rx.recv().unwrap();
15005        }));
15006      });
15007      publishing_executor.request_drain_if_queued();
15008    });
15009    returned_rx
15010      .recv_timeout(Duration::from_secs(2))
15011      .expect("the old failed epoch must pause before executor recovery");
15012
15013    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15014    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15015      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15016    executor.request_drain_if_queued();
15017    release_tx.send(()).unwrap();
15018    publication.join().unwrap();
15019
15020    let healthy_delivery = healthy.deliveries()[0];
15021    let delayed_initial_delivery = initial.deliveries()[1];
15022    {
15023      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15024        .state
15025        .lock()
15026        .unwrap_or_else(std::sync::PoisonError::into_inner);
15027      let dispatch_state = state
15028        .dispatches
15029        .get(&dispatch)
15030        .expect("the rearmed dispatch must remain registered");
15031      assert_eq!(dispatch_state.epoch, 1);
15032      assert!(!dispatch_state.is_cancelling());
15033    }
15034
15035    executor.cancel_host_dispatch(delayed_failure);
15036
15037    assert_eq!(
15038      executor.dispatch_pending.load(Ordering::Acquire),
15039      dispatch,
15040      "the delayed old-epoch failure must not consume the rearmed executor capability"
15041    );
15042    assert_eq!(
15043      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15044        .state
15045        .lock()
15046        .unwrap_or_else(std::sync::PoisonError::into_inner)
15047        .dispatches
15048        .get(&dispatch)
15049        .map(|state| (state.epoch, state.phase)),
15050      Some((1, CurrentThreadDispatchPhase::Pending)),
15051      "the delayed old-epoch cancellation must not remove the accepted rearm"
15052    );
15053
15054    assert_eq!(
15055      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
15056      Some(dispatch)
15057    );
15058    let mut host_turn = executor
15059      .try_admit_host_turn(dispatch)
15060      .expect("the current-epoch delivery must be admitted");
15061    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15062    host_turn.drive();
15063    assert!(
15064      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15065        .finish_delivery(healthy_delivery, false)
15066        .failed_dispatches
15067        .is_empty()
15068    );
15069    assert!(
15070      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15071        .finish_delivery(delayed_initial_delivery, false)
15072        .failed_dispatches
15073        .is_empty()
15074    );
15075    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
15076    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15077
15078    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
15079    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
15080    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
15081      .state
15082      .lock()
15083      .unwrap_or_else(std::sync::PoisonError::into_inner);
15084    assert!(state.entries.is_empty());
15085    assert!(state.dispatches.is_empty());
15086  }
15087
15088  #[cfg(napi_runtime_os_threads)]
15089  #[test]
15090  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
15091    use std::sync::mpsc;
15092
15093    {
15094      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15095        .state
15096        .lock()
15097        .unwrap_or_else(std::sync::PoisonError::into_inner);
15098      assert!(state.entries.is_empty());
15099      assert!(state.dispatches.is_empty());
15100    }
15101    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15102      .register(Arc::new(FailDuringLateRegistrationPublication));
15103
15104    let metrics = Arc::new(RuntimeMetrics::default());
15105    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15106      Arc::clone(&metrics),
15107      dispatch_late_registration_current_thread_tasks,
15108      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
15109    ));
15110    let scheduler = Arc::clone(&executor);
15111    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15112      scheduler.schedule(runnable)
15113    });
15114    metrics.runnable_scheduled();
15115    executor
15116      .queue
15117      .lock()
15118      .unwrap_or_else(std::sync::PoisonError::into_inner)
15119      .runnables
15120      .push_back(runnable);
15121    let replacement_dispatch = next_current_thread_dispatch_id();
15122    executor
15123      .recovery_dispatch
15124      .store(replacement_dispatch, Ordering::Release);
15125    executor
15126      .dispatch_pending
15127      .store(replacement_dispatch, Ordering::Release);
15128
15129    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
15130    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
15131    let publishing_executor = Arc::clone(&executor);
15132    let publication = std::thread::spawn(move || {
15133      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15134        *slot.borrow_mut() = Some(Box::new(move || {
15135          owner_retired_tx.send(()).unwrap();
15136          request_blocked_rx
15137            .recv_timeout(Duration::from_secs(2))
15138            .expect("the binding-like drain request must wait for the failure transition");
15139        }));
15140      });
15141      publishing_executor.request_drain_if_queued();
15142    });
15143    owner_retired_rx
15144      .recv_timeout(Duration::from_secs(2))
15145      .expect("the failed replacement publication must retire its owner");
15146
15147    // Match the binding sequence: publish the host registration first, then
15148    // request service for work that may have accumulated before registration.
15149    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15150    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15151      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15152    let requesting_executor = Arc::clone(&executor);
15153    let request = std::thread::spawn(move || {
15154      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
15155        *slot.borrow_mut() = Some(Box::new(move || {
15156          request_blocked_tx.send(()).unwrap();
15157        }));
15158      });
15159      requesting_executor.request_drain_if_queued();
15160    });
15161
15162    publication.join().unwrap();
15163    request.join().unwrap();
15164
15165    assert!(
15166      healthy.deliveries().is_empty(),
15167      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
15168    );
15169    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15170    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15171    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15172    assert_eq!(futures::executor::block_on(task.fallible()), None);
15173    {
15174      let scheduler = executor
15175        .scheduler_idle_lock
15176        .lock()
15177        .unwrap_or_else(std::sync::PoisonError::into_inner);
15178      assert_eq!(scheduler.active_dispatch_calls, 0);
15179      assert!(scheduler.dispatch_publications.is_empty());
15180    }
15181
15182    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
15183
15184    let scheduler = Arc::clone(&executor);
15185    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
15186      scheduler.schedule(runnable)
15187    });
15188    executor.schedule(later_runnable);
15189    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
15190    let later_delivery = healthy.deliveries()[0];
15191    assert_eq!(
15192      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
15193      Some(later_dispatch)
15194    );
15195    let mut host_turn = executor
15196      .try_admit_host_turn(later_dispatch)
15197      .expect("the later exact delivery must be admitted");
15198    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
15199    host_turn.drive();
15200    assert!(
15201      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15202        .finish_delivery(later_delivery, false)
15203        .failed_dispatches
15204        .is_empty()
15205    );
15206    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
15207    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15208
15209    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
15210    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
15211      .state
15212      .lock()
15213      .unwrap_or_else(std::sync::PoisonError::into_inner);
15214    assert!(state.entries.is_empty());
15215    assert!(state.dispatches.is_empty());
15216  }
15217
15218  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
15219  #[test]
15220  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
15221  {
15222    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15223      .lock()
15224      .unwrap()
15225      .clear();
15226    {
15227      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15228        .state
15229        .lock()
15230        .unwrap_or_else(std::sync::PoisonError::into_inner);
15231      assert!(state.entries.is_empty());
15232      assert!(state.dispatches.is_empty());
15233    }
15234    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15235      .next_delivery
15236      .store(0, Ordering::Relaxed);
15237    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15238      .register(Arc::new(FailDuringNestedUnwindPublication));
15239
15240    let metrics = Arc::new(RuntimeMetrics::default());
15241    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15242      Arc::clone(&metrics),
15243      dispatch_nested_unwind_current_thread_tasks,
15244      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
15245    ));
15246    let scheduler = Arc::clone(&executor);
15247    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
15248      scheduler.schedule(runnable)
15249    });
15250
15251    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
15252      *slot.borrow_mut() = Some(Box::new(|| {
15253        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15254          .next_delivery
15255          .store(u64::MAX, Ordering::Relaxed);
15256      }));
15257    });
15258    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
15259    assert!(
15260      publication.is_err(),
15261      "replacement delivery-id exhaustion must unwind through the outer failed publication"
15262    );
15263
15264    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
15265      .lock()
15266      .unwrap()
15267      .clone();
15268    assert_eq!(dispatches.len(), 2);
15269    let failed_dispatch = dispatches[0];
15270    let replacement_dispatch = dispatches[1];
15271    assert_ne!(failed_dispatch, replacement_dispatch);
15272    assert_eq!(
15273      executor.dispatch_pending.load(Ordering::Acquire),
15274      replacement_dispatch
15275    );
15276    assert_eq!(
15277      executor.recovery_dispatch.load(Ordering::Acquire),
15278      replacement_dispatch
15279    );
15280    {
15281      let scheduler = executor
15282        .scheduler_idle_lock
15283        .lock()
15284        .unwrap_or_else(std::sync::PoisonError::into_inner);
15285      assert_eq!(scheduler.active_dispatch_calls, 0);
15286      assert!(scheduler.dispatch_publications.is_empty());
15287    }
15288    {
15289      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15290        .state
15291        .lock()
15292        .unwrap_or_else(std::sync::PoisonError::into_inner);
15293      assert!(
15294        !state.dispatches.contains_key(&failed_dispatch),
15295        "outer unwind cleanup must retire the exact old cancelling dispatch"
15296      );
15297      assert!(
15298        !state.dispatches.contains_key(&replacement_dispatch),
15299        "the replacement publication guard must release its aborted registry state"
15300      );
15301    }
15302
15303    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15304      .next_delivery
15305      .store(1, Ordering::Relaxed);
15306    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
15307    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
15308    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15309      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
15310
15311    executor.request_drain_if_queued();
15312
15313    assert_eq!(
15314      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
15315      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
15316      "retry must reuse the replacement reserved before the nested unwind"
15317    );
15318    let delivery = healthy.deliveries()[0];
15319    assert_eq!(delivery.dispatch, replacement_dispatch);
15320    assert_eq!(
15321      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
15322      Some(replacement_dispatch)
15323    );
15324    let mut host_turn = executor
15325      .try_admit_host_turn(replacement_dispatch)
15326      .expect("the retried replacement delivery must be admitted");
15327    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
15328    host_turn.drive();
15329    assert!(
15330      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15331        .finish_delivery(delivery, false)
15332        .failed_dispatches
15333        .is_empty()
15334    );
15335    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
15336    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15337
15338    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
15339    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
15340      .state
15341      .lock()
15342      .unwrap_or_else(std::sync::PoisonError::into_inner);
15343    assert!(state.entries.is_empty());
15344    assert!(state.dispatches.is_empty());
15345  }
15346
15347  #[test]
15348  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
15349    reset_current_thread_recovery_host_dispatches();
15350    let metrics = Arc::new(RuntimeMetrics::default());
15351    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15352      Arc::clone(&metrics),
15353      count_current_thread_recovery_host_dispatch,
15354    ));
15355
15356    let scheduler = Arc::clone(&executor);
15357    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
15358      scheduler.schedule(runnable)
15359    });
15360    executor.schedule(runnable);
15361    assert_eq!(current_thread_recovery_host_dispatches(), 1);
15362    let failed_dispatch = current_thread_dispatch(&executor);
15363
15364    // Model the host scheduler throwing after Rust accepted the callback but
15365    // before JavaScript could queue a fresh turn.
15366    executor.cancel_host_dispatch_for_test(failed_dispatch);
15367    assert_eq!(
15368      current_thread_recovery_host_dispatches(),
15369      2,
15370      "cancellation must request one replacement without another submission"
15371    );
15372    let replacement_dispatch = current_thread_dispatch(&executor);
15373    assert_ne!(replacement_dispatch, failed_dispatch);
15374    assert_eq!(
15375      executor.recovery_dispatch.load(Ordering::Acquire),
15376      replacement_dispatch
15377    );
15378
15379    executor.drive_host_turn_for_dispatch(replacement_dispatch);
15380    assert_eq!(futures::executor::block_on(task), 1);
15381    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15382    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15383  }
15384
15385  #[test]
15386  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
15387    reset_current_thread_recovery_host_dispatches();
15388    let metrics = Arc::new(RuntimeMetrics::default());
15389    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15390      Arc::clone(&metrics),
15391      count_current_thread_recovery_host_dispatch,
15392    ));
15393    let backend =
15394      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15395
15396    let registration = backend
15397      .work
15398      .try_register_async()
15399      .expect("the backend must accept work");
15400    let task = spawn_registered(
15401      &backend,
15402      Arc::clone(&metrics),
15403      std::future::pending::<()>(),
15404      registration,
15405    );
15406    let failed_dispatch = current_thread_dispatch(&executor);
15407    executor.cancel_host_dispatch_for_test(failed_dispatch);
15408    let replacement_dispatch = current_thread_dispatch(&executor);
15409
15410    executor.cancel_host_dispatch_for_test(replacement_dispatch);
15411
15412    assert_eq!(
15413      current_thread_recovery_host_dispatches(),
15414      2,
15415      "a second scheduler failure must not create an unbounded retry loop"
15416    );
15417    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
15418    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
15419    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15420    assert_eq!(
15421      futures::executor::block_on(task).unwrap_err().to_string(),
15422      "async runtime stopped before the task completed",
15423      "terminal host failure must settle the original operation as cancelled"
15424    );
15425    assert_eq!(backend.work.state.lock().unwrap().active, 0);
15426
15427    let later_registration = backend
15428      .work
15429      .try_register_async()
15430      .expect("terminal cancellation must reopen acceptance");
15431    let later_task = spawn_registered(
15432      &backend,
15433      Arc::clone(&metrics),
15434      async { 2usize },
15435      later_registration,
15436    );
15437    assert_eq!(
15438      current_thread_recovery_host_dispatches(),
15439      3,
15440      "terminal cancellation must not permanently close the executor"
15441    );
15442    executor.drive_host_turn();
15443    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
15444  }
15445
15446  /// Regression test for the hazard shape rolldown hit when a `worker_threads`
15447  /// Worker running watch mode is torn down on the CurrentThread flavor: the
15448  /// cleanup-hook dispatch failure cancels the queued coordinator runnable,
15449  /// whose JoinHandle is wrapped in `futures::Shared`. `Shared`'s notifier
15450  /// wakes its awaiters while holding its `wakers` mutex; the parked awaiter's
15451  /// schedule is rejected by the cancellation sweep, and dropping that
15452  /// runnable inline destroys the awaiter's `Shared` clone, whose destructor
15453  /// re-locks `wakers` on the same thread.
15454  #[cfg(napi_runtime_os_threads)]
15455  #[test]
15456  fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
15457    use std::{sync::mpsc, time::Duration};
15458
15459    let (done_tx, done_rx) = mpsc::channel();
15460    std::thread::spawn(move || {
15461      let metrics = Arc::new(RuntimeMetrics::default());
15462      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15463        Arc::clone(&metrics),
15464        accept_current_thread_host_dispatch,
15465      ));
15466
15467      // Task A: its JoinHandle is shared, exactly like a watcher close handle.
15468      let scheduler_a = Arc::clone(&executor);
15469      let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15470        scheduler_a.schedule(runnable)
15471      });
15472      let shared = async move { task_a.fallible().await }.boxed().shared();
15473      let shared_for_b = shared.clone();
15474
15475      // Task B: awaits a clone of that Shared and parks after one poll.
15476      let scheduler_b = Arc::clone(&executor);
15477      let (runnable_b, task_b) = async_task::spawn(
15478        async move {
15479          shared_for_b.await;
15480        },
15481        move |runnable| scheduler_b.schedule(runnable),
15482      );
15483      executor.schedule(runnable_b);
15484      executor.drive_host_turn();
15485      assert_eq!(
15486        metrics.queued_runnables.load(Ordering::Relaxed),
15487        0,
15488        "B must be parked and unscheduled after its first poll"
15489      );
15490
15491      // A's runnable is queued (woken, not yet polled) when the host dispatch
15492      // fails twice, so the terminal cancellation sweep drops it.
15493      executor.schedule(runnable_a);
15494      let failed_dispatch = current_thread_dispatch(&executor);
15495      executor.cancel_host_dispatch_for_test(failed_dispatch);
15496      let replacement_dispatch = current_thread_dispatch(&executor);
15497      executor.cancel_host_dispatch_for_test(replacement_dispatch);
15498
15499      assert!(
15500        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15501        "the cancellation sweep must reopen the executor"
15502      );
15503      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15504      assert!(
15505        futures::executor::block_on(task_b.fallible()).is_none(),
15506        "B's rejected wake must still cancel B"
15507      );
15508      drop(shared);
15509      done_tx.send(()).unwrap();
15510    });
15511    done_rx
15512      .recv_timeout(Duration::from_secs(5))
15513      .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
15514  }
15515
15516  /// Regression test for the closed-queue sibling of the hazard above. A wake
15517  /// can be in flight on any thread when shutdown starts: `async-task` marks
15518  /// the task `SCHEDULED` before it invokes the schedule closure, so the abort
15519  /// that `close_and_abort` issues for that task coalesces with the in-flight
15520  /// wake and returns, `begin_shutdown` closes the queue without waiting for
15521  /// the closure to land, and the resumed closure is rejected by the closed
15522  /// queue. That rejection must not drop the runnable inline either: the
15523  /// waker here is still inside `futures::Shared`'s notifier, holding its
15524  /// `wakers` mutex, and the rejected future owns a clone of the same `Shared`.
15525  #[cfg(napi_runtime_os_threads)]
15526  #[test]
15527  fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
15528    use std::{
15529      sync::{Barrier, mpsc},
15530      time::Duration,
15531    };
15532
15533    let (done_tx, done_rx) = mpsc::channel();
15534    std::thread::spawn(move || {
15535      let metrics = Arc::new(RuntimeMetrics::default());
15536      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
15537        Arc::clone(&metrics),
15538        accept_current_thread_host_dispatch,
15539      ));
15540      let backend =
15541        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15542      let generation = backend.generation();
15543
15544      // Task A: registered like production work; its JoinHandle is shared.
15545      let (abort_a, guard_a) = backend
15546        .work
15547        .try_register_async()
15548        .expect("the backend must accept work");
15549      let scheduler_a = Arc::clone(&executor);
15550      let (runnable_a, task_a) = async_task::spawn(
15551        RegisteredTaskFuture::new(
15552          Abortable::new(std::future::pending::<()>(), abort_a),
15553          guard_a,
15554          generation,
15555        ),
15556        move |runnable| scheduler_a.schedule(runnable),
15557      );
15558      let shared = async move { task_a.fallible().await.is_some() }
15559        .boxed()
15560        .shared();
15561      let shared_for_b = shared.clone();
15562
15563      // Task B: registered and abortable too, awaiting a clone of that Shared.
15564      // Its schedule closure pauses before it reaches the executor, exactly
15565      // where a waker thread may be descheduled while shutdown runs.
15566      let (abort_b, guard_b) = backend
15567        .work
15568        .try_register_async()
15569        .expect("the backend must accept work");
15570      let wake_in_flight = Arc::new(Barrier::new(2));
15571      let wake_release = Arc::new(Barrier::new(2));
15572      let scheduler_b = Arc::clone(&executor);
15573      let (runnable_b, task_b) = async_task::spawn(
15574        RegisteredTaskFuture::new(
15575          Abortable::new(
15576            async move {
15577              shared_for_b.await;
15578            },
15579            abort_b,
15580          ),
15581          guard_b,
15582          generation,
15583        ),
15584        {
15585          let wake_in_flight = Arc::clone(&wake_in_flight);
15586          let wake_release = Arc::clone(&wake_release);
15587          move |runnable| {
15588            wake_in_flight.wait();
15589            wake_release.wait();
15590            scheduler_b.schedule(runnable);
15591          }
15592        },
15593      );
15594      executor.schedule(runnable_b);
15595      executor.drive_host_turn();
15596      assert_eq!(
15597        metrics.queued_runnables.load(Ordering::Relaxed),
15598        0,
15599        "B must be parked and unscheduled after its first poll"
15600      );
15601
15602      // Cancel A on another thread: closing A notifies its awaiter, and the
15603      // Shared notifier wakes B while holding `wakers`. B's schedule closure
15604      // then parks at the barrier with that lock still held.
15605      let waker_thread = std::thread::spawn(move || drop(runnable_a));
15606      wake_in_flight.wait();
15607
15608      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
15609      // the abort wake is a no-op; the queue then closes.
15610      backend.begin_shutdown();
15611      wake_release.wait();
15612      backend.wait_until_idle();
15613
15614      waker_thread
15615        .join()
15616        .expect("the waker thread must not panic");
15617      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15618      assert!(
15619        futures::executor::block_on(task_b.fallible()).is_none(),
15620        "B's rejected wake must still cancel B"
15621      );
15622      assert_eq!(backend.work.state.lock().unwrap().active, 0);
15623      drop(shared);
15624      done_tx.send(()).unwrap();
15625    });
15626    done_rx
15627      .recv_timeout(Duration::from_secs(5))
15628      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
15629  }
15630
15631  /// Regression test for the terminal-failure sibling of the two hazards
15632  /// above. Task B awaits a `futures::Shared` and parks. Completing the Shared
15633  /// wakes B from inside its notifier, which holds `wakers`, and `schedule`
15634  /// publishes a host dispatch on that same stack. The host fails the
15635  /// delivery before `dispatch` returns, and fails the one replacement the
15636  /// same way, so the terminal failure transition runs while the waker's
15637  /// stack is still under `wakers`. Nothing may be dropped there: B's future
15638  /// owns a clone of the same `Shared`, whose destructor re-locks `wakers`.
15639  /// The transition moves B aside and reopens; the next host turn cancels B.
15640  #[cfg(napi_runtime_os_threads)]
15641  #[test]
15642  fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
15643    use std::{sync::mpsc, time::Duration};
15644
15645    let (done_tx, done_rx) = mpsc::channel();
15646    std::thread::spawn(move || {
15647      {
15648        let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15649          .state
15650          .lock()
15651          .unwrap_or_else(std::sync::PoisonError::into_inner);
15652        assert!(state.entries.is_empty());
15653        assert!(state.dispatches.is_empty());
15654      }
15655      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
15656        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15657      ));
15658      let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15659        .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15660      let metrics = Arc::new(RuntimeMetrics::default());
15661      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15662        Arc::clone(&metrics),
15663        dispatch_waker_stack_failure_current_thread_tasks,
15664        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
15665      ));
15666
15667      // Task B awaits a clone of a Shared and parks after its first poll.
15668      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15669      let shared = rx.map(|_| ()).boxed().shared();
15670      let shared_for_b = shared.clone();
15671      let scheduler_b = Arc::clone(&executor);
15672      let (runnable_b, task_b) = async_task::spawn(
15673        async move {
15674          shared_for_b.await;
15675        },
15676        move |runnable| scheduler_b.schedule(runnable),
15677      );
15678      executor.schedule(runnable_b);
15679
15680      // Service the accepted delivery the way `drive_current_thread_tasks`
15681      // and `acknowledge_current_thread_task_delivery` do.
15682      let delivery = driver.deliveries()[0];
15683      let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15684        .claim_delivery(delivery.capability())
15685        .expect("the accepted delivery must be claimable");
15686      let mut host_turn = executor
15687        .try_admit_host_turn(dispatch)
15688        .expect("the claimed delivery must be admitted");
15689      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15690      host_turn.drive();
15691      drop(host_turn);
15692      let completion =
15693        CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15694      assert!(completion.failed_dispatches.is_empty());
15695      assert!(!completion.redispatch);
15696      assert_eq!(
15697        metrics.queued_runnables.load(Ordering::Relaxed),
15698        0,
15699        "B must be parked and unscheduled after its first poll"
15700      );
15701
15702      // From now on every accepted delivery fails before `dispatch` returns.
15703      driver.fail.store(true, Ordering::SeqCst);
15704      // Completing the Shared wakes B from inside its notifier, under
15705      // `wakers`; B's schedule publishes the dispatch and its replacement on
15706      // this stack, both fail, and the terminal transition runs here.
15707      tx.send(()).expect("B must still hold its Shared clone");
15708      assert_eq!(
15709        driver.deliveries().len(),
15710        // The initial service, the wake's dispatch and its one replacement,
15711        // then the retirement turns and the final offer -- this host refuses
15712        // every one of them.
15713        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15714        "the wake must publish the dispatch, its one replacement, and the \
15715         retirement turns the terminal transition owes itself"
15716      );
15717      assert!(
15718        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15719        "the terminal failure must reopen the executor before the waker returns"
15720      );
15721      assert!(
15722        executor.has_rejected_work(),
15723        "B must be moved aside, not dropped on the waker's stack"
15724      );
15725      assert_eq!(
15726        metrics.queued_runnables.load(Ordering::Relaxed),
15727        0,
15728        "the moved runnable carries its queued debit"
15729      );
15730      let mut task_b = task_b.fallible();
15731      assert!(
15732        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15733        "B must still be pending: nothing may be dropped on the waker's stack"
15734      );
15735
15736      // The next host turn (what `drive_current_thread_tasks` runs for the
15737      // delivery a later submission publishes) cancels B on its own stack.
15738      executor.drive_host_turn();
15739      assert!(!executor.has_rejected_work());
15740      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15741      assert!(
15742        poll_ready_within(&mut task_b, Duration::from_secs(5))
15743          .expect("the host turn must cancel B")
15744          .is_none(),
15745        "the terminal failure must still cancel B"
15746      );
15747      drop(shared);
15748
15749      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15750      let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15751        .state
15752        .lock()
15753        .unwrap_or_else(std::sync::PoisonError::into_inner);
15754      assert!(state.entries.is_empty());
15755      assert!(state.dispatches.is_empty());
15756      done_tx.send(()).unwrap();
15757    });
15758    done_rx
15759      .recv_timeout(Duration::from_secs(5))
15760      .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15761  }
15762
15763  /// Poll `future` with a no-op waker until it is ready or `timeout` passes.
15764  /// The tests below drive the executor by hand, so readiness is produced on
15765  /// this thread and a wait can only fail by design, never by scheduling.
15766  #[cfg(napi_runtime_os_threads)]
15767  fn poll_ready_within<F: Future + Unpin>(
15768    future: &mut F,
15769    timeout: std::time::Duration,
15770  ) -> Option<F::Output> {
15771    let deadline = std::time::Instant::now() + timeout;
15772    let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15773    loop {
15774      if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15775        return Some(output);
15776      }
15777      if std::time::Instant::now() >= deadline {
15778        return None;
15779      }
15780      std::thread::sleep(std::time::Duration::from_millis(1));
15781    }
15782  }
15783
15784  /// Liveness regression test for the transition above. After the waker-stack
15785  /// terminal failure the driver stays registered and becomes healthy again.
15786  /// Work submitted from then on is new work: its schedule must publish a
15787  /// fresh delivery, and the host turn that services it cancels the work that
15788  /// was queued when the failure happened (B) before it runs the new task.
15789  /// Nothing else prompts the executor: no drain request, no re-registration,
15790  /// no shutdown.
15791  #[cfg(napi_runtime_os_threads)]
15792  #[test]
15793  fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
15794    use std::{sync::mpsc, time::Duration};
15795
15796    const WAIT: Duration = Duration::from_secs(5);
15797    let (done_tx, done_rx) = mpsc::channel();
15798    std::thread::spawn(move || {
15799      let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
15800      {
15801        let state = registry
15802          .state
15803          .lock()
15804          .unwrap_or_else(std::sync::PoisonError::into_inner);
15805        assert!(state.entries.is_empty());
15806        assert!(state.dispatches.is_empty());
15807      }
15808      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
15809      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15810      let metrics = Arc::new(RuntimeMetrics::default());
15811      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15812        Arc::clone(&metrics),
15813        dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
15814        registry,
15815      ));
15816
15817      // Service one accepted delivery the way the host does.
15818      let service = |index: usize| {
15819        let delivery = driver.deliveries()[index];
15820        let dispatch = registry
15821          .claim_delivery(delivery.capability())
15822          .expect("the accepted delivery must be claimable");
15823        let mut host_turn = executor
15824          .try_admit_host_turn(dispatch)
15825          .expect("the claimed delivery must be admitted");
15826        registry.mark_serviced(dispatch);
15827        host_turn.drive();
15828        drop(host_turn);
15829        let completion = registry.finish_delivery(delivery, false);
15830        assert!(completion.failed_dispatches.is_empty());
15831        assert!(!completion.redispatch);
15832      };
15833
15834      // Task B awaits a clone of a Shared and parks after its first poll.
15835      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15836      let shared = rx.map(|_| ()).boxed().shared();
15837      let shared_for_b = shared.clone();
15838      let scheduler_b = Arc::clone(&executor);
15839      let (runnable_b, task_b) = async_task::spawn(
15840        async move {
15841          shared_for_b.await;
15842        },
15843        move |runnable| scheduler_b.schedule(runnable),
15844      );
15845      executor.schedule(runnable_b);
15846      service(0);
15847      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15848
15849      // The waker-stack terminal failure: the wake's dispatch and its one
15850      // replacement both fail before `dispatch` returns.
15851      driver.fail.store(true, Ordering::SeqCst);
15852      tx.send(()).expect("B must still hold its Shared clone");
15853      // The dispatch, its one replacement, and the retirement turns the
15854      // transition publishes for itself -- which this still-failing host
15855      // loses too, down to the final offer.
15856      const AFTER_WAKE: usize =
15857        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15858      assert_eq!(driver.deliveries().len(), AFTER_WAKE);
15859      let mut task_b = task_b.fallible();
15860      assert!(
15861        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15862        "B must still be pending: nothing may be dropped on the waker's stack"
15863      );
15864
15865      // The host is healthy again and still registered. A later submission
15866      // must publish a delivery on its own.
15867      driver.fail.store(false, Ordering::SeqCst);
15868      let scheduler_later = Arc::clone(&executor);
15869      let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
15870        scheduler_later.schedule(runnable)
15871      });
15872      executor.schedule(runnable_later);
15873      assert_eq!(
15874        driver.deliveries().len(),
15875        AFTER_WAKE + 1,
15876        "a submission after the terminal failure must publish a fresh delivery"
15877      );
15878      assert!(
15879        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15880        "the executor must be open for the healthy host"
15881      );
15882
15883      // The host services that delivery: the pre-failure work is cancelled
15884      // and the new task runs.
15885      service(AFTER_WAKE);
15886      let mut task_later = task_later.fallible();
15887      assert_eq!(
15888        poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
15889        Some(42),
15890        "the later task must complete on the delivered host turn"
15891      );
15892      assert!(
15893        poll_ready_within(&mut task_b, WAIT)
15894          .expect("B must have been cancelled by the host turn")
15895          .is_none(),
15896        "the terminal failure must still cancel the work queued when it happened"
15897      );
15898      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15899      drop(shared);
15900
15901      registry.unregister(registration);
15902      let state = registry
15903        .state
15904        .lock()
15905        .unwrap_or_else(std::sync::PoisonError::into_inner);
15906      assert!(state.entries.is_empty());
15907      assert!(state.dispatches.is_empty());
15908      done_tx.send(()).unwrap();
15909    });
15910    done_rx
15911      .recv_timeout(WAIT)
15912      .expect("the healthy host never got a delivery for the later submission");
15913  }
15914
15915  /// Regression test for the continuation a WAKE owes itself.
15916  ///
15917  /// `6d452174` gave an ordinary submission its own continuation, but a
15918  /// genuine wake still had none. B parks on a `futures::Shared`; completing
15919  /// the Shared wakes B from inside its notifier, which holds `wakers`, so
15920  /// `schedule` publishes on a stack where nothing may be destroyed. The host
15921  /// loses that publication and the one replacement before `dispatch`
15922  /// returns, and the terminal transition moves B into `queue.rejected` and
15923  /// reopens -- correctly dropping nothing.
15924  ///
15925  /// What was missing is what happens next. The transition consumed the
15926  /// executor's one outstanding host request and left the queue empty, so
15927  /// `has_serviceable_work` is false and nothing published again: during
15928  /// normal operation there is no generation idle waiter, no host turn
15929  /// follows the failed publications, and B's `JoinHandle` and
15930  /// `GenerationWorkGuard` stayed live forever. Nothing in this test supplies
15931  /// an unrelated entry -- no later submission, no drain request, no
15932  /// unregistration, no shutdown. The host only services the delivery the
15933  /// transition published for ITSELF.
15934  #[cfg(napi_runtime_os_threads)]
15935  #[test]
15936  fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
15937    use std::{sync::mpsc, time::Duration};
15938
15939    const WAIT: Duration = Duration::from_secs(5);
15940    let (done_tx, done_rx) = mpsc::channel();
15941    std::thread::spawn(move || {
15942      let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
15943      {
15944        let state = registry
15945          .state
15946          .lock()
15947          .unwrap_or_else(std::sync::PoisonError::into_inner);
15948        assert!(state.entries.is_empty());
15949        assert!(state.dispatches.is_empty());
15950      }
15951      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15952      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15953      let metrics = Arc::new(RuntimeMetrics::default());
15954      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15955        Arc::clone(&metrics),
15956        dispatch_retirement_turn_current_thread_tasks,
15957        registry,
15958      ));
15959
15960      // Service one accepted delivery the way the host does.
15961      let service = |index: usize| {
15962        let delivery = driver.deliveries()[index];
15963        let dispatch = registry
15964          .claim_delivery(delivery.capability())
15965          .expect("the accepted delivery must be claimable");
15966        let mut host_turn = executor
15967          .try_admit_host_turn(dispatch)
15968          .expect("the claimed delivery must be admitted");
15969        registry.mark_serviced(dispatch);
15970        host_turn.drive();
15971        drop(host_turn);
15972        let completion = registry.finish_delivery(delivery, false);
15973        assert!(completion.failed_dispatches.is_empty());
15974        assert!(!completion.redispatch);
15975      };
15976
15977      // Task B awaits a clone of a Shared and parks after its first poll.
15978      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15979      let shared = rx.map(|_| ()).boxed().shared();
15980      let shared_for_b = shared.clone();
15981      let scheduler_b = Arc::clone(&executor);
15982      let (runnable_b, task_b) = async_task::spawn(
15983        async move {
15984          shared_for_b.await;
15985        },
15986        move |runnable| scheduler_b.schedule(runnable),
15987      );
15988      executor.schedule(runnable_b);
15989      service(0);
15990      assert_eq!(
15991        metrics.queued_runnables.load(Ordering::Relaxed),
15992        0,
15993        "B must be parked and unscheduled after its first poll"
15994      );
15995
15996      // Exactly the wake's publication and its one replacement are lost; the
15997      // host is healthy again from the third delivery on.
15998      driver.fail_budget.store(2, Ordering::SeqCst);
15999      tx.send(()).expect("B must still hold its Shared clone");
16000
16001      assert_eq!(
16002        driver.deliveries().len(),
16003        4,
16004        "the wake must publish the dispatch, its one replacement, and the \
16005         retirement turn the terminal transition owes itself; without that \
16006         third publication no host turn ever follows and B stays pending \
16007         forever"
16008      );
16009      assert!(
16010        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16011        "the terminal failure must reopen the executor before the waker returns"
16012      );
16013      assert!(
16014        executor.has_rejected_work(),
16015        "B must be moved aside, not dropped on the waker's stack"
16016      );
16017      assert_eq!(
16018        metrics.queued_runnables.load(Ordering::Relaxed),
16019        0,
16020        "the moved runnable carries its queued debit"
16021      );
16022      let mut task_b = task_b.fallible();
16023      assert!(
16024        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16025        "B must still be pending: nothing may be dropped on the waker's stack"
16026      );
16027
16028      // The ONLY continuation: the host serves the turn the transition asked
16029      // for. No submission, no drain request, no unregistration, no shutdown.
16030      service(3);
16031      assert!(
16032        !executor.has_rejected_work(),
16033        "the retirement turn must cancel the moved work on its own stack"
16034      );
16035      assert!(
16036        poll_ready_within(&mut task_b, WAIT)
16037          .expect("the retirement turn must cancel B")
16038          .is_none(),
16039        "the terminal failure must still cancel the work queued when it happened"
16040      );
16041      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16042      drop(shared);
16043
16044      registry.unregister(registration);
16045      let state = registry
16046        .state
16047        .lock()
16048        .unwrap_or_else(std::sync::PoisonError::into_inner);
16049      assert!(state.entries.is_empty());
16050      assert!(state.dispatches.is_empty());
16051      done_tx.send(()).unwrap();
16052    });
16053    done_rx.recv_timeout(WAIT).expect(
16054      "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
16055       `queue.rejected` and its JoinHandle never settled",
16056    );
16057  }
16058
16059  /// Regression test for the continuation a NESTED terminal failure owes.
16060  ///
16061  /// `fc4d8e3b` gave a waker-stack terminal failure a continuation of its own:
16062  /// it publishes one fresh host dispatch, and the host turn that services it
16063  /// is the clean stack that retires the moved work. That publication calls
16064  /// the host driver again, so a one-shot bounds it -- and the one-shot used
16065  /// to DISCARD the nested request instead of handing it back:
16066  ///
16067  ///   thread W (waker)                     | thread T (producer)
16068  ///   -------------------------------------+---------------------------
16069  ///   Shared notifier wakes B -> schedule  |
16070  ///   publish D1 .. host fails             |
16071  ///   publish D2 (replacement) .. fails    |
16072  ///   terminal: B -> queue.rejected, reopen|
16073  ///   retirement scope ENTER, publish D3 --+--> schedule(C) -> queue.runnables
16074  ///                                        |    request_drain -> Coalesced
16075  ///   host fails D3, sees C serviceable    |
16076  ///   publish D4 (replacement) .. fails    |
16077  ///   terminal: C -> queue.rejected, reopen|
16078  ///   retirement scope already ACTIVE      |
16079  ///     -> returned, publishing NOTHING    |
16080  ///
16081  /// Four deliveries during the wake, every one of them lost, no host turn at
16082  /// all, and both B and C sat in `queue.rejected` with their `JoinHandle`s
16083  /// pending and their `GenerationWorkGuard`s live -- even though the host is
16084  /// healthy for its very next delivery. The nested request now goes back to
16085  /// the frame that owns the publication, which publishes a fifth delivery for
16086  /// it once D3 has unwound. Nothing in this test supplies an unrelated entry:
16087  /// no later submission, no drain request, no unregistration, no shutdown.
16088  #[cfg(napi_runtime_os_threads)]
16089  #[test]
16090  fn current_thread_nested_retirement_failure_keeps_its_continuation() {
16091    use std::{sync::mpsc, time::Duration};
16092
16093    const WAIT: Duration = Duration::from_secs(5);
16094    let (done_tx, done_rx) = mpsc::channel();
16095    std::thread::spawn(move || {
16096      let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
16097      {
16098        let state = registry
16099          .state
16100          .lock()
16101          .unwrap_or_else(std::sync::PoisonError::into_inner);
16102        assert!(state.entries.is_empty());
16103        assert!(state.dispatches.is_empty());
16104      }
16105      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16106      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16107      let metrics = Arc::new(RuntimeMetrics::default());
16108      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16109        Arc::clone(&metrics),
16110        dispatch_nested_retirement_current_thread_tasks,
16111        registry,
16112      ));
16113
16114      // Service one accepted delivery the way the host does.
16115      let service = |index: usize| {
16116        let delivery = driver.deliveries()[index];
16117        let dispatch = registry
16118          .claim_delivery(delivery.capability())
16119          .expect("the accepted delivery must be claimable");
16120        let mut host_turn = executor
16121          .try_admit_host_turn(dispatch)
16122          .expect("the claimed delivery must be admitted");
16123        registry.mark_serviced(dispatch);
16124        host_turn.drive();
16125        drop(host_turn);
16126        let completion = registry.finish_delivery(delivery, false);
16127        assert!(completion.failed_dispatches.is_empty());
16128        assert!(!completion.redispatch);
16129      };
16130
16131      // Task B awaits a clone of a Shared and parks after its first poll, so
16132      // its wake arrives from inside the notifier's `wakers` lock.
16133      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16134      let shared = rx.map(|_| ()).boxed().shared();
16135      let shared_for_b = shared.clone();
16136      let scheduler_b = Arc::clone(&executor);
16137      let (runnable_b, task_b) = async_task::spawn(
16138        async move {
16139          shared_for_b.await;
16140        },
16141        move |runnable| scheduler_b.schedule(runnable),
16142      );
16143      executor.schedule(runnable_b);
16144      service(0);
16145      assert_eq!(
16146        metrics.queued_runnables.load(Ordering::Relaxed),
16147        0,
16148        "B must be parked and unscheduled after its first poll"
16149      );
16150
16151      // Task C is queued by ANOTHER thread, from inside the retirement turn's
16152      // publication. The executor is reopened by then, so C lands in
16153      // `queue.runnables`; the publication already owns `dispatch_pending`, so
16154      // C's own drain request coalesces and adds no delivery of its own.
16155      let scheduler_c = Arc::clone(&executor);
16156      let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
16157        scheduler_c.schedule(runnable);
16158      });
16159      let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
16160      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16161      let producer_executor = Arc::clone(&executor);
16162      let producer = std::thread::spawn(move || {
16163        let runnable = gate_rx
16164          .recv()
16165          .expect("the retirement turn must open the window");
16166        producer_executor.schedule(runnable);
16167        queued_tx.send(()).expect("W must still be waiting");
16168      });
16169      driver.during_delivery(3, move || {
16170        gate_tx.send(runnable_c).expect("the producer must be live");
16171        queued_rx
16172          .recv_timeout(WAIT)
16173          .expect("the producer must queue C before the retirement turn fails");
16174      });
16175
16176      // Every delivery of the interleaving is lost: the wake's publication,
16177      // its one replacement, the retirement turn the terminal transition
16178      // publishes for itself, and the replacement that turn's failure reserves
16179      // once it sees C queued. The host is healthy again from the sixth.
16180      driver.fail_budget.store(4, Ordering::SeqCst);
16181      tx.send(()).expect("B must still hold its Shared clone");
16182      producer.join().expect("the producer must not panic");
16183
16184      assert_eq!(
16185        driver.deliveries().len(),
16186        6,
16187        "the nested terminal transition may not publish on this stack, but the \
16188         retirement request it hands back must be serviced after the outer \
16189         publication unwinds; without that sixth delivery no host turn ever \
16190         follows and both B and C stay pending forever"
16191      );
16192      assert!(
16193        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16194        "both terminal failures must reopen the executor before the waker returns"
16195      );
16196      assert!(
16197        executor.has_rejected_work(),
16198        "B and C must be moved aside, not dropped on the waker's stack"
16199      );
16200      assert_eq!(
16201        metrics.queued_runnables.load(Ordering::Relaxed),
16202        0,
16203        "the moved runnables carry their queued debit"
16204      );
16205      let mut task_b = task_b.fallible();
16206      let mut task_c = task_c.fallible();
16207      assert!(
16208        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16209        "B must still be pending: nothing may be dropped on the waker's stack"
16210      );
16211      assert!(
16212        poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
16213        "C must still be pending: nothing may be dropped on the waker's stack"
16214      );
16215
16216      // The ONLY continuation: the host serves the delivery the retirement
16217      // request produced. No submission, no drain request, no unregistration,
16218      // no shutdown.
16219      service(5);
16220      assert!(
16221        !executor.has_rejected_work(),
16222        "the retirement turn must cancel both moved runnables on its own stack"
16223      );
16224      assert!(
16225        poll_ready_within(&mut task_b, WAIT)
16226          .expect("the retirement turn must cancel B")
16227          .is_none(),
16228        "the terminal failure must still cancel the work queued when it happened"
16229      );
16230      assert!(
16231        poll_ready_within(&mut task_c, WAIT)
16232          .expect("the retirement turn must cancel C")
16233          .is_none(),
16234        "the nested terminal failure must cancel the work it moved aside too"
16235      );
16236      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16237      drop(shared);
16238
16239      registry.unregister(registration);
16240      let state = registry
16241        .state
16242        .lock()
16243        .unwrap_or_else(std::sync::PoisonError::into_inner);
16244      assert!(state.entries.is_empty());
16245      assert!(state.dispatches.is_empty());
16246      done_tx.send(()).unwrap();
16247    });
16248    done_rx.recv_timeout(WAIT).expect(
16249      "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
16250       runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
16251    );
16252  }
16253
16254  /// Regression test for the BOUND on the retirement turns `e2a8ed9c` opened.
16255  ///
16256  /// That commit made the frame owning a waker-stack retirement publication
16257  /// loop while nested terminal transitions kept handing work back. Its
16258  /// termination argument -- every turn consumes one distinct live task --
16259  /// bounds the turns by the live tasks, which is no bound at all in wall
16260  /// clock: a producer that keeps submitting keeps supplying the runnable each
16261  /// nested transition needs.
16262  ///
16263  ///   thread W (waker)                     | thread T (producer)
16264  ///   -------------------------------------+---------------------------
16265  ///   Shared notifier wakes B -> schedule  |
16266  ///   publish D1 --------------------------+--> schedule(C1) -> runnables
16267  ///   host fails D1 synchronously          |    request_drain -> Coalesced
16268  ///   publish D2 (replacement) ------------+--> schedule(C2)
16269  ///   host fails D2 synchronously          |
16270  ///   terminal: C1,C2 -> rejected, reopen  |
16271  ///   retirement turn 1: publish D3 -------+--> schedule(C3)   ... and so on
16272  ///
16273  /// Every turn is two more host callbacks and two more producer submissions,
16274  /// so the turns ran for exactly as long as the producer did: measured on
16275  /// `e2a8ed9c`, 32768 submissions produced 32769 deliveries inside one wake,
16276  /// and a producer left running for 400ms held this single `schedule`
16277  /// callback -- and `futures::Shared`'s `wakers` mutex under it -- for 432ms.
16278  ///
16279  /// The owner frame now takes at most `REJECTED_WORK_RETIREMENT_ATTEMPTS`
16280  /// turns a producer can interleave with, and then makes the one offer a
16281  /// producer cannot inflate (`publish_final_rejected_work_retirement`). This
16282  /// host refuses that one too, so what is left is the honest end state for a
16283  /// host that never accepts: the work waits, undropped, for a depth-0 entry.
16284  /// Nothing here supplies one until `wait_until_idle` -- no submission, no
16285  /// drain request, no unregistration -- and that is what
16286  /// `current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget`
16287  /// pins from the other side, where the host accepts the final offer.
16288  #[cfg(napi_runtime_os_threads)]
16289  #[test]
16290  fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
16291    use std::{sync::mpsc, time::Duration};
16292
16293    const WAIT: Duration = Duration::from_secs(5);
16294    /// Far more submissions than the turns may ever spend, so what stops them
16295    /// is the executor's budget and not the producer running dry.
16296    const PRODUCER_BUDGET: usize = 4096;
16297    /// One publication and its one replacement for the wake itself, two more
16298    /// for each retirement turn, and the single final offer.
16299    const BOUNDED_DELIVERIES: usize =
16300      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
16301
16302    let (done_tx, done_rx) = mpsc::channel();
16303    std::thread::spawn(move || {
16304      let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
16305      {
16306        let state = registry
16307          .state
16308          .lock()
16309          .unwrap_or_else(std::sync::PoisonError::into_inner);
16310        assert!(state.entries.is_empty());
16311        assert!(state.dispatches.is_empty());
16312      }
16313      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16314      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16315      let metrics = Arc::new(RuntimeMetrics::default());
16316      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16317        Arc::clone(&metrics),
16318        dispatch_sustained_producer_current_thread_tasks,
16319        registry,
16320      ));
16321      let backend =
16322        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16323
16324      // Service one accepted delivery the way the host does.
16325      let service = |index: usize| {
16326        let delivery = driver.deliveries()[index];
16327        let dispatch = registry
16328          .claim_delivery(delivery.capability())
16329          .expect("the accepted delivery must be claimable");
16330        let mut host_turn = executor
16331          .try_admit_host_turn(dispatch)
16332          .expect("the claimed delivery must be admitted");
16333        registry.mark_serviced(dispatch);
16334        host_turn.drive();
16335        drop(host_turn);
16336        let completion = registry.finish_delivery(delivery, false);
16337        assert!(completion.failed_dispatches.is_empty());
16338        assert!(!completion.redispatch);
16339      };
16340
16341      // B awaits a clone of a Shared and parks after its first poll, so its
16342      // wake arrives from inside the notifier's `wakers` lock. It is a
16343      // registered task, so its `GenerationWorkGuard` is observable.
16344      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16345      let shared = rx.map(|_| ()).boxed().shared();
16346      let shared_for_b = shared.clone();
16347      let work_registration = backend
16348        .work
16349        .try_register_async()
16350        .expect("the backend must accept work");
16351      let task_b = spawn_registered(
16352        &backend,
16353        Arc::clone(&metrics),
16354        async move {
16355          shared_for_b.await;
16356        },
16357        work_registration,
16358      );
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      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16366
16367      // The producer queues one runnable from inside every publication. The
16368      // publication already owns `dispatch_pending`, so each of those drain
16369      // requests coalesces and adds no delivery of its own -- every delivery
16370      // below is one the waker's stack published.
16371      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16372      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16373      let producer_executor = Arc::clone(&executor);
16374      let producer = std::thread::spawn(move || {
16375        let mut submissions = 0usize;
16376        while queue_one_rx.recv().is_ok() {
16377          let scheduler = Arc::clone(&producer_executor);
16378          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16379            scheduler.schedule(runnable);
16380          });
16381          task.detach();
16382          producer_executor.schedule(runnable);
16383          submissions += 1;
16384          if queued_tx.send(()).is_err() {
16385            break;
16386          }
16387        }
16388        submissions
16389      });
16390      driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
16391
16392      // From here the host fails every delivery synchronously, on the calling
16393      // thread, and never recovers.
16394      driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
16395      let before = driver.deliveries().len();
16396      tx.send(()).expect("B must still hold its Shared clone");
16397      let during_wake = driver.deliveries().len() - before;
16398      let unspent = driver.disarm_producer();
16399      let submissions = producer.join().expect("the producer must not panic");
16400
16401      assert_eq!(
16402        during_wake, BOUNDED_DELIVERIES,
16403        "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
16404         on the waker's stack plus its one final offer; without a budget it \
16405         services the producer's stream instead and this wake never returns"
16406      );
16407      assert_eq!(
16408        submissions, during_wake,
16409        "the producer must have queued a runnable during every publication"
16410      );
16411      assert!(
16412        unspent > 0,
16413        "the producer must still have been willing to submit when the turns stopped"
16414      );
16415      assert!(
16416        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16417        "every terminal failure, and the final offer, must reopen the executor \
16418         before the waker returns"
16419      );
16420      assert!(
16421        executor.has_rejected_work(),
16422        "the moved work must be parked, not dropped on the waker's stack"
16423      );
16424      assert_eq!(
16425        executor.dispatch_pending.load(Ordering::Acquire),
16426        0,
16427        "a host that refused every offer including the final one leaves none \
16428         outstanding: this is the documented end state for a host that never accepts"
16429      );
16430      assert_eq!(
16431        metrics.queued_runnables.load(Ordering::Relaxed),
16432        0,
16433        "the moved runnables carry their queued debit"
16434      );
16435      let mut task_b = Box::pin(task_b);
16436      assert!(
16437        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
16438        "B must still be pending: nothing may be dropped on the waker's stack"
16439      );
16440      assert_eq!(
16441        backend.work.state.lock().unwrap().active,
16442        1,
16443        "B's generation guard rides with its parked runnable"
16444      );
16445
16446      // The first depth-0 entry retires it, and no `GenerationWorkGuard`
16447      // outlives that wait. Nothing above supplied this entry: no submission,
16448      // no drain request, no unregistration.
16449      backend.wait_until_idle();
16450      assert!(!executor.has_rejected_work());
16451      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16452      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16453      assert!(
16454        poll_ready_within(&mut task_b, WAIT)
16455          .expect("the idle wait must cancel B")
16456          .is_err(),
16457        "the terminal failure must still cancel the work queued when it happened"
16458      );
16459      drop(shared);
16460
16461      registry.unregister(registration);
16462      let state = registry
16463        .state
16464        .lock()
16465        .unwrap_or_else(std::sync::PoisonError::into_inner);
16466      assert!(state.entries.is_empty());
16467      assert!(state.dispatches.is_empty());
16468      done_tx.send(()).unwrap();
16469    });
16470    done_rx.recv_timeout(WAIT).expect(
16471      "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
16472       the wake never returned",
16473    );
16474  }
16475
16476  /// Regression test for the offer the attempt budget may not swallow -- the
16477  /// reviewer's interleaving, with a FINITE producer.
16478  ///
16479  /// `fd471429` capped the retirement turns at
16480  /// `REJECTED_WORK_RETIREMENT_ATTEMPTS` and argued that exhausting the budget
16481  /// strands nothing, because the producer that drove it to exhaustion
16482  /// supplies the next continuation itself with its next ordinary submission.
16483  /// It does not, if it has just made its last one:
16484  ///
16485  ///   thread W (waker)                     | thread T (producer, budget 18)
16486  ///   -------------------------------------+---------------------------
16487  ///   ... turns 1..7 ...                   | ... C3..C16 ...
16488  ///   turn 8: publish D17 -----------------+--> schedule(C17)
16489  ///   host fails D17, sees C17 serviceable |
16490  ///   publish D18 (replacement) -----------+--> schedule(C18)  <- last one
16491  ///   host fails D18                       |    (producer now quiescent)
16492  ///   terminal #9: C18 -> rejected, reopen |
16493  ///   budget spent -> turns stop           |
16494  ///
16495  /// The host is healthy from its next delivery and stays registered, but no
16496  /// dispatch is outstanding, `queue.rejected` holds B and every C, and
16497  /// nothing publishes again. Measured on `fd471429`: 18 deliveries during the
16498  /// wake, `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle`
16499  /// pending, its future never dropped, its `GenerationWorkGuard` still
16500  /// active, and no delivery afterwards.
16501  ///
16502  /// The budget bounds what a producer can inflate; it may not swallow the
16503  /// continuation itself. After it, one final offer is made with the
16504  /// terminal-cancellation claim held, so nothing can interleave with it and
16505  /// it needs no budget of its own -- and this host accepts it. That host turn
16506  /// is the depth-0 stack that retires everything the wake moved aside.
16507  /// Nothing here supplies any other entry: no submission after the wake, no
16508  /// drain request, no unregistration, no shutdown.
16509  #[cfg(napi_runtime_os_threads)]
16510  #[test]
16511  fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
16512    use std::{sync::mpsc, time::Duration};
16513
16514    const WAIT: Duration = Duration::from_secs(5);
16515    /// Every delivery the budget can pay for, and not one more: the producer's
16516    /// last runnable is queued inside the last of them and it then goes
16517    /// quiescent.
16518    const BUDGETED_DELIVERIES: usize =
16519      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16520
16521    let (done_tx, done_rx) = mpsc::channel();
16522    std::thread::spawn(move || {
16523      struct DropFlag(Arc<AtomicBool>);
16524      impl Drop for DropFlag {
16525        fn drop(&mut self) {
16526          self.0.store(true, Ordering::SeqCst);
16527        }
16528      }
16529
16530      let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
16531      {
16532        let state = registry
16533          .state
16534          .lock()
16535          .unwrap_or_else(std::sync::PoisonError::into_inner);
16536        assert!(state.entries.is_empty());
16537        assert!(state.dispatches.is_empty());
16538      }
16539      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
16540      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16541      let metrics = Arc::new(RuntimeMetrics::default());
16542      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16543        Arc::clone(&metrics),
16544        dispatch_final_retirement_offer_current_thread_tasks,
16545        registry,
16546      ));
16547      let backend =
16548        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16549
16550      let service = |index: usize| {
16551        let delivery = driver.deliveries()[index];
16552        let dispatch = registry
16553          .claim_delivery(delivery.capability())
16554          .expect("the accepted delivery must be claimable");
16555        let mut host_turn = executor
16556          .try_admit_host_turn(dispatch)
16557          .expect("the claimed delivery must be admitted");
16558        registry.mark_serviced(dispatch);
16559        host_turn.drive();
16560        drop(host_turn);
16561        let completion = registry.finish_delivery(delivery, false);
16562        assert!(completion.failed_dispatches.is_empty());
16563        assert!(!completion.redispatch);
16564      };
16565
16566      let dropped = Arc::new(AtomicBool::new(false));
16567      let flag = DropFlag(Arc::clone(&dropped));
16568      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16569      let shared = rx.map(|_| ()).boxed().shared();
16570      let shared_for_b = shared.clone();
16571      let work_registration = backend
16572        .work
16573        .try_register_async()
16574        .expect("the backend must accept work");
16575      let task_b = spawn_registered(
16576        &backend,
16577        Arc::clone(&metrics),
16578        async move {
16579          let _flag = flag;
16580          shared_for_b.await;
16581        },
16582        work_registration,
16583      );
16584      service(0);
16585      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16586      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16587
16588      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
16589      let (queued_tx, queued_rx) = mpsc::channel::<()>();
16590      let producer_executor = Arc::clone(&executor);
16591      let producer = std::thread::spawn(move || {
16592        let mut submissions = 0usize;
16593        while queue_one_rx.recv().is_ok() {
16594          let scheduler = Arc::clone(&producer_executor);
16595          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16596            scheduler.schedule(runnable);
16597          });
16598          task.detach();
16599          producer_executor.schedule(runnable);
16600          submissions += 1;
16601          if queued_tx.send(()).is_err() {
16602            break;
16603          }
16604        }
16605        submissions
16606      });
16607      // The producer feeds exactly the deliveries the budget can pay for, and
16608      // the host refuses exactly those: it is healthy from the next one.
16609      driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
16610      driver
16611        .fail_budget
16612        .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
16613
16614      let before = driver.deliveries().len();
16615      tx.send(()).expect("B must still hold its Shared clone");
16616      let during_wake = driver.deliveries().len() - before;
16617      let unspent = driver.disarm_producer();
16618      let submissions = producer.join().expect("the producer must not panic");
16619
16620      assert_eq!(
16621        during_wake,
16622        BUDGETED_DELIVERIES + 1,
16623        "the budget must be followed by the one offer a producer cannot \
16624         inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
16625         deliveries and never asks the host that would have accepted"
16626      );
16627      assert_eq!(
16628        submissions, BUDGETED_DELIVERIES,
16629        "the producer must have queued a runnable during every budgeted \
16630         publication and gone quiescent before the final offer"
16631      );
16632      assert_eq!(
16633        unspent, 0,
16634        "the producer must have run dry exactly as the budget ran out"
16635      );
16636      assert_eq!(
16637        driver.fail_budget.load(Ordering::SeqCst),
16638        0,
16639        "the host must have refused every budgeted delivery and no more"
16640      );
16641      assert!(
16642        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16643        "the final offer must reopen the executor before the waker returns"
16644      );
16645      assert!(
16646        executor.has_rejected_work(),
16647        "nothing may be dropped on the waker's stack"
16648      );
16649      assert_ne!(
16650        executor.dispatch_pending.load(Ordering::Acquire),
16651        0,
16652        "the wake must leave a dispatch outstanding: that accepted delivery is \
16653         the continuation the parked work has"
16654      );
16655      let mut task_b = Box::pin(task_b);
16656      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16657      assert!(!dropped.load(Ordering::SeqCst));
16658      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16659
16660      // The ONLY continuation: the host serves the final offer. No submission,
16661      // no drain request, no unregistration, no shutdown.
16662      service(driver.deliveries().len() - 1);
16663      assert!(
16664        !executor.has_rejected_work(),
16665        "that host turn is a depth-0 stack and must retire everything the wake \
16666         moved aside"
16667      );
16668      assert!(
16669        poll_ready_within(&mut task_b, WAIT)
16670          .expect("the final offer's host turn must cancel B")
16671          .is_err()
16672      );
16673      assert!(
16674        dropped.load(Ordering::SeqCst),
16675        "B's future must be destroyed, on the host's stack and not the waker's"
16676      );
16677      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16678      assert_eq!(
16679        backend.work.state.lock().unwrap().active,
16680        0,
16681        "no GenerationWorkGuard may outlive the retirement"
16682      );
16683      drop(shared);
16684
16685      registry.unregister(registration);
16686      let state = registry
16687        .state
16688        .lock()
16689        .unwrap_or_else(std::sync::PoisonError::into_inner);
16690      assert!(state.entries.is_empty());
16691      assert!(state.dispatches.is_empty());
16692      done_tx.send(()).unwrap();
16693    });
16694    done_rx.recv_timeout(WAIT).expect(
16695      "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16696    );
16697  }
16698
16699  /// Regression test for the turn a REFUSED offer owes, with no producer at
16700  /// all.
16701  ///
16702  /// Until this commit the turns were driven by a thread-local flag that only
16703  /// a NESTED terminal transition set. An offer the host simply refused with
16704  /// an empty queue reserves no replacement, so it reached no transition and
16705  /// set no flag, and the owner frame returned after a single offer with seven
16706  /// attempts unspent -- the same stranded state the budget's exhaustion
16707  /// leaves, reached with nothing racing at all. Measured before: 3 deliveries
16708  /// during the wake, `dispatch_pending` 0, B parked in `queue.rejected` with
16709  /// its guard held, and a host that was healthy for its very next delivery
16710  /// never asked for one.
16711  ///
16712  /// What a turn owes is executor state, not a message: work parked and no
16713  /// dispatch outstanding. Reading that instead of a flag makes both routes
16714  /// one route, and this host -- which refuses the wake's publication, its
16715  /// replacement and the first offer, and accepts the second -- is served.
16716  #[cfg(napi_runtime_os_threads)]
16717  #[test]
16718  fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16719    use std::{sync::mpsc, time::Duration};
16720
16721    const WAIT: Duration = Duration::from_secs(5);
16722    let (done_tx, done_rx) = mpsc::channel();
16723    std::thread::spawn(move || {
16724      struct DropFlag(Arc<AtomicBool>);
16725      impl Drop for DropFlag {
16726        fn drop(&mut self) {
16727          self.0.store(true, Ordering::SeqCst);
16728        }
16729      }
16730
16731      let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16732      {
16733        let state = registry
16734          .state
16735          .lock()
16736          .unwrap_or_else(std::sync::PoisonError::into_inner);
16737        assert!(state.entries.is_empty());
16738        assert!(state.dispatches.is_empty());
16739      }
16740      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16741      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16742      let metrics = Arc::new(RuntimeMetrics::default());
16743      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16744        Arc::clone(&metrics),
16745        dispatch_retirement_retry_current_thread_tasks,
16746        registry,
16747      ));
16748      let backend =
16749        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16750
16751      let service = |index: usize| {
16752        let delivery = driver.deliveries()[index];
16753        let dispatch = registry
16754          .claim_delivery(delivery.capability())
16755          .expect("the accepted delivery must be claimable");
16756        let mut host_turn = executor
16757          .try_admit_host_turn(dispatch)
16758          .expect("the claimed delivery must be admitted");
16759        registry.mark_serviced(dispatch);
16760        host_turn.drive();
16761        drop(host_turn);
16762        let completion = registry.finish_delivery(delivery, false);
16763        assert!(completion.failed_dispatches.is_empty());
16764        assert!(!completion.redispatch);
16765      };
16766
16767      let dropped = Arc::new(AtomicBool::new(false));
16768      let flag = DropFlag(Arc::clone(&dropped));
16769      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16770      let shared = rx.map(|_| ()).boxed().shared();
16771      let shared_for_b = shared.clone();
16772      let work_registration = backend
16773        .work
16774        .try_register_async()
16775        .expect("the backend must accept work");
16776      let task_b = spawn_registered(
16777        &backend,
16778        Arc::clone(&metrics),
16779        async move {
16780          let _flag = flag;
16781          shared_for_b.await;
16782        },
16783        work_registration,
16784      );
16785      service(0);
16786      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16787      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16788
16789      // The wake's publication, its one replacement, and the first retirement
16790      // offer are lost. The host is healthy from the second offer on, and no
16791      // producer ever runs: every offer after the first is refused with an
16792      // empty queue, which reserves no replacement and reaches no transition.
16793      let before = driver.deliveries().len();
16794      driver.fail_budget.store(3, Ordering::SeqCst);
16795      tx.send(()).expect("B must still hold its Shared clone");
16796      let during_wake = driver.deliveries().len() - before;
16797
16798      assert_eq!(
16799        during_wake, 4,
16800        "a refused offer owes another turn just as a nested transition does: \
16801         work is parked and no dispatch is outstanding"
16802      );
16803      assert_ne!(
16804        executor.dispatch_pending.load(Ordering::Acquire),
16805        0,
16806        "the accepted second offer is the continuation the parked work has"
16807      );
16808      assert!(executor.has_rejected_work());
16809      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16810      let mut task_b = Box::pin(task_b);
16811      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16812      assert!(!dropped.load(Ordering::SeqCst));
16813
16814      // The ONLY continuation: the host serves the offer it accepted.
16815      service(driver.deliveries().len() - 1);
16816      assert!(!executor.has_rejected_work());
16817      assert!(
16818        poll_ready_within(&mut task_b, WAIT)
16819          .expect("the accepted offer's host turn must cancel B")
16820          .is_err()
16821      );
16822      assert!(dropped.load(Ordering::SeqCst));
16823      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16824      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16825      drop(shared);
16826
16827      registry.unregister(registration);
16828      let state = registry
16829        .state
16830        .lock()
16831        .unwrap_or_else(std::sync::PoisonError::into_inner);
16832      assert!(state.entries.is_empty());
16833      assert!(state.dispatches.is_empty());
16834      done_tx.send(()).unwrap();
16835    });
16836    done_rx
16837      .recv_timeout(WAIT)
16838      .expect("a refused retirement offer left B stranded with a healthy host");
16839  }
16840
16841  /// Regression test for the continuation the FINAL offer owes when the host
16842  /// services it from inside `dispatch`.
16843  ///
16844  /// `6cfe2002` makes one last offer after the attempt budget with the
16845  /// terminal-cancellation claim held, so nothing can inflate it, and then
16846  /// reopens and returns. A driver may claim, drive and acknowledge a delivery
16847  /// inside `dispatch` -- the registry supports that synchronous completion,
16848  /// and `RacingTaskDriver` in this module drives one inline -- and such a turn
16849  /// on THIS offer can do nothing at all:
16850  /// `retire_rejected_work_off_waker_stack` returns at nonzero schedule depth,
16851  /// and `take_runnable` returns `None` while `cancelling_failed_dispatch` is
16852  /// held. It only consumes `dispatch_pending`. The offer then returned with
16853  /// the executor reopened, no dispatch outstanding and `queue.rejected` still
16854  /// holding B. Measured before this commit: 11 deliveries during the wake,
16855  /// `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle` pending, its
16856  /// future never dropped, its `GenerationWorkGuard` still active -- and not
16857  /// one delivery afterwards, with the driver registered and healthy.
16858  ///
16859  /// An offer the host ACCEPTED but could not act on leaves exactly the state
16860  /// a refused one leaves, and the invariant is the one this branch already
16861  /// reads off the executor: while work is parked in `queue.rejected` and no
16862  /// dispatch is outstanding, this stack still owes a continuation. So the
16863  /// offer reports whether the host accepted it, and an accepted offer whose
16864  /// dispatch was consumed is followed by ONE ordinary publication -- the
16865  /// executor is open again, so that is the path `5857092f` reopened it for.
16866  /// It is one publication and not a loop, and a nested terminal transition
16867  /// inside it finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held and
16868  /// publishes nothing.
16869  ///
16870  /// Nothing here supplies any other entry: no submission after the wake, no
16871  /// drain request, no unregistration, no shutdown.
16872  #[cfg(napi_runtime_os_threads)]
16873  #[test]
16874  fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
16875    use std::{sync::mpsc, time::Duration};
16876
16877    const WAIT: Duration = Duration::from_secs(5);
16878    /// The wake's publication, its one replacement, and every budgeted turn.
16879    const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16880
16881    let (done_tx, done_rx) = mpsc::channel();
16882    std::thread::spawn(move || {
16883      struct DropFlag(Arc<AtomicBool>);
16884      impl Drop for DropFlag {
16885        fn drop(&mut self) {
16886          self.0.store(true, Ordering::SeqCst);
16887        }
16888      }
16889
16890      let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
16891      {
16892        let state = registry
16893          .state
16894          .lock()
16895          .unwrap_or_else(std::sync::PoisonError::into_inner);
16896        assert!(state.entries.is_empty());
16897        assert!(state.dispatches.is_empty());
16898      }
16899      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16900      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16901      let metrics = Arc::new(RuntimeMetrics::default());
16902      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16903        Arc::clone(&metrics),
16904        dispatch_inline_final_offer_current_thread_tasks,
16905        registry,
16906      ));
16907      let backend =
16908        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16909
16910      let service = |index: usize| {
16911        let delivery = driver.deliveries()[index];
16912        let dispatch = registry
16913          .claim_delivery(delivery.capability())
16914          .expect("the accepted delivery must be claimable");
16915        let mut host_turn = executor
16916          .try_admit_host_turn(dispatch)
16917          .expect("the claimed delivery must be admitted");
16918        registry.mark_serviced(dispatch);
16919        host_turn.drive();
16920        drop(host_turn);
16921        let completion = registry.finish_delivery(delivery, false);
16922        assert!(completion.failed_dispatches.is_empty());
16923        assert!(!completion.redispatch);
16924      };
16925
16926      let dropped = Arc::new(AtomicBool::new(false));
16927      let flag = DropFlag(Arc::clone(&dropped));
16928      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16929      let shared = rx.map(|_| ()).boxed().shared();
16930      let shared_for_b = shared.clone();
16931      let work_registration = backend
16932        .work
16933        .try_register_async()
16934        .expect("the backend must accept work");
16935      let task_b = spawn_registered(
16936        &backend,
16937        Arc::clone(&metrics),
16938        async move {
16939          let _flag = flag;
16940          shared_for_b.await;
16941        },
16942        work_registration,
16943      );
16944      service(0);
16945      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16946      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16947
16948      // The wake's publication, its one replacement and every budgeted turn
16949      // are refused. The delivery right after them is the final offer, and the
16950      // host claims, drives and acknowledges THAT one from inside `dispatch`:
16951      // on the waker's stack, with the terminal-cancellation claim held.
16952      let before = driver.deliveries().len();
16953      let serviced_inline = Arc::new(AtomicBool::new(false));
16954      {
16955        let executor = Arc::clone(&executor);
16956        let hook_driver = Arc::clone(&driver);
16957        let serviced_inline = Arc::clone(&serviced_inline);
16958        driver.during_delivery(before + REFUSED_DELIVERIES, move || {
16959          let delivery = *hook_driver
16960            .deliveries()
16961            .last()
16962            .expect("the final offer must be recorded before the hook runs");
16963          let dispatch = registry
16964            .claim_delivery(delivery.capability())
16965            .expect("the final offer must be claimable");
16966          let mut host_turn = executor
16967            .try_admit_host_turn(dispatch)
16968            .expect("the claimed final offer must be admitted");
16969          registry.mark_serviced(dispatch);
16970          host_turn.drive();
16971          drop(host_turn);
16972          let completion = registry.finish_delivery(delivery, false);
16973          assert!(completion.failed_dispatches.is_empty());
16974          serviced_inline.store(true, Ordering::SeqCst);
16975        });
16976      }
16977      driver
16978        .fail_budget
16979        .store(REFUSED_DELIVERIES, Ordering::SeqCst);
16980      tx.send(()).expect("B must still hold its Shared clone");
16981      let during_wake = driver.deliveries().len() - before;
16982
16983      assert!(
16984        serviced_inline.load(Ordering::SeqCst),
16985        "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
16986      );
16987      assert_eq!(
16988        during_wake,
16989        REFUSED_DELIVERIES + 2,
16990        "an accepted offer whose host turn could not act owes the same continuation a refused \
16991         one owes: work parked and no dispatch outstanding"
16992      );
16993      assert_ne!(
16994        executor.dispatch_pending.load(Ordering::Acquire),
16995        0,
16996        "the publication that follows the consumed offer is the continuation the parked work has"
16997      );
16998      assert!(executor.has_rejected_work());
16999      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17000      let mut task_b = Box::pin(task_b);
17001      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
17002      assert!(!dropped.load(Ordering::SeqCst));
17003
17004      // The ONLY continuation: the host serves it on a clean stack.
17005      service(driver.deliveries().len() - 1);
17006      assert!(!executor.has_rejected_work());
17007      assert!(
17008        poll_ready_within(&mut task_b, WAIT)
17009          .expect("the host turn that follows the consumed offer must cancel B")
17010          .is_err()
17011      );
17012      assert!(dropped.load(Ordering::SeqCst));
17013      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17014      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17015      drop(shared);
17016
17017      registry.unregister(registration);
17018      let state = registry
17019        .state
17020        .lock()
17021        .unwrap_or_else(std::sync::PoisonError::into_inner);
17022      assert!(state.entries.is_empty());
17023      assert!(state.dispatches.is_empty());
17024      done_tx.send(()).unwrap();
17025    });
17026    done_rx.recv_timeout(WAIT).expect(
17027      "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
17028    );
17029  }
17030
17031  /// Regression test for the continuation an ordinary `spawn` owes itself.
17032  /// `async-task` runs one scheduler callback for two different things: a
17033  /// wake, which any `Waker` can raise from any stack, and the first schedule
17034  /// of a freshly submitted task, which `spawn` makes on its caller's own
17035  /// stack. Only the wake can arrive under a foreign lock. When the host fails
17036  /// a submission's publication and its one replacement synchronously, the
17037  /// terminal transition is that submission's ONLY continuation: it produced
17038  /// no host turn, `complete_current_thread_task_delivery` has already run,
17039  /// and nothing else will reach `retire_rejected_work`. Treating the frame as
17040  /// a waker's parked the runnable in `queue.rejected` with the driver
17041  /// registered and healthy again, so the `JoinHandle` stayed pending forever
17042  /// and its `GenerationWorkGuard` stayed active. Nothing here supplies a
17043  /// later entry: no drain request, no host turn, no unregistration, no
17044  /// shutdown.
17045  #[cfg(napi_runtime_os_threads)]
17046  #[test]
17047  fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
17048    use std::time::Duration;
17049
17050    let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
17051    {
17052      let state = registry
17053        .state
17054        .lock()
17055        .unwrap_or_else(std::sync::PoisonError::into_inner);
17056      assert!(state.entries.is_empty());
17057      assert!(state.dispatches.is_empty());
17058    }
17059    let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
17060    let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
17061    let metrics = Arc::new(RuntimeMetrics::default());
17062    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
17063      Arc::clone(&metrics),
17064      dispatch_submission_failure_current_thread_tasks,
17065      registry,
17066    ));
17067    let backend =
17068      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17069
17070    // Every accepted delivery fails before `dispatch` returns, the way a
17071    // closing threadsafe function does.
17072    driver.fail.store(true, Ordering::SeqCst);
17073
17074    let work_registration = backend
17075      .work
17076      .try_register_async()
17077      .expect("the backend must accept work");
17078    let task = spawn_registered(
17079      &backend,
17080      Arc::clone(&metrics),
17081      std::future::pending::<()>(),
17082      work_registration,
17083    );
17084
17085    assert_eq!(
17086      driver.deliveries().len(),
17087      2,
17088      "the submission must publish the dispatch and exactly one replacement"
17089    );
17090    assert!(
17091      !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
17092      "the terminal failure must reopen the executor"
17093    );
17094    assert!(
17095      !executor.has_rejected_work(),
17096      "a submission's own stack is not a waker's: the rejected work must be        cancelled here, because nothing else will"
17097    );
17098    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17099    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17100
17101    let mut task = Box::pin(task);
17102    assert_eq!(
17103      poll_ready_within(&mut task, Duration::ZERO)
17104        .expect("the submission must settle without any later entry")
17105        .unwrap_err()
17106        .to_string(),
17107      "async runtime stopped before the task completed",
17108      "terminal host failure must settle the submitted operation as cancelled"
17109    );
17110
17111    registry.unregister(registration);
17112    let state = registry
17113      .state
17114      .lock()
17115      .unwrap_or_else(std::sync::PoisonError::into_inner);
17116    assert!(state.entries.is_empty());
17117    assert!(state.dispatches.is_empty());
17118  }
17119
17120  /// MultiThread twin of
17121  /// `current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline`.
17122  /// The same wake is in flight when shutdown starts: B's schedule closure
17123  /// runs inside `futures::Shared`'s notifier, under its `wakers` mutex, and
17124  /// B's future owns a clone of that `Shared`. `MultiThreadExecutor::schedule`
17125  /// has no closed-queue branch: the late runnable is pushed to the injector
17126  /// (or the worker's LIFO slot) exactly like any other, and the drainer that
17127  /// claims it under the published stop cancels it on its own stack
17128  /// (`claim_runnable` -> `RunnableClaim::Cancel` -> `cancel_runnable`). That
17129  /// stack never holds `wakers`, so the drop only waits for the waker thread
17130  /// to leave the notifier. This pins that shape: a closed-queue rejection
17131  /// added to `schedule` that dropped the runnable inline would deadlock
17132  /// here, exactly as the CurrentThread executor did.
17133  #[cfg(napi_runtime_os_threads)]
17134  #[test]
17135  fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
17136    use std::{
17137      sync::{Barrier, mpsc},
17138      time::Duration,
17139    };
17140
17141    let (done_tx, done_rx) = mpsc::channel();
17142    std::thread::spawn(move || {
17143      let metrics = Arc::new(RuntimeMetrics::default());
17144      let options = RuntimeOptions {
17145        flavor: RuntimeFlavor::MultiThread,
17146        worker_threads: 2,
17147        max_blocking_tasks: 1,
17148        thread_name_prefix: "mt-inline-drop".to_string(),
17149        park_deadline: None,
17150        drain_linger: DEFAULT_DRAIN_LINGER,
17151      };
17152      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17153      let backend =
17154        RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
17155      let generation = backend.generation();
17156
17157      // Task A: registered like production work; its JoinHandle is shared.
17158      let (abort_a, guard_a) = backend
17159        .work
17160        .try_register_async()
17161        .expect("the backend must accept work");
17162      let scheduler_a = Arc::clone(&executor);
17163      let (runnable_a, task_a) = async_task::spawn(
17164        RegisteredTaskFuture::new(
17165          Abortable::new(std::future::pending::<()>(), abort_a),
17166          guard_a,
17167          generation,
17168        ),
17169        move |runnable| scheduler_a.schedule(runnable),
17170      );
17171      let shared = async move { task_a.fallible().await.is_some() }
17172        .boxed()
17173        .shared();
17174      let shared_for_b = shared.clone();
17175
17176      // Task B: registered and abortable too, awaiting a clone of that Shared.
17177      // Its schedule closure pauses before it reaches the executor, exactly
17178      // where a waker thread may be descheduled while shutdown runs.
17179      let (abort_b, guard_b) = backend
17180        .work
17181        .try_register_async()
17182        .expect("the backend must accept work");
17183      let wake_in_flight = Arc::new(Barrier::new(2));
17184      let wake_release = Arc::new(Barrier::new(2));
17185      let scheduler_b = Arc::clone(&executor);
17186      let (runnable_b, task_b) = async_task::spawn(
17187        RegisteredTaskFuture::new(
17188          Abortable::new(
17189            async move {
17190              shared_for_b.await;
17191            },
17192            abort_b,
17193          ),
17194          guard_b,
17195          generation,
17196        ),
17197        {
17198          let wake_in_flight = Arc::clone(&wake_in_flight);
17199          let wake_release = Arc::clone(&wake_release);
17200          move |runnable| {
17201            wake_in_flight.wait();
17202            wake_release.wait();
17203            scheduler_b.schedule(runnable);
17204          }
17205        },
17206      );
17207      // The first poll runs on a pool drainer; wait until B has parked on the
17208      // Shared and left the drainer, so the wake below is a real re-schedule
17209      // from the notifier rather than a wake-while-running that the drainer
17210      // would re-publish from its own stack.
17211      executor.schedule(runnable_b);
17212      wait_until("B parked after its first poll", || {
17213        metrics.runnable_polls.load(Ordering::SeqCst) == 1
17214          && metrics.active_runnables.load(Ordering::SeqCst) == 0
17215          && metrics.queued_runnables.load(Ordering::SeqCst) == 0
17216      });
17217
17218      // Cancel A on another thread: closing A notifies its awaiter, and the
17219      // Shared notifier wakes B while holding `wakers`. B's schedule closure
17220      // then parks at the barrier with that lock still held.
17221      let waker_thread = std::thread::spawn(move || drop(runnable_a));
17222      wake_in_flight.wait();
17223
17224      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
17225      // the abort wake is a no-op; the stop is then published.
17226      backend.begin_shutdown();
17227      wake_release.wait();
17228      backend.wait_until_idle();
17229
17230      waker_thread
17231        .join()
17232        .expect("the waker thread must not panic");
17233      assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
17234      assert!(
17235        futures::executor::block_on(task_b.fallible()).is_none(),
17236        "B's late wake must still cancel B"
17237      );
17238      assert_eq!(backend.work.state.lock().unwrap().active, 0);
17239      drop(shared);
17240      done_tx.send(()).unwrap();
17241    });
17242    done_rx
17243      .recv_timeout(Duration::from_secs(5))
17244      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
17245  }
17246
17247  #[cfg(napi_runtime_os_threads)]
17248  #[test]
17249  fn current_thread_recovery_reservation_blocks_new_host_publication() {
17250    use std::sync::mpsc;
17251
17252    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
17253    let metrics = Arc::new(RuntimeMetrics::default());
17254    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17255      Arc::clone(&metrics),
17256      count_current_thread_interleaved_recovery_host_dispatch,
17257    ));
17258    let backend =
17259      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17260
17261    let registration = backend
17262      .work
17263      .try_register_async()
17264      .expect("the backend must accept work");
17265    let task = spawn_registered(
17266      &backend,
17267      Arc::clone(&metrics),
17268      std::future::pending::<()>(),
17269      registration,
17270    );
17271    let failed_dispatch = current_thread_dispatch(&executor);
17272    assert_eq!(
17273      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17274      1
17275    );
17276
17277    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
17278    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
17279    let cancelling_executor = Arc::clone(&executor);
17280    let cancellation = std::thread::spawn(move || {
17281      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
17282        *slot.borrow_mut() = Some(Box::new(move || {
17283          recovery_cleared_tx.send(()).unwrap();
17284          release_recovery_rx.recv().unwrap();
17285        }));
17286      });
17287      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
17288    });
17289    recovery_cleared_rx
17290      .recv_timeout(Duration::from_secs(2))
17291      .expect("failed dispatch cancellation must pause after consuming the old capability");
17292
17293    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
17294    let (publication_done_tx, publication_done_rx) = mpsc::channel();
17295    let publishing_executor = Arc::clone(&executor);
17296    let publication = std::thread::spawn(move || {
17297      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
17298        *slot.borrow_mut() = Some(Box::new(move || {
17299          publication_blocked_tx.send(()).unwrap();
17300        }));
17301      });
17302      publishing_executor.request_drain_if_queued();
17303      publication_done_tx.send(()).unwrap();
17304    });
17305
17306    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
17307      release_recovery_tx.send(()).unwrap();
17308      cancellation.join().unwrap();
17309      publication.join().unwrap();
17310      panic!("a newly registered host must wait for recovery reservation: {error}");
17311    }
17312    assert!(
17313      matches!(
17314        publication_done_rx.try_recv(),
17315        Err(mpsc::TryRecvError::Empty)
17316      ),
17317      "the new host must not publish in the consumed-to-recovery gap"
17318    );
17319    release_recovery_tx.send(()).unwrap();
17320
17321    cancellation.join().unwrap();
17322    publication_done_rx
17323      .recv_timeout(Duration::from_secs(2))
17324      .unwrap();
17325    publication.join().unwrap();
17326
17327    let replacement_dispatch = current_thread_dispatch(&executor);
17328    assert_ne!(replacement_dispatch, failed_dispatch);
17329    assert_eq!(
17330      executor.recovery_dispatch.load(Ordering::Acquire),
17331      replacement_dispatch
17332    );
17333    assert_eq!(
17334      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17335      3,
17336      "the original publication and two host attempts for its one replacement must run"
17337    );
17338
17339    executor.cancel_host_dispatch_for_test(replacement_dispatch);
17340
17341    assert_eq!(
17342      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17343      3,
17344      "failure of the tagged replacement must cancel work without a second replacement"
17345    );
17346    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17347    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17348    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17349    assert_eq!(
17350      futures::executor::block_on(task).unwrap_err().to_string(),
17351      "async runtime stopped before the task completed"
17352    );
17353
17354    let later_registration = backend
17355      .work
17356      .try_register_async()
17357      .expect("terminal cancellation must reopen acceptance");
17358    let later_task = spawn_registered(
17359      &backend,
17360      Arc::clone(&metrics),
17361      async { 2usize },
17362      later_registration,
17363    );
17364    assert_eq!(
17365      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
17366      4
17367    );
17368    executor.drive_host_turn();
17369    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
17370  }
17371
17372  #[cfg(napi_runtime_os_threads)]
17373  #[test]
17374  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
17375    use std::sync::mpsc;
17376
17377    struct BlockingDrop {
17378      entered: mpsc::Sender<()>,
17379      release: mpsc::Receiver<()>,
17380    }
17381
17382    impl Drop for BlockingDrop {
17383      fn drop(&mut self) {
17384        self.entered.send(()).unwrap();
17385        self.release.recv().unwrap();
17386      }
17387    }
17388
17389    reset_current_thread_recovery_host_dispatches();
17390    let metrics = Arc::new(RuntimeMetrics::default());
17391    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17392      Arc::clone(&metrics),
17393      count_current_thread_recovery_host_dispatch,
17394    ));
17395    let backend =
17396      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
17397
17398    let holder_registration = backend
17399      .work
17400      .try_register_work()
17401      .expect("the backend must accept the blocking holder");
17402    let (holder_started_tx, holder_started_rx) = mpsc::channel();
17403    let (release_holder_tx, release_holder_rx) = mpsc::channel();
17404    let holder_executor = Arc::clone(&executor);
17405    let holder = std::thread::spawn(move || {
17406      let handle = holder_executor.schedule_blocking(
17407        move || {
17408          holder_started_tx.send(()).unwrap();
17409          release_holder_rx.recv().unwrap();
17410        },
17411        holder_registration,
17412      );
17413      futures::executor::block_on(handle).unwrap();
17414    });
17415    holder_started_rx
17416      .recv_timeout(Duration::from_secs(2))
17417      .expect("the first blocking call must hold the CurrentThread lane");
17418
17419    let cancelled_registration = backend
17420      .work
17421      .try_register_work()
17422      .expect("the backend must accept queued blocking work");
17423    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
17424    let (release_drop_tx, release_drop_rx) = mpsc::channel();
17425    let blocking_drop = BlockingDrop {
17426      entered: drop_entered_tx,
17427      release: release_drop_rx,
17428    };
17429    let cancelled = executor.schedule_blocking(
17430      move || {
17431        let _blocking_drop = blocking_drop;
17432      },
17433      cancelled_registration,
17434    );
17435
17436    let async_registration = backend
17437      .work
17438      .try_register_async()
17439      .expect("the backend must accept queued async work");
17440    let pending = spawn_registered(
17441      &backend,
17442      Arc::clone(&metrics),
17443      std::future::pending::<()>(),
17444      async_registration,
17445    );
17446    let failed_dispatch = current_thread_dispatch(&executor);
17447    executor.cancel_host_dispatch_for_test(failed_dispatch);
17448    let replacement_dispatch = current_thread_dispatch(&executor);
17449
17450    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
17451    let cancelling_executor = Arc::clone(&executor);
17452    let cancellation = std::thread::spawn(move || {
17453      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
17454      cancel_done_tx.send(()).unwrap();
17455    });
17456    drop_entered_rx
17457      .recv_timeout(Duration::from_secs(2))
17458      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
17459    let active_during_drop = backend.work.state.lock().unwrap().active;
17460    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
17461
17462    release_drop_tx.send(()).unwrap();
17463    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17464    cancellation.join().unwrap();
17465
17466    assert_eq!(
17467      active_during_drop, 2,
17468      "the blocking capture's work registration must remain active until its destructor returns"
17469    );
17470    assert!(
17471      cancellation_blocked,
17472      "terminal cancellation must wait for capture destruction"
17473    );
17474    assert!(futures::executor::block_on(cancelled).is_err());
17475    assert!(futures::executor::block_on(pending).is_err());
17476    assert_eq!(backend.work.state.lock().unwrap().active, 1);
17477
17478    release_holder_tx.send(()).unwrap();
17479    holder.join().unwrap();
17480    assert_eq!(backend.work.state.lock().unwrap().active, 0);
17481  }
17482
17483  #[test]
17484  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
17485    reset_current_thread_one_shot_host_dispatches();
17486    let metrics = Arc::new(RuntimeMetrics::default());
17487    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17488      Arc::clone(&metrics),
17489      accept_only_first_current_thread_host_dispatch,
17490    ));
17491
17492    let scheduler = Arc::clone(&executor);
17493    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
17494      scheduler.schedule(runnable)
17495    });
17496    executor.schedule(runnable);
17497    let failed_dispatch = current_thread_dispatch(&executor);
17498
17499    executor.cancel_host_dispatch_for_test(failed_dispatch);
17500
17501    assert_eq!(
17502      current_thread_one_shot_host_dispatches(),
17503      2,
17504      "the original and its one bounded replacement must be attempted"
17505    );
17506    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
17507    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
17508    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
17509    assert!(
17510      futures::executor::block_on(task.fallible()).is_none(),
17511      "an unavailable replacement host must settle the original task as cancelled"
17512    );
17513  }
17514
17515  enum TestCurrentThreadTaskDriverBehavior {
17516    Healthy,
17517    PanicInLivenessAndSweep,
17518    PanicInDispatchAndSweep,
17519  }
17520
17521  struct TestCurrentThreadTaskDriver {
17522    behavior: TestCurrentThreadTaskDriverBehavior,
17523    dispatched: AtomicBool,
17524  }
17525
17526  #[derive(Default)]
17527  struct ManualCurrentThreadTaskDriver {
17528    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17529  }
17530
17531  struct FailDuringAuthoritativePanicPublication;
17532  struct FailDuringLateRegistrationPublication;
17533  struct FailDuringNestedUnwindPublication;
17534  struct FailDuringReplacementRepublishPublication;
17535  struct FirstHostileCurrentThreadPanic;
17536  struct LaterHostileCurrentThreadPanic;
17537
17538  impl Drop for FirstHostileCurrentThreadPanic {
17539    fn drop(&mut self) {
17540      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17541      panic!("the first retained CurrentThread panic payload was dropped");
17542    }
17543  }
17544
17545  impl Drop for LaterHostileCurrentThreadPanic {
17546    fn drop(&mut self) {
17547      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
17548      panic!("a later CurrentThread panic payload escaped containment");
17549    }
17550  }
17551
17552  impl ManualCurrentThreadTaskDriver {
17553    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17554      self.deliveries.lock().unwrap().clone()
17555    }
17556  }
17557
17558  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
17559    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17560      self.deliveries.lock().unwrap().push(delivery);
17561      true
17562    }
17563  }
17564
17565  /// Accepts every delivery; once `fail` is set it also fails the accepted
17566  /// delivery before `dispatch` returns, from a helper thread it joins, the
17567  /// way a host whose env is tearing down reports a threadsafe-function call
17568  /// that cannot land.
17569  struct SynchronouslyFailingCurrentThreadTaskDriver {
17570    registry: &'static CurrentThreadTaskDriverRegistry,
17571    fail: AtomicBool,
17572    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17573  }
17574
17575  impl SynchronouslyFailingCurrentThreadTaskDriver {
17576    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17577      Self {
17578        registry,
17579        fail: AtomicBool::new(false),
17580        deliveries: Mutex::default(),
17581      }
17582    }
17583
17584    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17585      self.deliveries.lock().unwrap().clone()
17586    }
17587  }
17588
17589  impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
17590    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17591      self.deliveries.lock().unwrap().push(delivery);
17592      if self.fail.load(Ordering::SeqCst) {
17593        let registry = self.registry;
17594        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17595          .join()
17596          .expect("the failing helper must not panic");
17597        assert!(
17598          completion.failed_dispatches.is_empty(),
17599          "the failed delivery must remain provisional until its publication retires"
17600        );
17601      }
17602      true
17603    }
17604  }
17605
17606  /// Accepts every delivery, and fails the first `fail_budget` of them before
17607  /// `dispatch` returns -- from a helper thread it joins, exactly like
17608  /// `SynchronouslyFailingCurrentThreadTaskDriver`. Once the budget is spent
17609  /// the host is healthy again, so the retirement turn a waker-stack terminal
17610  /// failure publishes for itself is accepted instead of lost.
17611  /// A one-shot the test runs from inside a chosen delivery; see
17612  /// `BoundedFailureCurrentThreadTaskDriver::during_delivery`.
17613  type DeliveryHook = Box<dyn FnOnce() + Send>;
17614
17615  struct BoundedFailureCurrentThreadTaskDriver {
17616    registry: &'static CurrentThreadTaskDriverRegistry,
17617    fail_budget: AtomicUsize,
17618    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17619    during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
17620  }
17621
17622  impl BoundedFailureCurrentThreadTaskDriver {
17623    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17624      Self {
17625        registry,
17626        fail_budget: AtomicUsize::new(0),
17627        deliveries: Mutex::default(),
17628        during_delivery: Mutex::default(),
17629      }
17630    }
17631
17632    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17633      self.deliveries.lock().unwrap().clone()
17634    }
17635
17636    /// Run `hook` from inside the `index`-th delivery (0-based over every
17637    /// delivery this driver receives), before that delivery is failed. This is
17638    /// the window another thread needs to queue work while a publication is in
17639    /// flight.
17640    fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
17641      *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
17642    }
17643  }
17644
17645  impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
17646    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17647      let index = {
17648        let mut deliveries = self.deliveries.lock().unwrap();
17649        deliveries.push(delivery);
17650        deliveries.len() - 1
17651      };
17652      let hook = {
17653        let mut slot = self.during_delivery.lock().unwrap();
17654        if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
17655          slot.take().map(|(_, hook)| hook)
17656        } else {
17657          None
17658        }
17659      };
17660      if let Some(hook) = hook {
17661        hook();
17662      }
17663      let budget = self.fail_budget.load(Ordering::SeqCst);
17664      if budget != 0 {
17665        self.fail_budget.store(budget - 1, Ordering::SeqCst);
17666        let registry = self.registry;
17667        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
17668          .join()
17669          .expect("the failing helper must not panic");
17670        assert!(
17671          completion.failed_dispatches.is_empty(),
17672          "the failed delivery must remain provisional until its publication retires"
17673        );
17674      }
17675      true
17676    }
17677  }
17678
17679  /// Accepts every delivery and, while `fail` is set, fails it synchronously
17680  /// from inside `dispatch` on the calling thread -- the option
17681  /// [`CurrentThreadTaskDriver::dispatch`] documents for an SPI host. While a
17682  /// producer is armed it also rendezvouses with the producer thread from
17683  /// inside every delivery, so exactly one runnable is queued while each
17684  /// publication is in flight.
17685  struct SustainedProducerGate {
17686    remaining: usize,
17687    queue_one: std::sync::mpsc::Sender<()>,
17688    queued: std::sync::mpsc::Receiver<()>,
17689  }
17690
17691  struct SustainedProducerCurrentThreadTaskDriver {
17692    registry: &'static CurrentThreadTaskDriverRegistry,
17693    /// Deliveries still to be failed: `usize::MAX` fails every one of them,
17694    /// `n` fails the next `n` and is healthy after, `0` is healthy.
17695    fail_budget: AtomicUsize,
17696    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17697    producer: Mutex<Option<SustainedProducerGate>>,
17698  }
17699
17700  impl SustainedProducerCurrentThreadTaskDriver {
17701    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17702      Self {
17703        registry,
17704        fail_budget: AtomicUsize::new(0),
17705        deliveries: Mutex::default(),
17706        producer: Mutex::default(),
17707      }
17708    }
17709
17710    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17711      self.deliveries.lock().unwrap().clone()
17712    }
17713
17714    fn arm_producer(
17715      &self,
17716      remaining: usize,
17717      queue_one: std::sync::mpsc::Sender<()>,
17718      queued: std::sync::mpsc::Receiver<()>,
17719    ) {
17720      *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17721        remaining,
17722        queue_one,
17723        queued,
17724      });
17725    }
17726
17727    /// Close the gate and report how many submissions the producer was still
17728    /// willing to make.
17729    fn disarm_producer(&self) -> usize {
17730      self
17731        .producer
17732        .lock()
17733        .unwrap()
17734        .take()
17735        .map_or(0, |gate| gate.remaining)
17736    }
17737  }
17738
17739  impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17740    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17741      self.deliveries.lock().unwrap().push(delivery);
17742      {
17743        let mut slot = self.producer.lock().unwrap();
17744        if let Some(gate) = slot.as_mut()
17745          && gate.remaining != 0
17746        {
17747          gate.remaining -= 1;
17748          gate.queue_one.send(()).expect("the producer must be live");
17749          gate
17750            .queued
17751            .recv_timeout(std::time::Duration::from_secs(5))
17752            .expect("the producer must queue one runnable per publication");
17753        }
17754      }
17755      let budget = self.fail_budget.load(Ordering::SeqCst);
17756      if budget != 0 {
17757        if budget != usize::MAX {
17758          self.fail_budget.store(budget - 1, Ordering::SeqCst);
17759        }
17760        let completion = self.registry.finish_delivery(delivery, true);
17761        assert!(
17762          completion.failed_dispatches.is_empty(),
17763          "the failed delivery must remain provisional until its publication retires"
17764        );
17765      }
17766      true
17767    }
17768  }
17769
17770  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17771    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17772      let completion =
17773        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17774      assert!(
17775        completion.failed_dispatches.is_empty(),
17776        "the failed delivery must remain provisional until its publication retires"
17777      );
17778      true
17779    }
17780  }
17781
17782  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17783    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17784      let completion =
17785        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
17786      assert!(
17787        completion.failed_dispatches.is_empty(),
17788        "the failed delivery must remain provisional until its publication retires"
17789      );
17790      true
17791    }
17792  }
17793
17794  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
17795    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17796      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
17797      assert!(
17798        completion.failed_dispatches.is_empty(),
17799        "the failed delivery must remain provisional until its publication retires"
17800      );
17801      true
17802    }
17803  }
17804
17805  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
17806    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17807      let completion =
17808        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
17809      assert!(
17810        completion.failed_dispatches.is_empty(),
17811        "the failed delivery must remain provisional until its publication retires"
17812      );
17813      true
17814    }
17815  }
17816
17817  struct ReentrantCurrentThreadTaskDriver {
17818    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17819    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
17820  }
17821
17822  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
17823    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17824      true
17825    }
17826
17827    fn is_live(&self) -> bool {
17828      let registration = *self.registration.lock().unwrap();
17829      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
17830        registry.unregister(id);
17831      }
17832      false
17833    }
17834  }
17835
17836  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
17837    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17838    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17839  }
17840
17841  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
17842    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17843      true
17844    }
17845
17846    fn is_live(&self) -> bool {
17847      if let Some(registry) = self.registry.upgrade() {
17848        for registration in self.registrations.lock().unwrap().iter().copied() {
17849          let completion = registry.unregister(registration);
17850          assert!(completion.failed_dispatches.is_empty());
17851          assert!(!completion.redispatch);
17852        }
17853      }
17854      false
17855    }
17856  }
17857
17858  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17859    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17860    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17861    drops: Arc<AtomicUsize>,
17862  }
17863
17864  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17865    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17866      if let Some(registry) = self.registry.upgrade() {
17867        for registration in self.registrations.lock().unwrap().iter().copied() {
17868          let completion = registry.unregister(registration);
17869          assert!(completion.failed_dispatches.is_empty());
17870          assert!(!completion.redispatch);
17871        }
17872      }
17873      true
17874    }
17875  }
17876
17877  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17878    fn drop(&mut self) {
17879      self.drops.fetch_add(1, Ordering::SeqCst);
17880      panic!("intentional reentrant CurrentThread task-driver destructor panic");
17881    }
17882  }
17883
17884  struct PanickingDropCurrentThreadTaskDriver {
17885    drops: Arc<AtomicUsize>,
17886  }
17887
17888  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
17889    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17890      true
17891    }
17892  }
17893
17894  impl Drop for PanickingDropCurrentThreadTaskDriver {
17895    fn drop(&mut self) {
17896      self.drops.fetch_add(1, Ordering::SeqCst);
17897      panic!("intentional CurrentThread task-driver destructor panic");
17898    }
17899  }
17900
17901  struct ReentrantCurrentThreadTaskDriverDrop {
17902    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17903    fallback: CurrentThreadTaskDriverId,
17904  }
17905
17906  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
17907    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17908      true
17909    }
17910  }
17911
17912  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
17913    fn drop(&mut self) {
17914      if let Some(registry) = self.registry.upgrade() {
17915        registry.unregister(self.fallback);
17916      }
17917    }
17918  }
17919
17920  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
17921    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17922      assert!(
17923        !matches!(
17924          self.behavior,
17925          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
17926        ),
17927        "intentional task-driver dispatch panic"
17928      );
17929      self.dispatched.store(true, Ordering::SeqCst);
17930      true
17931    }
17932
17933    fn is_live(&self) -> bool {
17934      assert!(
17935        !matches!(
17936          self.behavior,
17937          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
17938        ),
17939        "intentional task-driver liveness panic"
17940      );
17941      true
17942    }
17943
17944    fn on_swept(&self) {
17945      assert!(
17946        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
17947        "intentional task-driver sweep panic"
17948      );
17949    }
17950  }
17951
17952  #[cfg(panic = "unwind")]
17953  #[test]
17954  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
17955    let registry = CurrentThreadTaskDriverRegistry::default();
17956    let fallback = Arc::new(TestCurrentThreadTaskDriver {
17957      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
17958      dispatched: AtomicBool::new(false),
17959    });
17960    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
17961    registry.register(Arc::new(TestCurrentThreadTaskDriver {
17962      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
17963      dispatched: AtomicBool::new(false),
17964    }));
17965    registry.register(Arc::new(TestCurrentThreadTaskDriver {
17966      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
17967      dispatched: AtomicBool::new(false),
17968    }));
17969
17970    assert_eq!(
17971      registry.dispatch(37),
17972      CurrentThreadHostDispatchResult::Accepted,
17973      "a panicking newest host must fall back to a live host"
17974    );
17975    assert!(fallback.dispatched.load(Ordering::SeqCst));
17976  }
17977
17978  #[cfg(panic = "unwind")]
17979  #[test]
17980  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
17981    let registry = CurrentThreadTaskDriverRegistry::default();
17982    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
17983    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
17984    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
17985    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
17986    registry
17987      .next_delivery
17988      .store(u64::MAX - 1, Ordering::Relaxed);
17989
17990    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
17991    assert!(
17992      publication.is_err(),
17993      "delivery-id exhaustion on the second host must unwind the publication"
17994    );
17995    {
17996      let state = registry
17997        .state
17998        .lock()
17999        .unwrap_or_else(std::sync::PoisonError::into_inner);
18000      assert!(
18001        !state.dispatches.contains_key(&38),
18002        "unwind cleanup must release the exact registry publication barrier"
18003      );
18004      assert!(
18005        state
18006          .entries
18007          .values()
18008          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
18009        "failed transactional reservation must leave every physical host slot clean"
18010      );
18011    }
18012    assert!(first.deliveries().is_empty());
18013    assert!(second.deliveries().is_empty());
18014
18015    registry.next_delivery.store(0, Ordering::Relaxed);
18016    assert_eq!(
18017      registry.dispatch(38),
18018      CurrentThreadHostDispatchResult::Accepted
18019    );
18020    let first_delivery = first.deliveries()[0];
18021    let second_delivery = second.deliveries()[0];
18022    assert_eq!(
18023      registry.claim_delivery(first_delivery.capability()),
18024      Some(38)
18025    );
18026    assert_eq!(
18027      registry.claim_delivery(second_delivery.capability()),
18028      Some(38)
18029    );
18030    registry.mark_serviced(38);
18031    assert!(
18032      registry
18033        .finish_delivery(first_delivery, false)
18034        .failed_dispatches
18035        .is_empty()
18036    );
18037    assert!(
18038      registry
18039        .finish_delivery(second_delivery, false)
18040        .failed_dispatches
18041        .is_empty()
18042    );
18043
18044    assert_eq!(
18045      registry.dispatch(39),
18046      CurrentThreadHostDispatchResult::Accepted
18047    );
18048    let first_later = first.deliveries()[1];
18049    let second_later = second.deliveries()[1];
18050    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
18051    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
18052    registry.mark_serviced(39);
18053    assert!(
18054      registry
18055        .finish_delivery(first_later, false)
18056        .failed_dispatches
18057        .is_empty()
18058    );
18059    assert!(
18060      registry
18061        .finish_delivery(second_later, false)
18062        .failed_dispatches
18063        .is_empty()
18064    );
18065    assert!(
18066      registry
18067        .state
18068        .lock()
18069        .unwrap_or_else(std::sync::PoisonError::into_inner)
18070        .dispatches
18071        .is_empty()
18072    );
18073    let state = registry
18074      .state
18075      .lock()
18076      .unwrap_or_else(std::sync::PoisonError::into_inner);
18077    assert!(
18078      state
18079        .entries
18080        .values()
18081        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
18082    );
18083  }
18084
18085  #[test]
18086  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
18087    let registry = CurrentThreadTaskDriverRegistry::default();
18088    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
18089    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
18090    let dispatch = 40;
18091
18092    assert_eq!(
18093      registry.dispatch(dispatch),
18094      CurrentThreadHostDispatchResult::Accepted
18095    );
18096    let failed_delivery = failed.deliveries()[0];
18097    let failure = current_thread_dispatch_failure(dispatch, 0);
18098    assert_eq!(
18099      registry
18100        .finish_delivery(failed_delivery, true)
18101        .failed_dispatches,
18102      vec![failure]
18103    );
18104    registry.unregister(failed_id);
18105
18106    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18107    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18108    assert!(
18109      registry.rearm_failed_publication(failure),
18110      "a closed unreferenced dispatch must reopen for its armed publication owner"
18111    );
18112    assert_eq!(
18113      registry.dispatch(dispatch),
18114      CurrentThreadHostDispatchResult::Accepted
18115    );
18116    let replacement_delivery = replacement.deliveries()[0];
18117    assert_eq!(replacement_delivery.dispatch, dispatch);
18118    assert_eq!(
18119      registry.claim_delivery(replacement_delivery.capability()),
18120      Some(dispatch)
18121    );
18122    registry.mark_serviced(dispatch);
18123    assert!(
18124      registry
18125        .finish_delivery(replacement_delivery, false)
18126        .failed_dispatches
18127        .is_empty()
18128    );
18129    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18130  }
18131
18132  #[test]
18133  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
18134    struct RacingTaskDriver {
18135      executor: Arc<CurrentThreadExecutor>,
18136      drives: bool,
18137      dispatches: Mutex<Vec<u64>>,
18138    }
18139
18140    impl CurrentThreadTaskDriver for RacingTaskDriver {
18141      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18142        self.dispatches.lock().unwrap().push(delivery.dispatch);
18143        if self.drives {
18144          self
18145            .executor
18146            .drive_host_turn_for_dispatch(delivery.dispatch);
18147        }
18148        true
18149      }
18150    }
18151
18152    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
18153      Arc::new(RuntimeMetrics::default()),
18154      accept_current_thread_host_dispatch,
18155    ));
18156    let polls = Arc::new(AtomicUsize::new(0));
18157    let task_polls = Arc::clone(&polls);
18158    let scheduler = Arc::clone(&executor);
18159    let (runnable, task) = async_task::spawn(
18160      async move {
18161        task_polls.fetch_add(1, Ordering::SeqCst);
18162      },
18163      move |runnable| scheduler.schedule(runnable),
18164    );
18165    executor.schedule(runnable);
18166    let dispatch = current_thread_dispatch(&executor);
18167
18168    let registry = CurrentThreadTaskDriverRegistry::default();
18169    let responsive = Arc::new(RacingTaskDriver {
18170      executor: Arc::clone(&executor),
18171      drives: true,
18172      dispatches: Mutex::new(Vec::new()),
18173    });
18174    let starved = Arc::new(RacingTaskDriver {
18175      executor: Arc::clone(&executor),
18176      drives: false,
18177      dispatches: Mutex::new(Vec::new()),
18178    });
18179    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
18180    // The newest host is intentionally live but unresponsive.
18181    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
18182
18183    assert_eq!(
18184      registry.dispatch(dispatch),
18185      CurrentThreadHostDispatchResult::Accepted
18186    );
18187    assert_eq!(
18188      responsive.dispatches.lock().unwrap().as_slice(),
18189      &[dispatch]
18190    );
18191    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
18192    assert_eq!(
18193      polls.load(Ordering::SeqCst),
18194      1,
18195      "the responsive older host must win even when the newest host is starved"
18196    );
18197
18198    // The delayed callback from the starved host carries the same consumed
18199    // capability and cannot poll the task a second time.
18200    executor.drive_host_turn_for_dispatch(dispatch);
18201    assert_eq!(polls.load(Ordering::SeqCst), 1);
18202    futures::executor::block_on(task);
18203  }
18204
18205  #[test]
18206  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
18207    const HOSTS: usize = 1_024;
18208
18209    let registry = CurrentThreadTaskDriverRegistry::default();
18210    let mut ids = Vec::with_capacity(HOSTS);
18211    let mut drivers = Vec::with_capacity(HOSTS);
18212    for _ in 0..HOSTS {
18213      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
18214      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
18215      drivers.push(driver);
18216    }
18217
18218    let dispatch = u64::MAX - 300;
18219    assert_eq!(
18220      registry.dispatch(dispatch),
18221      CurrentThreadHostDispatchResult::Accepted
18222    );
18223    let deliveries = drivers
18224      .iter()
18225      .map(|driver| driver.deliveries()[0])
18226      .collect::<Vec<_>>();
18227    {
18228      let state = registry
18229        .state
18230        .lock()
18231        .unwrap_or_else(std::sync::PoisonError::into_inner);
18232      assert_eq!(state.delivery_drivers.len(), HOSTS);
18233      assert_eq!(
18234        state
18235          .dispatches
18236          .get(&dispatch)
18237          .map(|state| state.references),
18238        Some(HOSTS)
18239      );
18240      assert_eq!(
18241        state
18242          .ordered_snapshot()
18243          .iter()
18244          .map(|(id, _)| *id)
18245          .collect::<Vec<_>>(),
18246        ids,
18247        "keyed driver lookup must retain registration order"
18248      );
18249    }
18250
18251    for delivery in &deliveries {
18252      assert_eq!(
18253        registry.claim_delivery(delivery.capability()),
18254        Some(dispatch)
18255      );
18256    }
18257    registry.mark_serviced(dispatch);
18258    for delivery in deliveries {
18259      assert!(
18260        registry
18261          .finish_delivery(delivery, false)
18262          .failed_dispatches
18263          .is_empty()
18264      );
18265    }
18266    {
18267      let state = registry
18268        .state
18269        .lock()
18270        .unwrap_or_else(std::sync::PoisonError::into_inner);
18271      assert!(state.delivery_drivers.is_empty());
18272      assert!(state.dispatches.is_empty());
18273    }
18274
18275    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
18276    for id in &removed {
18277      let completion = registry.unregister(*id);
18278      assert!(completion.failed_dispatches.is_empty());
18279      assert!(!completion.redispatch);
18280    }
18281    let expected = ids
18282      .iter()
18283      .copied()
18284      .filter(|id| !removed.contains(id))
18285      .collect::<Vec<_>>();
18286    let live = registry.live().live;
18287    assert_eq!(
18288      live
18289        .as_slice()
18290        .iter()
18291        .map(|(id, _)| *id)
18292        .collect::<Vec<_>>(),
18293      expected,
18294      "O(1) removals must preserve the relative order of surviving host registrations"
18295    );
18296    drop(live);
18297
18298    for id in expected {
18299      registry.unregister(id);
18300    }
18301    let state = registry
18302      .state
18303      .lock()
18304      .unwrap_or_else(std::sync::PoisonError::into_inner);
18305    assert!(state.entries.is_empty());
18306    assert!(state.oldest.is_none());
18307    assert!(state.newest.is_none());
18308  }
18309
18310  #[test]
18311  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
18312    let registry = CurrentThreadTaskDriverRegistry::default();
18313    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
18314    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
18315    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
18316    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
18317    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
18318    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
18319
18320    let dispatch = 41;
18321    assert_eq!(
18322      registry.dispatch(dispatch),
18323      CurrentThreadHostDispatchResult::Accepted
18324    );
18325    let failing_delivery = failing.deliveries()[0];
18326    let delayed_delivery = delayed.deliveries()[0];
18327    let healthy_delivery = healthy.deliveries()[0];
18328    assert_eq!(failing_delivery.dispatch, dispatch);
18329    assert_eq!(delayed_delivery.dispatch, dispatch);
18330    assert_eq!(healthy_delivery.dispatch, dispatch);
18331
18332    assert!(
18333      registry
18334        .finish_delivery(failing_delivery, true)
18335        .failed_dispatches
18336        .is_empty(),
18337      "one host failure must not recover while other accepted attempts remain"
18338    );
18339    assert_eq!(
18340      registry.claim_delivery(healthy_delivery.capability()),
18341      Some(dispatch)
18342    );
18343    registry.mark_serviced(dispatch);
18344    assert!(
18345      registry
18346        .finish_delivery(healthy_delivery, false)
18347        .failed_dispatches
18348        .is_empty()
18349    );
18350    assert_eq!(
18351      registry.claim_delivery(delayed_delivery.capability()),
18352      None,
18353      "a delayed losing delivery must become stale after another host services the dispatch"
18354    );
18355    assert!(
18356      registry
18357        .finish_delivery(delayed_delivery, true)
18358        .failed_dispatches
18359        .is_empty(),
18360      "a stale losing failure must not recover or cancel a serviced dispatch"
18361    );
18362    assert!(
18363      registry.state.lock().unwrap().dispatches.is_empty(),
18364      "all per-dispatch acknowledgement state must retire"
18365    );
18366  }
18367
18368  #[test]
18369  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
18370    let registry = CurrentThreadTaskDriverRegistry::default();
18371    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
18372    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
18373
18374    let dispatch = 56;
18375    assert_eq!(
18376      registry.dispatch(dispatch),
18377      CurrentThreadHostDispatchResult::Accepted
18378    );
18379    let delivery = no_op.deliveries()[0];
18380    let completion = registry.finish_delivery(delivery, false);
18381    let failure = current_thread_dispatch_failure(dispatch, 0);
18382    assert_eq!(
18383      completion.failed_dispatches,
18384      vec![failure],
18385      "a callback return without an exact capability claim must enter recovery"
18386    );
18387    assert!(
18388      registry
18389        .state
18390        .lock()
18391        .unwrap()
18392        .dispatches
18393        .get(&dispatch)
18394        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18395    );
18396    registry.finish_cancellation(failure);
18397    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18398  }
18399
18400  #[test]
18401  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
18402    struct FailDuringPublication {
18403      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18404    }
18405
18406    impl CurrentThreadTaskDriver for FailDuringPublication {
18407      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18408        let completion = self
18409          .registry
18410          .upgrade()
18411          .expect("the registry must outlive publication")
18412          .finish_delivery(delivery, true);
18413        assert!(
18414          completion.failed_dispatches.is_empty(),
18415          "failure must remain provisional until the broadcast closes"
18416        );
18417        true
18418      }
18419    }
18420
18421    struct RejectProvisional;
18422
18423    impl CurrentThreadTaskDriver for RejectProvisional {
18424      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18425        false
18426      }
18427    }
18428
18429    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18430    registry.register(Arc::new(RejectProvisional));
18431    // Newest-first publication calls the accepted-then-failing host while the
18432    // rejecting host still has an unresolved provisional delivery.
18433    registry.register(Arc::new(FailDuringPublication {
18434      registry: Arc::downgrade(&registry),
18435    }));
18436
18437    let failure = current_thread_dispatch_failure(57, 0);
18438    assert_eq!(
18439      registry.dispatch(57),
18440      CurrentThreadHostDispatchResult::Failed(failure),
18441      "the final provisional rejection must expose the earlier accepted failure"
18442    );
18443    assert!(
18444      registry
18445        .state
18446        .lock()
18447        .unwrap()
18448        .dispatches
18449        .get(&57)
18450        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18451    );
18452    assert_eq!(
18453      registry.dispatch(57),
18454      CurrentThreadHostDispatchResult::Failed(failure),
18455      "a racing replacement publication must preserve terminal failure until cancellation runs"
18456    );
18457    registry.finish_cancellation(failure);
18458  }
18459
18460  #[test]
18461  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
18462    struct RejectProvisional;
18463
18464    impl CurrentThreadTaskDriver for RejectProvisional {
18465      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
18466        false
18467      }
18468    }
18469
18470    let registry = CurrentThreadTaskDriverRegistry::default();
18471    registry.register(Arc::new(RejectProvisional));
18472    registry.register(Arc::new(RejectProvisional));
18473
18474    assert_eq!(
18475      registry.dispatch(58),
18476      CurrentThreadHostDispatchResult::Unavailable
18477    );
18478    assert!(
18479      registry.state.lock().unwrap().dispatches.is_empty(),
18480      "a broadcast with no accepted attempt must not enter failure recovery"
18481    );
18482  }
18483
18484  #[test]
18485  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
18486    let registry = CurrentThreadTaskDriverRegistry::default();
18487    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
18488    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
18489
18490    assert_eq!(
18491      registry.dispatch(1),
18492      CurrentThreadHostDispatchResult::Accepted
18493    );
18494    let first = blocked.deliveries()[0];
18495    for dispatch in 2..=256 {
18496      assert_eq!(
18497        registry.dispatch(dispatch),
18498        CurrentThreadHostDispatchResult::Accepted
18499      );
18500      assert_eq!(
18501        blocked.deliveries().len(),
18502        1,
18503        "a blocked host must never receive a second queued or in-flight callback"
18504      );
18505    }
18506    {
18507      let state = registry.state.lock().unwrap();
18508      let entry = state
18509        .entries
18510        .get(&blocked_id)
18511        .expect("the blocked host must remain registered");
18512      assert_eq!(entry.pending, Some(256));
18513      assert_eq!(
18514        state.dispatches.len(),
18515        2,
18516        "only the in-flight and latest pending dispatches may retain registry state"
18517      );
18518    }
18519
18520    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
18521    registry.mark_serviced(1);
18522    let completion = registry.finish_delivery(first, false);
18523    assert!(completion.failed_dispatches.is_empty());
18524    assert!(completion.redispatch);
18525    assert_eq!(
18526      registry.dispatch(256),
18527      CurrentThreadHostDispatchResult::Accepted
18528    );
18529    let deliveries = blocked.deliveries();
18530    assert_eq!(deliveries.len(), 2);
18531    assert_eq!(
18532      deliveries[1].dispatch, 256,
18533      "completion must schedule exactly the latest coalesced capability"
18534    );
18535    assert_eq!(registry.claim_delivery(first.capability()), None);
18536    assert_eq!(
18537      registry.claim_delivery(deliveries[1].capability()),
18538      Some(256)
18539    );
18540    registry.mark_serviced(256);
18541    assert!(
18542      registry
18543        .finish_delivery(deliveries[1], false)
18544        .failed_dispatches
18545        .is_empty()
18546    );
18547    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18548  }
18549
18550  #[test]
18551  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
18552    struct RejectAfterFirst {
18553      calls: AtomicUsize,
18554      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
18555    }
18556
18557    impl CurrentThreadTaskDriver for RejectAfterFirst {
18558      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18559        self.deliveries.lock().unwrap().push(delivery);
18560        self.calls.fetch_add(1, Ordering::SeqCst) == 0
18561      }
18562    }
18563
18564    let registry = CurrentThreadTaskDriverRegistry::default();
18565    let host = Arc::new(RejectAfterFirst {
18566      calls: AtomicUsize::new(0),
18567      deliveries: Mutex::new(Vec::new()),
18568    });
18569    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18570    assert_eq!(
18571      registry.dispatch(11),
18572      CurrentThreadHostDispatchResult::Accepted
18573    );
18574    let first = host.deliveries.lock().unwrap()[0];
18575    assert_eq!(
18576      registry.dispatch(12),
18577      CurrentThreadHostDispatchResult::Accepted
18578    );
18579
18580    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
18581    registry.mark_serviced(11);
18582    let completion = registry.finish_delivery(first, false);
18583    assert!(completion.redispatch);
18584    let failure = current_thread_dispatch_failure(12, 0);
18585    assert_eq!(
18586      registry.dispatch(12),
18587      CurrentThreadHostDispatchResult::Failed(failure),
18588      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
18589    );
18590    assert!(
18591      registry
18592        .state
18593        .lock()
18594        .unwrap()
18595        .dispatches
18596        .get(&12)
18597        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18598    );
18599    registry.finish_cancellation(failure);
18600    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18601  }
18602
18603  #[test]
18604  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
18605    let registry = CurrentThreadTaskDriverRegistry::default();
18606    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
18607    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
18608    assert_eq!(
18609      registry.dispatch(73),
18610      CurrentThreadHostDispatchResult::Accepted
18611    );
18612    let old_delivery = old.deliveries()[0];
18613
18614    let completion = registry.unregister(old_id);
18615    let failure = current_thread_dispatch_failure(73, 0);
18616    assert_eq!(
18617      completion.failed_dispatches,
18618      vec![failure],
18619      "unregistering the final accepting host must retire its exact dispatch"
18620    );
18621    registry.finish_cancellation(failure);
18622    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
18623    let replacement_id =
18624      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
18625    assert_ne!(replacement_id, old_id);
18626    assert_eq!(
18627      registry.dispatch(74),
18628      CurrentThreadHostDispatchResult::Accepted
18629    );
18630    let replacement_delivery = replacement.deliveries()[0];
18631    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
18632
18633    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18634    assert!(
18635      registry
18636        .finish_delivery(old_delivery, true)
18637        .failed_dispatches
18638        .is_empty()
18639    );
18640    assert!(
18641      registry
18642        .fail_delivery(old_delivery.capability())
18643        .failed_dispatches
18644        .is_empty()
18645    );
18646    assert_eq!(
18647      registry.claim_delivery(replacement_delivery.capability()),
18648      Some(74)
18649    );
18650    registry.mark_serviced(74);
18651    assert!(
18652      registry
18653        .finish_delivery(replacement_delivery, false)
18654        .failed_dispatches
18655        .is_empty()
18656    );
18657    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18658  }
18659
18660  #[test]
18661  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
18662    struct QueuePendingThenReject {
18663      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
18664    }
18665
18666    impl CurrentThreadTaskDriver for QueuePendingThenReject {
18667      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
18668        assert_eq!(delivery.dispatch, 81);
18669        let completion = self
18670          .registry
18671          .upgrade()
18672          .expect("the registry must outlive publication")
18673          .dispatch_with_completion(82);
18674        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
18675        assert!(completion.failed_dispatches.is_empty());
18676        false
18677      }
18678    }
18679
18680    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18681    registry.register(Arc::new(QueuePendingThenReject {
18682      registry: Arc::downgrade(&registry),
18683    }));
18684
18685    let completion = registry.dispatch_with_completion(81);
18686    assert_eq!(
18687      completion.result,
18688      CurrentThreadHostDispatchResult::Unavailable
18689    );
18690    let failure = current_thread_dispatch_failure(82, 0);
18691    assert_eq!(
18692      completion.failed_dispatches,
18693      vec![failure],
18694      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18695    );
18696    assert!(
18697      registry
18698        .state
18699        .lock()
18700        .unwrap()
18701        .dispatches
18702        .get(&82)
18703        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18704    );
18705    registry.finish_cancellation(failure);
18706    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18707  }
18708
18709  #[test]
18710  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18711    let registry = CurrentThreadTaskDriverRegistry::default();
18712    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18713    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18714    assert_eq!(
18715      registry.dispatch(91),
18716      CurrentThreadHostDispatchResult::Accepted
18717    );
18718    let old_delivery = host.deliveries()[0];
18719    assert_eq!(
18720      registry.dispatch(92),
18721      CurrentThreadHostDispatchResult::Accepted
18722    );
18723
18724    registry.retire_for_shutdown();
18725    {
18726      let state = registry.state.lock().unwrap();
18727      assert!(state.dispatches.is_empty());
18728      let entry = state
18729        .entries
18730        .get(&host_id)
18731        .expect("the host must remain registered");
18732      assert_eq!(entry.pending, None);
18733      assert!(
18734        entry.delivery.is_some(),
18735        "the physical callback remains single-flight until it actually retires"
18736      );
18737    }
18738    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18739
18740    assert_eq!(
18741      registry.dispatch(93),
18742      CurrentThreadHostDispatchResult::Accepted
18743    );
18744    assert_eq!(host.deliveries().len(), 1);
18745    let completion = registry.finish_delivery(old_delivery, true);
18746    assert!(
18747      completion.failed_dispatches.is_empty(),
18748      "a retired-generation failure must not cancel restarted work"
18749    );
18750    assert!(completion.redispatch);
18751    assert_eq!(
18752      registry.dispatch(93),
18753      CurrentThreadHostDispatchResult::Accepted
18754    );
18755    let restarted_delivery = host.deliveries()[1];
18756    assert_eq!(restarted_delivery.dispatch, 93);
18757    assert_eq!(
18758      registry.claim_delivery(restarted_delivery.capability()),
18759      Some(93)
18760    );
18761    registry.mark_serviced(93);
18762    assert!(
18763      registry
18764        .finish_delivery(restarted_delivery, false)
18765        .failed_dispatches
18766        .is_empty()
18767    );
18768
18769    registry.unregister(host_id);
18770    let state = registry.state.lock().unwrap();
18771    assert!(state.entries.is_empty());
18772    assert!(state.dispatches.is_empty());
18773  }
18774
18775  #[cfg(napi_runtime_os_threads)]
18776  #[test]
18777  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18778    use std::{sync::mpsc, time::Duration};
18779
18780    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18781    let fallback = Arc::new(TestCurrentThreadTaskDriver {
18782      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18783      dispatched: AtomicBool::new(false),
18784    });
18785    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18786    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
18787      registry: Arc::downgrade(&registry),
18788      registration: Mutex::new(None),
18789    });
18790    let reentrant_id =
18791      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18792    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
18793
18794    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
18795    let selection_registry = Arc::clone(&registry);
18796    let selection_thread = std::thread::spawn(move || {
18797      selection_tx
18798        .send(selection_registry.current().map(|(id, _)| id))
18799        .unwrap();
18800    });
18801    assert_eq!(
18802      selection_rx
18803        .recv_timeout(Duration::from_secs(10))
18804        .expect("is_live re-entry deadlocked"),
18805      Some(fallback_id)
18806    );
18807    selection_thread.join().unwrap();
18808
18809    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
18810      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
18811        registry: Arc::downgrade(&registry),
18812        fallback: fallback_id,
18813      });
18814    let drop_id = registry.register(Arc::clone(&drop_driver));
18815    drop(drop_driver);
18816
18817    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
18818    let drop_registry = Arc::clone(&registry);
18819    let drop_thread = std::thread::spawn(move || {
18820      drop_registry.unregister(drop_id);
18821      drop_tx.send(()).unwrap();
18822    });
18823    drop_rx
18824      .recv_timeout(Duration::from_secs(10))
18825      .expect("driver destructor re-entry deadlocked");
18826    drop_thread.join().unwrap();
18827    assert!(
18828      registry.current().is_none(),
18829      "the destructor must unregister the fallback"
18830    );
18831  }
18832
18833  #[cfg(not(target_family = "wasm"))]
18834  #[test]
18835  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
18836    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
18837
18838    if std::env::var_os(CHILD_ENV).is_some() {
18839      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18840      let fallback = Arc::new(TestCurrentThreadTaskDriver {
18841        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18842        dispatched: AtomicBool::new(false),
18843      });
18844      let fallback_id =
18845        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18846      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
18847        registry: Arc::downgrade(&registry),
18848        registrations: Mutex::new(Vec::new()),
18849      });
18850      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18851
18852      let first_drops = Arc::new(AtomicUsize::new(0));
18853      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18854        drops: Arc::clone(&first_drops),
18855      });
18856      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18857      drop(first);
18858
18859      let second_drops = Arc::new(AtomicUsize::new(0));
18860      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18861        drops: Arc::clone(&second_drops),
18862      });
18863      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18864      drop(second);
18865      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
18866
18867      assert_eq!(
18868        registry.current().map(|(id, _)| id),
18869        Some(fallback_id),
18870        "selection must retry after reentrant removals and preserve the healthy fallback"
18871      );
18872      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18873      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18874      let state = registry
18875        .state
18876        .lock()
18877        .unwrap_or_else(std::sync::PoisonError::into_inner);
18878      assert_eq!(state.entries.len(), 1);
18879      assert!(state.entries.contains_key(&fallback_id));
18880      assert!(state.dispatches.is_empty());
18881      return;
18882    }
18883
18884    let output = std::process::Command::new(std::env::current_exe().unwrap())
18885      .arg("--exact")
18886      .arg(
18887        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
18888      )
18889      .arg("--nocapture")
18890      .env(CHILD_ENV, "1")
18891      .output()
18892      .expect("the hostile task-driver snapshot subprocess test must start");
18893    assert!(
18894      output.status.success(),
18895      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18896      output.status.code(),
18897      String::from_utf8_lossy(&output.stdout),
18898      String::from_utf8_lossy(&output.stderr)
18899    );
18900  }
18901
18902  #[cfg(not(target_family = "wasm"))]
18903  #[test]
18904  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
18905    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
18906
18907    if std::env::var_os(CHILD_ENV).is_some() {
18908      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18909
18910      let first_drops = Arc::new(AtomicUsize::new(0));
18911      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18912        drops: Arc::clone(&first_drops),
18913      });
18914      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18915      drop(first);
18916
18917      let second_drops = Arc::new(AtomicUsize::new(0));
18918      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18919        drops: Arc::clone(&second_drops),
18920      });
18921      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18922      drop(second);
18923
18924      let reentrant_drops = Arc::new(AtomicUsize::new(0));
18925      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18926        registry: Arc::downgrade(&registry),
18927        registrations: Mutex::new(Vec::new()),
18928        drops: Arc::clone(&reentrant_drops),
18929      });
18930      let reentrant_id =
18931        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18932      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
18933      drop(reentrant);
18934
18935      let dispatch = 59;
18936      let failure = current_thread_dispatch_failure(dispatch, 0);
18937      assert_eq!(
18938        registry.dispatch(dispatch),
18939        CurrentThreadHostDispatchResult::Failed(failure),
18940        "reentrant removal of every provisional host must report accepted delivery failure"
18941      );
18942      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
18943      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18944      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18945      registry.finish_cancellation(failure);
18946      let state = registry
18947        .state
18948        .lock()
18949        .unwrap_or_else(std::sync::PoisonError::into_inner);
18950      assert!(state.entries.is_empty());
18951      assert!(state.dispatches.is_empty());
18952      return;
18953    }
18954
18955    let output = std::process::Command::new(std::env::current_exe().unwrap())
18956      .arg("--exact")
18957      .arg(
18958        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
18959      )
18960      .arg("--nocapture")
18961      .env(CHILD_ENV, "1")
18962      .output()
18963      .expect("the hostile task-driver delivery subprocess test must start");
18964    assert!(
18965      output.status.success(),
18966      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18967      output.status.code(),
18968      String::from_utf8_lossy(&output.stdout),
18969      String::from_utf8_lossy(&output.stderr)
18970    );
18971  }
18972
18973  // Builds the `JoinHandleInner::Task` literal directly, whose `dependency`
18974  // field only exists where the MultiThread machinery is compiled in.
18975  #[cfg(napi_runtime_os_threads)]
18976  #[test]
18977  fn dropping_join_handle_detaches_task_like_tokio() {
18978    let completed = Arc::new(AtomicBool::new(false));
18979    let completed_task = Arc::clone(&completed);
18980    let (runnable, task) = async_task::spawn(
18981      async move {
18982        completed_task.store(true, Ordering::SeqCst);
18983        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
18984      },
18985      |_| {},
18986    );
18987    let handle = JoinHandle(JoinHandleInner::Task {
18988      task: task.fallible(),
18989      awaiter: None,
18990      dependency: Arc::new(TaskDependency::default()),
18991    });
18992
18993    drop(handle);
18994    runnable.run();
18995    assert!(completed.load(Ordering::SeqCst));
18996  }
18997
18998  #[cfg(not(target_family = "wasm"))]
18999  #[test]
19000  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
19001    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
19002
19003    if std::env::var_os(CHILD_ENV).is_some() {
19004      let wakes = Arc::new(AtomicUsize::new(0));
19005      let drops = Arc::new(AtomicUsize::new(0));
19006      let (runnable, task) = async_task::spawn(
19007        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
19008        |_| {},
19009      );
19010      let mut handle = JoinHandle(JoinHandleInner::Task {
19011        task: task.fallible(),
19012        awaiter: None,
19013        dependency: Arc::new(TaskDependency::default()),
19014      });
19015      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19016      {
19017        let mut cx = Context::from_waker(&waker);
19018        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19019      }
19020      drop(waker);
19021
19022      runnable.run();
19023
19024      let waker = futures::task::noop_waker();
19025      let mut cx = Context::from_waker(&waker);
19026      assert!(matches!(
19027        Pin::new(&mut handle).poll(&mut cx),
19028        Poll::Ready(Ok(()))
19029      ));
19030      assert_eq!(
19031        drops.load(Ordering::SeqCst),
19032        1,
19033        "a completed handle must not retain the caller's awaiter"
19034      );
19035      drop(handle);
19036      assert!(wakes.load(Ordering::SeqCst) >= 1);
19037      assert_eq!(drops.load(Ordering::SeqCst), 1);
19038      return;
19039    }
19040
19041    let output = std::process::Command::new(std::env::current_exe().unwrap())
19042      .arg("--exact")
19043      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
19044      .arg("--nocapture")
19045      .env(CHILD_ENV, "1")
19046      .output()
19047      .expect("the JoinHandle completion awaiter subprocess must start");
19048    assert!(
19049      output.status.success(),
19050      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19051      output.status.code(),
19052      String::from_utf8_lossy(&output.stdout),
19053      String::from_utf8_lossy(&output.stderr)
19054    );
19055  }
19056
19057  #[cfg(not(target_family = "wasm"))]
19058  #[test]
19059  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
19060    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
19061
19062    if std::env::var_os(CHILD_ENV).is_some() {
19063      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
19064      let wakes = Arc::new(AtomicUsize::new(0));
19065      let drops = Arc::new(AtomicUsize::new(0));
19066      let mut handle = controller.spawn(std::future::pending::<()>());
19067      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
19068      {
19069        let mut cx = Context::from_waker(&waker);
19070        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19071      }
19072      drop(waker);
19073
19074      controller
19075        .shutdown()
19076        .expect("shutdown must survive the hostile awaiter");
19077      drop(handle);
19078      assert!(wakes.load(Ordering::SeqCst) >= 1);
19079      assert_eq!(drops.load(Ordering::SeqCst), 1);
19080      return;
19081    }
19082
19083    let output = std::process::Command::new(std::env::current_exe().unwrap())
19084      .arg("--exact")
19085      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
19086      .arg("--nocapture")
19087      .env(CHILD_ENV, "1")
19088      .output()
19089      .expect("the JoinHandle shutdown awaiter subprocess must start");
19090    assert!(
19091      output.status.success(),
19092      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
19093      output.status.code(),
19094      String::from_utf8_lossy(&output.stdout),
19095      String::from_utf8_lossy(&output.stderr)
19096    );
19097  }
19098
19099  #[cfg(napi_runtime_os_threads)]
19100  #[test]
19101  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
19102    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
19103    let mut handle = controller.spawn(std::future::pending::<()>());
19104    let old_generation = controller.backend().generation();
19105    let state = Arc::new(JoinAwaiterWakerState {
19106      events: Mutex::new(Vec::new()),
19107    });
19108    let waker = {
19109      let _generation = RuntimeGenerationGuard::enter(old_generation);
19110      join_awaiter_waker(Arc::clone(&state))
19111    };
19112    {
19113      let _generation = RuntimeGenerationGuard::enter(old_generation);
19114      let mut cx = Context::from_waker(&waker);
19115      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19116    }
19117    {
19118      let _generation = RuntimeGenerationGuard::enter(old_generation);
19119      drop(waker);
19120    }
19121
19122    controller
19123      .shutdown()
19124      .expect("the first generation must stop");
19125    controller.start().expect("the runtime must restart");
19126    let new_generation = controller.backend().generation();
19127    assert_ne!(new_generation, old_generation);
19128    state.clear();
19129
19130    let replacement = futures::task::noop_waker();
19131    {
19132      let _generation = RuntimeGenerationGuard::enter(new_generation);
19133      let mut cx = Context::from_waker(&replacement);
19134      assert!(matches!(
19135        Pin::new(&mut handle).poll(&mut cx),
19136        Poll::Ready(Err(_))
19137      ));
19138    }
19139
19140    let events = state.snapshot();
19141    assert!(
19142      !events.is_empty(),
19143      "completion must retire the registered awaiter waker"
19144    );
19145    assert!(
19146      events.iter().all(|(kind, generation)| {
19147        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19148      }),
19149      "old-generation awaiter retirement escaped its generation: {events:?}"
19150    );
19151    controller
19152      .shutdown()
19153      .expect("the replacement generation must stop");
19154  }
19155
19156  #[cfg(napi_runtime_os_threads)]
19157  #[test]
19158  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
19159    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
19160    let mut handle = controller.spawn(std::future::pending::<()>());
19161    let old_generation = controller.backend().generation();
19162    let state = Arc::new(JoinAwaiterWakerState {
19163      events: Mutex::new(Vec::new()),
19164    });
19165    let waker = {
19166      let _generation = RuntimeGenerationGuard::enter(old_generation);
19167      join_awaiter_waker(Arc::clone(&state))
19168    };
19169    {
19170      let _generation = RuntimeGenerationGuard::enter(old_generation);
19171      let mut cx = Context::from_waker(&waker);
19172      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
19173    }
19174    {
19175      let _generation = RuntimeGenerationGuard::enter(old_generation);
19176      drop(waker);
19177    }
19178
19179    controller
19180      .shutdown()
19181      .expect("the first generation must stop");
19182    controller.start().expect("the runtime must restart");
19183    let new_generation = controller.backend().generation();
19184    assert_ne!(new_generation, old_generation);
19185    state.clear();
19186
19187    {
19188      let _generation = RuntimeGenerationGuard::enter(new_generation);
19189      drop(handle);
19190    }
19191
19192    let events = state.snapshot();
19193    assert!(
19194      !events.is_empty(),
19195      "handle drop must retire the registered awaiter waker"
19196    );
19197    assert!(
19198      events.iter().all(|(kind, generation)| {
19199        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
19200      }),
19201      "old-generation awaiter destruction escaped its generation: {events:?}"
19202    );
19203    controller
19204      .shutdown()
19205      .expect("the replacement generation must stop");
19206  }
19207
19208  #[cfg(napi_runtime_os_threads)]
19209  #[test]
19210  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
19211    use std::{sync::mpsc, time::Duration};
19212
19213    let options = RuntimeOptions {
19214      flavor: RuntimeFlavor::MultiThread,
19215      worker_threads: 2,
19216      max_blocking_tasks: 1,
19217      thread_name_prefix: "worker-classification".to_string(),
19218      park_deadline: None,
19219      drain_linger: DEFAULT_DRAIN_LINGER,
19220    };
19221    let executor =
19222      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19223    let id = executor.id;
19224    let (tx, rx) = mpsc::sync_channel(1);
19225
19226    executor.pool.spawn(move || {
19227      tx.send((
19228        ON_POOL_WORKER.with(std::cell::Cell::get),
19229        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
19230      ))
19231      .unwrap();
19232    });
19233
19234    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
19235    assert_eq!(
19236      worker,
19237      Some(id),
19238      "nested Rayon work must use cooperative block_on"
19239    );
19240    assert_eq!(
19241      driver, None,
19242      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
19243    );
19244  }
19245
19246  #[cfg(napi_runtime_os_threads)]
19247  #[test]
19248  fn multi_thread_block_on_does_not_park_pool_worker() {
19249    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
19250    // future depending on pool-scheduled blocking work must not park its worker.
19251    // If every worker parks this way, no worker remains to pick up the blocking
19252    // jobs they await.
19253    use std::sync::{Barrier, mpsc};
19254    use std::time::Duration;
19255
19256    let (done_tx, done_rx) = mpsc::channel();
19257    let runner = std::thread::spawn(move || {
19258      let metrics = Arc::new(RuntimeMetrics::default());
19259      let options = RuntimeOptions {
19260        flavor: RuntimeFlavor::MultiThread,
19261        worker_threads: 2,
19262        max_blocking_tasks: 2,
19263        thread_name_prefix: "rd1-test".to_string(),
19264        park_deadline: None,
19265        drain_linger: DEFAULT_DRAIN_LINGER,
19266      };
19267      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19268
19269      let task_count = 2usize;
19270      // Guarantees every task is being polled on its own pool worker *before*
19271      // any of them parks in `block_on`, so the deadlock is deterministic.
19272      let barrier = Arc::new(Barrier::new(task_count));
19273
19274      let mut tasks = Vec::new();
19275      for _ in 0..task_count {
19276        let exec = Arc::clone(&executor);
19277        let barrier = Arc::clone(&barrier);
19278        let body = async move {
19279          barrier.wait();
19280          let inner_exec = Arc::clone(&exec);
19281          let mut inner = std::pin::pin!(async move {
19282            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
19283            assert_eq!(value, 7);
19284          });
19285          // Synchronous `block_on` from inside a pool-worker task: the vector.
19286          exec.block_on(inner.as_mut());
19287        };
19288        let scheduler = Arc::clone(&executor);
19289        let (runnable, task) =
19290          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
19291        executor.schedule(runnable);
19292        tasks.push(task);
19293      }
19294
19295      for task in tasks {
19296        futures::executor::block_on(task);
19297      }
19298      let _ = done_tx.send(());
19299    });
19300
19301    match done_rx.recv_timeout(Duration::from_secs(10)) {
19302      Ok(()) => runner.join().unwrap(),
19303      Err(error) => panic!(
19304        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
19305      ),
19306    }
19307  }
19308
19309  #[cfg(napi_runtime_os_threads)]
19310  #[test]
19311  fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
19312    // Idle-linger residence bound (adversarial rayon-starvation shape).
19313    //
19314    // W=2 pool. Worker B runs a blocking closure that spawns a raw rayon job
19315    // R onto the SAME pool (it lands in worker B's rayon deque) and then
19316    // cooperatively block_on's R's completion -- so worker B never returns to
19317    // rayon's scheduler. Worker A's drain frame runs the queues empty and
19318    // lingers on its own parker. A producer then publishes one trivial
19319    // runnable every ~200us (< the 500us idle budget), stopping only after R
19320    // ran. Every wake pops the MOST recently registered parker -- the
19321    // lingering drainer, which re-registers on top after each task -- and
19322    // every executed task clears the idle deadline, so without a bound on the
19323    // frame's TOTAL lingering residence worker A never exits its drain frame,
19324    // never returns its thread to rayon, and R (which only an idle rayon
19325    // worker can steal) starves indefinitely: producer never stops, worker B
19326    // never unparks. The frame residence bound must force the drainer out
19327    // through `finish_draining` (exit-and-respawn through rayon's queue is
19328    // exactly the interleaving point that lets R run), well inside the
19329    // watchdog below.
19330    use std::sync::mpsc;
19331
19332    for round in 0..3 {
19333      let metrics = Arc::new(RuntimeMetrics::default());
19334      let options = RuntimeOptions {
19335        flavor: RuntimeFlavor::MultiThread,
19336        worker_threads: 2,
19337        max_blocking_tasks: 1,
19338        thread_name_prefix: format!("linger-starve-{round}"),
19339        park_deadline: None,
19340        drain_linger: DEFAULT_DRAIN_LINGER,
19341      };
19342      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19343
19344      let r_done = Arc::new(AtomicBool::new(false));
19345      let give_up = Arc::new(AtomicBool::new(false));
19346      let (done_tx, done_rx) = mpsc::channel::<Duration>();
19347
19348      // T1 pins worker A until the cooperative driver (worker B, inside the
19349      // blocking closure's re-entrant block_on) has parked. Worker A's drain
19350      // frame then observes empty queues and starts lingering AFTER the
19351      // cooperative driver registered, making the lingering drainer the
19352      // most-recently-registered (i.e. wake-preferred) parker.
19353      let t1_executor = Arc::clone(&executor);
19354      let t1_started = Arc::new(AtomicBool::new(false));
19355      let t1_started_setter = Arc::clone(&t1_started);
19356      let t1_body = async move {
19357        t1_started_setter.store(true, Ordering::SeqCst);
19358        let deadline = Instant::now() + Duration::from_secs(10);
19359        while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
19360          if Instant::now() >= deadline {
19361            // Give the worker back; the watchdog below reports the failure.
19362            return;
19363          }
19364          std::hint::spin_loop();
19365        }
19366      };
19367      let t1_scheduler = Arc::clone(&executor);
19368      let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
19369      executor.schedule(t1_runnable);
19370      wait_until("the worker-pinning task to start", || {
19371        t1_started.load(Ordering::SeqCst)
19372      });
19373
19374      // The blocking closure on worker B: spawn R, then block_on its result.
19375      let blocking_executor = Arc::clone(&executor);
19376      let r_done_setter = Arc::clone(&r_done);
19377      let started = Instant::now();
19378      let handle = executor.schedule_blocking(move || {
19379        let (r_tx, r_rx) = oneshot::channel::<()>();
19380        blocking_executor.pool.spawn(move || {
19381          r_done_setter.store(true, Ordering::SeqCst);
19382          let _ = r_tx.send(());
19383        });
19384        let mut wait_for_r = std::pin::pin!(async move {
19385          let _ = r_rx.await;
19386        });
19387        blocking_executor.block_on(wait_for_r.as_mut());
19388        let _ = done_tx.send(started.elapsed());
19389      });
19390
19391      // Wait until both the cooperative driver and the lingering drainer are
19392      // registered; if the linger expired first (R already ran), that round
19393      // completed without ever reaching the adversarial interleaving.
19394      let arm_deadline = Instant::now() + Duration::from_secs(15);
19395      while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
19396        && !r_done.load(Ordering::SeqCst)
19397      {
19398        assert!(
19399          Instant::now() < arm_deadline,
19400          "timed out arming the linger-starvation interleaving"
19401        );
19402        std::thread::yield_now();
19403      }
19404
19405      // Producer: one trivial runnable every ~200us (spin-paced, so cadence
19406      // stays far below the 500us idle budget), until R completed.
19407      let producer_executor = Arc::clone(&executor);
19408      let producer_r_done = Arc::clone(&r_done);
19409      let producer_give_up = Arc::clone(&give_up);
19410      let producer = std::thread::spawn(move || {
19411        let cadence = Duration::from_micros(200);
19412        let mut next_tick = Instant::now();
19413        while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
19414          let scheduler = Arc::clone(&producer_executor);
19415          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
19416          producer_executor.schedule(runnable);
19417          task.detach();
19418          next_tick += cadence;
19419          while Instant::now() < next_tick {
19420            std::hint::spin_loop();
19421          }
19422        }
19423      });
19424
19425      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
19426      give_up.store(true, Ordering::SeqCst);
19427      producer.join().unwrap();
19428      match outcome {
19429        Ok(elapsed) => {
19430          futures::executor::block_on(handle).expect("blocking closure join");
19431          drop(t1_task);
19432          eprintln!("round {round}: completed in {elapsed:?}");
19433        }
19434        Err(error) => panic!(
19435          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
19436           sub-budget periodic work must not keep a drain frame resident forever"
19437        ),
19438      }
19439    }
19440  }
19441
19442  #[cfg(napi_runtime_os_threads)]
19443  #[test]
19444  #[ignore = "microbenchmark: run explicitly in release mode"]
19445  fn parked_drivers_registry_cycle_microbench() {
19446    // Isolates the registry traffic of one steady-state linger cycle at
19447    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
19448    // drainer deregisters (the entry is already gone, so the position scan
19449    // walks the WHOLE vector and fails), then re-registers (push). Also
19450    // measures the timeout-path deregister (entry present) and a
19451    // wake_one_owner_dependency sweep over dependency-less lingerers.
19452    for &n in &[8usize, 64, 256] {
19453      let registry = ParkedDrivers::default();
19454      let parkers: Vec<Arc<DriverParker>> =
19455        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
19456      for parker in &parkers {
19457        registry.register_with_role(parker, true, None);
19458      }
19459      let hot = &parkers[n - 1];
19460
19461      let iters = 2_000_000u32;
19462      let start = Instant::now();
19463      for _ in 0..iters {
19464        assert!(registry.wake_one());
19465        registry.deregister(hot);
19466        assert!(hot.consume_permit());
19467        registry.register_with_role(hot, true, None);
19468      }
19469      let wake_cycle = start.elapsed() / iters;
19470
19471      let start = Instant::now();
19472      for _ in 0..iters {
19473        registry.deregister(hot);
19474        registry.register_with_role(hot, true, None);
19475      }
19476      let timeout_cycle = start.elapsed() / iters;
19477
19478      let owner = BlockingOwnerToken {
19479        executor_id: u64::MAX,
19480        frame: u64::MAX,
19481      };
19482      let start = Instant::now();
19483      for _ in 0..iters {
19484        assert!(!registry.wake_one_owner_dependency(owner));
19485      }
19486      let owner_sweep = start.elapsed() / iters;
19487
19488      eprintln!(
19489        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
19490         timeout-deregister+re-register {timeout_cycle:?}, \
19491         owner-dependency sweep {owner_sweep:?}"
19492      );
19493    }
19494  }
19495
19496  #[cfg(napi_runtime_os_threads)]
19497  #[test]
19498  fn multi_thread_block_on_from_caller_thread_still_parks() {
19499    // The non-pool (napi caller) path must keep using the plain parking
19500    // `block_on`; here we just assert it drives a ready future to completion.
19501    let metrics = Arc::new(RuntimeMetrics::default());
19502    let options = RuntimeOptions {
19503      flavor: RuntimeFlavor::MultiThread,
19504      worker_threads: 3,
19505      max_blocking_tasks: 2,
19506      thread_name_prefix: "rd1-caller".to_string(),
19507      park_deadline: None,
19508      drain_linger: DEFAULT_DRAIN_LINGER,
19509    };
19510    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19511
19512    let mut output = None;
19513    let mut future = std::pin::pin!(async {
19514      output = Some(41usize + 1);
19515    });
19516    executor.block_on(future.as_mut());
19517    assert_eq!(output, Some(42));
19518  }
19519
19520  #[cfg(napi_runtime_os_threads)]
19521  #[test]
19522  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
19523    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
19524    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
19525    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
19526    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
19527    // This pins ONLY the cross-thread wake path: the pool task completing must wake
19528    // the parked caller's waker so `block_on` returns the pool-computed value.
19529    //
19530    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
19531    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
19532    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
19533    // parking waker is registered on the task), signals "caller parked" to a releaser
19534    // thread. The releaser then -- and only then -- releases the gate, so the pool
19535    // task completes strictly AFTER the caller registered its waker. This forecloses
19536    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
19537    // caller's first poll, because its gate is opened only in response to that first
19538    // `Pending` poll. The completion is thus forced to travel the real cross-thread
19539    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
19540    // if that wake is broken the caller parks forever and the bounded recv_timeout
19541    // below fails loudly instead of hanging the suite.
19542    //
19543    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
19544    // caller-is-pool-worker case, which has dedicated reentrancy tests.
19545    use std::sync::mpsc;
19546    use std::time::Duration;
19547
19548    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
19549    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
19550    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
19551    // is already registered before the gate can open.
19552    struct CallerProbe {
19553      task: Task<usize>,
19554      parked_tx: Option<mpsc::Sender<()>>,
19555    }
19556    impl Future for CallerProbe {
19557      type Output = usize;
19558      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
19559        // `Task` is `Unpin`; poll it through a fresh `Pin`.
19560        let poll = Pin::new(&mut self.task).poll(cx);
19561        if poll.is_pending()
19562          && let Some(tx) = self.parked_tx.take()
19563        {
19564          // Waker is now registered on `task`; release the gate is safe.
19565          let _ = tx.send(());
19566        }
19567        poll
19568      }
19569    }
19570
19571    let (done_tx, done_rx) = mpsc::channel();
19572    let runner = std::thread::spawn(move || {
19573      let metrics = Arc::new(RuntimeMetrics::default());
19574      let options = RuntimeOptions {
19575        flavor: RuntimeFlavor::MultiThread,
19576        worker_threads: 2,
19577        max_blocking_tasks: 2,
19578        thread_name_prefix: "rd10-caller".to_string(),
19579        park_deadline: None,
19580        drain_linger: DEFAULT_DRAIN_LINGER,
19581      };
19582      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19583
19584      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
19585      // worker registers the gate waker and returns `Pending`. When the releaser sends
19586      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
19587      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
19588      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
19589      let scheduler = Arc::clone(&executor);
19590      let (runnable, task) = async_task::spawn(
19591        async move { gate_rx.await.expect("gate sender dropped") },
19592        move |r| {
19593          scheduler.schedule(r);
19594        },
19595      );
19596      executor.schedule(runnable);
19597
19598      // Releaser thread: opens the gate ONLY after the caller has parked (registered
19599      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
19600      // completes, so completion must travel the cross-thread wake path.
19601      let (parked_tx, parked_rx) = mpsc::channel::<()>();
19602      let releaser = std::thread::spawn(move || {
19603        if parked_rx.recv().is_ok() {
19604          let _ = gate_tx.send(42usize);
19605        }
19606      });
19607
19608      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
19609      // `futures::executor::block_on` rather than driving the queue cooperatively.
19610      let mut output = None;
19611      {
19612        let mut future = std::pin::pin!(async {
19613          output = Some(
19614            CallerProbe {
19615              task,
19616              parked_tx: Some(parked_tx),
19617            }
19618            .await,
19619          );
19620        });
19621        executor.block_on(future.as_mut());
19622      }
19623
19624      releaser.join().unwrap();
19625      assert_eq!(
19626        output,
19627        Some(42usize),
19628        "block_on must return the pool-computed value"
19629      );
19630      let _ = done_tx.send(());
19631    });
19632
19633    match done_rx.recv_timeout(Duration::from_secs(10)) {
19634      Ok(()) => runner.join().unwrap(),
19635      Err(error) => panic!(
19636        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
19637      ),
19638    }
19639  }
19640
19641  #[cfg(napi_runtime_os_threads)]
19642  #[test]
19643  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
19644    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
19645    // before the work that will wake it is scheduled. Once every pool worker
19646    // is parked this way, `ensure_drainer` cannot spawn a replacement because
19647    // the parked workers are still counted as active drainers. A later
19648    // `schedule()` must wake a parked driver directly so the runnable can run.
19649    use std::sync::{Barrier, mpsc};
19650    use std::time::Duration;
19651
19652    let (done_tx, done_rx) = mpsc::channel();
19653    let runner = std::thread::spawn(move || {
19654      let metrics = Arc::new(RuntimeMetrics::default());
19655      let options = RuntimeOptions {
19656        flavor: RuntimeFlavor::MultiThread,
19657        worker_threads: 2,
19658        max_blocking_tasks: 2,
19659        thread_name_prefix: "rd1-park".to_string(),
19660        park_deadline: None,
19661        drain_linger: DEFAULT_DRAIN_LINGER,
19662      };
19663      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19664
19665      let task_count = 2usize;
19666      let barrier = Arc::new(Barrier::new(task_count));
19667
19668      let mut senders = Vec::new();
19669      let mut tasks = Vec::new();
19670      for _ in 0..task_count {
19671        let (tx, rx) = oneshot::channel::<usize>();
19672        senders.push(tx);
19673        let exec = Arc::clone(&executor);
19674        let barrier = Arc::clone(&barrier);
19675        let body = async move {
19676          // Make sure both pool workers are occupied before either parks.
19677          barrier.wait();
19678          // Child task whose wake routes through `schedule()`: the oneshot
19679          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
19680          let child_exec = Arc::clone(&exec);
19681          let (child_runnable, child_task) =
19682            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
19683              child_exec.schedule(r)
19684            });
19685          exec.schedule(child_runnable);
19686          let mut inner = std::pin::pin!(async move {
19687            assert_eq!(child_task.await, 9usize);
19688          });
19689          exec.block_on(inner.as_mut());
19690        };
19691        let scheduler = Arc::clone(&executor);
19692        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
19693        executor.schedule(runnable);
19694        tasks.push(task);
19695      }
19696
19697      // Give both workers time to poll their child Pending and park in block_on.
19698      std::thread::sleep(Duration::from_millis(200));
19699      for tx in senders {
19700        let _ = tx.send(9usize);
19701      }
19702
19703      for task in tasks {
19704        futures::executor::block_on(task);
19705      }
19706      let _ = done_tx.send(());
19707    });
19708
19709    match done_rx.recv_timeout(Duration::from_secs(10)) {
19710      Ok(()) => runner.join().unwrap(),
19711      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19712    }
19713  }
19714
19715  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
19716  /// Used to observe executor-internal states (e.g. "both drivers are
19717  /// registered as parked") without sleeps. The bound is a HANG detector,
19718  /// not a latency assertion: starved CI runners can stall fresh threads for
19719  /// hundreds of milliseconds, so it is deliberately generous.
19720  #[cfg(napi_runtime_os_threads)]
19721  fn wait_until(what: &str, condition: impl Fn() -> bool) {
19722    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19723    while !condition() {
19724      assert!(
19725        std::time::Instant::now() < deadline,
19726        "timed out waiting for: {what}"
19727      );
19728      std::thread::yield_now();
19729    }
19730  }
19731
19732  /// Bounded join for harness threads that already signalled completion of
19733  /// their assertions: everything left on the thread is teardown (executor
19734  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
19735  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
19736  /// into a silent libtest-worker hang for the full CI step timeout (observed
19737  /// on Windows in run 29195160953: several park-deadline-family tests never
19738  /// completed while every in-test assertion had already passed). Fail loudly
19739  /// and NAME the awaited phase instead.
19740  #[cfg(napi_runtime_os_threads)]
19741  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19742    let deadline = std::time::Instant::now() + timeout;
19743    while !handle.is_finished() {
19744      assert!(
19745        std::time::Instant::now() < deadline,
19746        "thread wedged past its {timeout:?} bound while: {what}"
19747      );
19748      std::thread::sleep(Duration::from_millis(10));
19749    }
19750    handle.join().unwrap();
19751  }
19752
19753  #[cfg(napi_runtime_os_threads)]
19754  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19755    executor: &Arc<MultiThreadExecutor>,
19756    start_counter: &AtomicU64,
19757    claim: impl FnOnce() -> T + Send + 'static,
19758  ) -> T {
19759    use std::sync::mpsc;
19760
19761    let (claimed_tx, claimed_rx) = mpsc::channel();
19762    let (release_claim_tx, release_claim_rx) = mpsc::channel();
19763    let (started_tx, started_rx) = mpsc::channel();
19764    let (release_start_tx, release_start_rx) = mpsc::channel();
19765    let starts_before = start_counter.load(Ordering::SeqCst);
19766    let worker = std::thread::spawn(move || {
19767      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19768        *slot.borrow_mut() = Some(Box::new(move || {
19769          claimed_tx.send(()).unwrap();
19770          release_claim_rx.recv().unwrap();
19771        }));
19772      });
19773      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19774        *slot.borrow_mut() = Some(Box::new(move || {
19775          started_tx.send(()).unwrap();
19776          release_start_rx.recv().unwrap();
19777        }));
19778      });
19779      claim()
19780    });
19781    claimed_rx
19782      .recv_timeout(Duration::from_secs(2))
19783      .expect("the scheduler claim must pause after removing work");
19784
19785    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19786    let verdict = executor.try_begin_deadlock_verdict();
19787    let claim_verdict_rejected = verdict.is_none();
19788    drop(verdict);
19789    release_claim_tx.send(()).unwrap();
19790
19791    started_rx
19792      .recv_timeout(Duration::from_secs(2))
19793      .expect("the scheduler claim must pause after publishing its start");
19794    let admission_active_after_start =
19795      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19796    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
19797    let verdict = executor.try_begin_deadlock_verdict();
19798    let start_verdict_rejected = verdict.is_none();
19799    drop(verdict);
19800    release_start_tx.send(()).unwrap();
19801    let output = worker.join().unwrap();
19802
19803    assert!(
19804      admission_active,
19805      "removing scheduler work must retain a detector admission until start accounting"
19806    );
19807    assert!(
19808      claim_verdict_rejected,
19809      "a deadline verdict must not close while removed work has not published its start"
19810    );
19811    assert!(
19812      start_published,
19813      "work start accounting must publish before admission retires"
19814    );
19815    assert!(
19816      admission_active_after_start,
19817      "the detector admission must remain active until after start accounting"
19818    );
19819    assert!(
19820      start_verdict_rejected,
19821      "a deadline verdict must not close between start accounting and admission retirement"
19822    );
19823    output
19824  }
19825
19826  #[cfg(napi_runtime_os_threads)]
19827  #[test]
19828  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
19829    let executor =
19830      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
19831
19832    let fifo_ran = Arc::new(AtomicBool::new(false));
19833    let fifo_task_ran = Arc::clone(&fifo_ran);
19834    let (fifo, fifo_task) = async_task::spawn(
19835      async move {
19836        fifo_task_ran.store(true, Ordering::SeqCst);
19837      },
19838      |_| {},
19839    );
19840    executor.metrics.runnable_scheduled();
19841    executor.push_fifo_raw(fifo);
19842    let fifo_executor = Arc::clone(&executor);
19843    assert!(run_claim_with_deadlock_gate_assertion(
19844      &executor,
19845      &executor.metrics.runnable_polls,
19846      move || {
19847        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19848        fifo_executor.run_one_fifo_runnable()
19849      }
19850    ));
19851    futures::executor::block_on(fifo_task);
19852    assert!(fifo_ran.load(Ordering::SeqCst));
19853
19854    let fifo_order = Arc::new(Mutex::new(Vec::new()));
19855    let mut fifo_tasks = Vec::new();
19856    for name in ["first", "second"] {
19857      let task_order = Arc::clone(&fifo_order);
19858      let (runnable, task) = async_task::spawn(
19859        async move {
19860          task_order.lock().unwrap().push(name);
19861        },
19862        |_| {},
19863      );
19864      executor.metrics.runnable_scheduled();
19865      executor.push_fifo_raw(runnable);
19866      fifo_tasks.push(task);
19867    }
19868    for _ in 0..2 {
19869      let fifo_executor = Arc::clone(&executor);
19870      assert!(run_claim_with_deadlock_gate_assertion(
19871        &executor,
19872        &executor.metrics.runnable_polls,
19873        move || {
19874          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19875          fifo_executor.run_one_runnable()
19876        }
19877      ));
19878    }
19879    for task in fifo_tasks {
19880      futures::executor::block_on(task);
19881    }
19882    assert_eq!(
19883      *fifo_order.lock().unwrap(),
19884      ["first", "second"],
19885      "the detector-enabled normal fallback must preserve FIFO order"
19886    );
19887
19888    let lifo_ran = Arc::new(AtomicBool::new(false));
19889    let lifo_task_ran = Arc::clone(&lifo_ran);
19890    let (lifo, lifo_task) = async_task::spawn(
19891      async move {
19892        lifo_task_ran.store(true, Ordering::SeqCst);
19893      },
19894      |_| {},
19895    );
19896    executor.metrics.runnable_scheduled();
19897    let lifo_executor = Arc::clone(&executor);
19898    assert!(run_claim_with_deadlock_gate_assertion(
19899      &executor,
19900      &executor.metrics.runnable_polls,
19901      move || {
19902        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
19903        LIFO_SLOT.with(|slot| {
19904          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
19905        });
19906        lifo_executor.run_one_runnable()
19907      }
19908    ));
19909    futures::executor::block_on(lifo_task);
19910    assert!(lifo_ran.load(Ordering::SeqCst));
19911
19912    executor.begin_shutdown();
19913    executor.wait_until_scheduler_idle();
19914  }
19915
19916  #[cfg(napi_runtime_os_threads)]
19917  #[test]
19918  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
19919    let executor =
19920      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
19921
19922    let fifo_ran = Arc::new(AtomicBool::new(false));
19923    let fifo_job_ran = Arc::clone(&fifo_ran);
19924    executor.metrics.blocking_scheduled();
19925    executor
19926      .blocking_queue
19927      .lock()
19928      .unwrap()
19929      .push(QueuedBlocking {
19930        id: BlockingJobId(u64::MAX - 90),
19931        run: Box::new(move || {
19932          fifo_job_ran.store(true, Ordering::SeqCst);
19933        }),
19934      });
19935    let fifo_executor = Arc::clone(&executor);
19936    assert!(run_claim_with_deadlock_gate_assertion(
19937      &executor,
19938      &executor.metrics.blocking_tasks_started,
19939      move || {
19940        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19941        fifo_executor.run_one_blocking()
19942      }
19943    ));
19944    assert!(fifo_ran.load(Ordering::SeqCst));
19945
19946    let owner = executor.fresh_owner_token();
19947    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19948    let exact_job = BlockingJobId(u64::MAX - 91);
19949    let exact_ran = Arc::new(AtomicBool::new(false));
19950    let exact_job_ran = Arc::clone(&exact_ran);
19951    executor.metrics.blocking_scheduled();
19952    executor
19953      .blocking_queue
19954      .lock()
19955      .unwrap()
19956      .push(QueuedBlocking {
19957        id: exact_job,
19958        run: Box::new(move || {
19959          exact_job_ran.store(true, Ordering::SeqCst);
19960        }),
19961      });
19962    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
19963    dependency.set_owned(BlockingDependency {
19964      job: exact_job,
19965      owner: Some(owner),
19966    });
19967    let exact_executor = Arc::clone(&executor);
19968    assert!(run_claim_with_deadlock_gate_assertion(
19969      &executor,
19970      &executor.metrics.blocking_tasks_started,
19971      move || {
19972        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
19973        let _owner = BlockingOwnerGuard::enter(Some(owner));
19974        exact_executor.try_owned_blocking_over_cap(&dependency)
19975      }
19976    ));
19977    assert!(exact_ran.load(Ordering::SeqCst));
19978
19979    drop(active_owner);
19980    executor.begin_shutdown();
19981    executor.wait_until_scheduler_idle();
19982  }
19983
19984  #[cfg(napi_runtime_os_threads)]
19985  #[test]
19986  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
19987    // Empty-registry behavior: with nothing registered, `wake_one` must return
19988    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
19989    // the common case, since queue-wakes fire on every schedule while drivers
19990    // park only in rare re-entrant `block_on`). Proxy for "did not take the
19991    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
19992    // anyway, while (control) a wake_one issued with a parker registered must
19993    // take the slow path and block on that held lock until it is released.
19994    use std::sync::mpsc;
19995    use std::time::Duration;
19996
19997    let registry = Arc::new(ParkedDrivers::default());
19998
19999    // Fast path: empty registry -> wake_one completes although the lock is held.
20000    let guard = registry.parked.lock().unwrap();
20001    let (done_tx, done_rx) = mpsc::channel();
20002    let waker_thread = {
20003      let registry = Arc::clone(&registry);
20004      std::thread::spawn(move || {
20005        done_tx.send(registry.wake_one()).unwrap();
20006      })
20007    };
20008    let woke = done_rx
20009      .recv_timeout(Duration::from_secs(5))
20010      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
20011    assert!(!woke, "an empty registry has nobody to wake");
20012    waker_thread.join().unwrap();
20013    drop(guard);
20014
20015    // Control: with a parker registered the SAME held lock must block
20016    // wake_one (slow path). This proves the fast path above genuinely skipped
20017    // the lock rather than wake_one never locking at all.
20018    let parker = Arc::new(DriverParker::default());
20019    registry.register(&parker);
20020    let guard = registry.parked.lock().unwrap();
20021    let (done_tx, done_rx) = mpsc::channel();
20022    let waker_thread = {
20023      let registry = Arc::clone(&registry);
20024      std::thread::spawn(move || {
20025        done_tx.send(registry.wake_one()).unwrap();
20026      })
20027    };
20028    assert!(
20029      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20030      "wake_one with a registered parker must take the slow path (block on the held lock)"
20031    );
20032    drop(guard);
20033    let woke = done_rx
20034      .recv_timeout(Duration::from_secs(5))
20035      .expect("slow-path wake_one must complete once the lock is released");
20036    assert!(
20037      woke,
20038      "the slow path must pop and wake the registered parker"
20039    );
20040    waker_thread.join().unwrap();
20041    // The popped parker received its permit: a park now returns immediately
20042    // instead of sleeping (bounded by the test harness if broken).
20043    parker.park();
20044    assert_eq!(
20045      registry.count.load(Ordering::SeqCst),
20046      0,
20047      "wake_one deregisters what it pops"
20048    );
20049  }
20050
20051  #[cfg(napi_runtime_os_threads)]
20052  #[test]
20053  fn driver_parker_permit_granted_while_running_is_not_lost() {
20054    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
20055    // parked) must be stored as a permit that makes the next `park` return
20056    // immediately; multiple such wakes coalesce into ONE permit.
20057    use std::sync::mpsc;
20058    use std::time::Duration;
20059
20060    let parker = Arc::new(DriverParker::default());
20061    // Two wakes while "running" (nobody parked yet).
20062    parker.unpark();
20063    parker.unpark();
20064
20065    let (tx, rx) = mpsc::channel();
20066    let driver = {
20067      let parker = Arc::clone(&parker);
20068      std::thread::spawn(move || {
20069        parker.park(); // must consume the stored permit without sleeping
20070        tx.send(1).unwrap();
20071        parker.park(); // no permit left: must genuinely sleep
20072        tx.send(2).unwrap();
20073      })
20074    };
20075
20076    assert_eq!(
20077      rx.recv_timeout(Duration::from_secs(5))
20078        .expect("a stored permit must not be lost"),
20079      1
20080    );
20081    assert!(
20082      rx.recv_timeout(Duration::from_millis(300)).is_err(),
20083      "two pre-park wakes must coalesce into one permit (second park must sleep)"
20084    );
20085    parker.unpark();
20086    assert_eq!(
20087      rx.recv_timeout(Duration::from_secs(5))
20088        .expect("unpark must wake a sleeping driver"),
20089      2
20090    );
20091    driver.join().unwrap();
20092  }
20093
20094  #[cfg(napi_runtime_os_threads)]
20095  #[test]
20096  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
20097    use std::sync::mpsc;
20098
20099    let parker = Arc::new(DriverParker::default());
20100    assert!(
20101      Instant::now().checked_add(Duration::MAX).is_none(),
20102      "Duration::MAX must exercise the unrepresentable-deadline path"
20103    );
20104    let (done_tx, done_rx) = mpsc::channel();
20105    let driver = {
20106      let parker = Arc::clone(&parker);
20107      std::thread::spawn(move || {
20108        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
20109      })
20110    };
20111    wait_until("the unbounded timeout driver to sleep", || {
20112      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
20113    });
20114    assert!(
20115      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
20116      "an unrepresentable timeout must not expire immediately"
20117    );
20118
20119    let unparker = {
20120      let parker = Arc::clone(&parker);
20121      std::thread::spawn(move || parker.unpark())
20122    };
20123    assert!(
20124      done_rx
20125        .recv_timeout(Duration::from_secs(1))
20126        .expect("an unrepresentable timeout must remain wakeable"),
20127      "a permit must be reported as a wake"
20128    );
20129    unparker.join().unwrap();
20130    driver.join().unwrap();
20131  }
20132
20133  #[cfg(napi_runtime_os_threads)]
20134  #[test]
20135  fn parked_drivers_wake_one_wakes_exactly_one() {
20136    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
20137    // other stays parked. Targeted wakes must neither wake everyone nor lose
20138    // the wake.
20139    use std::sync::mpsc;
20140    use std::time::Duration;
20141
20142    let registry = Arc::new(ParkedDrivers::default());
20143    let (woke_tx, woke_rx) = mpsc::channel();
20144    let mut drivers = Vec::new();
20145    for index in 0..2 {
20146      let registry = Arc::clone(&registry);
20147      let woke_tx = woke_tx.clone();
20148      drivers.push(std::thread::spawn(move || {
20149        let parker = Arc::new(DriverParker::default());
20150        registry.register(&parker);
20151        parker.park();
20152        registry.deregister(&parker);
20153        woke_tx.send(index).unwrap();
20154      }));
20155    }
20156    wait_until("both drivers registered as parked", || {
20157      registry.count.load(Ordering::SeqCst) == 2
20158    });
20159
20160    assert!(registry.wake_one(), "wake_one must wake a parked driver");
20161    woke_rx
20162      .recv_timeout(Duration::from_secs(5))
20163      .expect("exactly one driver must wake");
20164    assert!(
20165      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
20166      "the second driver must stay parked after a single wake_one"
20167    );
20168    assert_eq!(
20169      registry.count.load(Ordering::SeqCst),
20170      1,
20171      "one driver must remain registered"
20172    );
20173
20174    assert!(
20175      registry.wake_one(),
20176      "a second wake_one must wake the remaining driver"
20177    );
20178    woke_rx
20179      .recv_timeout(Duration::from_secs(5))
20180      .expect("the second driver must wake");
20181    for driver in drivers {
20182      driver.join().unwrap();
20183    }
20184  }
20185
20186  #[cfg(napi_runtime_os_threads)]
20187  #[test]
20188  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
20189    use std::sync::mpsc;
20190
20191    struct ReenterRegistryOnDrop {
20192      registry: Arc<ParkedDrivers>,
20193      dropped: mpsc::Sender<()>,
20194    }
20195
20196    #[expect(
20197      clippy::manual_noop_waker,
20198      reason = "the test observes the waker payload's Drop"
20199    )]
20200    impl std::task::Wake for ReenterRegistryOnDrop {
20201      fn wake(self: Arc<Self>) {}
20202    }
20203
20204    impl Drop for ReenterRegistryOnDrop {
20205      fn drop(&mut self) {
20206        let parker = Arc::new(DriverParker::default());
20207        self.registry.register(&parker);
20208        self.registry.deregister(&parker);
20209        self.dropped.send(()).unwrap();
20210      }
20211    }
20212
20213    let registry = Arc::new(ParkedDrivers::default());
20214    let dependency = Arc::new(TaskDependency::default());
20215    let (dropped_tx, dropped_rx) = mpsc::channel();
20216    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
20217      registry: Arc::clone(&registry),
20218      dropped: dropped_tx,
20219    }));
20220    dependency.register_waiter(&waiter);
20221    drop(waiter);
20222
20223    let parker = Arc::new(DriverParker::default());
20224    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20225    drop(dependency);
20226
20227    let retiring_registry = Arc::clone(&registry);
20228    let retiring_parker = Arc::clone(&parker);
20229    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
20230    dropped_rx
20231      .recv_timeout(Duration::from_secs(1))
20232      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
20233    retiring.join().unwrap();
20234  }
20235
20236  #[cfg(napi_runtime_os_threads)]
20237  #[test]
20238  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
20239    use std::sync::mpsc;
20240
20241    let registry = Arc::new(ParkedDrivers::default());
20242    let owner = BlockingOwnerToken {
20243      executor_id: u64::MAX - 70,
20244      frame: u64::MAX - 71,
20245    };
20246    let dependency = Arc::new(TaskDependency::default());
20247    dependency.set_owned(BlockingDependency {
20248      job: BlockingJobId(u64::MAX - 72),
20249      owner: Some(owner),
20250    });
20251    let parker = Arc::new(DriverParker::default());
20252    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
20253
20254    let (predicate_tx, predicate_rx) = mpsc::channel();
20255    let (release_tx, release_rx) = mpsc::channel();
20256    let selecting_registry = Arc::clone(&registry);
20257    let selector = std::thread::spawn(move || {
20258      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
20259        *slot.borrow_mut() = Some(Box::new(move || {
20260          predicate_tx.send(()).unwrap();
20261          release_rx.recv().unwrap();
20262        }));
20263      });
20264      selecting_registry.wake_one_owner_dependency(owner)
20265    });
20266
20267    predicate_rx
20268      .recv_timeout(Duration::from_secs(2))
20269      .expect("owner selection must reach the dependency predicate");
20270    let registry_was_unlocked = registry.parked.try_lock().is_ok();
20271    release_tx.send(()).unwrap();
20272    assert!(
20273      selector.join().unwrap(),
20274      "the exact-owner parker must still be selected"
20275    );
20276
20277    assert!(
20278      registry_was_unlocked,
20279      "owner dependency predicates must not run while the parked-driver registry is locked"
20280    );
20281    assert!(parker.consume_permit());
20282    assert_eq!(parker.take_owner_handoff(), Some(owner));
20283  }
20284
20285  #[cfg(napi_runtime_os_threads)]
20286  #[test]
20287  fn owner_handoff_skips_newer_untagged_dependency() {
20288    let registry = ParkedDrivers::default();
20289    let owner = BlockingOwnerToken {
20290      executor_id: u64::MAX - 1,
20291      frame: u64::MAX - 2,
20292    };
20293    let owned_dependency = Arc::new(TaskDependency::default());
20294    owned_dependency.set_owned(BlockingDependency {
20295      job: BlockingJobId(u64::MAX - 3),
20296      owner: Some(owner),
20297    });
20298    let untagged_dependency = Arc::new(TaskDependency::default());
20299    untagged_dependency.set_owned(BlockingDependency {
20300      job: BlockingJobId(u64::MAX - 4),
20301      owner: None,
20302    });
20303    let owned = Arc::new(DriverParker::default());
20304    let untagged = Arc::new(DriverParker::default());
20305
20306    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
20307    // Register the unrelated dependency last so an unfiltered LIFO retry
20308    // would consume the exact owner handoff.
20309    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
20310
20311    assert!(registry.wake_one_owner_dependency(owner));
20312    assert!(
20313      owned.consume_permit(),
20314      "the exact owner dependency must receive the retry"
20315    );
20316    assert!(
20317      !untagged.consume_permit(),
20318      "an untagged dependency must not consume an exact owner handoff"
20319    );
20320    registry.deregister(&untagged);
20321  }
20322
20323  #[cfg(napi_runtime_os_threads)]
20324  #[test]
20325  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
20326    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
20327    let owner = executor.fresh_owner_token();
20328    let valid_dependency = Arc::new(TaskDependency::default());
20329    valid_dependency.set_owned(BlockingDependency {
20330      job: BlockingJobId(u64::MAX - 45),
20331      owner: Some(owner),
20332    });
20333    let withdrawn_dependency = Arc::new(TaskDependency::default());
20334    withdrawn_dependency.set_owned(BlockingDependency {
20335      job: BlockingJobId(u64::MAX - 46),
20336      owner: Some(owner),
20337    });
20338    let valid = Arc::new(DriverParker::default());
20339    let withdrawn = Arc::new(DriverParker::default());
20340
20341    executor
20342      .parked_drivers
20343      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
20344    executor.parked_drivers.register_with_role(
20345      &withdrawn,
20346      true,
20347      Some(Arc::clone(&withdrawn_dependency)),
20348    );
20349    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
20350    withdrawn_dependency.clear();
20351
20352    assert_eq!(
20353      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
20354      Some(false),
20355      "a selected dependency withdrawn before retry must forward the owner handoff"
20356    );
20357    assert!(
20358      valid.consume_permit(),
20359      "the next live same-owner dependency must receive the forwarded wake"
20360    );
20361    assert_eq!(
20362      valid.take_owner_handoff(),
20363      Some(owner),
20364      "the forwarded wake must retain the exact owner identity"
20365    );
20366  }
20367
20368  #[cfg(napi_runtime_os_threads)]
20369  #[test]
20370  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
20371    use std::sync::mpsc;
20372
20373    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
20374    let owner = executor.fresh_owner_token();
20375    let fallback_dependency = Arc::new(TaskDependency::default());
20376    fallback_dependency.set_owned(BlockingDependency {
20377      job: BlockingJobId(u64::MAX - 47),
20378      owner: Some(owner),
20379    });
20380    let exiting_dependency = Arc::new(TaskDependency::default());
20381    exiting_dependency.set_owned(BlockingDependency {
20382      job: BlockingJobId(u64::MAX - 48),
20383      owner: Some(owner),
20384    });
20385    let fallback = Arc::new(DriverParker::default());
20386    let exiting = Arc::new(DriverParker::default());
20387
20388    executor.parked_drivers.register_with_role(
20389      &fallback,
20390      true,
20391      Some(Arc::clone(&fallback_dependency)),
20392    );
20393    executor.parked_drivers.register_with_role(
20394      &exiting,
20395      true,
20396      Some(Arc::clone(&exiting_dependency)),
20397    );
20398
20399    let (selected_tx, selected_rx) = mpsc::channel();
20400    let (release_selection_tx, release_selection_rx) = mpsc::channel();
20401    let selector_executor = Arc::clone(&executor);
20402    let selector = std::thread::spawn(move || {
20403      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
20404        *slot.borrow_mut() = Some(Box::new(move || {
20405          selected_tx.send(()).unwrap();
20406          release_selection_rx.recv().unwrap();
20407        }));
20408      });
20409      assert!(
20410        selector_executor
20411          .parked_drivers
20412          .wake_one_owner_dependency(owner)
20413      );
20414    });
20415    selected_rx
20416      .recv_timeout(Duration::from_secs(2))
20417      .expect("selection must pause after removing the exact-owner parker");
20418
20419    let (exited_tx, exited_rx) = mpsc::channel();
20420    let exiting_executor = Arc::clone(&executor);
20421    let exiting_driver = Arc::clone(&exiting);
20422    let driver = std::thread::spawn(move || {
20423      exiting_driver.park();
20424      exiting_executor.parked_drivers.deregister(&exiting_driver);
20425      exiting_executor.forward_owner_handoff(&exiting_driver);
20426      exited_tx.send(()).unwrap();
20427    });
20428    exiting.unpark();
20429    exited_rx
20430      .recv_timeout(Duration::from_secs(2))
20431      .expect("the direct future wake must let the selected driver exit");
20432
20433    release_selection_tx.send(()).unwrap();
20434    selector.join().unwrap();
20435    driver.join().unwrap();
20436
20437    assert!(
20438      fallback.consume_permit(),
20439      "an exiting selected driver must forward the owner handoff to the next dependency"
20440    );
20441    assert_eq!(
20442      fallback.take_owner_handoff(),
20443      Some(owner),
20444      "the forwarded handoff must retain the exact owner identity"
20445    );
20446  }
20447
20448  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20449  #[test]
20450  fn cooperative_panic_forwards_pending_owner_handoff() {
20451    use std::sync::mpsc;
20452
20453    struct PanicAfterWake {
20454      dependency: BlockingDependency,
20455      first_poll: Option<mpsc::Sender<()>>,
20456    }
20457
20458    impl Future for PanicAfterWake {
20459      type Output = ();
20460
20461      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20462        if let Some(first_poll) = self.first_poll.take() {
20463          // This shape never retires its publication -- the driver panics
20464          // instead -- so the returned token is dropped straight away.
20465          let mut published = PublishedBlockingDependencies::default();
20466          record_blocking_dependency(&mut published, self.dependency);
20467          first_poll.send(()).unwrap();
20468          return Poll::Pending;
20469        }
20470        panic!("intentional cooperative driver panic after owner handoff");
20471      }
20472    }
20473
20474    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
20475    let owner = executor.fresh_owner_token();
20476    let fallback_dependency = Arc::new(TaskDependency::default());
20477    fallback_dependency.set_owned(BlockingDependency {
20478      job: BlockingJobId(u64::MAX - 49),
20479      owner: Some(owner),
20480    });
20481    let fallback = Arc::new(DriverParker::default());
20482    executor.parked_drivers.register_with_role(
20483      &fallback,
20484      true,
20485      Some(Arc::clone(&fallback_dependency)),
20486    );
20487
20488    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20489    let (panicked_tx, panicked_rx) = mpsc::channel();
20490    let driver_executor = Arc::clone(&executor);
20491    let driver = std::thread::spawn(move || {
20492      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20493      let mut future = std::pin::pin!(PanicAfterWake {
20494        dependency: BlockingDependency {
20495          job: BlockingJobId(u64::MAX - 50),
20496          owner: Some(owner)
20497        },
20498        first_poll: Some(first_poll_tx),
20499      });
20500      let panicked = catch_unwind(AssertUnwindSafe(|| {
20501        driver_executor.block_on(future.as_mut());
20502      }))
20503      .is_err();
20504      panicked_tx.send(panicked).unwrap();
20505    });
20506
20507    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20508    wait_until("the panicking cooperative driver to park", || {
20509      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20510    });
20511    assert!(
20512      executor.parked_drivers.wake_one_owner_dependency(owner),
20513      "the exact owner handoff must select the cooperative driver"
20514    );
20515    assert!(
20516      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20517      "the test future must unwind from its second poll"
20518    );
20519    driver.join().unwrap();
20520
20521    assert!(
20522      fallback.consume_permit(),
20523      "panic cleanup must forward the unconsumed owner handoff"
20524    );
20525    assert_eq!(
20526      fallback.take_owner_handoff(),
20527      Some(owner),
20528      "panic cleanup must preserve the exact owner identity"
20529    );
20530  }
20531
20532  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20533  #[test]
20534  fn cooperative_panic_flushes_lifo_slot() {
20535    struct ScheduleThenPanic {
20536      executor: Arc<MultiThreadExecutor>,
20537      ran: Arc<AtomicBool>,
20538    }
20539
20540    impl Future for ScheduleThenPanic {
20541      type Output = ();
20542
20543      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20544        let ran = Arc::clone(&self.ran);
20545        let scheduler = Arc::clone(&self.executor);
20546        let (runnable, task) = async_task::spawn(
20547          async move {
20548            ran.store(true, Ordering::SeqCst);
20549          },
20550          move |runnable| scheduler.schedule(runnable),
20551        );
20552        self.executor.schedule(runnable);
20553        task.detach();
20554        panic!("intentional cooperative driver panic with a populated LIFO slot");
20555      }
20556    }
20557
20558    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
20559    executor
20560      .active_drainers
20561      .store(executor.max_drainers, Ordering::SeqCst);
20562    let ran = Arc::new(AtomicBool::new(false));
20563    let panicked = {
20564      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20565      let mut future = std::pin::pin!(ScheduleThenPanic {
20566        executor: Arc::clone(&executor),
20567        ran: Arc::clone(&ran),
20568      });
20569      catch_unwind(AssertUnwindSafe(|| {
20570        executor.block_on(future.as_mut());
20571      }))
20572      .is_err()
20573    };
20574
20575    assert!(panicked, "the test future must unwind from its first poll");
20576    assert!(
20577      executor.pop_lifo_slot().is_none(),
20578      "panic cleanup must empty the LIFO slot"
20579    );
20580    assert_eq!(
20581      executor.injector.len(),
20582      1,
20583      "panic cleanup must move the slot runnable to the shared FIFO"
20584    );
20585    assert!(
20586      executor.run_one(),
20587      "the flushed runnable must remain executable"
20588    );
20589    assert!(ran.load(Ordering::SeqCst));
20590    executor.active_drainers.store(0, Ordering::SeqCst);
20591  }
20592
20593  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
20594  #[test]
20595  fn cooperative_panic_compensates_absorbed_queue_wake() {
20596    use std::sync::mpsc;
20597
20598    struct PanicAfterWake {
20599      first_poll: Option<mpsc::Sender<()>>,
20600    }
20601
20602    impl Future for PanicAfterWake {
20603      type Output = ();
20604
20605      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20606        if let Some(first_poll) = self.first_poll.take() {
20607          first_poll.send(()).unwrap();
20608          return Poll::Pending;
20609        }
20610        panic!("intentional cooperative driver panic after a queue wake");
20611      }
20612    }
20613
20614    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
20615    executor
20616      .active_drainers
20617      .store(executor.max_drainers, Ordering::SeqCst);
20618    let fallback = Arc::new(DriverParker::default());
20619    executor.parked_drivers.register(&fallback);
20620    let (first_poll_tx, first_poll_rx) = mpsc::channel();
20621    let (panicked_tx, panicked_rx) = mpsc::channel();
20622    let driver_executor = Arc::clone(&executor);
20623    let driver = std::thread::spawn(move || {
20624      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
20625      let mut future = std::pin::pin!(PanicAfterWake {
20626        first_poll: Some(first_poll_tx)
20627      });
20628      let panicked = catch_unwind(AssertUnwindSafe(|| {
20629        driver_executor.block_on(future.as_mut());
20630      }))
20631      .is_err();
20632      panicked_tx.send(panicked).unwrap();
20633    });
20634
20635    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
20636    wait_until(
20637      "the panicking cooperative driver to park after the fallback",
20638      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
20639    );
20640
20641    let ran = Arc::new(AtomicBool::new(false));
20642    let ran_task = Arc::clone(&ran);
20643    let scheduler = Arc::clone(&executor);
20644    let (runnable, task) = async_task::spawn(
20645      async move {
20646        ran_task.store(true, Ordering::SeqCst);
20647      },
20648      move |runnable| scheduler.schedule(runnable),
20649    );
20650    task.detach();
20651    executor.metrics.runnable_scheduled();
20652    executor.push_fifo_raw(runnable);
20653    assert!(
20654      executor.parked_drivers.wake_one(),
20655      "the queued runnable's wake must be absorbed by the cooperative driver"
20656    );
20657
20658    assert!(
20659      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
20660      "the test future must unwind after absorbing the queue wake"
20661    );
20662    driver.join().unwrap();
20663    assert!(
20664      fallback.consume_permit(),
20665      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
20666    );
20667    assert!(
20668      executor.run_one(),
20669      "the queued runnable must remain executable"
20670    );
20671    assert!(ran.load(Ordering::SeqCst));
20672    executor.active_drainers.store(0, Ordering::SeqCst);
20673  }
20674
20675  #[cfg(napi_runtime_os_threads)]
20676  #[test]
20677  fn multi_thread_future_wake_targets_its_own_parked_driver() {
20678    // Future-wake targeting invariant: a future-wake must reach the driver that
20679    // AWAITS that future, not "some parked driver". D1 parks first awaiting
20680    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
20681    // though D2 parked more recently -- a future-wake misrouted through the
20682    // registry (which pops most-recently-parked) would wake D2, leave D1
20683    // parked forever, and trip the bounded timeout. The old notify_all
20684    // broadcast never distinguished drivers, so this pins the new obligation.
20685    use std::sync::mpsc;
20686    use std::time::Duration;
20687
20688    let (done_tx, done_rx) = mpsc::channel();
20689    let runner = std::thread::spawn(move || {
20690      let metrics = Arc::new(RuntimeMetrics::default());
20691      let options = RuntimeOptions {
20692        flavor: RuntimeFlavor::MultiThread,
20693        worker_threads: 2,
20694        max_blocking_tasks: 2,
20695        thread_name_prefix: "wake-target".to_string(),
20696        park_deadline: None,
20697        drain_linger: DEFAULT_DRAIN_LINGER,
20698      };
20699      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20700
20701      let (tx1, rx1) = oneshot::channel::<usize>();
20702      let (tx2, rx2) = oneshot::channel::<usize>();
20703      let t1_done = Arc::new(AtomicBool::new(false));
20704      let t2_done = Arc::new(AtomicBool::new(false));
20705
20706      // T1: parks FIRST, awaiting rx1.
20707      let t1_exec = Arc::clone(&executor);
20708      let t1_flag = Arc::clone(&t1_done);
20709      let t1_body = async move {
20710        let mut inner = std::pin::pin!(async move {
20711          assert_eq!(rx1.await.unwrap(), 7usize);
20712        });
20713        t1_exec.block_on(inner.as_mut());
20714        t1_flag.store(true, Ordering::SeqCst);
20715      };
20716      let sched1 = Arc::clone(&executor);
20717      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20718      executor.schedule(runnable1);
20719      wait_until("driver 1 parked", || {
20720        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20721      });
20722
20723      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
20724      // `schedule`, so no wake_one can pop the already-parked D1) and a
20725      // drainer is spawned for it on the free second worker.
20726      let t2_exec = Arc::clone(&executor);
20727      let t2_flag = Arc::clone(&t2_done);
20728      let t2_body = async move {
20729        let mut inner = std::pin::pin!(async move {
20730          assert_eq!(rx2.await.unwrap(), 9usize);
20731        });
20732        t2_exec.block_on(inner.as_mut());
20733        t2_flag.store(true, Ordering::SeqCst);
20734      };
20735      let sched2 = Arc::clone(&executor);
20736      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20737      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20738      executor.push_fifo_raw(runnable2);
20739      executor.ensure_drainer();
20740      wait_until("driver 2 parked", || {
20741        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20742      });
20743
20744      // Complete D1's future. The wake must land on D1 (first-parked), the
20745      // driver that owns rx1's waker -- not on the more recently parked D2.
20746      tx1.send(7usize).unwrap();
20747      wait_until("T1 completed via its own driver's wake", || {
20748        t1_done.load(Ordering::SeqCst)
20749      });
20750      futures::executor::block_on(task1);
20751      assert!(
20752        !t2_done.load(Ordering::SeqCst),
20753        "D2 must still be parked: its future was never completed"
20754      );
20755      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
20756      // linger): its linger parker joins the registry alongside D2 for the
20757      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
20758      // in the window between the cooperative parker's wake-deregistration
20759      // and the drain frame's linger registration, then 2 while lingering,
20760      // then 1 again after expiry -- so a `count == 1` wait can return
20761      // during the transient window and prove nothing about the linger.
20762      // Observe the lifecycle itself instead, via the test counters that
20763      // `drain` maintains: the linger parker must REGISTER, and its idle
20764      // budget must EXPIRE (`drain_linger_expiries` is incremented only
20765      // after the expiry path deregistered the parker, and no further work
20766      // exists to start another episode), after which the registry has
20767      // deterministically settled: EXACTLY the untargeted driver D2 remains
20768      // (a misrouted wake would have left D1 cooperatively parked instead,
20769      // already excluded by the `t1_done` wait above, and a linger parker
20770      // stranded in the registry would read 2 here).
20771      wait_until(
20772        "the retiring drain frame registered its linger parker",
20773        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20774      );
20775      wait_until("the lingering drainer's idle budget expired", || {
20776        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20777      });
20778      assert_eq!(
20779        executor.parked_drivers.count.load(Ordering::SeqCst),
20780        1,
20781        "exactly the untargeted driver must remain parked after the linger expired"
20782      );
20783      assert!(
20784        !t2_done.load(Ordering::SeqCst),
20785        "D2 must still be parked after the lingering drainer expired"
20786      );
20787
20788      // Teardown: release D2 as well.
20789      tx2.send(9usize).unwrap();
20790      futures::executor::block_on(task2);
20791      assert!(t2_done.load(Ordering::SeqCst));
20792      let _ = done_tx.send(());
20793    });
20794
20795    match done_rx.recv_timeout(Duration::from_secs(10)) {
20796      Ok(()) => runner.join().unwrap(),
20797      Err(error) => panic!(
20798        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
20799      ),
20800    }
20801  }
20802
20803  #[cfg(napi_runtime_os_threads)]
20804  #[test]
20805  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
20806    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
20807    // with queue work remaining must hand the wake on because it may have
20808    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
20809    // is parked as a cooperative driver, a runnable is pushed without a wake,
20810    // and the test thread forces a ready future through the cooperative branch.
20811    // Exit compensation is the ONLY mechanism left that can wake the parked
20812    // worker to drain the runnable.
20813    use std::sync::mpsc;
20814    use std::time::Duration;
20815
20816    let (done_tx, done_rx) = mpsc::channel();
20817    let runner = std::thread::spawn(move || {
20818      let metrics = Arc::new(RuntimeMetrics::default());
20819      let options = RuntimeOptions {
20820        flavor: RuntimeFlavor::MultiThread,
20821        worker_threads: 1,
20822        max_blocking_tasks: 1,
20823        thread_name_prefix: "wake-compensate".to_string(),
20824        park_deadline: None,
20825        drain_linger: DEFAULT_DRAIN_LINGER,
20826      };
20827      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20828
20829      // Park the sole worker as a cooperative driver awaiting a gate.
20830      let (gate_tx, gate_rx) = oneshot::channel::<()>();
20831      let td_exec = Arc::clone(&executor);
20832      let td_body = async move {
20833        let mut inner = std::pin::pin!(async move {
20834          gate_rx.await.unwrap();
20835        });
20836        td_exec.block_on(inner.as_mut());
20837      };
20838      let sched = Arc::clone(&executor);
20839      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
20840      executor.schedule(td_runnable);
20841      wait_until("the sole driver parked", || {
20842        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20843      });
20844
20845      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
20846      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
20847      let (ran_tx, ran_rx) = mpsc::channel::<()>();
20848      let sched_stranded = Arc::clone(&executor);
20849      let (stranded_runnable, stranded_task) = async_task::spawn(
20850        async move {
20851          ran_tx.send(()).unwrap();
20852        },
20853        move |r| sched_stranded.schedule(r),
20854      );
20855      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20856      executor.push_fifo_raw(stranded_runnable);
20857      stranded_task.detach();
20858
20859      // Drive a ready future through the cooperative branch on THIS thread
20860      // (fake on-pool marker, as in the executor-scoping test). Its first
20861      // poll is Ready, so the loop performs no work itself -- only the exit
20862      // compensation can hand the queued runnable to the parked driver.
20863      {
20864        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20865        let mut ready = std::pin::pin!(async {});
20866        executor.block_on(ready.as_mut());
20867      }
20868
20869      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
20870        "exit compensation must wake the parked driver to run the stranded queued runnable",
20871      );
20872
20873      // Teardown: release the parked driver.
20874      gate_tx.send(()).unwrap();
20875      futures::executor::block_on(td_task);
20876      let _ = done_tx.send(());
20877    });
20878
20879    match done_rx.recv_timeout(Duration::from_secs(10)) {
20880      Ok(()) => runner.join().unwrap(),
20881      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
20882    }
20883  }
20884
20885  #[cfg(napi_runtime_os_threads)]
20886  #[test]
20887  fn cooperative_exit_compensates_blocking_only_residue() {
20888    use std::sync::mpsc;
20889
20890    struct Controlled {
20891      ready: Arc<AtomicBool>,
20892      entered: Option<mpsc::Sender<()>>,
20893    }
20894
20895    impl Future for Controlled {
20896      type Output = ();
20897
20898      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20899        if let Some(entered) = self.entered.take() {
20900          entered.send(()).unwrap();
20901        }
20902        if self.ready.load(Ordering::SeqCst) {
20903          Poll::Ready(())
20904        } else {
20905          Poll::Pending
20906        }
20907      }
20908    }
20909
20910    let options = RuntimeOptions {
20911      flavor: RuntimeFlavor::MultiThread,
20912      worker_threads: 2,
20913      max_blocking_tasks: 1,
20914      thread_name_prefix: "blocking-exit-compensation".to_string(),
20915      park_deadline: None,
20916      drain_linger: DEFAULT_DRAIN_LINGER,
20917    };
20918    let executor =
20919      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20920    let ready = Arc::new(AtomicBool::new(false));
20921    let (entered_tx, entered_rx) = mpsc::channel();
20922    let (returned_tx, returned_rx) = mpsc::channel();
20923    let (release_tx, release_rx) = mpsc::channel();
20924    let task_exec = Arc::clone(&executor);
20925    let task_ready = Arc::clone(&ready);
20926    let scheduler = Arc::clone(&executor);
20927    let (runnable, task) = async_task::spawn(
20928      async move {
20929        let mut future = std::pin::pin!(Controlled {
20930          ready: task_ready,
20931          entered: Some(entered_tx)
20932        });
20933        task_exec.block_on(future.as_mut());
20934        returned_tx.send(()).unwrap();
20935        release_rx.recv().unwrap();
20936      },
20937      move |runnable| scheduler.schedule(runnable),
20938    );
20939    executor.schedule(runnable);
20940    entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
20941
20942    // The cooperative frame must be PARKED (registered) before the timer task
20943    // is scheduled, so that schedule's wake targets the frame instead of
20944    // spawning a second drainer. A second drainer would later LINGER as a
20945    // registered blocking-capable parker, and `wake_one_blocking` prefers the
20946    // most recently parked candidate: on a starved runner (observed on the
20947    // Windows CI) the linger parker absorbs the blocking wake below, the
20948    // drainer services the job, and the frame -- whose exit compensation is
20949    // the subject under test -- sleeps in its timer-bounded park until the
20950    // 120s heap timer, far past any sane recv bound.
20951    wait_until("the cooperative frame parked", || {
20952      executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20953    });
20954
20955    let timer_exec = Arc::clone(&executor);
20956    let timer_scheduler = Arc::clone(&executor);
20957    let (timer_runnable, timer_task) = async_task::spawn(
20958      async move {
20959        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
20960      },
20961      move |runnable| timer_scheduler.schedule(runnable),
20962    );
20963    executor.schedule(timer_runnable);
20964    wait_until("the dedicated timekeeper parked", || {
20965      executor
20966        .timers
20967        .inner
20968        .lock()
20969        .unwrap()
20970        .timekeeper_parker
20971        .is_some()
20972    });
20973    // Re-parked after running the timer runnable: the blocking wake below
20974    // must find the frame registered (the sole candidate), so the frame
20975    // absorbs the wake, observes its ready future, and its EXIT COMPENSATION
20976    // services the blocking-only residue.
20977    wait_until(
20978      "the cooperative frame re-parked in its timer-bounded park",
20979      || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
20980    );
20981
20982    ready.store(true, Ordering::SeqCst);
20983    let (blocking_tx, blocking_rx) = mpsc::channel();
20984    let blocking = executor.schedule_blocking(move || {
20985      blocking_tx.send(()).unwrap();
20986    });
20987    // Generous hang-detector bounds, not latency assertions: starved CI
20988    // runners (msys2 CLANG64) can stall a woken thread for whole seconds.
20989    blocking_rx
20990      .recv_timeout(Duration::from_secs(10))
20991      .expect("blocking-only residue must be serviced by a Rayon worker");
20992    returned_rx
20993      .recv_timeout(Duration::from_secs(10))
20994      .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
20995    release_tx.send(()).unwrap();
20996    futures::executor::block_on(task);
20997    futures::executor::block_on(blocking).unwrap();
20998    executor.shutdown_timers();
20999    futures::executor::block_on(timer_task);
21000  }
21001
21002  #[cfg(napi_runtime_os_threads)]
21003  #[test]
21004  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
21005    let options = RuntimeOptions {
21006      flavor: RuntimeFlavor::MultiThread,
21007      worker_threads: 2,
21008      max_blocking_tasks: 1,
21009      thread_name_prefix: "stopped-exit-compensation".to_string(),
21010      park_deadline: None,
21011      drain_linger: DEFAULT_DRAIN_LINGER,
21012    };
21013    let executor =
21014      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21015    let ran = Arc::new(AtomicBool::new(false));
21016    let blocking_ran = Arc::clone(&ran);
21017
21018    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
21019    // become observable while this job remains queued, matching the original
21020    // begin_shutdown ordering window exactly.
21021    executor
21022      .active_drainers
21023      .store(executor.max_drainers, Ordering::SeqCst);
21024    let blocking = executor.schedule_blocking(move || {
21025      blocking_ran.store(true, Ordering::SeqCst);
21026    });
21027    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
21028
21029    executor.stop.begin_shutdown();
21030    executor.compensate_queued_work_wake(true);
21031    let ran_after_stop = ran.load(Ordering::SeqCst);
21032
21033    executor.active_drainers.store(0, Ordering::SeqCst);
21034    executor.begin_shutdown();
21035    let blocking_result = futures::executor::block_on(blocking);
21036
21037    assert!(
21038      !ran_after_stop,
21039      "exit compensation started queued blocking work after stop"
21040    );
21041    assert!(
21042      blocking_result.is_err(),
21043      "shutdown must cancel the queued blocking job"
21044    );
21045  }
21046
21047  #[cfg(napi_runtime_os_threads)]
21048  #[test]
21049  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
21050    // Targeted-wake stress: N rounds of {producer: publish work,
21051    // wake_one()} racing {consumer: check work, register, re-check, park} --
21052    // the exact schedule-vs-park protocol `schedule` and
21053    // `cooperative_block_on` run. This walks the interleaving at every offset
21054    // the OS scheduler produces; a single lost wakeup parks the consumer
21055    // forever with work pending, and the bounded recv_timeout turns that into
21056    // a loud failure instead of a hung suite. (No loom in this repo; std
21057    // threads.)
21058    use std::sync::mpsc;
21059    use std::time::Duration;
21060
21061    const ROUNDS: usize = 20_000;
21062
21063    let (done_tx, done_rx) = mpsc::channel();
21064    let registry = Arc::new(ParkedDrivers::default());
21065    let work = Arc::new(AtomicBool::new(false));
21066
21067    let consumer = {
21068      let registry = Arc::clone(&registry);
21069      let work = Arc::clone(&work);
21070      std::thread::spawn(move || {
21071        let parker = Arc::new(DriverParker::default());
21072        for _ in 0..ROUNDS {
21073          loop {
21074            if work.swap(false, Ordering::SeqCst) {
21075              break;
21076            }
21077            // The cooperative park protocol: register FIRST, then re-check,
21078            // then park (see `ParkedDrivers::wake_one`).
21079            registry.register(&parker);
21080            if work.load(Ordering::SeqCst) {
21081              registry.deregister(&parker);
21082              continue;
21083            }
21084            parker.park();
21085            registry.deregister(&parker);
21086          }
21087        }
21088        done_tx.send(()).unwrap();
21089      })
21090    };
21091
21092    for _ in 0..ROUNDS {
21093      work.store(true, Ordering::SeqCst);
21094      // The schedule side: publish work, then wake one parked driver. When
21095      // wake_one misses (consumer not registered yet), the consumer's
21096      // post-register re-check must see the work instead.
21097      let _ = registry.wake_one();
21098      while work.load(Ordering::SeqCst) {
21099        std::hint::spin_loop();
21100      }
21101    }
21102
21103    done_rx
21104      .recv_timeout(Duration::from_secs(30))
21105      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
21106    consumer.join().unwrap();
21107  }
21108
21109  #[cfg(napi_runtime_os_threads)]
21110  #[test]
21111  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
21112    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
21113    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
21114    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
21115    let options = RuntimeOptions {
21116      flavor: RuntimeFlavor::MultiThread,
21117      worker_threads: 2,
21118      max_blocking_tasks: 2,
21119      thread_name_prefix: "lifo-order".to_string(),
21120      park_deadline: None,
21121      drain_linger: DEFAULT_DRAIN_LINGER,
21122    };
21123    let executor =
21124      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21125    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21126
21127    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
21128    // could race the manual `run_one` calls below).
21129    let old_order = Arc::clone(&order);
21130    let sched = Arc::clone(&executor);
21131    let (old_runnable, old_task) = async_task::spawn(
21132      async move {
21133        old_order.lock().unwrap().push("old");
21134      },
21135      move |r| sched.schedule(r),
21136    );
21137    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
21138    executor.push_fifo_raw(old_runnable);
21139    old_task.detach();
21140
21141    // Simulate being on this executor's pool worker: the schedule below must
21142    // take the slot path (which never touches the FIFO nor spawns a drainer).
21143    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21144    let new_order = Arc::clone(&order);
21145    let sched = Arc::clone(&executor);
21146    let (new_runnable, new_task) = async_task::spawn(
21147      async move {
21148        new_order.lock().unwrap().push("new");
21149      },
21150      move |r| sched.schedule(r),
21151    );
21152    executor.schedule(new_runnable);
21153    new_task.detach();
21154    assert_eq!(
21155      executor.injector.len(),
21156      1,
21157      "the slot-path schedule must not push to the shared FIFO"
21158    );
21159
21160    assert!(executor.run_one(), "run_one must pop the slot first");
21161    assert!(executor.run_one(), "run_one must then pop the FIFO");
21162    assert!(!executor.run_one(), "no work must remain");
21163    assert_eq!(
21164      *order.lock().unwrap(),
21165      vec!["new", "old"],
21166      "the slot runnable (newest) must run before older FIFO work"
21167    );
21168  }
21169
21170  #[cfg(napi_runtime_os_threads)]
21171  #[test]
21172  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
21173    let options = RuntimeOptions {
21174      flavor: RuntimeFlavor::MultiThread,
21175      worker_threads: 2,
21176      max_blocking_tasks: 1,
21177      thread_name_prefix: "fifo-owner".to_string(),
21178      park_deadline: None,
21179      drain_linger: DEFAULT_DRAIN_LINGER,
21180    };
21181    let executor =
21182      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21183    let observed_owner = Arc::new(Mutex::new(None));
21184    let observed = Arc::clone(&observed_owner);
21185    let (runnable, task) = async_task::spawn(
21186      async move {
21187        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21188      },
21189      |_| {},
21190    );
21191    task.detach();
21192    executor.metrics.runnable_scheduled();
21193    executor.push_fifo_raw(runnable);
21194    executor.active_drainers.store(1, Ordering::Release);
21195
21196    let owner = executor.fresh_owner_token();
21197    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
21198    Arc::clone(&executor).drain();
21199
21200    assert_eq!(
21201      *observed_owner.lock().unwrap(),
21202      Some(None),
21203      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
21204    );
21205    assert_eq!(
21206      BLOCKING_OWNER.with(std::cell::Cell::get),
21207      Some(owner),
21208      "the ambient blocking owner must be restored after the runnable"
21209    );
21210  }
21211
21212  #[cfg(napi_runtime_os_threads)]
21213  #[test]
21214  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
21215    // Drain integration: with one worker, task P scheduling
21216    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
21217    // jumping ahead of the older queued T1/T2.
21218    use std::sync::mpsc;
21219    use std::time::Duration;
21220
21221    let (done_tx, done_rx) = mpsc::channel();
21222    let runner = std::thread::spawn(move || {
21223      let options = RuntimeOptions {
21224        flavor: RuntimeFlavor::MultiThread,
21225        worker_threads: 1,
21226        max_blocking_tasks: 1,
21227        thread_name_prefix: "lifo-drain".to_string(),
21228        park_deadline: None,
21229        drain_linger: DEFAULT_DRAIN_LINGER,
21230      };
21231      let executor =
21232        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21233      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21234
21235      let p_exec = Arc::clone(&executor);
21236      let p_order = Arc::clone(&order);
21237      let p_body = async move {
21238        p_order.lock().unwrap().push("P");
21239        // Scheduled from the pool worker mid-drain: must land in the slot.
21240        let c_order = Arc::clone(&p_order);
21241        let sched = Arc::clone(&p_exec);
21242        let (c_runnable, c_task) = async_task::spawn(
21243          async move {
21244            c_order.lock().unwrap().push("C");
21245          },
21246          move |r| sched.schedule(r),
21247        );
21248        p_exec.schedule(c_runnable);
21249        c_task.detach();
21250      };
21251
21252      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
21253      // mirroring the `runnable_scheduled` accounting a real schedule does so
21254      // the queued-runnables counter stays balanced.
21255      let mut tasks = Vec::new();
21256      let sched = Arc::clone(&executor);
21257      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
21258      executor.metrics.runnable_scheduled();
21259      executor.push_fifo_raw(p_runnable);
21260      tasks.push(p_task);
21261      for name in ["T1", "T2"] {
21262        let t_order = Arc::clone(&order);
21263        let sched = Arc::clone(&executor);
21264        let (t_runnable, t_task) = async_task::spawn(
21265          async move {
21266            t_order.lock().unwrap().push(name);
21267          },
21268          move |r| sched.schedule(r),
21269        );
21270        executor.metrics.runnable_scheduled();
21271        executor.push_fifo_raw(t_runnable);
21272        tasks.push(t_task);
21273      }
21274      executor.ensure_drainer();
21275
21276      for task in tasks {
21277        futures::executor::block_on(task);
21278      }
21279      wait_until("the detached child C ran", || {
21280        order.lock().unwrap().len() == 4
21281      });
21282      assert_eq!(
21283        *order.lock().unwrap(),
21284        vec!["P", "C", "T1", "T2"],
21285        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
21286      );
21287      let _ = done_tx.send(());
21288    });
21289
21290    match done_rx.recv_timeout(Duration::from_secs(10)) {
21291      Ok(()) => runner.join().unwrap(),
21292      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
21293    }
21294  }
21295
21296  #[cfg(napi_runtime_os_threads)]
21297  #[test]
21298  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
21299    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
21300    // NEWEST takes the slot; the displaced occupant must land on the shared
21301    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
21302    // the displacement wake cannot spawn a real drainer racing the manual
21303    // `run_one` calls.
21304    let options = RuntimeOptions {
21305      flavor: RuntimeFlavor::MultiThread,
21306      worker_threads: 2,
21307      max_blocking_tasks: 2,
21308      thread_name_prefix: "lifo-displace".to_string(),
21309      park_deadline: None,
21310      drain_linger: DEFAULT_DRAIN_LINGER,
21311    };
21312    let executor =
21313      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21314    executor
21315      .active_drainers
21316      .store(executor.max_drainers, Ordering::SeqCst);
21317    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21318
21319    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21320    for name in ["a", "b"] {
21321      let n_order = Arc::clone(&order);
21322      let sched = Arc::clone(&executor);
21323      let (runnable, task) = async_task::spawn(
21324        async move {
21325          n_order.lock().unwrap().push(name);
21326        },
21327        move |r| sched.schedule(r),
21328      );
21329      executor.schedule(runnable);
21330      task.detach();
21331    }
21332    assert_eq!(
21333      executor.injector.len(),
21334      1,
21335      "the displaced occupant (a) must have been pushed to the shared FIFO"
21336    );
21337
21338    assert!(executor.run_one(), "run_one must pop the slot (b) first");
21339    assert!(
21340      executor.run_one(),
21341      "run_one must then pop the displaced (a) from the FIFO"
21342    );
21343    assert!(!executor.run_one(), "no work must remain");
21344    assert_eq!(
21345      *order.lock().unwrap(),
21346      vec!["b", "a"],
21347      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
21348    );
21349    executor.active_drainers.store(0, Ordering::SeqCst);
21350  }
21351
21352  #[cfg(napi_runtime_os_threads)]
21353  #[test]
21354  fn lifo_slot_is_executor_scoped() {
21355    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
21356    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
21357    // scheduling onto executor B must bypass the slot (B's FIFO); and with
21358    // A's runnable IN the slot, B must neither pop nor displace it.
21359    let options = RuntimeOptions {
21360      flavor: RuntimeFlavor::MultiThread,
21361      worker_threads: 2,
21362      max_blocking_tasks: 2,
21363      thread_name_prefix: "lifo-scope".to_string(),
21364      park_deadline: None,
21365      drain_linger: DEFAULT_DRAIN_LINGER,
21366    };
21367    let exec1 =
21368      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21369    let exec2 =
21370      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21371    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21372    exec1
21373      .active_drainers
21374      .store(exec1.max_drainers, Ordering::SeqCst);
21375    exec2
21376      .active_drainers
21377      .store(exec2.max_drainers, Ordering::SeqCst);
21378    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21379
21380    // As exec1's worker: r1 goes to the slot, tagged exec1.
21381    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
21382    let r1_order = Arc::clone(&order);
21383    let sched = Arc::clone(&exec1);
21384    let (r1, t1) = async_task::spawn(
21385      async move {
21386        r1_order.lock().unwrap().push("one");
21387      },
21388      move |r| sched.schedule(r),
21389    );
21390    exec1.schedule(r1);
21391    t1.detach();
21392    assert!(
21393      exec1.injector.is_empty(),
21394      "r1 must sit in the slot, not exec1's FIFO"
21395    );
21396
21397    {
21398      // Nested: now act as exec2's worker while exec1's runnable occupies the
21399      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
21400      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
21401      let r2_order = Arc::clone(&order);
21402      let sched = Arc::clone(&exec2);
21403      let (r2, t2) = async_task::spawn(
21404        async move {
21405          r2_order.lock().unwrap().push("two");
21406        },
21407        move |r| sched.schedule(r),
21408      );
21409      exec2.schedule(r2);
21410      t2.detach();
21411      assert_eq!(
21412        exec2.injector.len(),
21413        1,
21414        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
21415      );
21416      assert!(
21417        exec2.pop_lifo_slot().is_none(),
21418        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
21419      );
21420      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
21421    }
21422
21423    // Back as exec1's worker: its slot entry is intact and runnable.
21424    assert!(
21425      exec1.run_one(),
21426      "exec1's slot entry must have survived the foreign activity"
21427    );
21428    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
21429    exec1.active_drainers.store(0, Ordering::SeqCst);
21430    exec2.active_drainers.store(0, Ordering::SeqCst);
21431  }
21432
21433  #[cfg(napi_runtime_os_threads)]
21434  #[test]
21435  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
21436    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
21437    // next FROM the pool worker (every link lands in the slot) crosses
21438    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
21439    // flushed to the FIFO before `finish_draining` (so its re-check sees the
21440    // runnable and re-arms a drainer); a missing flush strands the chain at
21441    // the first budget boundary (lost task + queued_runnables leak).
21442    use std::sync::mpsc;
21443    use std::time::Duration;
21444
21445    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
21446
21447    fn spawn_link(
21448      executor: &Arc<MultiThreadExecutor>,
21449      remaining: u64,
21450      counter: Arc<AtomicU64>,
21451      done: mpsc::Sender<()>,
21452    ) {
21453      let body_exec = Arc::clone(executor);
21454      let body = async move {
21455        counter.fetch_add(1, Ordering::SeqCst);
21456        if remaining > 0 {
21457          spawn_link(&body_exec, remaining - 1, counter, done);
21458        } else {
21459          done.send(()).unwrap();
21460        }
21461      };
21462      let sched = Arc::clone(executor);
21463      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21464      executor.schedule(runnable);
21465      task.detach();
21466    }
21467
21468    let (done_tx, done_rx) = mpsc::channel();
21469    let runner = std::thread::spawn(move || {
21470      let metrics = Arc::new(RuntimeMetrics::default());
21471      let options = RuntimeOptions {
21472        flavor: RuntimeFlavor::MultiThread,
21473        worker_threads: 1,
21474        max_blocking_tasks: 1,
21475        thread_name_prefix: "lifo-budget".to_string(),
21476        park_deadline: None,
21477        drain_linger: DEFAULT_DRAIN_LINGER,
21478      };
21479      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21480
21481      let counter = Arc::new(AtomicU64::new(0));
21482      let (chain_tx, chain_rx) = mpsc::channel();
21483      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
21484
21485      chain_rx
21486        .recv_timeout(Duration::from_secs(10))
21487        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
21488      assert_eq!(
21489        counter.load(Ordering::SeqCst),
21490        CHAIN,
21491        "every chain link must have run"
21492      );
21493      wait_until("runnable counters settle to zero (no slot leak)", || {
21494        metrics.queued_runnables.load(Ordering::Relaxed) == 0
21495          && metrics.active_runnables.load(Ordering::Relaxed) == 0
21496      });
21497      let _ = done_tx.send(());
21498    });
21499
21500    match done_rx.recv_timeout(Duration::from_secs(15)) {
21501      Ok(()) => runner.join().unwrap(),
21502      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
21503    }
21504  }
21505
21506  #[cfg(napi_runtime_os_threads)]
21507  #[test]
21508  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
21509    // Direct flush primitive test: pin the flush behavior used at drain
21510    // exits, before cooperative parks and by the unwind guard -- a
21511    // slot-resident runnable moves to the shared FIFO with its task still
21512    // completable, never dropped. (The pre-park call is defensive: `run_one`
21513    // pops the slot before any park is reachable, so this primitive is its
21514    // only observable seam.)
21515    let options = RuntimeOptions {
21516      flavor: RuntimeFlavor::MultiThread,
21517      worker_threads: 2,
21518      max_blocking_tasks: 2,
21519      thread_name_prefix: "lifo-flush".to_string(),
21520      park_deadline: None,
21521      drain_linger: DEFAULT_DRAIN_LINGER,
21522    };
21523    let executor =
21524      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21525    executor
21526      .active_drainers
21527      .store(executor.max_drainers, Ordering::SeqCst);
21528    let ran = Arc::new(AtomicBool::new(false));
21529
21530    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21531    let ran_flag = Arc::clone(&ran);
21532    let sched = Arc::clone(&executor);
21533    let (runnable, task) = async_task::spawn(
21534      async move {
21535        ran_flag.store(true, Ordering::SeqCst);
21536      },
21537      move |r| sched.schedule(r),
21538    );
21539    executor.schedule(runnable);
21540    task.detach();
21541    assert!(
21542      executor.injector.is_empty(),
21543      "the runnable must start in the slot"
21544    );
21545
21546    executor.flush_lifo_slot();
21547    assert!(
21548      executor.pop_lifo_slot().is_none(),
21549      "the slot must be empty after a flush"
21550    );
21551    assert_eq!(
21552      executor.injector.len(),
21553      1,
21554      "the flushed runnable must be on the shared FIFO"
21555    );
21556    assert!(executor.run_one(), "the flushed runnable must still run");
21557    assert!(
21558      ran.load(Ordering::SeqCst),
21559      "the flushed task must complete, not be dropped"
21560    );
21561    executor.active_drainers.store(0, Ordering::SeqCst);
21562  }
21563
21564  #[cfg(napi_runtime_os_threads)]
21565  #[test]
21566  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
21567    // Blocking-owner isolation must cover the LIFO slot path: a runnable
21568    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
21569    // borrow the driving owner frame's over-cap privilege; the ambient owner
21570    // is restored afterwards.
21571    let options = RuntimeOptions {
21572      flavor: RuntimeFlavor::MultiThread,
21573      worker_threads: 2,
21574      max_blocking_tasks: 2,
21575      thread_name_prefix: "lifo-owner".to_string(),
21576      park_deadline: None,
21577      drain_linger: DEFAULT_DRAIN_LINGER,
21578    };
21579    let executor =
21580      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21581    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
21582
21583    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21584
21585    // Install the slot entry from RUNNABLE context (no ambient owner), the
21586    // only context that may use the slot because schedules issued under an
21587    // owner frame are diverted to the shared FIFO.
21588    let observed_slot = Arc::clone(&observed);
21589    let sched = Arc::clone(&executor);
21590    let (runnable, task) = async_task::spawn(
21591      async move {
21592        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
21593      },
21594      move |r| sched.schedule(r),
21595    );
21596    executor.schedule(runnable);
21597    task.detach();
21598    assert!(
21599      executor.injector.is_empty(),
21600      "the runnable must sit in the slot"
21601    );
21602
21603    // The POP can still happen while an owner frame is ambient (a cooperative
21604    // `block_on` entered from a blocking closure drives `run_one` with the
21605    // closure's token on the stack); the slot branch must clear it.
21606    let token = executor.fresh_owner_token();
21607    let _owner = BlockingOwnerGuard::enter(Some(token));
21608    assert!(executor.run_one(), "the slot runnable must run");
21609    assert_eq!(
21610      *observed.lock().unwrap(),
21611      Some(None),
21612      "a slot runnable must observe a CLEARED blocking owner"
21613    );
21614    assert_eq!(
21615      BLOCKING_OWNER.with(std::cell::Cell::get),
21616      Some(token),
21617      "the ambient owner frame must be restored after the slot runnable"
21618    );
21619  }
21620
21621  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
21622  // each other on every poll. Scheduled from a pool worker, each wake lands in
21623  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
21624  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
21625  // its partner's stored waker so both complete.
21626  #[cfg(napi_runtime_os_threads)]
21627  struct PingPongShared {
21628    count: AtomicU64,
21629    stop: AtomicBool,
21630    stop_after: u64,
21631    wakers: Mutex<[Option<std::task::Waker>; 2]>,
21632  }
21633
21634  #[cfg(napi_runtime_os_threads)]
21635  struct PingPong {
21636    shared: Arc<PingPongShared>,
21637    me: usize,
21638  }
21639
21640  #[cfg(napi_runtime_os_threads)]
21641  impl Future for PingPong {
21642    type Output = ();
21643    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21644      let shared = &self.shared;
21645      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
21646      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
21647        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
21648        if let Some(partner) = partner {
21649          partner.wake();
21650        }
21651        return Poll::Ready(());
21652      }
21653      let mut wakers = shared.wakers.lock().unwrap();
21654      wakers[self.me] = Some(cx.waker().clone());
21655      if let Some(partner) = wakers[1 - self.me].take() {
21656        drop(wakers);
21657        partner.wake();
21658      }
21659      Poll::Pending
21660    }
21661  }
21662
21663  #[cfg(napi_runtime_os_threads)]
21664  fn spawn_ping_pong_pair(
21665    executor: &Arc<MultiThreadExecutor>,
21666    shared: &Arc<PingPongShared>,
21667  ) -> Vec<Task<()>> {
21668    let mut tasks = Vec::new();
21669    for me in 0..2 {
21670      let sched = Arc::clone(executor);
21671      let (runnable, task) = async_task::spawn(
21672        PingPong {
21673          shared: Arc::clone(shared),
21674          me,
21675        },
21676        move |r| sched.schedule(r),
21677      );
21678      executor.schedule(runnable);
21679      tasks.push(task);
21680    }
21681    tasks
21682  }
21683
21684  #[cfg(napi_runtime_os_threads)]
21685  #[test]
21686  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
21687    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
21688    // a hot wake/await pair holds a worker for at most one budget window
21689    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
21690    // a third task queued behind an unbounded ping-pong pair must still run;
21691    // if slot pops bypassed the budget the drain would never exit and T3
21692    // would starve forever (caught by the bounded timeout).
21693    use std::sync::mpsc;
21694    use std::time::Duration;
21695
21696    let (done_tx, done_rx) = mpsc::channel();
21697    let runner = std::thread::spawn(move || {
21698      let options = RuntimeOptions {
21699        flavor: RuntimeFlavor::MultiThread,
21700        worker_threads: 1,
21701        max_blocking_tasks: 0,
21702        thread_name_prefix: "lifo-streak".to_string(),
21703        park_deadline: None,
21704        drain_linger: DEFAULT_DRAIN_LINGER,
21705      };
21706      let executor =
21707        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21708
21709      let shared = Arc::new(PingPongShared {
21710        count: AtomicU64::new(0),
21711        stop: AtomicBool::new(false),
21712        stop_after: u64::MAX,
21713        wakers: Mutex::new([None, None]),
21714      });
21715      let pair = spawn_ping_pong_pair(&executor, &shared);
21716
21717      // T3: queued on the shared FIFO behind the hot pair.
21718      let (t3_tx, t3_rx) = mpsc::channel::<()>();
21719      let sched = Arc::clone(&executor);
21720      let (t3_runnable, t3_task) = async_task::spawn(
21721        async move {
21722          t3_tx.send(()).unwrap();
21723        },
21724        move |r| sched.schedule(r),
21725      );
21726      executor.schedule(t3_runnable);
21727
21728      t3_rx
21729        .recv_timeout(Duration::from_secs(10))
21730        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21731      assert!(
21732        shared.count.load(Ordering::SeqCst) >= 32,
21733        "the pair must have been bouncing through the slot before T3 got its turn"
21734      );
21735
21736      // Teardown: stop the pair and release whichever side is waiting.
21737      shared.stop.store(true, Ordering::SeqCst);
21738      let stored: Vec<_> = shared
21739        .wakers
21740        .lock()
21741        .unwrap()
21742        .iter_mut()
21743        .filter_map(Option::take)
21744        .collect();
21745      for waker in stored {
21746        waker.wake();
21747      }
21748      for task in pair {
21749        futures::executor::block_on(task);
21750      }
21751      futures::executor::block_on(t3_task);
21752      let _ = done_tx.send(());
21753    });
21754
21755    match done_rx.recv_timeout(Duration::from_secs(15)) {
21756      Ok(()) => runner.join().unwrap(),
21757      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21758    }
21759  }
21760
21761  #[cfg(napi_runtime_os_threads)]
21762  #[test]
21763  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21764    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
21765    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
21766    // pair must not turn that priority into a permanent wedge. The budget
21767    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
21768    // blocking queue, and the queued blocking job completes once the pair
21769    // finishes its (long) run on the SOLE worker.
21770    use std::sync::mpsc;
21771    use std::time::Duration;
21772
21773    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
21774
21775    let (done_tx, done_rx) = mpsc::channel();
21776    let runner = std::thread::spawn(move || {
21777      let options = RuntimeOptions {
21778        flavor: RuntimeFlavor::MultiThread,
21779        worker_threads: 1,
21780        max_blocking_tasks: 1,
21781        thread_name_prefix: "lifo-blocking".to_string(),
21782        park_deadline: None,
21783        drain_linger: DEFAULT_DRAIN_LINGER,
21784      };
21785      let executor =
21786        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21787
21788      let shared = Arc::new(PingPongShared {
21789        count: AtomicU64::new(0),
21790        stop: AtomicBool::new(false),
21791        stop_after: BOUNCES,
21792        wakers: Mutex::new([None, None]),
21793      });
21794      let pair = spawn_ping_pong_pair(&executor, &shared);
21795
21796      // Queued while the pair keeps the slot hot on the only worker.
21797      let blocking = executor.schedule_blocking(|| 11usize);
21798
21799      assert_eq!(
21800        futures::executor::block_on(blocking).unwrap(),
21801        11,
21802        "the queued blocking job must complete despite the hot LIFO pair"
21803      );
21804      for task in pair {
21805        futures::executor::block_on(task);
21806      }
21807      assert!(
21808        shared.count.load(Ordering::SeqCst) >= BOUNCES,
21809        "the pair must have actually run its full bounce quota"
21810      );
21811      let _ = done_tx.send(());
21812    });
21813
21814    match done_rx.recv_timeout(Duration::from_secs(15)) {
21815      Ok(()) => runner.join().unwrap(),
21816      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
21817    }
21818  }
21819
21820  #[cfg(napi_runtime_os_threads)]
21821  #[test]
21822  fn infinite_hot_runnable_yields_to_blocking_fifo() {
21823    use std::sync::mpsc;
21824
21825    struct HotFuture {
21826      stop: Arc<AtomicBool>,
21827    }
21828
21829    impl Future for HotFuture {
21830      type Output = ();
21831
21832      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21833        if self.stop.load(Ordering::SeqCst) {
21834          Poll::Ready(())
21835        } else {
21836          cx.waker().wake_by_ref();
21837          Poll::Pending
21838        }
21839      }
21840    }
21841
21842    let options = RuntimeOptions {
21843      flavor: RuntimeFlavor::MultiThread,
21844      worker_threads: 2,
21845      max_blocking_tasks: 1,
21846      thread_name_prefix: "blocking-fairness".to_string(),
21847      park_deadline: None,
21848      drain_linger: DEFAULT_DRAIN_LINGER,
21849    };
21850    let executor =
21851      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21852    let stop = Arc::new(AtomicBool::new(false));
21853    let scheduler = Arc::clone(&executor);
21854    let (runnable, task) = async_task::spawn(
21855      HotFuture {
21856        stop: Arc::clone(&stop),
21857      },
21858      move |r| scheduler.schedule(r),
21859    );
21860    task.detach();
21861    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
21862    executor
21863      .blocking_queue
21864      .lock()
21865      .unwrap()
21866      .push(QueuedBlocking {
21867        id: BlockingJobId(u64::MAX),
21868        run: Box::new(move || blocking_tx.send(()).unwrap()),
21869      });
21870
21871    let _driver = OnPoolWorkerGuard::enter(executor.id);
21872    executor.schedule(runnable);
21873    let mut runnable_streak = 0;
21874    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
21875      assert!(executor.run_one_fair(&mut runnable_streak));
21876    }
21877    blocking_rx
21878      .try_recv()
21879      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
21880
21881    stop.store(true, Ordering::SeqCst);
21882    assert!(executor.run_one_fair(&mut runnable_streak));
21883  }
21884
21885  #[cfg(napi_runtime_os_threads)]
21886  #[test]
21887  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
21888    use std::sync::mpsc;
21889
21890    struct HotFuture {
21891      stop: Arc<AtomicBool>,
21892    }
21893
21894    impl Future for HotFuture {
21895      type Output = ();
21896
21897      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21898        if self.stop.load(Ordering::SeqCst) {
21899          Poll::Ready(())
21900        } else {
21901          cx.waker().wake_by_ref();
21902          Poll::Pending
21903        }
21904      }
21905    }
21906
21907    let options = RuntimeOptions {
21908      flavor: RuntimeFlavor::MultiThread,
21909      worker_threads: 1,
21910      max_blocking_tasks: 1,
21911      thread_name_prefix: "exact-dependency-fairness".to_string(),
21912      park_deadline: None,
21913      drain_linger: DEFAULT_DRAIN_LINGER,
21914    };
21915    let executor =
21916      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21917    let stop = Arc::new(AtomicBool::new(false));
21918    let (queued_tx, queued_rx) = mpsc::channel();
21919    let (exact_tx, exact_rx) = mpsc::channel();
21920    let owner_executor = Arc::clone(&executor);
21921    let owner_stop = Arc::clone(&stop);
21922    let owner = executor.schedule_blocking(move || {
21923      let scheduler = Arc::clone(&owner_executor);
21924      let hot_stop = Arc::clone(&owner_stop);
21925      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
21926        scheduler.schedule(runnable);
21927      });
21928      owner_executor.schedule(runnable);
21929
21930      let exact_stop = Arc::clone(&owner_stop);
21931      let exact = owner_executor.schedule_blocking(move || {
21932        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
21933        41usize
21934      });
21935      queued_tx.send(()).unwrap();
21936
21937      let mut value = None;
21938      {
21939        let mut future = std::pin::pin!(async {
21940          value = Some(exact.await.unwrap());
21941          hot_task.await;
21942        });
21943        assert_eq!(
21944          owner_executor.block_on(future.as_mut()),
21945          BlockOnOutcome::Completed
21946        );
21947      }
21948      value.unwrap()
21949    });
21950
21951    queued_rx
21952      .recv_timeout(Duration::from_secs(2))
21953      .expect("the exact dependency must be queued behind the saturated owner");
21954    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
21955    stop.store(true, Ordering::SeqCst);
21956    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
21957    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
21958    assert_eq!(
21959      exact_before_stop,
21960      Some(true),
21961      "a hot local runnable must yield to the exact dependency before it is stopped"
21962    );
21963  }
21964
21965  #[cfg(napi_runtime_os_threads)]
21966  #[test]
21967  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
21968    use std::sync::mpsc;
21969
21970    struct BlockingPoll {
21971      started: Option<mpsc::Sender<()>>,
21972      released: Arc<(Mutex<bool>, Condvar)>,
21973    }
21974
21975    impl Future for BlockingPoll {
21976      type Output = ();
21977
21978      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21979        if let Some(started) = self.started.take() {
21980          started.send(()).unwrap();
21981        }
21982        let (released, changed) = &*self.released;
21983        let mut released = released
21984          .lock()
21985          .unwrap_or_else(std::sync::PoisonError::into_inner);
21986        while !*released {
21987          released = changed
21988            .wait(released)
21989            .unwrap_or_else(std::sync::PoisonError::into_inner);
21990        }
21991        Poll::Ready(())
21992      }
21993    }
21994
21995    let options = RuntimeOptions {
21996      flavor: RuntimeFlavor::MultiThread,
21997      worker_threads: 3,
21998      max_blocking_tasks: 2,
21999      thread_name_prefix: "exact-before-last-slot".to_string(),
22000      park_deadline: None,
22001      drain_linger: DEFAULT_DRAIN_LINGER,
22002    };
22003    let executor =
22004      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22005
22006    let released = Arc::new((Mutex::new(false), Condvar::new()));
22007    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
22008    let mut blockers = Vec::new();
22009    for _ in 0..2 {
22010      let scheduler = Arc::clone(&executor);
22011      let blocker_released = Arc::clone(&released);
22012      let started = blocker_started_tx.clone();
22013      let (runnable, task) = async_task::spawn(
22014        BlockingPoll {
22015          started: Some(started),
22016          released: blocker_released,
22017        },
22018        move |runnable| scheduler.schedule(runnable),
22019      );
22020      executor.schedule(runnable);
22021      blockers.push(task);
22022    }
22023    drop(blocker_started_tx);
22024    for _ in 0..2 {
22025      blocker_started_rx
22026        .recv_timeout(Duration::from_secs(2))
22027        .expect("two workers must be gated before the owner starts");
22028    }
22029
22030    let dependency_started = Arc::new(AtomicBool::new(false));
22031    let owner_executor = Arc::clone(&executor);
22032    let owner_dependency_started = Arc::clone(&dependency_started);
22033    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
22034    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
22035    let owner_signal = Arc::clone(&dependency_signal);
22036    let owner = executor.schedule_blocking(move || {
22037      let unrelated_signal = Arc::clone(&owner_signal);
22038      let unrelated_started = Arc::clone(&owner_dependency_started);
22039      let unrelated = owner_executor.schedule_blocking(move || {
22040        let observed = unrelated_started.load(Ordering::SeqCst);
22041        unrelated_observed_tx.send(observed).unwrap();
22042        let (ready, changed) = &*unrelated_signal;
22043        let mut ready = ready
22044          .lock()
22045          .unwrap_or_else(std::sync::PoisonError::into_inner);
22046        while !*ready {
22047          ready = changed
22048            .wait(ready)
22049            .unwrap_or_else(std::sync::PoisonError::into_inner);
22050        }
22051      });
22052
22053      let exact_signal = Arc::clone(&owner_signal);
22054      let exact_started = Arc::clone(&owner_dependency_started);
22055      let exact = owner_executor.schedule_blocking(move || {
22056        exact_started.store(true, Ordering::SeqCst);
22057        let (ready, changed) = &*exact_signal;
22058        *ready
22059          .lock()
22060          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22061        changed.notify_all();
22062        41usize
22063      });
22064
22065      let mut output = None;
22066      {
22067        let mut future = std::pin::pin!(async {
22068          output = Some(exact.await.unwrap());
22069          unrelated.await.unwrap();
22070        });
22071        assert_eq!(
22072          owner_executor.block_on(future.as_mut()),
22073          BlockOnOutcome::Completed
22074        );
22075      }
22076      output.unwrap()
22077    });
22078
22079    let (owner_done_tx, owner_done_rx) = mpsc::channel();
22080    let owner_waiter = std::thread::spawn(move || {
22081      owner_done_tx
22082        .send(futures::executor::block_on(owner))
22083        .unwrap();
22084    });
22085    let (completed_while_gated, owner_result) =
22086      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
22087        Ok(result) => (true, result),
22088        Err(_) => {
22089          // Let a broken implementation recover so the regression fails
22090          // deterministically instead of leaving blocked Rayon workers behind.
22091          let (released, changed) = &*released;
22092          *released
22093            .lock()
22094            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22095          changed.notify_all();
22096          (
22097            false,
22098            owner_done_rx
22099              .recv_timeout(Duration::from_secs(2))
22100              .expect("releasing the other workers must let the broken ordering recover"),
22101          )
22102        }
22103      };
22104    let unrelated_observed = unrelated_observed_rx
22105      .recv_timeout(Duration::from_secs(2))
22106      .expect("the unrelated FIFO job must eventually run");
22107
22108    {
22109      let (released, changed) = &*released;
22110      *released
22111        .lock()
22112        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
22113      changed.notify_all();
22114    }
22115    owner_waiter.join().unwrap();
22116    for blocker in blockers {
22117      futures::executor::block_on(blocker);
22118    }
22119    executor.begin_shutdown();
22120    executor.wait_until_scheduler_idle();
22121
22122    assert!(
22123      completed_while_gated,
22124      "the exact dependency must complete while unrelated workers remain gated"
22125    );
22126    assert_eq!(owner_result.unwrap(), 41);
22127    assert!(
22128      unrelated_observed,
22129      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
22130    );
22131  }
22132
22133  #[cfg(napi_runtime_os_threads)]
22134  #[test]
22135  fn blocking_saturation_preserves_a_runnable_execution_lane() {
22136    use std::{sync::mpsc, time::Duration};
22137
22138    let options = RuntimeOptions {
22139      flavor: RuntimeFlavor::MultiThread,
22140      worker_threads: 2,
22141      max_blocking_tasks: 2,
22142      thread_name_prefix: "blocking-reserve".to_string(),
22143      park_deadline: None,
22144      drain_linger: DEFAULT_DRAIN_LINGER,
22145    }
22146    .validate()
22147    .expect("production options must reserve a runnable lane");
22148    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
22149    let executor =
22150      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22151    let (release_tx, release_rx) = mpsc::channel();
22152    let (started_tx, started_rx) = mpsc::channel();
22153    let first = executor.schedule_blocking(move || {
22154      started_tx.send(()).unwrap();
22155      release_rx.recv().unwrap();
22156    });
22157    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
22158
22159    let (queued_tx, queued_rx) = mpsc::channel();
22160    let second = executor.schedule_blocking(move || {
22161      queued_tx.send(()).unwrap();
22162    });
22163    assert!(
22164      queued_rx.try_recv().is_err(),
22165      "the second blocking job must remain queued while the sole blocking slot is occupied"
22166    );
22167
22168    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
22169    let scheduler = Arc::clone(&executor);
22170    let (runnable, task) = async_task::spawn(
22171      async move {
22172        runnable_tx.send(()).unwrap();
22173      },
22174      move |r| scheduler.schedule(r),
22175    );
22176    task.detach();
22177    executor.schedule(runnable);
22178    runnable_rx
22179      .recv_timeout(Duration::from_secs(1))
22180      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
22181
22182    release_tx.send(()).unwrap();
22183    futures::executor::block_on(first).unwrap();
22184    futures::executor::block_on(second).unwrap();
22185    queued_rx
22186      .recv_timeout(Duration::from_secs(1))
22187      .expect("queued blocking work must run after the occupied slot is released");
22188  }
22189
22190  #[cfg(napi_runtime_os_threads)]
22191  #[test]
22192  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
22193    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
22194    // schedule same-executor work, which lands in THIS worker's LIFO slot --
22195    // no FIFO push, no wake. The Ready exit of
22196    // `cooperative_block_on` must flush the slot: the caller may wait on that
22197    // child synchronously (and the enclosing drain frame can be arbitrarily
22198    // far away -- here it does not exist at all, via the fake on-pool
22199    // marker), so a stranded slot entry is an invisible, dead task.
22200    use std::sync::mpsc;
22201    use std::time::Duration;
22202
22203    struct SpawnOnReady {
22204      executor: Arc<MultiThreadExecutor>,
22205      child_tx: Option<mpsc::Sender<u64>>,
22206    }
22207    impl Future for SpawnOnReady {
22208      type Output = ();
22209      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22210        let child_tx = self.child_tx.take().expect("polled once");
22211        let sched = Arc::clone(&self.executor);
22212        let (runnable, task) = async_task::spawn(
22213          async move {
22214            child_tx.send(21).unwrap();
22215          },
22216          move |r| sched.schedule(r),
22217        );
22218        // On-pool + same executor + no ambient owner: lands in the slot.
22219        self.executor.schedule(runnable);
22220        task.detach();
22221        Poll::Ready(())
22222      }
22223    }
22224
22225    let (done_tx, done_rx) = mpsc::channel();
22226    let runner = std::thread::spawn(move || {
22227      let options = RuntimeOptions {
22228        flavor: RuntimeFlavor::MultiThread,
22229        worker_threads: 2,
22230        max_blocking_tasks: 2,
22231        thread_name_prefix: "exit-flush".to_string(),
22232        park_deadline: None,
22233        drain_linger: DEFAULT_DRAIN_LINGER,
22234      };
22235      let executor =
22236        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22237
22238      let (child_tx, child_rx) = mpsc::channel::<u64>();
22239      {
22240        // The test-only on-pool marker runs the cooperative branch on THIS
22241        // thread, with NO enclosing drain loop to bail the slot out.
22242        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
22243        let mut future = std::pin::pin!(SpawnOnReady {
22244          executor: Arc::clone(&executor),
22245          child_tx: Some(child_tx)
22246        });
22247        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
22248      }
22249
22250      // Synchronous bounded wait, NOT via the executor: only the exit flush
22251      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
22252      // complete the child.
22253      let value = child_rx
22254        .recv_timeout(Duration::from_secs(5))
22255        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
22256      assert_eq!(value, 21);
22257      let _ = done_tx.send(());
22258    });
22259
22260    match done_rx.recv_timeout(Duration::from_secs(10)) {
22261      Ok(()) => runner.join().unwrap(),
22262      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
22263    }
22264  }
22265
22266  /// Perpetual same-executor chain: each link schedules its successor from
22267  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
22268  /// raised. `hops` counts executed links.
22269  #[cfg(napi_runtime_os_threads)]
22270  fn spawn_hot_chain(
22271    executor: &Arc<MultiThreadExecutor>,
22272    stop: Arc<AtomicBool>,
22273    hops: Arc<AtomicU64>,
22274  ) {
22275    if stop.load(Ordering::SeqCst) {
22276      return;
22277    }
22278    let link_exec = Arc::clone(executor);
22279    let body = async move {
22280      hops.fetch_add(1, Ordering::SeqCst);
22281      spawn_hot_chain(&link_exec, stop, hops);
22282    };
22283    let sched = Arc::clone(executor);
22284    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
22285    executor.schedule(runnable);
22286    task.detach();
22287  }
22288
22289  #[cfg(napi_runtime_os_threads)]
22290  #[test]
22291  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
22292    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
22293    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
22294    // the FIFO, so a hot chain (every link schedules its successor into the
22295    // slot) keeps run_one succeeding forever and the FIFO is never reached
22296    // from this thread. On the direct 1-worker test topology a FIFO task that
22297    // completes the awaited future then never runs: deadlock. The budgeted
22298    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
22299    // gets its turn.
22300    use std::sync::mpsc;
22301    use std::time::Duration;
22302
22303    let (done_tx, done_rx) = mpsc::channel();
22304    let runner = std::thread::spawn(move || {
22305      let options = RuntimeOptions {
22306        flavor: RuntimeFlavor::MultiThread,
22307        worker_threads: 1,
22308        // This locality test intentionally bypasses RuntimeOptions::validate
22309        // to isolate one physical worker without admitting blocking work.
22310        max_blocking_tasks: 0,
22311        thread_name_prefix: "coop-budget".to_string(),
22312        park_deadline: None,
22313        drain_linger: DEFAULT_DRAIN_LINGER,
22314      };
22315      let executor =
22316        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22317
22318      let stop = Arc::new(AtomicBool::new(false));
22319      let hops = Arc::new(AtomicU64::new(0));
22320
22321      // M runs on the sole worker: it queues F (the task that completes the
22322      // awaited future) on the FIFO, heats the slot with the chain, then
22323      // block_on's the value F produces.
22324      let m_exec = Arc::clone(&executor);
22325      let m_stop = Arc::clone(&stop);
22326      let m_hops = Arc::clone(&hops);
22327      let m_body = async move {
22328        let (tx, rx) = oneshot::channel::<u64>();
22329        // Schedule F first: it takes the slot momentarily...
22330        let sched_f = Arc::clone(&m_exec);
22331        let (f_runnable, f_task) = async_task::spawn(
22332          async move {
22333            let _ = tx.send(7);
22334          },
22335          move |r| sched_f.schedule(r),
22336        );
22337        m_exec.schedule(f_runnable);
22338        f_task.detach();
22339        // ...then the chain starter displaces it to the FIFO and keeps the
22340        // slot hot from here on.
22341        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
22342
22343        let mut inner = std::pin::pin!(async move {
22344          assert_eq!(
22345            rx.await.unwrap(),
22346            7,
22347            "F must run and complete the awaited future"
22348          );
22349        });
22350        m_exec.block_on(inner.as_mut());
22351        m_stop.store(true, Ordering::SeqCst);
22352      };
22353      let sched_m = Arc::clone(&executor);
22354      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
22355      executor.schedule(m_runnable);
22356
22357      futures::executor::block_on(m_task);
22358      assert!(
22359        hops.load(Ordering::SeqCst) >= 64,
22360        "the chain must have been genuinely hot (>= one budget window) before F ran"
22361      );
22362      let _ = done_tx.send(());
22363    });
22364
22365    match done_rx.recv_timeout(Duration::from_secs(10)) {
22366      Ok(()) => runner.join().unwrap(),
22367      Err(error) => panic!(
22368        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
22369      ),
22370    }
22371  }
22372
22373  #[cfg(napi_runtime_os_threads)]
22374  #[test]
22375  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
22376    use std::sync::mpsc;
22377
22378    struct RefillLifo {
22379      executor: Arc<MultiThreadExecutor>,
22380      fifo_ran: Arc<AtomicBool>,
22381    }
22382
22383    impl Future for RefillLifo {
22384      type Output = ();
22385
22386      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
22387        if self.fifo_ran.load(Ordering::SeqCst) {
22388          return Poll::Ready(());
22389        }
22390        let scheduler = Arc::clone(&self.executor);
22391        let (runnable, task) =
22392          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
22393        self.executor.schedule(runnable);
22394        task.detach();
22395        Poll::Pending
22396      }
22397    }
22398
22399    let (done_tx, done_rx) = mpsc::channel();
22400    let runner = std::thread::spawn(move || {
22401      let options = RuntimeOptions {
22402        flavor: RuntimeFlavor::MultiThread,
22403        worker_threads: 1,
22404        max_blocking_tasks: 0,
22405        thread_name_prefix: "forced-fifo".to_string(),
22406        park_deadline: None,
22407        drain_linger: DEFAULT_DRAIN_LINGER,
22408      };
22409      let executor =
22410        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22411      executor
22412        .active_drainers
22413        .store(executor.max_drainers, Ordering::SeqCst);
22414
22415      let fifo_ran = Arc::new(AtomicBool::new(false));
22416      let fifo_flag = Arc::clone(&fifo_ran);
22417      let scheduler = Arc::clone(&executor);
22418      let (fifo, task) = async_task::spawn(
22419        async move {
22420          fifo_flag.store(true, Ordering::SeqCst);
22421        },
22422        move |runnable| scheduler.schedule(runnable),
22423      );
22424      task.detach();
22425      executor.metrics.runnable_scheduled();
22426      executor.push_fifo_raw(fifo);
22427
22428      {
22429        let _driver = OnPoolWorkerGuard::enter(executor.id);
22430        let mut future = std::pin::pin!(RefillLifo {
22431          executor: Arc::clone(&executor),
22432          fifo_ran: Arc::clone(&fifo_ran)
22433        });
22434        executor.block_on(future.as_mut());
22435        while executor.run_one() {}
22436      }
22437      executor.active_drainers.store(0, Ordering::SeqCst);
22438      assert!(fifo_ran.load(Ordering::SeqCst));
22439      done_tx.send(()).unwrap();
22440    });
22441    done_rx
22442      .recv_timeout(Duration::from_secs(3))
22443      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
22444    runner.join().unwrap();
22445  }
22446
22447  #[cfg(napi_runtime_os_threads)]
22448  #[test]
22449  fn blocking_closure_spawn_is_not_stranded_in_slot() {
22450    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
22451    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
22452    // same-executor task and then waits for it synchronously would deadlock if
22453    // the child were slotted because the slot only drains after the closure
22454    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
22455    // push to the shared FIFO with a wake.
22456    use std::sync::mpsc;
22457    use std::time::Duration;
22458
22459    let (done_tx, done_rx) = mpsc::channel();
22460    let runner = std::thread::spawn(move || {
22461      let options = RuntimeOptions {
22462        flavor: RuntimeFlavor::MultiThread,
22463        worker_threads: 2,
22464        max_blocking_tasks: 2,
22465        thread_name_prefix: "blk-spawn-wait".to_string(),
22466        park_deadline: None,
22467        drain_linger: DEFAULT_DRAIN_LINGER,
22468      };
22469      let executor =
22470        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22471
22472      // Closure spawns a child and BLOCKS on the channel the child must fill.
22473      // A spare worker exists; only a real FIFO push + wake can reach it.
22474      let blk_exec = Arc::clone(&executor);
22475      let handle = executor.schedule_blocking(move || {
22476        let (tx, rx) = mpsc::channel::<u64>();
22477        let sched = Arc::clone(&blk_exec);
22478        let (runnable, task) = async_task::spawn(
22479          async move {
22480            tx.send(21).unwrap();
22481          },
22482          move |r| sched.schedule(r),
22483        );
22484        blk_exec.schedule(runnable);
22485        task.detach();
22486        rx.recv_timeout(Duration::from_secs(5))
22487          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
22488      });
22489      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
22490
22491      // Variant: the closure exits WITHOUT waiting; the child must still
22492      // complete promptly (no stranding on the closure-exit path either).
22493      let (v_tx, v_rx) = mpsc::channel::<u64>();
22494      let v_exec = Arc::clone(&executor);
22495      let v_handle = executor.schedule_blocking(move || {
22496        let sched = Arc::clone(&v_exec);
22497        let (runnable, task) = async_task::spawn(
22498          async move {
22499            v_tx.send(42).unwrap();
22500          },
22501          move |r| sched.schedule(r),
22502        );
22503        v_exec.schedule(runnable);
22504        task.detach();
22505        1u64
22506      });
22507      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
22508      assert_eq!(
22509        v_rx
22510          .recv_timeout(Duration::from_secs(5))
22511          .expect("fire-and-forget child must complete"),
22512        42
22513      );
22514      let _ = done_tx.send(());
22515    });
22516
22517    match done_rx.recv_timeout(Duration::from_secs(15)) {
22518      Ok(()) => runner.join().unwrap(),
22519      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
22520    }
22521  }
22522
22523  #[cfg(napi_runtime_os_threads)]
22524  #[test]
22525  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
22526    // Saturated nested-blocking invariant: a blocking closure re-enters
22527    // `block_on` and awaits another `spawn_blocking` while
22528    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
22529    // The driver must not park while still holding its blocking slot, because
22530    // the inner job would then have no capacity to run.
22531    use std::sync::{Barrier, mpsc};
22532    use std::time::Duration;
22533
22534    let (done_tx, done_rx) = mpsc::channel();
22535    let runner = std::thread::spawn(move || {
22536      let metrics = Arc::new(RuntimeMetrics::default());
22537      let options = RuntimeOptions {
22538        flavor: RuntimeFlavor::MultiThread,
22539        worker_threads: 2,
22540        max_blocking_tasks: 2,
22541        thread_name_prefix: "rd1-nested-blk".to_string(),
22542        park_deadline: None,
22543        drain_linger: DEFAULT_DRAIN_LINGER,
22544      };
22545      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
22546
22547      let outer_count = 2usize; // saturates max_blocking_tasks
22548      let barrier = Arc::new(Barrier::new(outer_count));
22549
22550      let mut handles = Vec::new();
22551      for _ in 0..outer_count {
22552        let exec = Arc::clone(&executor);
22553        let barrier = Arc::clone(&barrier);
22554        let handle = executor.schedule_blocking(move || {
22555          // Both outer blocking jobs hold a slot before either nests.
22556          barrier.wait();
22557          let inner_exec = Arc::clone(&exec);
22558          let mut value = 0usize;
22559          {
22560            let mut inner = std::pin::pin!(async {
22561              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
22562            });
22563            exec.block_on(inner.as_mut());
22564          }
22565          value
22566        });
22567        handles.push(handle);
22568      }
22569
22570      for handle in handles {
22571        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
22572      }
22573      let _ = done_tx.send(());
22574    });
22575
22576    match done_rx.recv_timeout(Duration::from_secs(10)) {
22577      Ok(()) => runner.join().unwrap(),
22578      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
22579    }
22580  }
22581
22582  #[cfg(napi_runtime_os_threads)]
22583  #[test]
22584  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
22585    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
22586    let owner = executor.fresh_owner_token();
22587    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22588    let job = BlockingJobId(u64::MAX - 5);
22589    let dependency = TaskDependency::default();
22590    dependency.set_owned(BlockingDependency { job, owner: None });
22591    let ran = Arc::new(AtomicBool::new(false));
22592    let job_ran = Arc::clone(&ran);
22593    executor
22594      .blocking_queue
22595      .lock()
22596      .unwrap()
22597      .push(QueuedBlocking {
22598        id: job,
22599        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
22600      });
22601
22602    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22603    assert!(
22604      !executor.try_owned_blocking_over_cap(&dependency),
22605      "an untagged worker must not infer ancestry from a sole active owner"
22606    );
22607    assert!(!ran.load(Ordering::SeqCst));
22608    assert!(
22609      executor
22610        .blocking_queue
22611        .lock()
22612        .unwrap()
22613        .jobs
22614        .contains_key(&job),
22615      "rejected owner inference must leave the exact job queued"
22616    );
22617  }
22618
22619  #[cfg(napi_runtime_os_threads)]
22620  #[test]
22621  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
22622    use std::sync::{Barrier, mpsc};
22623
22624    let options = RuntimeOptions {
22625      flavor: RuntimeFlavor::MultiThread,
22626      worker_threads: 2,
22627      max_blocking_tasks: 2,
22628      thread_name_prefix: "exact-owner-frame".to_string(),
22629      park_deadline: None,
22630      drain_linger: DEFAULT_DRAIN_LINGER,
22631    };
22632    let executor =
22633      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22634    let owners_saturated = Arc::new(Barrier::new(3));
22635    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
22636    let begin_lending = Arc::new(Barrier::new(3));
22637    let release_dependencies = Arc::new(Barrier::new(3));
22638    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
22639    let mut owners = Vec::new();
22640
22641    for owner_index in 0..2 {
22642      let owner_executor = Arc::clone(&executor);
22643      let owners_saturated = Arc::clone(&owners_saturated);
22644      let dependency_queued_tx = dependency_queued_tx.clone();
22645      let begin_lending = Arc::clone(&begin_lending);
22646      let release_dependencies = Arc::clone(&release_dependencies);
22647      let dependency_started_tx = dependency_started_tx.clone();
22648      owners.push(executor.schedule_blocking(move || {
22649        let owner_thread = std::thread::current().id();
22650        owners_saturated.wait();
22651        let child_started = dependency_started_tx.clone();
22652        let child_release = Arc::clone(&release_dependencies);
22653        let child = owner_executor.schedule_blocking(move || {
22654          assert_eq!(
22655            std::thread::current().id(),
22656            owner_thread,
22657            "an owner frame must execute only its own exact dependency on its lent lane"
22658          );
22659          child_started.send(owner_index).unwrap();
22660          child_release.wait();
22661          owner_index
22662        });
22663        dependency_queued_tx.send(()).unwrap();
22664        begin_lending.wait();
22665
22666        let mut result = None;
22667        {
22668          let mut future = std::pin::pin!(async {
22669            result = Some(child.await.unwrap());
22670          });
22671          assert_eq!(
22672            owner_executor.block_on(future.as_mut()),
22673            BlockOnOutcome::Completed
22674          );
22675        }
22676        result.unwrap()
22677      }));
22678    }
22679    drop(dependency_queued_tx);
22680    drop(dependency_started_tx);
22681    owners_saturated.wait();
22682
22683    for _ in 0..2 {
22684      dependency_queued_rx
22685        .recv_timeout(Duration::from_secs(2))
22686        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
22687    }
22688    let (unrelated_tx, unrelated_rx) = mpsc::channel();
22689    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
22690    begin_lending.wait();
22691
22692    let mut started = Vec::new();
22693    for _ in 0..2 {
22694      started.push(
22695        dependency_started_rx
22696          .recv_timeout(Duration::from_secs(2))
22697          .expect("each owner must service its own exact dependency"),
22698      );
22699    }
22700    started.sort_unstable();
22701    assert_eq!(started, vec![0, 1]);
22702    assert!(
22703      unrelated_rx.try_recv().is_err(),
22704      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22705    );
22706
22707    release_dependencies.wait();
22708    for (owner_index, owner) in owners.into_iter().enumerate() {
22709      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22710    }
22711    unrelated_rx
22712      .recv_timeout(Duration::from_secs(2))
22713      .expect("the unrelated job must run after an owner releases a counted lane");
22714    futures::executor::block_on(unrelated).unwrap();
22715  }
22716
22717  #[cfg(napi_runtime_os_threads)]
22718  #[test]
22719  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22720    use std::sync::{Mutex, mpsc};
22721
22722    const DEPENDENCY_COUNT: usize = 4;
22723
22724    let options = RuntimeOptions {
22725      flavor: RuntimeFlavor::MultiThread,
22726      worker_threads: 3,
22727      max_blocking_tasks: 1,
22728      thread_name_prefix: "parked-unrelated-worker".to_string(),
22729      park_deadline: None,
22730      drain_linger: DEFAULT_DRAIN_LINGER,
22731    };
22732    let executor =
22733      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22734    let (parked_tx, parked_rx) = mpsc::channel();
22735    let (release_parked_tx, release_parked_rx) = mpsc::channel();
22736    executor.pool.spawn(move || {
22737      parked_tx.send(()).unwrap();
22738      release_parked_rx.recv().unwrap();
22739    });
22740    parked_rx
22741      .recv_timeout(Duration::from_secs(2))
22742      .expect("the unrelated Rayon worker must remain occupied");
22743
22744    let owner_executor = Arc::clone(&executor);
22745    let owner = executor.schedule_blocking(move || {
22746      let owner = BLOCKING_OWNER
22747        .with(std::cell::Cell::get)
22748        .expect("the blocking closure must carry its exact owner frame");
22749      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22750      let results = Arc::new(Mutex::new(Vec::new()));
22751      let descendant_results = Arc::clone(&results);
22752      let descendant_executor = Arc::clone(&owner_executor);
22753      rayon::scope(move |scope| {
22754        scope.spawn(move |_| {
22755          assert_eq!(
22756            BLOCKING_OWNER.with(std::cell::Cell::get),
22757            None,
22758            "the parked owner must force its descendant onto the remaining worker"
22759          );
22760          let _owner = BlockingOwnerGuard::enter(Some(owner));
22761          descendant_started_tx.send(()).unwrap();
22762          for value in 0..DEPENDENCY_COUNT {
22763            let handle = descendant_executor.schedule_blocking(move || value);
22764            let mut result = None;
22765            {
22766              let mut future = std::pin::pin!(async {
22767                result = Some(handle.await.unwrap());
22768              });
22769              assert_eq!(
22770                descendant_executor.block_on(future.as_mut()),
22771                BlockOnOutcome::Completed
22772              );
22773            }
22774            descendant_results.lock().unwrap().push(result.unwrap());
22775          }
22776        });
22777        descendant_started_rx
22778          .recv_timeout(Duration::from_secs(2))
22779          .expect("the nested descendant must start on the only unoccupied worker");
22780      });
22781      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22782    });
22783
22784    assert_eq!(
22785      futures::executor::block_on(owner).unwrap(),
22786      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
22787    );
22788
22789    let (idle_tx, idle_rx) = mpsc::channel();
22790    let idle_executor = Arc::clone(&executor);
22791    let idle_waiter = std::thread::spawn(move || {
22792      idle_executor.begin_shutdown();
22793      idle_executor.wait_until_scheduler_idle();
22794      idle_tx.send(()).unwrap();
22795    });
22796    idle_rx
22797      .recv_timeout(Duration::from_secs(2))
22798      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
22799
22800    release_parked_tx.send(()).unwrap();
22801    idle_waiter.join().unwrap();
22802  }
22803
22804  #[cfg(napi_runtime_os_threads)]
22805  #[test]
22806  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
22807    use std::sync::{Condvar, Mutex, mpsc};
22808
22809    let options = RuntimeOptions {
22810      flavor: RuntimeFlavor::MultiThread,
22811      worker_threads: 3,
22812      max_blocking_tasks: 1,
22813      thread_name_prefix: "same-owner-rearm".to_string(),
22814      park_deadline: None,
22815      drain_linger: DEFAULT_DRAIN_LINGER,
22816    };
22817    let executor =
22818      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22819    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
22820    let (job_started_tx, job_started_rx) = mpsc::channel();
22821    let owner_executor = Arc::clone(&executor);
22822    let owner_releases = Arc::clone(&releases);
22823    let owner = executor.schedule_blocking(move || {
22824      let owner = BLOCKING_OWNER
22825        .with(std::cell::Cell::get)
22826        .expect("the blocking closure must carry its exact owner frame");
22827      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
22828      let results = Arc::new(Mutex::new(Vec::new()));
22829      let scope_results = Arc::clone(&results);
22830      rayon::scope(move |scope| {
22831        for index in 0..2 {
22832          let descendant_ready_tx = descendant_ready_tx.clone();
22833          let descendant_executor = Arc::clone(&owner_executor);
22834          let descendant_results = Arc::clone(&scope_results);
22835          let job_started_tx = job_started_tx.clone();
22836          let releases = Arc::clone(&owner_releases);
22837          scope.spawn(move |_| {
22838            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22839            let _owner = BlockingOwnerGuard::enter(Some(owner));
22840            descendant_ready_tx.send(()).unwrap();
22841            let handle = descendant_executor.schedule_blocking(move || {
22842              job_started_tx.send(index).unwrap();
22843              let (released, changed) = &*releases;
22844              let mut state = released.lock().unwrap();
22845              while !state[index] {
22846                state = changed.wait(state).unwrap();
22847              }
22848              index
22849            });
22850            let mut result = None;
22851            {
22852              let mut future = std::pin::pin!(async {
22853                result = Some(handle.await.unwrap());
22854              });
22855              assert_eq!(
22856                descendant_executor.block_on(future.as_mut()),
22857                BlockOnOutcome::Completed
22858              );
22859            }
22860            descendant_results.lock().unwrap().push(result.unwrap());
22861          });
22862        }
22863        drop(descendant_ready_tx);
22864        for _ in 0..2 {
22865          descendant_ready_rx
22866            .recv_timeout(Duration::from_secs(2))
22867            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
22868        }
22869      });
22870      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22871    });
22872
22873    let first = job_started_rx
22874      .recv_timeout(Duration::from_secs(2))
22875      .expect("one exact dependency must borrow the owner lane");
22876    assert!(
22877      job_started_rx
22878        .recv_timeout(Duration::from_millis(100))
22879        .is_err(),
22880      "one owner frame must not service two dependencies concurrently"
22881    );
22882    let unrelated_parker = Arc::new(DriverParker::default());
22883    executor
22884      .parked_drivers
22885      .register_with_role(&unrelated_parker, true, None);
22886    {
22887      let (released, changed) = &*releases;
22888      released.lock().unwrap()[first] = true;
22889      changed.notify_all();
22890    }
22891
22892    let second = job_started_rx
22893      .recv_timeout(Duration::from_secs(2))
22894      .expect("completion must rearm one parked same-owner dependency");
22895    assert_ne!(second, first);
22896    assert!(
22897      !unrelated_parker.consume_permit(),
22898      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
22899    );
22900    executor.parked_drivers.deregister(&unrelated_parker);
22901    {
22902      let (released, changed) = &*releases;
22903      released.lock().unwrap()[second] = true;
22904      changed.notify_all();
22905    }
22906
22907    let mut results = futures::executor::block_on(owner).unwrap();
22908    results.sort_unstable();
22909    assert_eq!(results, vec![0, 1]);
22910  }
22911
22912  #[cfg(napi_runtime_os_threads)]
22913  #[test]
22914  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
22915    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
22916    let owner = executor.fresh_owner_token();
22917    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22918    let failed_dependency = Arc::new(TaskDependency::default());
22919    failed_dependency.set_owned(BlockingDependency {
22920      job: BlockingJobId(u64::MAX - 21),
22921      owner: Some(owner),
22922    });
22923    let parked_dependency = Arc::new(TaskDependency::default());
22924    parked_dependency.set_owned(BlockingDependency {
22925      job: BlockingJobId(u64::MAX - 22),
22926      owner: Some(owner),
22927    });
22928    let parker = Arc::new(DriverParker::default());
22929
22930    let queue = executor.blocking_queue.lock().unwrap();
22931    let claimant_executor = Arc::clone(&executor);
22932    let claimant_dependency = Arc::clone(&failed_dependency);
22933    let claimant = std::thread::spawn(move || {
22934      let _owner = BlockingOwnerGuard::enter(Some(owner));
22935      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
22936    });
22937    wait_until("the failed claimant to reserve the owner lane", || {
22938      !executor.active_blocking_owners.is_available(owner)
22939    });
22940    executor
22941      .parked_drivers
22942      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
22943    failed_dependency.clear();
22944    drop(queue);
22945
22946    assert!(
22947      !claimant.join().unwrap(),
22948      "the cancelled dependency must not execute"
22949    );
22950    assert!(
22951      parker.consume_permit(),
22952      "releasing a failed exact claim must hand the owner lane to another parked dependency"
22953    );
22954  }
22955
22956  #[cfg(napi_runtime_os_threads)]
22957  #[test]
22958  fn exact_owner_lending_does_not_start_blocking_after_stop() {
22959    use std::sync::mpsc;
22960
22961    let options = RuntimeOptions {
22962      flavor: RuntimeFlavor::MultiThread,
22963      worker_threads: 2,
22964      max_blocking_tasks: 1,
22965      thread_name_prefix: "stopped-exact-owner".to_string(),
22966      park_deadline: None,
22967      drain_linger: DEFAULT_DRAIN_LINGER,
22968    };
22969    let executor =
22970      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22971    let ran = Arc::new(AtomicBool::new(false));
22972    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
22973    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22974    let owner_executor = Arc::clone(&executor);
22975    let child_ran = Arc::clone(&ran);
22976    let owner = executor.schedule_blocking(move || {
22977      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
22978        *slot.borrow_mut() = Some(Box::new(move || {
22979          claim_entered_tx.send(()).unwrap();
22980          release_claim_rx.recv().unwrap();
22981        }));
22982      });
22983      let child = owner_executor.schedule_blocking(move || {
22984        child_ran.store(true, Ordering::SeqCst);
22985      });
22986      let mut future = std::pin::pin!(async move {
22987        let _ = child.await;
22988      });
22989      owner_executor.block_on(future.as_mut())
22990    });
22991
22992    claim_entered_rx
22993      .recv_timeout(Duration::from_secs(2))
22994      .expect("the cooperative driver must pause before the exact-owner queue claim");
22995    assert_eq!(
22996      executor.blocking_queue.lock().unwrap().jobs.len(),
22997      1,
22998      "the exact dependency must remain queued before stop publication"
22999    );
23000
23001    // Pin the reported race: the cooperative driver already passed its loop's
23002    // stop check, then shutdown publishes stop before the exact queue claim.
23003    executor.stop.begin_shutdown();
23004    release_claim_tx.send(()).unwrap();
23005
23006    assert_eq!(
23007      futures::executor::block_on(owner).unwrap(),
23008      BlockOnOutcome::Stopped,
23009      "the cooperative driver must stop instead of executing its queued dependency"
23010    );
23011    assert!(
23012      !ran.load(Ordering::SeqCst),
23013      "exact-owner lending started blocking work after stop"
23014    );
23015    assert_eq!(
23016      executor.blocking_queue.lock().unwrap().jobs.len(),
23017      1,
23018      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
23019    );
23020
23021    executor.begin_shutdown();
23022    executor.wait_until_scheduler_idle();
23023    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23024    assert!(!ran.load(Ordering::SeqCst));
23025  }
23026
23027  #[cfg(napi_runtime_os_threads)]
23028  #[test]
23029  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
23030    const DEPENDENCY_COUNT: usize = 1024;
23031
23032    let options = RuntimeOptions {
23033      flavor: RuntimeFlavor::MultiThread,
23034      worker_threads: 2,
23035      max_blocking_tasks: 1,
23036      thread_name_prefix: "linear-owner-lending".to_string(),
23037      park_deadline: None,
23038      drain_linger: DEFAULT_DRAIN_LINGER,
23039    };
23040    let executor =
23041      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23042    let completed = Arc::new(AtomicUsize::new(0));
23043
23044    for index in 0..DEPENDENCY_COUNT {
23045      let owner = executor.fresh_owner_token();
23046      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23047      let dependency = TaskDependency::new(u64::MAX, Some(owner));
23048      let blocking = BlockingDependency {
23049        job: BlockingJobId(u64::MAX - index as u64),
23050        owner: Some(owner),
23051      };
23052      dependency.set_owned(blocking);
23053      let completed = Arc::clone(&completed);
23054      executor
23055        .blocking_queue
23056        .lock()
23057        .unwrap()
23058        .push(QueuedBlocking {
23059          id: blocking.job,
23060          run: Box::new(move || {
23061            completed.fetch_add(1, Ordering::SeqCst);
23062          }),
23063        });
23064
23065      let _owner = BlockingOwnerGuard::enter(Some(owner));
23066      assert!(executor.try_owned_blocking_over_cap(&dependency));
23067    }
23068
23069    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
23070    assert_eq!(
23071      executor.owner_lending_attempts.load(Ordering::Relaxed),
23072      DEPENDENCY_COUNT,
23073      "targeted lending must perform one O(1) claim attempt per exact dependency"
23074    );
23075    let queue = executor.blocking_queue.lock().unwrap();
23076    assert!(queue.is_empty());
23077    assert_eq!(queue.head, None);
23078    assert_eq!(queue.tail, None);
23079  }
23080
23081  #[cfg(napi_runtime_os_threads)]
23082  #[test]
23083  fn one_dependency_interspersed_owner_publications_stays_linear() {
23084    const PUBLICATION_COUNT: usize = 1024;
23085
23086    let executor = multi_thread_executor(2, None, "linear-publication-lending");
23087    let owner = executor.fresh_owner_token();
23088    let wrong_owner = executor.fresh_owner_token();
23089    let dependency = TaskDependency::new(u64::MAX, Some(owner));
23090    let completed = Arc::new(AtomicUsize::new(0));
23091    for index in 0..PUBLICATION_COUNT {
23092      let cancelled_source = TaskDependency::default();
23093      cancelled_source.set_owned(BlockingDependency {
23094        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
23095        owner: Some(owner),
23096      });
23097      dependency.set_borrowed(
23098        &cancelled_source
23099          .get()
23100          .expect("the source publication must initially be live"),
23101      );
23102      cancelled_source.clear();
23103
23104      dependency.set_owned(BlockingDependency {
23105        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
23106        owner: Some(wrong_owner),
23107      });
23108      let blocking = BlockingDependency {
23109        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
23110        owner: Some(owner),
23111      };
23112      dependency.set_owned(blocking);
23113      let completed = Arc::clone(&completed);
23114      executor
23115        .blocking_queue
23116        .lock()
23117        .unwrap()
23118        .push(QueuedBlocking {
23119          id: blocking.job,
23120          run: Box::new(move || {
23121            completed.fetch_add(1, Ordering::SeqCst);
23122          }),
23123        });
23124    }
23125    dependency.reset_entry_visits();
23126
23127    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
23128    let _owner = BlockingOwnerGuard::enter(Some(owner));
23129    for _ in 0..PUBLICATION_COUNT {
23130      assert!(
23131        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23132        "the owner-handoff predicate must find the next exact publication"
23133      );
23134      assert!(
23135        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23136        "the park-availability predicate must find the next serviceable publication"
23137      );
23138      assert!(executor.try_owned_blocking_over_cap(&dependency));
23139    }
23140
23141    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
23142    assert!(
23143      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23144      "wrong-owner publications must not satisfy an exact handoff"
23145    );
23146    assert!(
23147      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23148      "wrong-owner publications must not make the owner's lane appear serviceable"
23149    );
23150    assert!(
23151      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
23152      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
23153      dependency.entry_visits()
23154    );
23155    assert!(executor.blocking_queue.lock().unwrap().is_empty());
23156  }
23157
23158  #[cfg(napi_runtime_os_threads)]
23159  #[test]
23160  fn binding_unowned_dependency_updates_exact_owner_predicate() {
23161    let owner = BlockingOwnerToken {
23162      executor_id: u64::MAX - 200,
23163      frame: u64::MAX - 201,
23164    };
23165    let dependency = TaskDependency::default();
23166    let blocking = BlockingDependency {
23167      job: BlockingJobId(u64::MAX - 202),
23168      owner: None,
23169    };
23170    dependency.set_owned(blocking);
23171
23172    assert!(
23173      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23174      "an unowned publication must not satisfy an exact owner handoff"
23175    );
23176    assert!(
23177      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
23178      "an unowned publication is available to the ambient owner"
23179    );
23180
23181    let selected = dependency
23182      .select_for_owner(owner)
23183      .expect("the owner must bind the unowned publication");
23184    assert_eq!(selected.publication.dependency.owner, Some(owner));
23185    assert!(
23186      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
23187      "binding must immediately publish the entry to exact-owner predicates"
23188    );
23189    assert!(dependency.try_claim(&selected));
23190    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23191  }
23192
23193  #[cfg(napi_runtime_os_threads)]
23194  #[test]
23195  fn owner_selection_preserves_unowned_and_exact_publication_order() {
23196    let owner = BlockingOwnerToken {
23197      executor_id: u64::MAX - 210,
23198      frame: u64::MAX - 211,
23199    };
23200    let dependency = TaskDependency::default();
23201    let first = BlockingDependency {
23202      job: BlockingJobId(u64::MAX - 212),
23203      owner: None,
23204    };
23205    let second = BlockingDependency {
23206      job: BlockingJobId(u64::MAX - 213),
23207      owner: Some(owner),
23208    };
23209    dependency.set_owned(first);
23210    dependency.set_owned(second);
23211
23212    let selected = dependency
23213      .select_for_owner(owner)
23214      .expect("the first publication must be found");
23215    assert_eq!(
23216      selected.publication.dependency,
23217      BlockingDependency {
23218        owner: Some(owner),
23219        ..first
23220      }
23221    );
23222    assert!(dependency.try_claim(&selected));
23223
23224    let selected = dependency
23225      .select_for_owner(owner)
23226      .expect("the second publication must remain eligible");
23227    assert_eq!(selected.publication.dependency, second);
23228  }
23229
23230  #[cfg(napi_runtime_os_threads)]
23231  #[test]
23232  fn blocking_queue_exact_removal_reclaims_fifo_links() {
23233    const JOB_COUNT: u64 = 4096;
23234
23235    let mut queue = BlockingQueue {
23236      closed: false,
23237      head: None,
23238      tail: None,
23239      jobs: FxHashMap::default(),
23240    };
23241    for id in 1..=JOB_COUNT {
23242      queue.push(QueuedBlocking {
23243        id: BlockingJobId(id),
23244        run: Box::new(|| {}),
23245      });
23246    }
23247    for id in (2..=JOB_COUNT).step_by(2) {
23248      drop(
23249        queue
23250          .remove(BlockingJobId(id))
23251          .expect("even job must remain indexed"),
23252      );
23253    }
23254    for id in (1..=JOB_COUNT).step_by(2) {
23255      drop(
23256        queue
23257          .remove(BlockingJobId(id))
23258          .expect("odd job must remain indexed"),
23259      );
23260    }
23261    assert!(queue.jobs.is_empty());
23262    assert_eq!(queue.head, None);
23263    assert_eq!(queue.tail, None);
23264
23265    let order = Arc::new(Mutex::new(Vec::new()));
23266    for id in 1..=3 {
23267      let order = Arc::clone(&order);
23268      queue.push(QueuedBlocking {
23269        id: BlockingJobId(id),
23270        run: Box::new(move || order.lock().unwrap().push(id)),
23271      });
23272    }
23273    drop(
23274      queue
23275        .remove(BlockingJobId(2))
23276        .expect("middle job must be removable"),
23277    );
23278    queue.pop_front().expect("FIFO head must remain linked")();
23279    queue.pop_front().expect("FIFO tail must remain linked")();
23280    assert!(queue.pop_front().is_none());
23281    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
23282    assert_eq!(queue.head, None);
23283    assert_eq!(queue.tail, None);
23284  }
23285
23286  #[cfg(not(target_family = "wasm"))]
23287  #[test]
23288  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
23289    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
23290
23291    if std::env::var_os(CHILD_ENV).is_some() {
23292      configure(RuntimeOptions {
23293        flavor: RuntimeFlavor::MultiThread,
23294        worker_threads: 2,
23295        max_blocking_tasks: 1,
23296        thread_name_prefix: "dependency-lending-metrics".to_string(),
23297        park_deadline: None,
23298        drain_linger: DEFAULT_DRAIN_LINGER,
23299      })
23300      .unwrap();
23301
23302      let outer = spawn_blocking(|| {
23303        let inner = spawn_blocking(|| 42usize);
23304        block_on(inner).unwrap()
23305      });
23306      assert_eq!(block_on(outer).unwrap(), 42);
23307
23308      // Result delivery wakes the joiner from inside the blocking closure,
23309      // immediately before the scheduler-owned metrics guard retires. Use
23310      // shutdown quiescence before sampling completed/live counters so this
23311      // test does not race that final bookkeeping.
23312      shutdown().unwrap();
23313      let snapshot = metrics();
23314      assert_eq!(snapshot.blocking_tasks_started, 2);
23315      assert_eq!(snapshot.blocking_tasks_completed, 2);
23316      assert_eq!(snapshot.active_blocking_tasks, 0);
23317      assert_eq!(
23318        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
23319        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
23320      );
23321      return;
23322    }
23323
23324    let output = std::process::Command::new(std::env::current_exe().unwrap())
23325      .arg("--exact")
23326      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
23327      .arg("--nocapture")
23328      .env(CHILD_ENV, "1")
23329      .output()
23330      .expect("the dependency-lending metrics subprocess must start");
23331    assert!(
23332      output.status.success(),
23333      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
23334      output.status.code(),
23335      String::from_utf8_lossy(&output.stdout),
23336      String::from_utf8_lossy(&output.stderr)
23337    );
23338  }
23339
23340  #[cfg(napi_runtime_os_threads)]
23341  #[test]
23342  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
23343    use std::sync::mpsc;
23344
23345    let (done_tx, done_rx) = mpsc::channel();
23346    let runner = std::thread::spawn(move || {
23347      let options = RuntimeOptions {
23348        flavor: RuntimeFlavor::MultiThread,
23349        worker_threads: 2,
23350        max_blocking_tasks: 1,
23351        thread_name_prefix: "exact-blocking-job".to_string(),
23352        park_deadline: None,
23353        drain_linger: DEFAULT_DRAIN_LINGER,
23354      };
23355      let executor =
23356        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
23357      let outer_exec = Arc::clone(&executor);
23358      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
23359      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
23360      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
23361      let outer = executor.schedule_blocking(move || {
23362        let sibling = outer_exec.schedule_blocking(move || {
23363          sibling_started_tx.send(()).unwrap();
23364          release_sibling_rx.recv().unwrap();
23365        });
23366        let awaited = outer_exec.schedule_blocking(move || {
23367          awaited_ran_tx.send(()).unwrap();
23368          17usize
23369        });
23370        let mut value = None;
23371        {
23372          let mut future = std::pin::pin!(async {
23373            value = Some(awaited.await.unwrap());
23374          });
23375          outer_exec.block_on(future.as_mut());
23376        }
23377        (sibling, value.unwrap())
23378      });
23379
23380      awaited_ran_rx
23381        .recv_timeout(Duration::from_secs(2))
23382        .expect("the exact awaited job must bypass the earlier detached sibling");
23383      let (sibling, value) = futures::executor::block_on(outer).unwrap();
23384      assert_eq!(value, 17);
23385      sibling_started_rx
23386        .recv_timeout(Duration::from_secs(2))
23387        .expect("the sibling must run after the owner releases its counted slot");
23388      release_sibling_tx.send(()).unwrap();
23389      futures::executor::block_on(sibling).unwrap();
23390      done_tx.send(()).unwrap();
23391    });
23392    done_rx
23393      .recv_timeout(Duration::from_secs(5))
23394      .expect("same-owner sibling selection deadlocked the exact awaited job");
23395    runner.join().unwrap();
23396  }
23397
23398  #[cfg(napi_runtime_os_threads)]
23399  #[test]
23400  fn blocking_dependency_propagates_through_async_join_lineage() {
23401    use std::sync::mpsc;
23402
23403    let (done_tx, done_rx) = mpsc::channel();
23404    let runner = std::thread::spawn(move || {
23405      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
23406      let owner_controller = Arc::clone(&controller);
23407      let (child_tx, child_rx) = mpsc::channel();
23408      let outer = controller
23409        .try_spawn_blocking(move || {
23410          let async_controller = Arc::clone(&owner_controller);
23411          let async_child = owner_controller
23412            .try_spawn(async move {
23413              async_controller
23414                .try_spawn_blocking(move || {
23415                  child_tx.send(()).unwrap();
23416                  23usize
23417                })
23418                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
23419                .await
23420                .unwrap()
23421            })
23422            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
23423          let mut value = None;
23424          {
23425            let mut future = std::pin::pin!(async {
23426              value = Some(async_child.await.unwrap());
23427            });
23428            owner_controller.block_on(future.as_mut());
23429          }
23430          value.unwrap()
23431        })
23432        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23433
23434      child_rx
23435        .recv_timeout(Duration::from_secs(2))
23436        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
23437      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
23438      controller.shutdown().unwrap();
23439      done_tx.send(()).unwrap();
23440    });
23441    done_rx
23442      .recv_timeout(Duration::from_secs(5))
23443      .expect("blocking dependency lineage deadlocked");
23444    runner.join().unwrap();
23445  }
23446
23447  #[cfg(napi_runtime_os_threads)]
23448  #[test]
23449  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
23450    use std::sync::mpsc;
23451
23452    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
23453    let outer_controller = Arc::clone(&controller);
23454    let (child_queued_tx, child_queued_rx) = mpsc::channel();
23455    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
23456    let (release_outer_tx, release_outer_rx) = oneshot::channel();
23457    let outer = controller
23458      .try_spawn_blocking(move || {
23459        let child_controller = Arc::clone(&outer_controller);
23460        let child = outer_controller
23461          .try_spawn(async move {
23462            let blocking = child_controller
23463              .try_spawn_blocking(move || {
23464                unrelated_ran_tx.send(()).unwrap();
23465                31usize
23466              })
23467              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
23468            child_queued_tx.send(()).unwrap();
23469            blocking.await.unwrap()
23470          })
23471          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
23472        let mut released = false;
23473        {
23474          let mut future = std::pin::pin!(async {
23475            release_outer_rx.await.unwrap();
23476            released = true;
23477          });
23478          outer_controller.block_on(future.as_mut());
23479        }
23480        assert!(released);
23481        child
23482      })
23483      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
23484
23485    child_queued_rx
23486      .recv_timeout(Duration::from_secs(2))
23487      .expect("the cooperative driver must service the unrelated async runnable");
23488    assert!(
23489      unrelated_ran_rx
23490        .recv_timeout(Duration::from_millis(200))
23491        .is_err(),
23492      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
23493    );
23494
23495    release_outer_tx.send(()).unwrap();
23496    let child = futures::executor::block_on(outer).unwrap();
23497    unrelated_ran_rx
23498      .recv_timeout(Duration::from_secs(2))
23499      .expect("the unrelated blocking job must run after the owner releases its counted slot");
23500    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
23501    controller.shutdown().unwrap();
23502  }
23503
23504  #[cfg(napi_runtime_os_threads)]
23505  #[test]
23506  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
23507    let parent = Arc::new(TaskDependency::default());
23508    let _context = TaskDependencyGuard::enter(&parent);
23509    let dependency = BlockingDependency {
23510      job: BlockingJobId(u64::MAX),
23511      owner: None,
23512    };
23513    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23514    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23515      receiver,
23516      dependency,
23517      published: PublishedBlockingDependencies::default(),
23518    });
23519    let waker = futures::task::noop_waker();
23520    let mut cx = Context::from_waker(&waker);
23521
23522    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23523    assert_eq!(parent.get_dependency(), Some(dependency));
23524    drop(handle);
23525    assert_eq!(
23526      parent.get_dependency(),
23527      None,
23528      "an abandoned JoinHandle must clear stale dependency lineage"
23529    );
23530    drop(sender);
23531  }
23532
23533  #[cfg(napi_runtime_os_threads)]
23534  #[test]
23535  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
23536    let owner = BlockingOwnerToken {
23537      executor_id: u64::MAX - 40,
23538      frame: u64::MAX - 41,
23539    };
23540    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23541    let _context = TaskDependencyGuard::enter(&parent);
23542    let dependency = BlockingDependency {
23543      job: BlockingJobId(u64::MAX - 42),
23544      owner: None,
23545    };
23546    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23547    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23548      receiver,
23549      dependency,
23550      published: PublishedBlockingDependencies::default(),
23551    });
23552    let waker = futures::task::noop_waker();
23553    let mut cx = Context::from_waker(&waker);
23554
23555    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23556    assert_eq!(
23557      parent.get_dependency(),
23558      Some(BlockingDependency {
23559        owner: Some(owner),
23560        ..dependency
23561      }),
23562      "the ambient task owner must enrich an untagged direct blocking dependency"
23563    );
23564    drop(handle);
23565    assert_eq!(
23566      parent.get_dependency(),
23567      None,
23568      "detach cleanup must match the stable job id after owner enrichment"
23569    );
23570    drop(sender);
23571  }
23572
23573  #[cfg(napi_runtime_os_threads)]
23574  #[test]
23575  fn one_poll_preserves_every_observed_blocking_dependency() {
23576    let parent = Arc::new(TaskDependency::default());
23577    let _context = TaskDependencyGuard::enter(&parent);
23578    let first = BlockingDependency {
23579      job: BlockingJobId(u64::MAX - 60),
23580      owner: None,
23581    };
23582    let second = BlockingDependency {
23583      job: BlockingJobId(u64::MAX - 61),
23584      owner: None,
23585    };
23586    let (first_sender, first_receiver) =
23587      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23588    let (second_sender, second_receiver) =
23589      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23590    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
23591      receiver: first_receiver,
23592      dependency: first,
23593      published: PublishedBlockingDependencies::default(),
23594    });
23595    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
23596      receiver: second_receiver,
23597      dependency: second,
23598      published: PublishedBlockingDependencies::default(),
23599    });
23600    let waker = futures::task::noop_waker();
23601    let mut cx = Context::from_waker(&waker);
23602
23603    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
23604    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
23605    assert_eq!(
23606      parent.get_dependencies(),
23607      vec![first, second],
23608      "publishing a later dependency in one user poll must not replace an earlier dependency"
23609    );
23610
23611    drop(first_handle);
23612    drop(second_handle);
23613    drop(first_sender);
23614    drop(second_sender);
23615  }
23616
23617  #[cfg(napi_runtime_os_threads)]
23618  #[test]
23619  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
23620    let owner = BlockingOwnerToken {
23621      executor_id: u64::MAX - 900,
23622      frame: u64::MAX - 901,
23623    };
23624    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23625    let _context = TaskDependencyGuard::enter(&parent);
23626    let waker = futures::task::noop_waker();
23627    let mut cx = Context::from_waker(&waker);
23628
23629    let j1 = BlockingDependency {
23630      job: BlockingJobId(u64::MAX - 910),
23631      owner: Some(owner),
23632    };
23633    let j2 = BlockingDependency {
23634      job: BlockingJobId(u64::MAX - 911),
23635      owner: Some(owner),
23636    };
23637
23638    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23639    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23640
23641    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23642      receiver: r1,
23643      dependency: j1,
23644      published: PublishedBlockingDependencies::default(),
23645    });
23646    // TWO Pending polls in one pass. Each publishes, so without coalescing the
23647    // handle leaves two live entries for J1 and can retire only the newer one.
23648    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23649    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23650    let entries_after_double_poll = parent
23651      .state
23652      .lock()
23653      .unwrap_or_else(std::sync::PoisonError::into_inner)
23654      .current
23655      .len();
23656    drop(h1);
23657
23658    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23659      receiver: r2,
23660      dependency: j2,
23661      published: PublishedBlockingDependencies::default(),
23662    });
23663    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23664
23665    assert_eq!(
23666      entries_after_double_poll, 1,
23667      "a repeat Pending poll must supersede the handle's own previous publication"
23668    );
23669    let selected = parent
23670      .select_for_owner(owner)
23671      .expect("a live dependency must be selectable");
23672    assert_eq!(
23673      selected.publication.dependency.job, j2.job,
23674      "a DETACHED job must not be selected for owner lending ahead of the live one"
23675    );
23676    drop((s1, s2));
23677  }
23678
23679  #[cfg(napi_runtime_os_threads)]
23680  #[test]
23681  fn handle_polled_under_a_nested_frame_retires_both_publications() {
23682    let owner = BlockingOwnerToken {
23683      executor_id: u64::MAX - 920,
23684      frame: u64::MAX - 921,
23685    };
23686    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23687    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23688    let waker = futures::task::noop_waker();
23689    let mut cx = Context::from_waker(&waker);
23690
23691    let j1 = BlockingDependency {
23692      job: BlockingJobId(u64::MAX - 930),
23693      owner: Some(owner),
23694    };
23695    let j2 = BlockingDependency {
23696      job: BlockingJobId(u64::MAX - 931),
23697      owner: Some(owner),
23698    };
23699    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23700    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23701    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23702      receiver: r1,
23703      dependency: j1,
23704      published: Default::default(),
23705    });
23706
23707    {
23708      let _outer_ctx = TaskDependencyGuard::enter(&outer);
23709      // A handle can publish into more than one frame: once here, and again
23710      // under a nested re-entrant frame. Detaching must retire BOTH, or the
23711      // forgotten entry keeps a detached job selectable for owner lending.
23712      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23713      {
23714        let _inner_ctx = TaskDependencyGuard::enter(&inner);
23715        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23716      }
23717      drop(h1);
23718      assert!(
23719        outer.get_dependencies().is_empty(),
23720        "detaching must retire the publication made in the outer frame too"
23721      );
23722      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23723        receiver: r2,
23724        dependency: j2,
23725        published: Default::default(),
23726      });
23727      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23728      let selected = outer.select_for_owner(owner).expect("selectable");
23729      assert_eq!(
23730        selected.publication.dependency.job, j2.job,
23731        "a detached job published in an outer frame must not outrank a live one"
23732      );
23733      drop(h2);
23734    }
23735    drop((s1, s2));
23736  }
23737
23738  #[cfg(napi_runtime_os_threads)]
23739  #[test]
23740  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23741    const FRAME_COUNT: usize = 64;
23742    let waker = futures::task::noop_waker();
23743    let mut cx = Context::from_waker(&waker);
23744    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23745    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23746      receiver,
23747      dependency: BlockingDependency {
23748        job: BlockingJobId(u64::MAX - 940),
23749        owner: None,
23750      },
23751      published: PublishedBlockingDependencies::default(),
23752    });
23753    let mut frames = Vec::new();
23754    for _ in 0..FRAME_COUNT {
23755      let frame = Arc::new(TaskDependency::default());
23756      frames.push(Arc::downgrade(&frame));
23757      let _context = TaskDependencyGuard::enter(&frame);
23758      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23759    }
23760    // A handle carried through a sequence of completed frames must not keep
23761    // any of them alive: it remembers each publication by `Weak`, and prunes
23762    // the expired tokens on its next publication, so the history stays at the
23763    // live frame count instead of growing with every frame ever seen.
23764    let retained = frames
23765      .iter()
23766      .filter(|frame| frame.strong_count() > 0)
23767      .count();
23768    assert_eq!(
23769      retained, 0,
23770      "a completed frame must not be kept alive by a pending handle's publication history"
23771    );
23772    drop(handle);
23773    drop(sender);
23774  }
23775
23776  #[cfg(napi_runtime_os_threads)]
23777  #[test]
23778  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23779    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
23780    // sequence, so H1 must keep the position its FIRST poll gave it: if
23781    // re-polling appended a replacement at the tail, H2 would be lent first,
23782    // and an H2 that waits on H1 would deadlock the owner's single lane.
23783    let owner = BlockingOwnerToken {
23784      executor_id: u64::MAX - 950,
23785      frame: u64::MAX - 951,
23786    };
23787    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23788    let _context = TaskDependencyGuard::enter(&parent);
23789    let waker = futures::task::noop_waker();
23790    let mut cx = Context::from_waker(&waker);
23791
23792    let j1 = BlockingDependency {
23793      job: BlockingJobId(u64::MAX - 960),
23794      owner: Some(owner),
23795    };
23796    let j2 = BlockingDependency {
23797      job: BlockingJobId(u64::MAX - 961),
23798      owner: Some(owner),
23799    };
23800    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23801    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23802    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23803      receiver: r1,
23804      dependency: j1,
23805      published: PublishedBlockingDependencies::default(),
23806    });
23807    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23808      receiver: r2,
23809      dependency: j2,
23810      published: PublishedBlockingDependencies::default(),
23811    });
23812
23813    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23814    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23815    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23816
23817    let selected = parent
23818      .select_for_owner(owner)
23819      .expect("a live dependency must be selectable");
23820    assert_eq!(
23821      selected.publication.dependency.job, j1.job,
23822      "re-polling a handle must not push its dependency behind one published later"
23823    );
23824    drop((h1, h2, s1, s2));
23825  }
23826
23827  #[cfg(napi_runtime_os_threads)]
23828  #[test]
23829  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
23830    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
23831    // `TaskDependency`) but each drains `current` at the start of its poll,
23832    // which is what a real task poll does. A drained frame no longer holds the
23833    // entry, so its token stops standing and is dropped on the next
23834    // publication: the history tracks frames that still hold an entry, not
23835    // every frame the handle has ever been polled under.
23836    const FRAME_COUNT: usize = 256;
23837    let waker = futures::task::noop_waker();
23838    let mut cx = Context::from_waker(&waker);
23839    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23840    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23841      receiver,
23842      dependency: BlockingDependency {
23843        job: BlockingJobId(u64::MAX - 970),
23844        owner: None,
23845      },
23846      published: PublishedBlockingDependencies::default(),
23847    });
23848    let mut retained = Vec::new();
23849    for _ in 0..FRAME_COUNT {
23850      let frame = Arc::new(TaskDependency::default());
23851      retained.push(Arc::clone(&frame));
23852      let _context = TaskDependencyGuard::enter(&frame);
23853      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23854      // The frame's next poll drains what the previous one published.
23855      frame.begin_poll();
23856    }
23857    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
23858      panic!("the handle must still be a blocking handle");
23859    };
23860    let remembered = usize::from(published.first.is_some()) + published.rest.len();
23861    assert!(
23862      remembered <= 2,
23863      "a drained frame must not stay in the publication history, remembered={remembered}"
23864    );
23865    drop(handle);
23866    drop(sender);
23867  }
23868
23869  #[cfg(napi_runtime_os_threads)]
23870  #[test]
23871  fn mass_detach_of_pending_blocking_handles_stays_linear() {
23872    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
23873    // poll pass -- so `begin_poll` never runs to drain `current` and all N
23874    // entries are simultaneously registered and still pending -- and are then
23875    // dropped together. Retiring a handle's own entry is O(1) and
23876    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
23877    //
23878    // This is the shape that made an earlier attempt at this optimisation
23879    // quadratic: it replaced the job index with a scan of `current` on every
23880    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
23881    // the dependency mutex.
23882    const HANDLE_COUNT: usize = 512;
23883
23884    let parent = Arc::new(TaskDependency::default());
23885    let _context = TaskDependencyGuard::enter(&parent);
23886    let waker = futures::task::noop_waker();
23887    let mut cx = Context::from_waker(&waker);
23888
23889    let mut senders = Vec::with_capacity(HANDLE_COUNT);
23890    let mut handles = Vec::with_capacity(HANDLE_COUNT);
23891    for index in 0..HANDLE_COUNT {
23892      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23893      senders.push(sender);
23894      handles.push(JoinHandle(JoinHandleInner::Blocking {
23895        receiver,
23896        dependency: BlockingDependency {
23897          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
23898          owner: None,
23899        },
23900        published: PublishedBlockingDependencies::default(),
23901      }));
23902    }
23903    for handle in &mut handles {
23904      assert!(Pin::new(handle).poll(&mut cx).is_pending());
23905    }
23906    assert_eq!(
23907      parent
23908        .state
23909        .lock()
23910        .unwrap_or_else(std::sync::PoisonError::into_inner)
23911        .current
23912        .len(),
23913      HANDLE_COUNT,
23914      "every handle must be registered and still pending before the detach"
23915    );
23916
23917    parent.reset_entry_visits();
23918    drop(handles);
23919
23920    assert!(
23921      parent.entry_visits() <= HANDLE_COUNT * 4,
23922      "mass-detach must visit O(n) entries across n handles, visits={}",
23923      parent.entry_visits()
23924    );
23925    assert_eq!(
23926      parent.get_dependency(),
23927      None,
23928      "every detached handle must have retired its own publication"
23929    );
23930    drop(senders);
23931  }
23932
23933  #[cfg(napi_runtime_os_threads)]
23934  #[test]
23935  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
23936    use std::sync::mpsc;
23937
23938    struct ReenterDependencyContext {
23939      dependency: Arc<TaskDependency>,
23940      woke: mpsc::Sender<()>,
23941    }
23942
23943    impl std::task::Wake for ReenterDependencyContext {
23944      fn wake(self: Arc<Self>) {
23945        let _context = TaskDependencyGuard::enter(&self.dependency);
23946        self.woke.send(()).unwrap();
23947      }
23948    }
23949
23950    let parent = Arc::new(TaskDependency::default());
23951    let nested = Arc::new(TaskDependency::default());
23952    let (woke_tx, woke_rx) = mpsc::channel();
23953    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
23954      dependency: Arc::clone(&nested),
23955      woke: woke_tx,
23956    }));
23957    parent.register_waiter(&waiter);
23958    let blocking = BlockingDependency {
23959      job: BlockingJobId(u64::MAX - 62),
23960      owner: None,
23961    };
23962
23963    let _context = TaskDependencyGuard::enter(&parent);
23964    let mut published = PublishedBlockingDependencies::default();
23965    record_blocking_dependency(&mut published, blocking);
23966    woke_rx
23967      .recv_timeout(Duration::from_secs(1))
23968      .expect("dependency notification must permit reentrant TLS context access");
23969
23970    parent.register_waiter(&waiter);
23971    clear_blocking_dependency(published);
23972    woke_rx
23973      .recv_timeout(Duration::from_secs(1))
23974      .expect("dependency clearing must permit reentrant TLS context access");
23975  }
23976
23977  #[cfg(napi_runtime_os_threads)]
23978  #[test]
23979  fn dependency_free_begin_poll_does_not_lock_state() {
23980    use std::sync::mpsc;
23981
23982    let dependency = Arc::new(TaskDependency::default());
23983    let state = dependency.state.lock().unwrap();
23984    let (completed_tx, completed_rx) = mpsc::channel();
23985    let polling_dependency = Arc::clone(&dependency);
23986    let polling = std::thread::spawn(move || {
23987      polling_dependency.begin_poll();
23988      completed_tx.send(()).unwrap();
23989    });
23990
23991    let completed_without_lock = completed_rx
23992      .recv_timeout(Duration::from_millis(200))
23993      .is_ok();
23994    drop(state);
23995    polling.join().unwrap();
23996    assert!(
23997      completed_without_lock,
23998      "a task with no published dependency must skip the dependency-state mutex"
23999    );
24000  }
24001
24002  #[cfg(not(target_family = "wasm"))]
24003  #[test]
24004  fn deep_dependency_liveness_chain_is_iterative() {
24005    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
24006
24007    if std::env::var_os(CHILD_ENV).is_some() {
24008      const DEPTH: usize = 100_000;
24009
24010      let mut links = Vec::with_capacity(DEPTH + 1);
24011      links.push(DependencyLink::root());
24012      for _ in 0..DEPTH {
24013        links.push(DependencyLink::derived(links.last().unwrap()));
24014      }
24015      assert!(links.last().unwrap().is_live());
24016      links[0].cancel();
24017      assert!(!links.last().unwrap().is_live());
24018      return;
24019    }
24020
24021    let output = std::process::Command::new(std::env::current_exe().unwrap())
24022      .arg("--exact")
24023      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
24024      .arg("--nocapture")
24025      .env(CHILD_ENV, "1")
24026      .output()
24027      .expect("the subprocess test must start");
24028    assert!(
24029      output.status.success(),
24030      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
24031      output.status.code(),
24032      String::from_utf8_lossy(&output.stdout),
24033      String::from_utf8_lossy(&output.stderr)
24034    );
24035  }
24036
24037  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24038  #[test]
24039  fn task_dependency_set_contains_panicking_waiter() {
24040    struct PanicWake;
24041    impl std::task::Wake for PanicWake {
24042      fn wake(self: Arc<Self>) {
24043        panic!("intentional TaskDependency::set waiter panic");
24044      }
24045    }
24046
24047    let dependency = TaskDependency::default();
24048    let blocking = BlockingDependency {
24049      job: BlockingJobId(u64::MAX),
24050      owner: None,
24051    };
24052    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24053
24054    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
24055    assert!(
24056      result.is_ok(),
24057      "set notification must not unwind its caller"
24058    );
24059    assert_eq!(
24060      dependency.get_dependency(),
24061      Some(blocking),
24062      "set must commit before notifying its waiter"
24063    );
24064  }
24065
24066  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24067  #[test]
24068  fn task_dependency_clear_contains_panicking_waiter() {
24069    struct PanicWake;
24070    impl std::task::Wake for PanicWake {
24071      fn wake(self: Arc<Self>) {
24072        panic!("intentional TaskDependency::clear waiter panic");
24073      }
24074    }
24075
24076    let dependency = TaskDependency::default();
24077    dependency.set_owned(BlockingDependency {
24078      job: BlockingJobId(u64::MAX),
24079      owner: None,
24080    });
24081    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24082
24083    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
24084    assert!(
24085      result.is_ok(),
24086      "clear notification must not unwind its caller"
24087    );
24088    assert_eq!(
24089      dependency.get_dependency(),
24090      None,
24091      "clear must commit before notifying its waiter"
24092    );
24093  }
24094
24095  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24096  #[test]
24097  fn task_dependency_clear_if_contains_panicking_waiter() {
24098    struct PanicWake;
24099    impl std::task::Wake for PanicWake {
24100      fn wake(self: Arc<Self>) {
24101        panic!("intentional TaskDependency::clear_if waiter panic");
24102      }
24103    }
24104
24105    let dependency = TaskDependency::default();
24106    let blocking = BlockingDependency {
24107      job: BlockingJobId(u64::MAX),
24108      owner: None,
24109    };
24110    let sequence = dependency.set_owned(blocking);
24111    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
24112
24113    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
24114    assert!(
24115      result.is_ok(),
24116      "retire notification must not unwind its caller"
24117    );
24118    assert_eq!(
24119      dependency.get_dependency(),
24120      None,
24121      "retire must commit before notifying its waiter"
24122    );
24123  }
24124
24125  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24126  #[test]
24127  fn task_dependency_waiter_replacement_contains_panicking_drop() {
24128    use std::sync::mpsc;
24129
24130    const GENERATION: u64 = u64::MAX - 20;
24131
24132    struct PanicOnDropWake {
24133      dropped: mpsc::Sender<Option<u64>>,
24134    }
24135
24136    #[expect(
24137      clippy::manual_noop_waker,
24138      reason = "the test observes the waker payload's Drop"
24139    )]
24140    impl std::task::Wake for PanicOnDropWake {
24141      fn wake(self: Arc<Self>) {}
24142    }
24143
24144    impl Drop for PanicOnDropWake {
24145      fn drop(&mut self) {
24146        self
24147          .dropped
24148          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24149          .unwrap();
24150        panic!("intentional replaced dependency waiter destructor panic");
24151      }
24152    }
24153
24154    let dependency = TaskDependency::new(GENERATION, None);
24155    let (dropped_tx, dropped_rx) = mpsc::channel();
24156    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24157      dropped: dropped_tx,
24158    }));
24159    dependency.register_waiter(&waiter);
24160    drop(waiter);
24161
24162    let replacement = futures::task::noop_waker();
24163    let result = catch_unwind(AssertUnwindSafe(|| {
24164      dependency.register_waiter(&replacement)
24165    }));
24166    assert!(
24167      result.is_ok(),
24168      "replacing a hostile waiter must not unwind registration"
24169    );
24170    assert_eq!(
24171      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24172      Some(GENERATION),
24173      "the replaced waiter must be destroyed under its runtime generation"
24174    );
24175  }
24176
24177  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
24178  #[test]
24179  fn task_dependency_retirement_contains_stored_waiter_drop() {
24180    use std::sync::mpsc;
24181
24182    const GENERATION: u64 = u64::MAX - 21;
24183
24184    struct PanicOnDropWake {
24185      dropped: mpsc::Sender<Option<u64>>,
24186    }
24187
24188    #[expect(
24189      clippy::manual_noop_waker,
24190      reason = "the test observes the waker payload's Drop"
24191    )]
24192    impl std::task::Wake for PanicOnDropWake {
24193      fn wake(self: Arc<Self>) {}
24194    }
24195
24196    impl Drop for PanicOnDropWake {
24197      fn drop(&mut self) {
24198        self
24199          .dropped
24200          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
24201          .unwrap();
24202        panic!("intentional stored dependency waiter destructor panic");
24203      }
24204    }
24205
24206    let dependency = TaskDependency::new(GENERATION, None);
24207    let (dropped_tx, dropped_rx) = mpsc::channel();
24208    let waiter = Waker::from(Arc::new(PanicOnDropWake {
24209      dropped: dropped_tx,
24210    }));
24211    dependency.register_waiter(&waiter);
24212    drop(waiter);
24213
24214    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
24215    assert!(
24216      result.is_ok(),
24217      "retiring a hostile stored waiter must not unwind"
24218    );
24219    assert_eq!(
24220      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
24221      Some(GENERATION),
24222      "the retained waiter must be destroyed under its runtime generation"
24223    );
24224  }
24225
24226  #[cfg(napi_runtime_os_threads)]
24227  #[test]
24228  fn detaching_task_clears_dependency_waiter_parent_retention() {
24229    struct RetainingWake {
24230      retained_parent: Arc<()>,
24231    }
24232
24233    impl std::task::Wake for RetainingWake {
24234      fn wake(self: Arc<Self>) {
24235        let _ = &self.retained_parent;
24236      }
24237    }
24238
24239    let dependency = Arc::new(TaskDependency::default());
24240    let retained_parent = Arc::new(());
24241    let retained_parent_weak = Arc::downgrade(&retained_parent);
24242    let waiter = Waker::from(Arc::new(RetainingWake {
24243      retained_parent: Arc::clone(&retained_parent),
24244    }));
24245    dependency.register_waiter(&waiter);
24246    drop(waiter);
24247    drop(retained_parent);
24248    assert!(
24249      retained_parent_weak.upgrade().is_some(),
24250      "the stored dependency waiter must retain its parent before detachment"
24251    );
24252
24253    let (runnable, task) = async_task::spawn(
24254      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24255      |_| {},
24256    );
24257    drop(JoinHandle(JoinHandleInner::Task {
24258      task: task.fallible(),
24259      awaiter: None,
24260      dependency: Arc::clone(&dependency),
24261    }));
24262    assert!(
24263      retained_parent_weak.upgrade().is_none(),
24264      "detachment must clear the dependency waiter instead of retaining its parent task"
24265    );
24266    drop(runnable);
24267  }
24268
24269  #[cfg(napi_runtime_os_threads)]
24270  #[test]
24271  fn detaching_task_clears_many_parent_publications_linearly() {
24272    const PUBLICATION_COUNT: usize = 1024;
24273
24274    struct CountWake(Arc<AtomicUsize>);
24275
24276    impl std::task::Wake for CountWake {
24277      fn wake(self: Arc<Self>) {
24278        self.0.fetch_add(1, Ordering::SeqCst);
24279      }
24280    }
24281
24282    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
24283      dependency
24284        .state
24285        .lock()
24286        .unwrap_or_else(std::sync::PoisonError::into_inner)
24287        .current
24288        .back()
24289        .expect("the sentinel publication must be appended")
24290        .publication
24291        .clone()
24292    }
24293
24294    let owner = BlockingOwnerToken {
24295      executor_id: u64::MAX - 300,
24296      frame: u64::MAX - 301,
24297    };
24298    let other_owner = BlockingOwnerToken {
24299      executor_id: u64::MAX - 302,
24300      frame: u64::MAX - 303,
24301    };
24302    let child = Arc::new(TaskDependency::default());
24303    for index in 0..PUBLICATION_COUNT {
24304      child.set_owned(BlockingDependency {
24305        job: BlockingJobId(1_000_000 + index as u64),
24306        owner: (index % 2 == 0).then_some(owner),
24307      });
24308    }
24309    let child_publications = child.snapshot_all_live();
24310    let source = child_publications[0].clone();
24311
24312    let parent = Arc::new(TaskDependency::default());
24313    let _context = TaskDependencyGuard::enter(&parent);
24314    let (runnable, task) = async_task::spawn(
24315      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
24316      |_| {},
24317    );
24318    let mut handle = JoinHandle(JoinHandleInner::Task {
24319      task: task.fallible(),
24320      awaiter: None,
24321      dependency: Arc::clone(&child),
24322    });
24323    let waker = futures::task::noop_waker();
24324    let mut cx = Context::from_waker(&waker);
24325    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
24326    assert_eq!(
24327      parent.get_dependencies().len(),
24328      PUBLICATION_COUNT,
24329      "polling the child handle must propagate every current publication"
24330    );
24331
24332    let mut wrong_claim_source = source.clone();
24333    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
24334    parent.set_borrowed(&wrong_claim_source);
24335    let wrong_claim_publication = latest_publication(&parent);
24336
24337    let mut wrong_job_source = source.clone();
24338    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
24339    parent.set_borrowed(&wrong_job_source);
24340    let wrong_job_publication = latest_publication(&parent);
24341
24342    let wrong_immediate_source = source.derived(None);
24343    parent.set_borrowed(&wrong_immediate_source);
24344    let wrong_immediate_publication = latest_publication(&parent);
24345
24346    let wakes = Arc::new(AtomicUsize::new(0));
24347    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
24348    parent.reset_entry_visits();
24349    drop(handle);
24350
24351    assert_eq!(
24352      wakes.load(Ordering::SeqCst),
24353      1,
24354      "one batched parent cleanup must emit one waiter notification"
24355    );
24356    assert!(
24357      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
24358      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
24359      parent.entry_visits()
24360    );
24361    assert!(
24362      wrong_claim_publication.is_live(),
24363      "claim identity must remain part of the match"
24364    );
24365    assert!(
24366      wrong_job_publication.is_live(),
24367      "job identity must remain part of the match"
24368    );
24369    assert!(
24370      wrong_immediate_publication.is_live(),
24371      "only an exact immediate-parent link may be retired"
24372    );
24373
24374    {
24375      let state = parent
24376        .state
24377        .lock()
24378        .unwrap_or_else(std::sync::PoisonError::into_inner);
24379      assert_eq!(state.current.len(), 3);
24380      // The `job_sequences` index is gone -- a blocking handle retires its own
24381      // entry by sequence -- so "the index retains no stale sequence" is
24382      // structurally impossible rather than something to assert. The
24383      // substantive property that assertion protected, that only the three
24384      // NON-matching sentinels survived the clear, is checked directly on the
24385      // authoritative structure.
24386      assert!(
24387        state
24388          .current
24389          .iter()
24390          .all(|entry| entry.publication.is_live()),
24391        "only the three non-matching sentinels may remain, all still live"
24392      );
24393      assert!(state.unowned_sequences.is_empty());
24394      assert_eq!(state.owner_sequences.len(), 1);
24395      assert_eq!(
24396        state.owner_sequences.get(&owner).map(VecDeque::len),
24397        Some(3)
24398      );
24399    }
24400    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
24401    assert!(
24402      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
24403      "cancelled unowned publications must not survive in availability predicates"
24404    );
24405    drop(runnable);
24406  }
24407
24408  #[cfg(not(target_family = "wasm"))]
24409  #[test]
24410  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
24411    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
24412
24413    if std::env::var_os(CHILD_ENV).is_some() {
24414      struct PanicWake;
24415      impl std::task::Wake for PanicWake {
24416        fn wake(self: Arc<Self>) {
24417          panic!("intentional dependency waker panic");
24418        }
24419      }
24420
24421      struct PanicOnDrop;
24422      impl Drop for PanicOnDrop {
24423        fn drop(&mut self) {
24424          panic!("intentional buffered blocking result destructor panic");
24425        }
24426      }
24427
24428      let parent = Arc::new(TaskDependency::default());
24429      let dependency = BlockingDependency {
24430        job: BlockingJobId(u64::MAX - 1),
24431        owner: None,
24432      };
24433      // The handle is built by hand rather than by polling, so hand it the
24434      // publication token a Pending poll would have recorded; detach retires
24435      // the entry by sequence.
24436      let sequence = parent.set_owned(dependency);
24437      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
24438      let _context = TaskDependencyGuard::enter(&parent);
24439      let (sender, receiver) = oneshot::channel();
24440      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
24441        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
24442      ));
24443      drop(JoinHandle(JoinHandleInner::Blocking {
24444        receiver,
24445        dependency,
24446        published: PublishedBlockingDependencies {
24447          first: Some(PublishedBlockingDependency {
24448            state: Arc::downgrade(&parent),
24449            sequence,
24450          }),
24451          rest: Vec::new(),
24452        },
24453      }));
24454      assert_eq!(parent.get_dependency(), None);
24455      return;
24456    }
24457
24458    let output = std::process::Command::new(std::env::current_exe().unwrap())
24459      .arg("--exact")
24460      .arg(
24461        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
24462      )
24463      .arg("--nocapture")
24464      .env(CHILD_ENV, "1")
24465      .output()
24466      .expect("the subprocess test must start");
24467    assert!(
24468      output.status.success(),
24469      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
24470      output.status.code(),
24471      String::from_utf8_lossy(&output.stdout),
24472      String::from_utf8_lossy(&output.stderr)
24473    );
24474  }
24475
24476  #[cfg(napi_runtime_os_threads)]
24477  #[test]
24478  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
24479    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
24480    // that does NOT own a counted blocking slot and re-enters `block_on`
24481    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
24482    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
24483    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
24484    // driver could use the over-cap fallback, the inner jobs would run while
24485    // both holders still occupy slots and push peak active blocking above 2.
24486    // The cap must be honored AND everything must still complete.
24487    use std::sync::{Barrier, mpsc};
24488    use std::time::Duration;
24489
24490    let (done_tx, done_rx) = mpsc::channel();
24491    let peak_out = Arc::new(Mutex::new(None));
24492    let peak_slot = Arc::clone(&peak_out);
24493    let runner = std::thread::spawn(move || {
24494      let metrics = Arc::new(RuntimeMetrics::default());
24495      let options = RuntimeOptions {
24496        flavor: RuntimeFlavor::MultiThread,
24497        worker_threads: 4,
24498        max_blocking_tasks: 2,
24499        thread_name_prefix: "rd1-cap".to_string(),
24500        park_deadline: None,
24501        drain_linger: DEFAULT_DRAIN_LINGER,
24502      };
24503      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24504
24505      let holder_count = 2usize; // saturates max_blocking_tasks
24506      let driver_count = 2usize;
24507
24508      // Holders signal once they hold a counted slot, then wait to be released.
24509      let (held_tx, held_rx) = mpsc::channel();
24510      let release = Arc::new(Barrier::new(holder_count + 1));
24511
24512      let mut holder_handles = Vec::new();
24513      for _ in 0..holder_count {
24514        let held_tx = held_tx.clone();
24515        let release = Arc::clone(&release);
24516        let handle = executor.schedule_blocking(move || {
24517          held_tx.send(()).unwrap();
24518          release.wait();
24519          0usize
24520        });
24521        holder_handles.push(handle);
24522      }
24523      // Wait until both holders hold their slots (cap fully saturated).
24524      for _ in 0..holder_count {
24525        held_rx.recv().unwrap();
24526      }
24527
24528      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
24529      // an inner spawn_blocking.
24530      let mut driver_tasks = Vec::new();
24531      for _ in 0..driver_count {
24532        let exec = Arc::clone(&executor);
24533        let body = async move {
24534          let inner_exec = Arc::clone(&exec);
24535          let mut inner = std::pin::pin!(async move {
24536            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24537            assert_eq!(v, 5);
24538          });
24539          exec.block_on(inner.as_mut());
24540        };
24541        let scheduler = Arc::clone(&executor);
24542        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
24543        executor.schedule(runnable);
24544        driver_tasks.push(task);
24545      }
24546
24547      // Let the driver tasks reach block_on. They must park until a holder
24548      // frees a slot instead of running their inner jobs over the cap.
24549      std::thread::sleep(Duration::from_millis(300));
24550
24551      // Release the holders so the inner jobs can run within the cap.
24552      release.wait();
24553
24554      for task in driver_tasks {
24555        futures::executor::block_on(task);
24556      }
24557      for handle in holder_handles {
24558        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24559      }
24560
24561      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24562      let _ = done_tx.send(());
24563    });
24564
24565    match done_rx.recv_timeout(Duration::from_secs(10)) {
24566      Ok(()) => runner.join().unwrap(),
24567      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
24568    }
24569    let peak = peak_out.lock().unwrap().expect("peak captured");
24570    assert!(
24571      peak <= 2,
24572      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24573    );
24574  }
24575
24576  #[cfg(napi_runtime_os_threads)]
24577  #[test]
24578  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
24579    // Blocking-owner isolation invariant: a counted blocking OWNER that
24580    // re-enters cooperative `block_on` can drive a plain queued runnable via
24581    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
24582    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
24583    // the cap is saturated, the inner job must wait for a slot instead of
24584    // borrowing the owner's token.
24585    //
24586    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
24587    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
24588    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
24589    // drainers, no replacement drainer can be spawned, so O itself runs R via
24590    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
24591    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
24592    // runs within the cap and the peak remains 2. Everything must still
24593    // complete.
24594    use std::sync::{Barrier, mpsc};
24595    use std::time::Duration;
24596
24597    let (done_tx, done_rx) = mpsc::channel();
24598    let peak_out = Arc::new(Mutex::new(None));
24599    let peak_slot = Arc::clone(&peak_out);
24600    let runner = std::thread::spawn(move || {
24601      let metrics = Arc::new(RuntimeMetrics::default());
24602      let options = RuntimeOptions {
24603        flavor: RuntimeFlavor::MultiThread,
24604        worker_threads: 2,
24605        max_blocking_tasks: 2,
24606        thread_name_prefix: "rd1-owner-runnable".to_string(),
24607        park_deadline: None,
24608        drain_linger: DEFAULT_DRAIN_LINGER,
24609      };
24610      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24611
24612      let saturated = Arc::new(Barrier::new(2));
24613      let release = Arc::new(Barrier::new(2));
24614
24615      // Holder H2: holds the second counted slot until released. Not the driver.
24616      let h2_saturated = Arc::clone(&saturated);
24617      let h2_release = Arc::clone(&release);
24618      let h2 = executor.schedule_blocking(move || {
24619        h2_saturated.wait();
24620        h2_release.wait();
24621        0usize
24622      });
24623
24624      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
24625      // spawns plain runnable R and drives it cooperatively via `block_on`.
24626      let o_exec = Arc::clone(&executor);
24627      let o_saturated = Arc::clone(&saturated);
24628      let o = executor.schedule_blocking(move || {
24629        o_saturated.wait(); // both slots held -> cap saturated
24630
24631        let r_exec = Arc::clone(&o_exec);
24632        let body = async move {
24633          let inner_exec = Arc::clone(&r_exec);
24634          let mut inner = std::pin::pin!(async move {
24635            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
24636            assert_eq!(v, 5);
24637          });
24638          r_exec.block_on(inner.as_mut());
24639        };
24640        let sched = Arc::clone(&o_exec);
24641        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
24642        o_exec.schedule(runnable);
24643
24644        let mut wait_r = std::pin::pin!(async move {
24645          task.await;
24646        });
24647        o_exec.block_on(wait_r.as_mut());
24648        1usize
24649      });
24650
24651      // Give O time to drive R into its inner `block_on`; R must be parked
24652      // waiting for a slot.
24653      std::thread::sleep(Duration::from_millis(300));
24654      // Release H2 so the inner job can run within the cap.
24655      release.wait();
24656
24657      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24658      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
24659
24660      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
24661      let _ = done_tx.send(());
24662    });
24663
24664    match done_rx.recv_timeout(Duration::from_secs(10)) {
24665      Ok(()) => runner.join().unwrap(),
24666      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
24667    }
24668    let peak = peak_out.lock().unwrap().expect("peak captured");
24669    assert!(
24670      peak <= 2,
24671      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
24672    );
24673  }
24674
24675  #[cfg(napi_runtime_os_threads)]
24676  #[test]
24677  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
24678    // The over-cap fallback must select the exact pending JoinHandle job, not
24679    // the front of the shared blocking queue.
24680    //
24681    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
24682    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
24683    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
24684    // The exact dependency must skip U and run D. Because U can only run once
24685    // a real slot frees after O completes, D must STRICTLY precede U.
24686    use std::sync::{Barrier, mpsc};
24687
24688    let (done_tx, done_rx) = mpsc::channel();
24689    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
24690    let order_read = Arc::clone(&order_out);
24691    let runner = std::thread::spawn(move || {
24692      let metrics = Arc::new(RuntimeMetrics::default());
24693      let options = RuntimeOptions {
24694        flavor: RuntimeFlavor::MultiThread,
24695        worker_threads: 2,
24696        max_blocking_tasks: 2,
24697        thread_name_prefix: "rd1-unrelated".to_string(),
24698        park_deadline: None,
24699        drain_linger: DEFAULT_DRAIN_LINGER,
24700      };
24701      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24702
24703      // O and H signal once they each hold a counted slot (cap saturated), then
24704      // wait so U is queued before O nests. H stays held until the very end.
24705      let (held_tx, held_rx) = mpsc::channel();
24706      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
24707      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
24708
24709      // Holder H: holds slot 2 for the whole test.
24710      let h_held = held_tx.clone();
24711      let h_rel = Arc::clone(&h_release);
24712      let h = executor.schedule_blocking(move || {
24713        h_held.send(()).unwrap();
24714        h_rel.wait();
24715        0usize
24716      });
24717
24718      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
24719      // its OWN descendant D.
24720      let o_exec = Arc::clone(&executor);
24721      let o_ready = Arc::clone(&u_ready);
24722      let o_order = Arc::clone(&order_out);
24723      let o = executor.schedule_blocking(move || {
24724        held_tx.send(()).unwrap();
24725        o_ready.wait(); // wait until U is queued ahead of D
24726        let inner_exec = Arc::clone(&o_exec);
24727        let d_order = Arc::clone(&o_order);
24728        let mut inner = std::pin::pin!(async move {
24729          let v = inner_exec
24730            .schedule_blocking(move || {
24731              d_order.lock().unwrap().push("D");
24732              5usize
24733            })
24734            .await
24735            .unwrap();
24736          assert_eq!(v, 5);
24737        });
24738        o_exec.block_on(inner.as_mut());
24739        1usize
24740      });
24741
24742      // Wait until BOTH O and H hold their slots -> cap saturated.
24743      held_rx.recv().unwrap();
24744      held_rx.recv().unwrap();
24745
24746      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
24747      let u_order = Arc::clone(&order_out);
24748      let u = executor.schedule_blocking(move || {
24749        u_order.lock().unwrap().push("U");
24750        2usize
24751      });
24752
24753      // Release O to nest now that U is queued ahead of D.
24754      u_ready.wait();
24755
24756      // O makes progress by running D over cap; U only runs after O frees a slot.
24757      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24758      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24759
24760      // Release H and finish.
24761      h_release.wait();
24762      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24763
24764      let _ = done_tx.send(());
24765    });
24766
24767    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24768      Ok(()) => runner.join().unwrap(),
24769      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24770    }
24771    let order = order_read.lock().unwrap().clone();
24772    assert_eq!(
24773      order,
24774      vec!["D", "U"],
24775      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24776    );
24777  }
24778
24779  #[cfg(napi_runtime_os_threads)]
24780  #[test]
24781  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24782    use std::sync::{Barrier, mpsc};
24783
24784    let opts = RuntimeOptions {
24785      flavor: RuntimeFlavor::MultiThread,
24786      worker_threads: 2,
24787      max_blocking_tasks: 2,
24788      thread_name_prefix: "rd1-scope".to_string(),
24789      park_deadline: None,
24790      drain_linger: DEFAULT_DRAIN_LINGER,
24791    };
24792    let exec1 =
24793      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24794    let exec2 =
24795      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24796    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24797
24798    let holder_count = opts.max_blocking_tasks;
24799    let (held_tx, held_rx) = mpsc::channel();
24800    let release = Arc::new(Barrier::new(holder_count + 1));
24801    let mut holder_handles = Vec::new();
24802    for _ in 0..holder_count {
24803      let held_tx = held_tx.clone();
24804      let release = Arc::clone(&release);
24805      holder_handles.push(exec2.schedule_blocking(move || {
24806        held_tx.send(()).unwrap();
24807        release.wait();
24808        0usize
24809      }));
24810    }
24811    for _ in 0..holder_count {
24812      held_rx.recv().unwrap();
24813    }
24814    assert_eq!(
24815      exec2.active_blocking.load(Ordering::Acquire),
24816      opts.max_blocking_tasks
24817    );
24818
24819    let exact = BlockingJobId(u64::MAX);
24820    let ran = Arc::new(AtomicBool::new(false));
24821    let ran_job = Arc::clone(&ran);
24822    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
24823      id: exact,
24824      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24825    });
24826    let dependency = TaskDependency::default();
24827
24828    let token1 = exec1.fresh_owner_token();
24829    {
24830      let _owner = BlockingOwnerGuard::enter(Some(token1));
24831      dependency.set_owned(BlockingDependency {
24832        job: exact,
24833        owner: Some(token1),
24834      });
24835      assert!(
24836        !exec2.try_owned_blocking_over_cap(&dependency),
24837        "a foreign-executor ambient token must fail the executor gate"
24838      );
24839    }
24840
24841    let token2 = exec2.fresh_owner_token();
24842    {
24843      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
24844      let _owner = BlockingOwnerGuard::enter(Some(token2));
24845      dependency.set_owned(BlockingDependency {
24846        job: BlockingJobId(u64::MAX - 1),
24847        owner: Some(token2),
24848      });
24849      assert!(
24850        !exec2.try_owned_blocking_over_cap(&dependency),
24851        "a foreign or unrelated dependency id must fail the exact-job gate"
24852      );
24853      dependency.set_owned(BlockingDependency {
24854        job: BlockingJobId(exact.0 - 2),
24855        owner: Some(token2),
24856      });
24857      assert!(
24858        !exec2.try_owned_blocking_over_cap(&dependency),
24859        "a different job id must not consume the queued dependency"
24860      );
24861      dependency.set_owned(BlockingDependency {
24862        job: exact,
24863        owner: Some(token2),
24864      });
24865      assert!(
24866        exec2.try_owned_blocking_over_cap(&dependency),
24867        "the exact awaited job must run over cap"
24868      );
24869    }
24870    assert!(
24871      ran.load(Ordering::SeqCst),
24872      "the exact dependency must have run"
24873    );
24874
24875    release.wait();
24876    for handle in holder_handles {
24877      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24878    }
24879  }
24880
24881  #[cfg(napi_runtime_os_threads)]
24882  #[test]
24883  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
24884    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
24885    let first_owner = executor.fresh_owner_token();
24886    let second_owner = executor.fresh_owner_token();
24887    let _first_owner =
24888      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
24889    let _second_owner =
24890      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
24891    let exact = BlockingJobId(u64::MAX - 30);
24892    let dependency = TaskDependency::default();
24893    dependency.set_owned(BlockingDependency {
24894      job: exact,
24895      owner: None,
24896    });
24897    let ran = Arc::new(AtomicBool::new(false));
24898    let ran_job = Arc::clone(&ran);
24899    executor
24900      .blocking_queue
24901      .lock()
24902      .unwrap()
24903      .push(QueuedBlocking {
24904        id: exact,
24905        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24906      });
24907
24908    assert!(
24909      !executor.try_owned_blocking_over_cap(&dependency),
24910      "an untagged driver must not choose between multiple active owner frames"
24911    );
24912    assert!(!ran.load(Ordering::SeqCst));
24913    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
24914    assert!(
24915      queued.is_some(),
24916      "ambiguous ownership must leave the exact job queued"
24917    );
24918  }
24919
24920  #[cfg(napi_runtime_os_threads)]
24921  #[test]
24922  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
24923    let executor = multi_thread_executor(2, None, "live-parked-dependency");
24924    let owner = executor.fresh_owner_token();
24925    let dependency = TaskDependency::new(u64::MAX, Some(owner));
24926    let stale = BlockingJobId(u64::MAX - 10);
24927    let live = BlockingJobId(u64::MAX - 9);
24928    dependency.set_owned(BlockingDependency {
24929      job: stale,
24930      owner: Some(owner),
24931    });
24932    dependency.begin_poll();
24933    dependency.set_owned(BlockingDependency {
24934      job: live,
24935      owner: Some(owner),
24936    });
24937
24938    let stale_ran = Arc::new(AtomicBool::new(false));
24939    let stale_job_ran = Arc::clone(&stale_ran);
24940    let live_ran = Arc::new(AtomicBool::new(false));
24941    let live_job_ran = Arc::clone(&live_ran);
24942    {
24943      let mut queue = executor
24944        .blocking_queue
24945        .lock()
24946        .unwrap_or_else(std::sync::PoisonError::into_inner);
24947      queue.push(QueuedBlocking {
24948        id: stale,
24949        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
24950      });
24951      queue.push(QueuedBlocking {
24952        id: live,
24953        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
24954      });
24955    }
24956
24957    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24958    let _owner = BlockingOwnerGuard::enter(Some(owner));
24959    assert!(
24960      executor.try_owned_blocking_over_cap(&dependency),
24961      "the owner lane must claim the currently published dependency"
24962    );
24963    assert!(
24964      live_ran.load(Ordering::SeqCst),
24965      "the live dependency must run"
24966    );
24967    assert!(
24968      !stale_ran.load(Ordering::SeqCst),
24969      "the stale registered id must remain queued"
24970    );
24971    let remaining = executor
24972      .blocking_queue
24973      .lock()
24974      .unwrap_or_else(std::sync::PoisonError::into_inner)
24975      .jobs
24976      .keys()
24977      .copied()
24978      .collect::<Vec<_>>();
24979    assert_eq!(remaining, vec![stale]);
24980  }
24981
24982  #[cfg(napi_runtime_os_threads)]
24983  #[test]
24984  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
24985    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
24986    let owner = executor.fresh_owner_token();
24987    let child = TaskDependency::new_task(u64::MAX);
24988    let parent = TaskDependency::new(u64::MAX, Some(owner));
24989    let exact = BlockingDependency {
24990      job: BlockingJobId(u64::MAX - 31),
24991      owner: Some(owner),
24992    };
24993
24994    child.set_owned(exact);
24995    let stale = child
24996      .get()
24997      .expect("the child must publish its direct dependency");
24998    child.begin_poll();
24999    parent.set_borrowed(&stale);
25000
25001    let ran = Arc::new(AtomicBool::new(false));
25002    let ran_job = Arc::clone(&ran);
25003    executor
25004      .blocking_queue
25005      .lock()
25006      .unwrap()
25007      .push(QueuedBlocking {
25008        id: exact.job,
25009        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25010      });
25011    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25012    let _owner = BlockingOwnerGuard::enter(Some(owner));
25013    assert!(
25014      !executor.try_owned_blocking_over_cap(&parent),
25015      "a publication copied before child cancellation must remain cancelled after parent install"
25016    );
25017    assert!(!ran.load(Ordering::SeqCst));
25018
25019    child.set_owned(exact);
25020    parent.set_borrowed(
25021      &child
25022        .get()
25023        .expect("the replacement publication must be live"),
25024    );
25025    assert!(
25026      executor.try_owned_blocking_over_cap(&parent),
25027      "a fresh child publication must still authorize its exact queued job"
25028    );
25029    assert!(ran.load(Ordering::SeqCst));
25030  }
25031
25032  #[cfg(napi_runtime_os_threads)]
25033  #[test]
25034  fn intermediate_repoll_does_not_cancel_child_source_claim() {
25035    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
25036    let owner = executor.fresh_owner_token();
25037    let child = TaskDependency::new_task(u64::MAX);
25038    let intermediate = TaskDependency::new_task(u64::MAX);
25039    let parent = TaskDependency::new(u64::MAX, Some(owner));
25040    let exact = BlockingDependency {
25041      job: BlockingJobId(u64::MAX - 43),
25042      owner: Some(owner),
25043    };
25044
25045    child.set_owned(exact);
25046    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
25047    parent.set_borrowed(
25048      &intermediate
25049        .get()
25050        .expect("the intermediate publication must be live"),
25051    );
25052
25053    intermediate.begin_poll();
25054    assert!(
25055      child.get().is_some(),
25056      "withdrawing an intermediate hop must not cancel the child's source claim"
25057    );
25058    assert!(
25059      parent.get().is_none(),
25060      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
25061    );
25062
25063    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
25064    parent.set_borrowed(
25065      &intermediate
25066        .get()
25067        .expect("the intermediate must republish the still-live source"),
25068    );
25069    let ran = Arc::new(AtomicBool::new(false));
25070    let ran_job = Arc::clone(&ran);
25071    executor
25072      .blocking_queue
25073      .lock()
25074      .unwrap()
25075      .push(QueuedBlocking {
25076        id: exact.job,
25077        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25078      });
25079
25080    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25081    let _owner = BlockingOwnerGuard::enter(Some(owner));
25082    assert!(
25083      executor.try_owned_blocking_over_cap(&parent),
25084      "a fresh chain through the repolled intermediate must retain the exact source claim"
25085    );
25086    assert!(ran.load(Ordering::SeqCst));
25087  }
25088
25089  #[cfg(napi_runtime_os_threads)]
25090  #[test]
25091  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
25092    use std::sync::mpsc;
25093
25094    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
25095    let owner = executor.fresh_owner_token();
25096    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
25097    let child = Arc::new(TaskDependency::new_task(u64::MAX));
25098    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25099    let exact = BlockingDependency {
25100      job: BlockingJobId(u64::MAX - 44),
25101      owner: Some(owner),
25102    };
25103    child.set_owned(exact);
25104    parent.set_borrowed(&child.get().expect("the child source must be live"));
25105
25106    let ran = Arc::new(AtomicBool::new(false));
25107    let ran_job = Arc::clone(&ran);
25108    executor
25109      .blocking_queue
25110      .lock()
25111      .unwrap()
25112      .push(QueuedBlocking {
25113        id: exact.job,
25114        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
25115      });
25116
25117    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
25118    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25119    let claimant_executor = Arc::clone(&executor);
25120    let claimant_parent = Arc::clone(&parent);
25121    let claimant = std::thread::spawn(move || {
25122      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
25123        *slot.borrow_mut() = Some(Box::new(move || {
25124          claim_entered_tx.send(()).unwrap();
25125          release_claim_rx.recv().unwrap();
25126        }));
25127      });
25128      let _owner = BlockingOwnerGuard::enter(Some(owner));
25129      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
25130    });
25131    claim_entered_rx
25132      .recv_timeout(Duration::from_secs(2))
25133      .expect("the exact claimant must hold the shared transition lock");
25134
25135    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
25136    let withdrawing_child = Arc::clone(&child);
25137    let withdrawal = std::thread::spawn(move || {
25138      withdrawing_child.begin_poll();
25139      withdrawn_tx.send(()).unwrap();
25140    });
25141    let withdrew_before_claim = withdrawn_rx
25142      .recv_timeout(Duration::from_millis(200))
25143      .is_ok();
25144    release_claim_tx.send(()).unwrap();
25145
25146    assert!(
25147      claimant.join().unwrap(),
25148      "the claim that linearized first must execute the exact job"
25149    );
25150    withdrawal.join().unwrap();
25151    assert!(
25152      !withdrew_before_claim,
25153      "source withdrawal must not interleave after claim validation but before claim commit"
25154    );
25155    assert!(ran.load(Ordering::SeqCst));
25156  }
25157
25158  #[cfg(napi_runtime_os_threads)]
25159  #[test]
25160  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
25161    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
25162    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
25163    // executor whose worker set the marker. A worker of executor A that re-enters
25164    // `block_on` on executor B must take the PARKING branch instead -- it owns
25165    // none of B's accounting, so driving B's queue would make it an unaccounted
25166    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
25167    //
25168    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
25169    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
25170    // `block_on` on each executor with the SAME probe shape and observe whether
25171    // the cooperative `run_one` driver actually ran a queued runnable.
25172    //
25173    // The probe future returns `Pending` once (self-waking so the parking branch
25174    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
25175    // `run_one` at least once before the future completes. A single runnable is
25176    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
25177    // no background drainer is ever spawned to steal it). After `block_on`
25178    // returns: the cooperative branch will have driven that runnable
25179    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
25180    // (`runnable_polls == 0`, still queued).
25181    struct PendThenReady {
25182      polls: usize,
25183    }
25184    impl Future for PendThenReady {
25185      type Output = ();
25186      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25187        self.polls += 1;
25188        if self.polls >= 2 {
25189          Poll::Ready(())
25190        } else {
25191          // Self-wake so the parking branch (`futures::executor::block_on`)
25192          // re-polls to completion without any external waker; the cooperative
25193          // branch re-polls unconditionally each loop iteration regardless.
25194          cx.waker().wake_by_ref();
25195          Poll::Pending
25196        }
25197      }
25198    }
25199
25200    // Push one already-resolvable runnable directly onto `target`'s queue (no
25201    // `schedule`, so no drainer is spawned) and detach its task.
25202    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
25203      let scheduler = Arc::clone(target);
25204      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
25205      target.push_fifo_raw(runnable);
25206      task.detach();
25207    }
25208
25209    let opts = RuntimeOptions {
25210      flavor: RuntimeFlavor::MultiThread,
25211      worker_threads: 2,
25212      max_blocking_tasks: 2,
25213      thread_name_prefix: "rd-onpool-scope".to_string(),
25214      park_deadline: None,
25215      drain_linger: DEFAULT_DRAIN_LINGER,
25216    };
25217    let exec1 =
25218      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25219    let exec2 =
25220      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
25221    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
25222
25223    // Simulate being on exec1's pool worker for the whole scenario.
25224    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
25225
25226    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
25227    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
25228    // touch its queue.
25229    enqueue_runnable(&exec2);
25230    {
25231      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25232      exec2.block_on(probe.as_mut());
25233    }
25234    assert_eq!(
25235      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
25236      0,
25237      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
25238    );
25239    assert_eq!(
25240      exec2.injector.len(),
25241      1,
25242      "the foreign call must leave exec2's runnable untouched on its queue"
25243    );
25244
25245    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
25246    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
25247    // runnable via `run_one`.
25248    enqueue_runnable(&exec1);
25249    {
25250      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
25251      exec1.block_on(probe.as_mut());
25252    }
25253    assert_eq!(
25254      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
25255      1,
25256      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
25257    );
25258    assert!(
25259      exec1.injector.is_empty(),
25260      "the cooperative branch must have drained exec1's queued runnable"
25261    );
25262
25263    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
25264    // (steal-drain replaces the former `queue.lock().clear()`).
25265    while exec2.steal_one().is_some() {}
25266  }
25267
25268  #[cfg(napi_runtime_os_threads)]
25269  #[test]
25270  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
25271    // A long-lived blocking consumer may consume the entire configured
25272    // blocking allowance, but it must not consume the reserve lane required
25273    // by the async producers that feed it.
25274    use std::sync::mpsc;
25275    use std::time::Duration;
25276
25277    const PRODUCER_COUNT: usize = 8;
25278
25279    fn schedule_pool_workload(
25280      executor: &Arc<MultiThreadExecutor>,
25281      tx: &mpsc::Sender<Option<usize>>,
25282      count: usize,
25283    ) {
25284      for i in 0..count {
25285        let tx = tx.clone();
25286        let scheduler = Arc::clone(executor);
25287        let (runnable, task) =
25288          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
25289            scheduler.schedule(r);
25290          });
25291        executor.schedule(runnable);
25292        task.detach();
25293      }
25294      let tx = tx.clone();
25295      let scheduler = Arc::clone(executor);
25296      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
25297        scheduler.schedule(r)
25298      });
25299      executor.schedule(runnable);
25300      task.detach();
25301    }
25302
25303    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
25304    let (done_tx, done_rx) = mpsc::sync_channel(1);
25305    let runner = std::thread::spawn(move || {
25306      let options = RuntimeOptions {
25307        flavor: RuntimeFlavor::MultiThread,
25308        worker_threads: 1,
25309        max_blocking_tasks: 1,
25310        thread_name_prefix: "rd2-pre-fix".to_string(),
25311        park_deadline: None,
25312        drain_linger: DEFAULT_DRAIN_LINGER,
25313      }
25314      .validate()
25315      .expect("production MultiThread options must provide a reserve lane");
25316      let executor =
25317        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
25318
25319      let (tx, rx) = mpsc::channel::<Option<usize>>();
25320      let (started_tx, started_rx) = mpsc::sync_channel(1);
25321      let handle = executor.schedule_blocking(move || {
25322        started_tx.send(()).unwrap();
25323        let mut sum = 0usize;
25324        while let Ok(msg) = rx.recv() {
25325          match msg {
25326            Some(value) => sum += value,
25327            None => break,
25328          }
25329        }
25330        sum
25331      });
25332      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
25333      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
25334      let sum = futures::executor::block_on(handle).unwrap();
25335      done_tx.send(sum).unwrap();
25336    });
25337
25338    match done_rx.recv_timeout(Duration::from_secs(10)) {
25339      Ok(sum) => {
25340        runner.join().unwrap();
25341        assert_eq!(sum, expected_sum);
25342      }
25343      Err(error) => {
25344        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
25345      }
25346    }
25347  }
25348
25349  #[test]
25350  fn current_thread_blocking_work_completes_inline() {
25351    let metrics = Arc::new(RuntimeMetrics::default());
25352    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
25353      CurrentThreadExecutor::new(Arc::clone(&metrics)),
25354    )));
25355    let registration = backend.work.try_register_work().unwrap();
25356
25357    let value =
25358      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
25359        .expect("blocking task should complete");
25360
25361    assert_eq!(value, 7);
25362    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
25363    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
25364  }
25365
25366  #[cfg(napi_runtime_os_threads)]
25367  #[test]
25368  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
25369    use std::sync::mpsc;
25370
25371    let controller = Arc::new(current_thread_controller(
25372      "current-thread-blocking-admission",
25373    ));
25374    let backend = controller.backend();
25375    let (first_started_tx, first_started_rx) = mpsc::channel();
25376    let (release_first_tx, release_first_rx) = mpsc::channel();
25377    let first_controller = Arc::clone(&controller);
25378    let first = std::thread::spawn(move || {
25379      let handle = first_controller
25380        .try_spawn_blocking(move || {
25381          first_started_tx.send(()).unwrap();
25382          release_first_rx.recv().unwrap();
25383          1usize
25384        })
25385        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
25386      futures::executor::block_on(handle).unwrap()
25387    });
25388    first_started_rx
25389      .recv_timeout(Duration::from_secs(1))
25390      .unwrap();
25391
25392    let (second_started_tx, second_started_rx) = mpsc::channel();
25393    let second_controller = Arc::clone(&controller);
25394    let second = std::thread::spawn(move || {
25395      let handle = second_controller
25396        .try_spawn_blocking(move || {
25397          second_started_tx.send(()).unwrap();
25398          2usize
25399        })
25400        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
25401      futures::executor::block_on(handle).unwrap()
25402    });
25403
25404    wait_until("both CurrentThread blocking calls to be accepted", || {
25405      backend
25406        .work
25407        .state
25408        .lock()
25409        .unwrap_or_else(std::sync::PoisonError::into_inner)
25410        .active
25411        == 2
25412    });
25413    assert!(
25414      second_started_rx
25415        .recv_timeout(Duration::from_millis(100))
25416        .is_err(),
25417      "CurrentThread must not execute two blocking closures concurrently"
25418    );
25419
25420    release_first_tx.send(()).unwrap();
25421    second_started_rx
25422      .recv_timeout(Duration::from_secs(1))
25423      .expect("the waiting blocking call must acquire the released lane");
25424    assert_eq!(first.join().unwrap(), 1);
25425    assert_eq!(second.join().unwrap(), 2);
25426    assert_eq!(
25427      controller
25428        .metrics
25429        .blocking_tasks_started
25430        .load(Ordering::Relaxed),
25431      2
25432    );
25433    assert_eq!(
25434      controller
25435        .metrics
25436        .blocking_tasks_completed
25437        .load(Ordering::Relaxed),
25438      2
25439    );
25440    assert_eq!(
25441      controller
25442        .metrics
25443        .active_blocking_tasks
25444        .load(Ordering::Relaxed),
25445      0
25446    );
25447    assert_eq!(
25448      controller
25449        .metrics
25450        .max_active_blocking_tasks
25451        .load(Ordering::Relaxed),
25452      1,
25453      "the public active-lane metric must remain within CurrentThread's reported capacity"
25454    );
25455    controller.shutdown().unwrap();
25456  }
25457
25458  #[cfg(napi_runtime_os_threads)]
25459  #[test]
25460  fn current_thread_nested_blocking_reuses_the_active_lane() {
25461    use std::sync::mpsc;
25462
25463    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
25464    let (done_tx, done_rx) = mpsc::channel();
25465    let runner_controller = Arc::clone(&controller);
25466    let runner = std::thread::spawn(move || {
25467      let nested_controller = Arc::clone(&runner_controller);
25468      let outer = runner_controller
25469        .try_spawn_blocking(move || {
25470          let inner = nested_controller
25471            .try_spawn_blocking(|| 41usize)
25472            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
25473          futures::executor::block_on(inner).unwrap() + 1
25474        })
25475        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
25476      done_tx
25477        .send(futures::executor::block_on(outer).unwrap())
25478        .unwrap();
25479    });
25480
25481    assert_eq!(
25482      done_rx
25483        .recv_timeout(Duration::from_secs(2))
25484        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
25485      42
25486    );
25487    runner.join().unwrap();
25488    assert_eq!(
25489      controller
25490        .metrics
25491        .blocking_tasks_started
25492        .load(Ordering::Relaxed),
25493      2
25494    );
25495    assert_eq!(
25496      controller
25497        .metrics
25498        .blocking_tasks_completed
25499        .load(Ordering::Relaxed),
25500      2
25501    );
25502    assert_eq!(
25503      controller
25504        .metrics
25505        .active_blocking_tasks
25506        .load(Ordering::Relaxed),
25507      0
25508    );
25509    assert_eq!(
25510      controller
25511        .metrics
25512        .max_active_blocking_tasks
25513        .load(Ordering::Relaxed),
25514      1,
25515      "nested work borrows the outer lane instead of reporting a second physical lane"
25516    );
25517    controller.shutdown().unwrap();
25518  }
25519
25520  #[cfg(napi_runtime_os_threads)]
25521  #[test]
25522  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
25523    use std::sync::mpsc;
25524
25525    struct HotFuture {
25526      stop: Arc<AtomicBool>,
25527    }
25528
25529    impl Future for HotFuture {
25530      type Output = ();
25531
25532      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25533        if self.stop.load(Ordering::SeqCst) {
25534          Poll::Ready(())
25535        } else {
25536          cx.waker().wake_by_ref();
25537          Poll::Pending
25538        }
25539      }
25540    }
25541
25542    let stop = Arc::new(AtomicBool::new(false));
25543    let (ready_tx, ready_rx) = mpsc::channel();
25544    let (exact_tx, exact_rx) = mpsc::channel();
25545    let (done_tx, done_rx) = mpsc::channel();
25546    let runner_stop = Arc::clone(&stop);
25547    let runner = std::thread::spawn(move || {
25548      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25549        RuntimeMetrics::default(),
25550      )));
25551      let owner = executor.fresh_owner_token();
25552      let job = BlockingJobId(u64::MAX - 70);
25553      let (result_tx, result_rx) = oneshot::channel();
25554      {
25555        let mut state = executor
25556          .blocking_admission
25557          .state
25558          .lock()
25559          .unwrap_or_else(std::sync::PoisonError::into_inner);
25560        state.owner = Some(owner);
25561        let exact_stop = Arc::clone(&runner_stop);
25562        state.queue.push(QueuedBlocking {
25563          id: job,
25564          run: Box::new(move || {
25565            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
25566            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
25567          }),
25568        });
25569      }
25570      let exact = JoinHandle(JoinHandleInner::Blocking {
25571        receiver: result_rx,
25572        dependency: BlockingDependency {
25573          job,
25574          owner: Some(owner),
25575        },
25576        published: PublishedBlockingDependencies::default(),
25577      });
25578      let _owner = executor.enter_blocking_owner(owner, false);
25579
25580      let scheduler = Arc::clone(&executor);
25581      let hot_stop = Arc::clone(&runner_stop);
25582      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
25583        scheduler.schedule(runnable);
25584      });
25585      executor.schedule(runnable);
25586      ready_tx.send(()).unwrap();
25587
25588      let mut value = None;
25589      {
25590        let mut future = std::pin::pin!(async {
25591          value = Some(exact.await.unwrap());
25592          hot_task.await;
25593        });
25594        assert_eq!(
25595          executor.block_on(future.as_mut()),
25596          BlockOnOutcome::Completed
25597        );
25598      }
25599      executor
25600        .blocking_admission
25601        .state
25602        .lock()
25603        .unwrap_or_else(std::sync::PoisonError::into_inner)
25604        .owner = None;
25605      done_tx.send(value.unwrap()).unwrap();
25606    });
25607
25608    ready_rx
25609      .recv_timeout(Duration::from_secs(2))
25610      .expect("the fairness driver must start");
25611    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
25612    stop.store(true, Ordering::SeqCst);
25613    assert_eq!(
25614      done_rx
25615        .recv_timeout(Duration::from_secs(2))
25616        .expect("the CurrentThread driver must retire"),
25617      41
25618    );
25619    runner.join().unwrap();
25620    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
25621    assert_eq!(
25622      exact_before_stop,
25623      Some(true),
25624      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
25625    );
25626  }
25627
25628  #[cfg(napi_runtime_os_threads)]
25629  #[test]
25630  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
25631    use std::sync::mpsc;
25632
25633    let controller = Arc::new(current_thread_controller(
25634      "current-thread-cross-driver-blocking",
25635    ));
25636    let backend = controller.backend();
25637    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25638      panic!("configured CurrentThread backend must create a CurrentThread executor");
25639    };
25640    let executor = Arc::clone(executor);
25641    drop(backend);
25642
25643    let (release_driver_tx, release_driver_rx) = oneshot::channel();
25644    let (driver_started_tx, driver_started_rx) = mpsc::channel();
25645    let driver_controller = Arc::clone(&controller);
25646    let driver = std::thread::spawn(move || {
25647      driver_started_tx.send(std::thread::current().id()).unwrap();
25648      block_on_with_controller(&driver_controller, async {
25649        release_driver_rx
25650          .await
25651          .expect("driver release sender must remain alive");
25652      });
25653    });
25654    let driver_thread = driver_started_rx
25655      .recv_timeout(Duration::from_secs(1))
25656      .unwrap();
25657    wait_until("the secondary CurrentThread driver to park", || {
25658      executor
25659        .stop
25660        .parkers
25661        .lock()
25662        .unwrap_or_else(std::sync::PoisonError::into_inner)
25663        .iter()
25664        .filter_map(Weak::upgrade)
25665        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25666    });
25667
25668    let (child_polled_tx, child_polled_rx) = mpsc::channel();
25669    let (release_child_tx, release_child_rx) = mpsc::channel();
25670    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
25671    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
25672    let (owner_done_tx, owner_done_rx) = mpsc::channel();
25673    let owner_controller = Arc::clone(&controller);
25674    let owner = std::thread::spawn(move || {
25675      let owner_thread = std::thread::current().id();
25676      let await_controller = Arc::clone(&owner_controller);
25677      let child_controller = Arc::clone(&owner_controller);
25678      let outer = owner_controller
25679        .try_spawn_blocking(move || {
25680          let spawn_controller = Arc::clone(&child_controller);
25681          let child = spawn_controller
25682            .try_spawn(async move {
25683              child_polled_tx.send(std::thread::current().id()).unwrap();
25684              release_child_rx.recv().unwrap();
25685              child_controller
25686                .try_spawn_blocking(move || {
25687                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
25688                  41usize
25689                })
25690                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
25691                .await
25692                .unwrap()
25693                + 1
25694            })
25695            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25696
25697          assert_eq!(
25698            child_polled_rx
25699              .recv_timeout(Duration::from_secs(1))
25700              .unwrap(),
25701            driver_thread,
25702            "the secondary driver must own the child poll that reaches blocking admission"
25703          );
25704          owner_awaiting_tx.send(owner_thread).unwrap();
25705          block_on_with_controller(&await_controller, child).unwrap()
25706        })
25707        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25708      owner_done_tx
25709        .send(futures::executor::block_on(outer))
25710        .unwrap();
25711    });
25712
25713    let owner_thread = owner_awaiting_rx
25714      .recv_timeout(Duration::from_secs(1))
25715      .unwrap();
25716    wait_until(
25717      "the blocking owner to park while awaiting the cross-driver child",
25718      || {
25719        executor
25720          .stop
25721          .parkers
25722          .lock()
25723          .unwrap_or_else(std::sync::PoisonError::into_inner)
25724          .iter()
25725          .filter_map(Weak::upgrade)
25726          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25727      },
25728    );
25729    release_child_tx.send(()).unwrap();
25730
25731    assert_eq!(
25732      blocking_ran_rx
25733        .recv_timeout(Duration::from_secs(2))
25734        .unwrap(),
25735      owner_thread,
25736      "the exact descendant must be serviced by the lane-owning driver"
25737    );
25738    assert_eq!(
25739      owner_done_rx
25740        .recv_timeout(Duration::from_secs(2))
25741        .expect("cross-driver blocking dependency must not deadlock")
25742        .unwrap(),
25743      42
25744    );
25745    release_driver_tx.send(()).unwrap();
25746    owner.join().unwrap();
25747    driver.join().unwrap();
25748    controller.shutdown().unwrap();
25749    assert_eq!(
25750      controller
25751        .metrics
25752        .blocking_tasks_started
25753        .load(Ordering::Relaxed),
25754      2
25755    );
25756    assert_eq!(
25757      controller
25758        .metrics
25759        .blocking_tasks_completed
25760        .load(Ordering::Relaxed),
25761      2
25762    );
25763    assert_eq!(
25764      controller
25765        .metrics
25766        .active_blocking_tasks
25767        .load(Ordering::Relaxed),
25768      0
25769    );
25770    assert_eq!(
25771      controller
25772        .metrics
25773        .max_active_blocking_tasks
25774        .load(Ordering::Relaxed),
25775      1,
25776      "cross-driver dependency service must reuse the owner's physical lane"
25777    );
25778  }
25779
25780  #[cfg(napi_runtime_os_threads)]
25781  #[test]
25782  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25783    use std::sync::mpsc;
25784
25785    let controller = Arc::new(current_thread_controller(
25786      "current-thread-blocking-stop-race",
25787    ));
25788    let backend = controller.backend();
25789    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25790      panic!("configured CurrentThread backend must create a CurrentThread executor");
25791    };
25792    let executor = Arc::clone(executor);
25793    drop(backend);
25794
25795    let ran = Arc::new(AtomicBool::new(false));
25796    let blocking_ran = Arc::clone(&ran);
25797    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
25798    let (release_admission_tx, release_admission_rx) = mpsc::channel();
25799    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
25800    let submit_controller = Arc::clone(&controller);
25801    let submitter = std::thread::spawn(move || {
25802      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25803        *slot.borrow_mut() = Some(Box::new(move || {
25804          admission_locked_tx.send(()).unwrap();
25805          release_admission_rx.recv().unwrap();
25806        }));
25807      });
25808      let handle = submit_controller
25809        .try_spawn_blocking(move || {
25810          blocking_ran.store(true, Ordering::SeqCst);
25811        })
25812        .unwrap_or_else(|_| {
25813          panic!("the submission linearized before shutdown and must be accepted")
25814        });
25815      blocking_result_tx
25816        .send(futures::executor::block_on(handle))
25817        .unwrap();
25818    });
25819    admission_locked_rx
25820      .recv_timeout(Duration::from_secs(2))
25821      .expect("the blocking submitter must pause while holding the admission lock");
25822
25823    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
25824    let shutdown_controller = Arc::clone(&controller);
25825    let shutdown = std::thread::spawn(move || {
25826      shutdown_result_tx
25827        .send(shutdown_controller.shutdown())
25828        .unwrap();
25829    });
25830    wait_until("CurrentThread shutdown to publish generation stop", || {
25831      executor.stop.is_stopping()
25832    });
25833    release_admission_tx.send(()).unwrap();
25834
25835    let blocking_error = blocking_result_rx
25836      .recv_timeout(Duration::from_secs(2))
25837      .expect("the admitted blocking handle must resolve")
25838      .expect_err("stop publication must reject the blocking lane claim");
25839    assert_eq!(
25840      blocking_error.to_string(),
25841      "the async runtime stopped before the blocking task could start"
25842    );
25843    assert!(
25844      !ran.load(Ordering::SeqCst),
25845      "blocking work must not start after stop publication"
25846    );
25847    shutdown_result_rx
25848      .recv_timeout(Duration::from_secs(2))
25849      .expect("shutdown must finish after the rejected admission retires")
25850      .unwrap();
25851    submitter.join().unwrap();
25852    shutdown.join().unwrap();
25853  }
25854
25855  #[cfg(napi_runtime_os_threads)]
25856  #[test]
25857  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
25858    use std::sync::mpsc;
25859
25860    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25861      RuntimeMetrics::default(),
25862    )));
25863    let ran = Arc::new(AtomicBool::new(false));
25864    let job_ran = Arc::clone(&ran);
25865    {
25866      let mut state = executor
25867        .blocking_admission
25868        .state
25869        .lock()
25870        .unwrap_or_else(std::sync::PoisonError::into_inner);
25871      state.queue.push(QueuedBlocking {
25872        id: BlockingJobId(u64::MAX - 71),
25873        run: Box::new(move || {
25874          job_ran.store(true, Ordering::SeqCst);
25875        }),
25876      });
25877    }
25878
25879    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25880    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25881    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25882    let claim_executor = Arc::clone(&executor);
25883    let claimant = std::thread::spawn(move || {
25884      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25885        *slot.borrow_mut() = Some(Box::new(move || {
25886          claim_locked_tx.send(()).unwrap();
25887          release_claim_rx.recv().unwrap();
25888        }));
25889      });
25890      claim_result_tx
25891        .send(claim_executor.run_one_blocking())
25892        .unwrap();
25893    });
25894    claim_locked_rx
25895      .recv_timeout(Duration::from_secs(2))
25896      .expect("the FIFO claimant must pause while holding the admission lock");
25897
25898    executor.stop.begin_shutdown();
25899    release_claim_tx.send(()).unwrap();
25900    assert!(
25901      !claim_result_rx
25902        .recv_timeout(Duration::from_secs(2))
25903        .expect("the stopped FIFO claim must finish"),
25904      "stop publication must reject the queued FIFO claim"
25905    );
25906    claimant.join().unwrap();
25907    assert!(
25908      !ran.load(Ordering::SeqCst),
25909      "queued FIFO work must not start after stop publication"
25910    );
25911    assert!(
25912      !executor
25913        .blocking_admission
25914        .state
25915        .lock()
25916        .unwrap_or_else(std::sync::PoisonError::into_inner)
25917        .queue
25918        .is_empty(),
25919      "the rejected FIFO job must remain queued for shutdown cancellation"
25920    );
25921
25922    executor.close_blocking_admission();
25923    assert!(
25924      executor
25925        .blocking_admission
25926        .state
25927        .lock()
25928        .unwrap_or_else(std::sync::PoisonError::into_inner)
25929        .queue
25930        .is_empty()
25931    );
25932  }
25933
25934  #[cfg(napi_runtime_os_threads)]
25935  #[test]
25936  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25937    use std::sync::mpsc;
25938
25939    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25940      RuntimeMetrics::default(),
25941    )));
25942    let ran = Arc::new(AtomicBool::new(false));
25943    let job_ran = Arc::clone(&ran);
25944    {
25945      let mut state = executor
25946        .blocking_admission
25947        .state
25948        .lock()
25949        .unwrap_or_else(std::sync::PoisonError::into_inner);
25950      state.queue.push(QueuedBlocking {
25951        id: BlockingJobId(u64::MAX - 73),
25952        run: Box::new(move || {
25953          job_ran.store(true, Ordering::SeqCst);
25954        }),
25955      });
25956    }
25957
25958    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25959    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25960    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25961    let claim_executor = Arc::clone(&executor);
25962    let claimant = std::thread::spawn(move || {
25963      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25964        *slot.borrow_mut() = Some(Box::new(move || {
25965          claim_locked_tx.send(()).unwrap();
25966          release_claim_rx.recv().unwrap();
25967        }));
25968      });
25969      claim_result_tx
25970        .send(claim_executor.run_one_blocking())
25971        .unwrap();
25972    });
25973    claim_locked_rx
25974      .recv_timeout(Duration::from_secs(2))
25975      .expect("the FIFO claimant must pause while holding the admission lock");
25976
25977    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25978    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25979    let cancelling_executor = Arc::clone(&executor);
25980    let cancellation = std::thread::spawn(move || {
25981      let cancelling = cancelling_executor
25982        .begin_failed_dispatch_cancellation()
25983        .expect("the test must publish terminal dispatch cancellation");
25984      cancellation_published_tx.send(()).unwrap();
25985      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25986      cancellation_done_tx.send(()).unwrap();
25987    });
25988    cancellation_published_rx
25989      .recv_timeout(Duration::from_secs(2))
25990      .expect("terminal dispatch cancellation must publish before queue cancellation");
25991    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25992    assert!(
25993      matches!(
25994        cancellation_done_rx.try_recv(),
25995        Err(mpsc::TryRecvError::Empty)
25996      ),
25997      "terminal cancellation must wait for the blocking-admission lock"
25998    );
25999
26000    release_claim_tx.send(()).unwrap();
26001    assert!(
26002      !claim_result_rx
26003        .recv_timeout(Duration::from_secs(2))
26004        .expect("the cancelled FIFO claim must finish"),
26005      "terminal cancellation publication must reject the queued FIFO claim"
26006    );
26007    cancellation_done_rx
26008      .recv_timeout(Duration::from_secs(2))
26009      .expect("terminal cancellation must drain the rejected FIFO job");
26010    claimant.join().unwrap();
26011    cancellation.join().unwrap();
26012
26013    assert!(
26014      !ran.load(Ordering::SeqCst),
26015      "terminally cancelled FIFO work must not start"
26016    );
26017    assert!(
26018      executor
26019        .blocking_admission
26020        .state
26021        .lock()
26022        .unwrap_or_else(std::sync::PoisonError::into_inner)
26023        .queue
26024        .is_empty(),
26025      "terminal cancellation must drain the rejected FIFO job"
26026    );
26027    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26028  }
26029
26030  #[cfg(napi_runtime_os_threads)]
26031  #[test]
26032  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
26033    use std::sync::mpsc;
26034
26035    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26036      RuntimeMetrics::default(),
26037    )));
26038    let owner = executor.fresh_owner_token();
26039    let job = BlockingJobId(u64::MAX - 72);
26040    let ran = Arc::new(AtomicBool::new(false));
26041    let job_ran = Arc::clone(&ran);
26042    {
26043      let mut state = executor
26044        .blocking_admission
26045        .state
26046        .lock()
26047        .unwrap_or_else(std::sync::PoisonError::into_inner);
26048      state.owner = Some(owner);
26049      state.queue.push(QueuedBlocking {
26050        id: job,
26051        run: Box::new(move || {
26052          job_ran.store(true, Ordering::SeqCst);
26053        }),
26054      });
26055    }
26056    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26057    dependency.set_owned(BlockingDependency {
26058      job,
26059      owner: Some(owner),
26060    });
26061
26062    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26063    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26064    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26065    let claim_executor = Arc::clone(&executor);
26066    let claim_dependency = Arc::clone(&dependency);
26067    let claimant = std::thread::spawn(move || {
26068      let _owner = claim_executor.enter_blocking_owner(owner, false);
26069      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26070        *slot.borrow_mut() = Some(Box::new(move || {
26071          claim_locked_tx.send(()).unwrap();
26072          release_claim_rx.recv().unwrap();
26073        }));
26074      });
26075      claim_result_tx
26076        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26077        .unwrap();
26078    });
26079    claim_locked_rx
26080      .recv_timeout(Duration::from_secs(2))
26081      .expect("the owner claimant must pause while holding the admission lock");
26082
26083    executor.stop.begin_shutdown();
26084    release_claim_tx.send(()).unwrap();
26085    assert!(
26086      !claim_result_rx
26087        .recv_timeout(Duration::from_secs(2))
26088        .expect("the stopped owner claim must finish"),
26089      "stop publication must reject the exact-owner claim"
26090    );
26091    claimant.join().unwrap();
26092    assert!(
26093      !ran.load(Ordering::SeqCst),
26094      "queued exact-owner work must not start after stop publication"
26095    );
26096    assert!(
26097      !executor
26098        .blocking_admission
26099        .state
26100        .lock()
26101        .unwrap_or_else(std::sync::PoisonError::into_inner)
26102        .queue
26103        .is_empty(),
26104      "the rejected exact-owner job must remain queued for shutdown cancellation"
26105    );
26106
26107    executor
26108      .blocking_admission
26109      .state
26110      .lock()
26111      .unwrap_or_else(std::sync::PoisonError::into_inner)
26112      .owner = None;
26113    executor.close_blocking_admission();
26114    assert!(
26115      executor
26116        .blocking_admission
26117        .state
26118        .lock()
26119        .unwrap_or_else(std::sync::PoisonError::into_inner)
26120        .queue
26121        .is_empty()
26122    );
26123  }
26124
26125  #[cfg(napi_runtime_os_threads)]
26126  #[test]
26127  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
26128    use std::sync::mpsc;
26129
26130    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
26131      RuntimeMetrics::default(),
26132    )));
26133    let owner = executor.fresh_owner_token();
26134    let job = BlockingJobId(u64::MAX - 74);
26135    let ran = Arc::new(AtomicBool::new(false));
26136    let job_ran = Arc::clone(&ran);
26137    {
26138      let mut state = executor
26139        .blocking_admission
26140        .state
26141        .lock()
26142        .unwrap_or_else(std::sync::PoisonError::into_inner);
26143      state.owner = Some(owner);
26144      state.queue.push(QueuedBlocking {
26145        id: job,
26146        run: Box::new(move || {
26147          job_ran.store(true, Ordering::SeqCst);
26148        }),
26149      });
26150    }
26151    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
26152    dependency.set_owned(BlockingDependency {
26153      job,
26154      owner: Some(owner),
26155    });
26156
26157    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
26158    let (release_claim_tx, release_claim_rx) = mpsc::channel();
26159    let (claim_result_tx, claim_result_rx) = mpsc::channel();
26160    let claim_executor = Arc::clone(&executor);
26161    let claim_dependency = Arc::clone(&dependency);
26162    let claimant = std::thread::spawn(move || {
26163      let _owner = claim_executor.enter_blocking_owner(owner, false);
26164      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
26165        *slot.borrow_mut() = Some(Box::new(move || {
26166          claim_locked_tx.send(()).unwrap();
26167          release_claim_rx.recv().unwrap();
26168        }));
26169      });
26170      claim_result_tx
26171        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
26172        .unwrap();
26173    });
26174    claim_locked_rx
26175      .recv_timeout(Duration::from_secs(2))
26176      .expect("the owner claimant must pause while holding the admission lock");
26177
26178    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
26179    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
26180    let cancelling_executor = Arc::clone(&executor);
26181    let cancellation = std::thread::spawn(move || {
26182      let cancelling = cancelling_executor
26183        .begin_failed_dispatch_cancellation()
26184        .expect("the test must publish terminal dispatch cancellation");
26185      cancellation_published_tx.send(()).unwrap();
26186      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
26187      cancellation_done_tx.send(()).unwrap();
26188    });
26189    cancellation_published_rx
26190      .recv_timeout(Duration::from_secs(2))
26191      .expect("terminal dispatch cancellation must publish before queue cancellation");
26192    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26193    assert!(
26194      matches!(
26195        cancellation_done_rx.try_recv(),
26196        Err(mpsc::TryRecvError::Empty)
26197      ),
26198      "terminal cancellation must wait for the blocking-admission lock"
26199    );
26200
26201    release_claim_tx.send(()).unwrap();
26202    assert!(
26203      !claim_result_rx
26204        .recv_timeout(Duration::from_secs(2))
26205        .expect("the cancelled owner claim must finish"),
26206      "terminal cancellation publication must reject the exact-owner claim"
26207    );
26208    cancellation_done_rx
26209      .recv_timeout(Duration::from_secs(2))
26210      .expect("terminal cancellation must drain the rejected exact-owner job");
26211    claimant.join().unwrap();
26212    cancellation.join().unwrap();
26213
26214    assert!(
26215      !ran.load(Ordering::SeqCst),
26216      "terminally cancelled exact-owner work must not start"
26217    );
26218    assert!(
26219      executor
26220        .blocking_admission
26221        .state
26222        .lock()
26223        .unwrap_or_else(std::sync::PoisonError::into_inner)
26224        .queue
26225        .is_empty(),
26226      "terminal cancellation must drain the rejected exact-owner job"
26227    );
26228    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
26229  }
26230
26231  #[cfg(napi_runtime_os_threads)]
26232  #[test]
26233  fn current_thread_shutdown_cancels_waiting_blocking_work() {
26234    use std::sync::mpsc;
26235
26236    struct DropSignal(mpsc::Sender<()>);
26237
26238    impl Drop for DropSignal {
26239      fn drop(&mut self) {
26240        let _ = self.0.send(());
26241      }
26242    }
26243
26244    let controller = Arc::new(current_thread_controller(
26245      "current-thread-blocking-shutdown",
26246    ));
26247    let backend = controller.backend();
26248    let (first_started_tx, first_started_rx) = mpsc::channel();
26249    let (release_first_tx, release_first_rx) = mpsc::channel();
26250    let first_controller = Arc::clone(&controller);
26251    let first = std::thread::spawn(move || {
26252      let handle = first_controller
26253        .try_spawn_blocking(move || {
26254          first_started_tx.send(()).unwrap();
26255          release_first_rx.recv().unwrap();
26256        })
26257        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
26258      futures::executor::block_on(handle)
26259    });
26260    first_started_rx
26261      .recv_timeout(Duration::from_secs(1))
26262      .unwrap();
26263
26264    let waiting_ran = Arc::new(AtomicBool::new(false));
26265    let waiting_ran_task = Arc::clone(&waiting_ran);
26266    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
26267    let waiting_drop = DropSignal(waiting_dropped_tx);
26268    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
26269    let waiting_controller = Arc::clone(&controller);
26270    let waiting = std::thread::spawn(move || {
26271      let handle = waiting_controller
26272        .try_spawn_blocking(move || {
26273          let _waiting_drop = waiting_drop;
26274          waiting_ran_task.store(true, Ordering::SeqCst);
26275        })
26276        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
26277      waiting_result_tx
26278        .send(futures::executor::block_on(handle))
26279        .unwrap();
26280    });
26281    wait_until("both CurrentThread blocking calls to be accepted", || {
26282      backend
26283        .work
26284        .state
26285        .lock()
26286        .unwrap_or_else(std::sync::PoisonError::into_inner)
26287        .active
26288        == 2
26289    });
26290
26291    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26292    let shutdown_controller = Arc::clone(&controller);
26293    let shutdown = std::thread::spawn(move || {
26294      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26295    });
26296    waiting_dropped_rx
26297      .recv_timeout(Duration::from_secs(1))
26298      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
26299    assert!(
26300      !waiting_ran.load(Ordering::SeqCst),
26301      "waiting blocking work must not start"
26302    );
26303    assert!(
26304      waiting_result_rx
26305        .recv_timeout(Duration::from_secs(1))
26306        .unwrap()
26307        .is_err(),
26308      "the waiting blocking handle must resolve as cancelled"
26309    );
26310    assert!(
26311      shutdown_rx
26312        .recv_timeout(Duration::from_millis(100))
26313        .is_err(),
26314      "shutdown must still wait for the blocking closure that already owns the lane"
26315    );
26316
26317    release_first_tx.send(()).unwrap();
26318    first.join().unwrap().unwrap();
26319    waiting.join().unwrap();
26320    shutdown_rx
26321      .recv_timeout(Duration::from_secs(1))
26322      .unwrap()
26323      .unwrap();
26324    shutdown.join().unwrap();
26325    assert_eq!(
26326      controller
26327        .metrics
26328        .blocking_tasks_started
26329        .load(Ordering::Relaxed),
26330      1
26331    );
26332    assert_eq!(
26333      controller
26334        .metrics
26335        .blocking_tasks_completed
26336        .load(Ordering::Relaxed),
26337      1
26338    );
26339    assert_eq!(
26340      controller
26341        .metrics
26342        .active_blocking_tasks
26343        .load(Ordering::Relaxed),
26344      0
26345    );
26346  }
26347
26348  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
26349  #[test]
26350  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
26351    use std::{panic::panic_any, sync::mpsc};
26352
26353    struct ReentrantPayload {
26354      controller: Arc<RuntimeController>,
26355      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26356    }
26357
26358    impl Drop for ReentrantPayload {
26359      fn drop(&mut self) {
26360        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26361        let shutdown = self.controller.shutdown();
26362        self.result.send((generation, shutdown)).unwrap();
26363      }
26364    }
26365
26366    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26367
26368    impl Drop for PanicPayloadOnDrop {
26369      fn drop(&mut self) {
26370        panic_any(self.0.take().expect("the result must panic only once"));
26371      }
26372    }
26373
26374    let controller = Arc::new(current_thread_controller(
26375      "current-thread-panic-payload-generation",
26376    ));
26377    let backend = controller.backend();
26378    let generation = backend.generation();
26379    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
26380      panic!("configured CurrentThread backend must create a CurrentThread executor");
26381    };
26382    let executor = Arc::clone(executor);
26383    drop(backend);
26384
26385    // Hold the physical lane long enough to force scheduler-owned delivery.
26386    // Dropping the receiver then makes the queued sender destroy the result.
26387    let owner = executor.fresh_owner_token();
26388    executor
26389      .blocking_admission
26390      .state
26391      .lock()
26392      .unwrap_or_else(std::sync::PoisonError::into_inner)
26393      .owner = Some(owner);
26394    let (result_tx, result_rx) = mpsc::channel();
26395    let payload_controller = Arc::clone(&controller);
26396    let handle = controller
26397      .try_spawn_blocking(move || {
26398        PanicPayloadOnDrop(Some(ReentrantPayload {
26399          controller: payload_controller,
26400          result: result_tx,
26401        }))
26402      })
26403      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26404    drop(handle);
26405    executor
26406      .blocking_admission
26407      .state
26408      .lock()
26409      .unwrap_or_else(std::sync::PoisonError::into_inner)
26410      .owner = None;
26411    let dispatch = current_thread_dispatch(&executor);
26412    controller.drive_current_thread_dispatch(dispatch, false);
26413
26414    let (observed_generation, shutdown) = result_rx
26415      .recv_timeout(Duration::from_secs(1))
26416      .expect("the caught result panic payload must be destroyed in the scheduler turn");
26417    assert_eq!(observed_generation, Some(generation));
26418    let error = shutdown.expect_err("payload destruction is still work in the active generation");
26419    assert!(error.to_string().contains("work running on that runtime"));
26420    controller.shutdown().unwrap();
26421  }
26422
26423  #[cfg(panic = "unwind")]
26424  #[test]
26425  fn current_thread_detached_blocking_result_drop_is_contained() {
26426    use std::sync::mpsc;
26427
26428    struct PanicOnDrop(mpsc::Sender<()>);
26429
26430    impl Drop for PanicOnDrop {
26431      fn drop(&mut self) {
26432        self.0.send(()).unwrap();
26433        panic!("intentional CurrentThread blocking result destructor panic");
26434      }
26435    }
26436
26437    let controller = current_thread_controller("current-thread-blocking-result-drop");
26438    let (dropped_tx, dropped_rx) = mpsc::channel();
26439    let handle = controller
26440      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
26441      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
26442
26443    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26444    dropped_rx
26445      .recv_timeout(Duration::from_secs(1))
26446      .expect("dropping the detached handle must reclaim its blocking result");
26447    assert!(
26448      result.is_ok(),
26449      "the blocking result destructor panic must be contained"
26450    );
26451    controller.shutdown().unwrap();
26452  }
26453
26454  // ---- Runtime option validation and configuration immutability -------------
26455  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
26456  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
26457
26458  #[test]
26459  fn validate_rejects_zero_worker_threads() {
26460    let error = RuntimeOptions {
26461      flavor: RuntimeFlavor::MultiThread,
26462      worker_threads: 0,
26463      max_blocking_tasks: 1,
26464      thread_name_prefix: "rd8".to_string(),
26465      park_deadline: None,
26466      drain_linger: DEFAULT_DRAIN_LINGER,
26467    }
26468    .validate()
26469    .expect_err("worker_threads == 0 must be rejected");
26470    assert_eq!(
26471      error.to_string(),
26472      "worker_threads must be greater than zero"
26473    );
26474  }
26475
26476  #[test]
26477  fn validate_rejects_zero_max_blocking_tasks() {
26478    let error = RuntimeOptions {
26479      flavor: RuntimeFlavor::MultiThread,
26480      worker_threads: 2,
26481      max_blocking_tasks: 0,
26482      thread_name_prefix: "rd8".to_string(),
26483      park_deadline: None,
26484      drain_linger: DEFAULT_DRAIN_LINGER,
26485    }
26486    .validate()
26487    .expect_err("max_blocking_tasks == 0 must be rejected");
26488    assert_eq!(
26489      error.to_string(),
26490      "max_blocking_tasks must be greater than zero"
26491    );
26492  }
26493
26494  #[test]
26495  fn validate_current_thread_coerces_worker_threads_to_one() {
26496    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
26497    // clamped to `.min(worker_threads)` == 1.
26498    let validated = RuntimeOptions {
26499      flavor: RuntimeFlavor::CurrentThread,
26500      worker_threads: 8,
26501      max_blocking_tasks: 8,
26502      thread_name_prefix: "rd8".to_string(),
26503      park_deadline: None,
26504      drain_linger: DEFAULT_DRAIN_LINGER,
26505    }
26506    .validate()
26507    .expect("CurrentThread options must validate");
26508    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
26509    assert_eq!(validated.worker_threads, 1);
26510    assert_eq!(validated.max_blocking_tasks, 1);
26511  }
26512
26513  // `validate()` rejects `MultiThread` on builds that cannot create OS threads
26514  // (after applying the clamp), so this success assertion is valid only where
26515  // `napi_runtime_os_threads` holds: native and `wasm32-wasip1-threads`.
26516  #[cfg(napi_runtime_os_threads)]
26517  #[test]
26518  fn validate_reserves_one_worker_from_blocking_admission() {
26519    let validated = RuntimeOptions {
26520      flavor: RuntimeFlavor::MultiThread,
26521      worker_threads: 2,
26522      max_blocking_tasks: 8,
26523      thread_name_prefix: "rd8".to_string(),
26524      park_deadline: None,
26525      drain_linger: DEFAULT_DRAIN_LINGER,
26526    }
26527    .validate()
26528    .expect("MultiThread options must validate");
26529    assert_eq!(validated.worker_threads, 2);
26530    assert_eq!(validated.max_blocking_tasks, 1);
26531  }
26532
26533  #[cfg(napi_runtime_os_threads)]
26534  #[test]
26535  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
26536    let validated = RuntimeOptions {
26537      flavor: RuntimeFlavor::MultiThread,
26538      worker_threads: 1,
26539      max_blocking_tasks: 8,
26540      thread_name_prefix: "rd8-minimum".to_string(),
26541      park_deadline: None,
26542      drain_linger: DEFAULT_DRAIN_LINGER,
26543    }
26544    .validate()
26545    .expect("MultiThread options must validate");
26546    assert_eq!(validated.worker_threads, 2);
26547    assert_eq!(validated.max_blocking_tasks, 1);
26548  }
26549
26550  #[cfg(napi_runtime_os_threads)]
26551  #[test]
26552  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
26553    let simulated_32_bit = RuntimeOptions {
26554      flavor: RuntimeFlavor::MultiThread,
26555      worker_threads: 256,
26556      max_blocking_tasks: 256,
26557      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
26558      park_deadline: None,
26559      drain_linger: DEFAULT_DRAIN_LINGER,
26560    }
26561    .validate_with_rayon_max_threads(Some(255))
26562    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
26563    assert_eq!(simulated_32_bit.worker_threads, 255);
26564    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
26565
26566    let rayon_max_threads = max_num_threads();
26567    let requested_threads = rayon_max_threads
26568      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
26569      .checked_add(1)
26570      .expect("supported worker counts must leave room for an oversized request");
26571    let controller = RuntimeController::new();
26572    controller
26573      .configure(RuntimeOptions {
26574        flavor: RuntimeFlavor::MultiThread,
26575        worker_threads: requested_threads,
26576        max_blocking_tasks: requested_threads,
26577        thread_name_prefix: "rd8-rayon-cap".to_string(),
26578        park_deadline: None,
26579        drain_linger: DEFAULT_DRAIN_LINGER,
26580      })
26581      .expect("an oversized request must clamp to the production worker ceiling");
26582
26583    let reported = controller.options();
26584    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
26585    assert_eq!(reported.worker_threads, expected_workers);
26586    assert_eq!(
26587      reported.max_blocking_tasks,
26588      expected_workers - 1,
26589      "the blocking cap must reserve a lane from the physically realizable worker count"
26590    );
26591  }
26592
26593  #[cfg(napi_runtime_os_threads)]
26594  #[test]
26595  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
26596    // u64::MAX microseconds (the exact value an unvalidated
26597    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
26598    // via the removed OnceLock) must clamp to the ceiling everywhere a
26599    // budget enters the system: full options validation, a partial patch
26600    // through the controller, and the executor's defensive constructor
26601    // clamp for direct unvalidated test construction.
26602    let extreme = Duration::from_micros(u64::MAX);
26603    let validated = RuntimeOptions {
26604      flavor: RuntimeFlavor::MultiThread,
26605      worker_threads: 2,
26606      max_blocking_tasks: 1,
26607      thread_name_prefix: "linger-clamp".to_string(),
26608      park_deadline: None,
26609      drain_linger: extreme,
26610    }
26611    .validate_with_rayon_max_threads(Some(2))
26612    .expect("an extreme linger budget must clamp, not fail validation");
26613    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
26614
26615    let controller = RuntimeController::new();
26616    controller
26617      .configure_partial(RuntimeOptionsPatch {
26618        drain_linger: Some(extreme),
26619        ..RuntimeOptionsPatch::default()
26620      })
26621      .expect("an extreme linger patch must clamp, not fail validation");
26622    assert_eq!(
26623      controller.options().drain_linger,
26624      MAX_DRAIN_LINGER,
26625      "introspection must report the clamped budget, not the requested one"
26626    );
26627    controller
26628      .configure_partial(RuntimeOptionsPatch {
26629        drain_linger: Some(Duration::ZERO),
26630        ..RuntimeOptionsPatch::default()
26631      })
26632      .expect("ZERO (lingering disabled) must remain a valid budget");
26633    assert_eq!(controller.options().drain_linger, Duration::ZERO);
26634
26635    let metrics = Arc::new(RuntimeMetrics::default());
26636    let unvalidated = RuntimeOptions {
26637      flavor: RuntimeFlavor::MultiThread,
26638      worker_threads: 2,
26639      max_blocking_tasks: 2,
26640      thread_name_prefix: "linger-clamp-exec".to_string(),
26641      park_deadline: None,
26642      drain_linger: extreme,
26643    };
26644    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
26645      .expect("the executor must construct with an extreme unvalidated budget");
26646    assert_eq!(
26647      executor.drain_linger,
26648      Some(MAX_DRAIN_LINGER),
26649      "the constructor's defensive clamp must bound unvalidated budgets"
26650    );
26651
26652    let metrics = Arc::new(RuntimeMetrics::default());
26653    let disabled = RuntimeOptions {
26654      flavor: RuntimeFlavor::MultiThread,
26655      worker_threads: 2,
26656      max_blocking_tasks: 2,
26657      thread_name_prefix: "linger-clamp-off".to_string(),
26658      park_deadline: None,
26659      drain_linger: Duration::ZERO,
26660    };
26661    let executor = MultiThreadExecutor::new(&disabled, metrics)
26662      .expect("the executor must construct with lingering disabled");
26663    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
26664  }
26665
26666  #[cfg(not(target_family = "wasm"))]
26667  #[test]
26668  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
26669    let controller = RuntimeController::new();
26670    let options = controller.options();
26671    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
26672    assert_eq!(
26673      options.max_blocking_tasks,
26674      options.worker_threads.saturating_sub(1).max(1),
26675      "the unconfigured Rust API must use the same validated reserve as the binding"
26676    );
26677  }
26678
26679  #[cfg(napi_runtime_os_threads)]
26680  #[test]
26681  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
26682    use std::{sync::mpsc, time::Duration};
26683
26684    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
26685    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
26686    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
26687    let first_controller = Arc::clone(&controller);
26688    let first = std::thread::spawn(move || {
26689      first_controller
26690        .configure_partial_inner(
26691          RuntimeOptionsPatch {
26692            max_blocking_tasks: Some(3),
26693            ..RuntimeOptionsPatch::default()
26694          },
26695          |candidate| {
26696            assert_eq!(
26697              (candidate.worker_threads, candidate.max_blocking_tasks),
26698              (8, 3)
26699            );
26700            first_merged_tx
26701              .send(())
26702              .expect("the race coordinator must still be listening");
26703            release_first_rx
26704              .recv_timeout(Duration::from_secs(5))
26705              .expect("the first partial configuration must be released");
26706          },
26707        )
26708        .expect("the first partial configuration must commit");
26709    });
26710    first_merged_rx
26711      .recv_timeout(Duration::from_secs(5))
26712      .expect("the first partial configuration must hold the controller lock after merging");
26713
26714    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26715    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26716    let second_controller = Arc::clone(&controller);
26717    let second = std::thread::spawn(move || {
26718      second_started_tx
26719        .send(())
26720        .expect("the race coordinator must still be listening");
26721      second_controller
26722        .configure_partial_inner(
26723          RuntimeOptionsPatch {
26724            worker_threads: Some(6),
26725            ..RuntimeOptionsPatch::default()
26726          },
26727          |candidate| {
26728            second_candidate_tx
26729              .send((candidate.worker_threads, candidate.max_blocking_tasks))
26730              .expect("the race coordinator must still be listening");
26731          },
26732        )
26733        .expect("the second partial configuration must commit");
26734    });
26735    second_started_rx
26736      .recv_timeout(Duration::from_secs(5))
26737      .expect("the second partial configuration thread must start");
26738    release_first_tx
26739      .send(())
26740      .expect("the first partial configuration must still be waiting");
26741
26742    first
26743      .join()
26744      .expect("the first configuration thread must not panic");
26745    second
26746      .join()
26747      .expect("the second configuration thread must not panic");
26748    assert_eq!(
26749      second_candidate_rx
26750        .recv_timeout(Duration::from_secs(5))
26751        .expect("the second partial configuration must expose its merged candidate"),
26752      (6, 3),
26753      "the second caller must merge against the first caller's committed options"
26754    );
26755
26756    let options = controller.options();
26757    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26758    assert_eq!(options.thread_name_prefix, "partial-config-race");
26759  }
26760
26761  #[cfg(napi_runtime_os_threads)]
26762  #[test]
26763  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26764    use std::{sync::mpsc, time::Duration};
26765
26766    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26767    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26768    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26769    let invalid_controller = Arc::clone(&controller);
26770    let invalid = std::thread::spawn(move || {
26771      invalid_controller
26772        .configure_partial_inner(
26773          RuntimeOptionsPatch {
26774            worker_threads: Some(0),
26775            ..RuntimeOptionsPatch::default()
26776          },
26777          |candidate| {
26778            assert_eq!(
26779              (candidate.worker_threads, candidate.max_blocking_tasks),
26780              (0, 2)
26781            );
26782            invalid_merged_tx
26783              .send(())
26784              .expect("the race coordinator must still be listening");
26785            release_invalid_rx
26786              .recv_timeout(Duration::from_secs(5))
26787              .expect("the invalid partial configuration must be released");
26788          },
26789        )
26790        .expect_err("the invalid partial configuration must be rejected")
26791        .to_string()
26792    });
26793    invalid_merged_rx
26794      .recv_timeout(Duration::from_secs(5))
26795      .expect("the invalid partial configuration must hold the lock after merging");
26796
26797    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
26798    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
26799    let valid_controller = Arc::clone(&controller);
26800    let valid = std::thread::spawn(move || {
26801      valid_started_tx
26802        .send(())
26803        .expect("the race coordinator must still be listening");
26804      valid_controller
26805        .configure_partial_inner(
26806          RuntimeOptionsPatch {
26807            max_blocking_tasks: Some(4),
26808            ..RuntimeOptionsPatch::default()
26809          },
26810          |candidate| {
26811            valid_candidate_tx
26812              .send((candidate.worker_threads, candidate.max_blocking_tasks))
26813              .expect("the race coordinator must still be listening");
26814          },
26815        )
26816        .expect("the valid partial configuration must commit");
26817    });
26818    valid_started_rx
26819      .recv_timeout(Duration::from_secs(5))
26820      .expect("the valid partial configuration thread must start");
26821    release_invalid_tx
26822      .send(())
26823      .expect("the invalid partial configuration must still be waiting");
26824
26825    assert_eq!(
26826      invalid
26827        .join()
26828        .expect("the invalid configuration thread must not panic"),
26829      "worker_threads must be greater than zero"
26830    );
26831    valid
26832      .join()
26833      .expect("the valid configuration thread must not panic");
26834    assert_eq!(
26835      valid_candidate_rx
26836        .recv_timeout(Duration::from_secs(5))
26837        .expect("the valid partial configuration must expose its merged candidate"),
26838      (6, 4),
26839      "the valid caller must merge against the last committed options, not the rejected candidate"
26840    );
26841
26842    let options = controller.options();
26843    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
26844    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
26845  }
26846
26847  #[test]
26848  fn configure_after_backend_started_is_rejected() {
26849    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
26850    // so starting it is cheap and spawns no OS threads.
26851    let controller = RuntimeController::new();
26852    controller
26853      .configure(RuntimeOptions {
26854        flavor: RuntimeFlavor::CurrentThread,
26855        worker_threads: 1,
26856        max_blocking_tasks: 1,
26857        thread_name_prefix: "rd8".to_string(),
26858        park_deadline: None,
26859        drain_linger: DEFAULT_DRAIN_LINGER,
26860      })
26861      .expect("first configure before the backend exists must succeed");
26862
26863    // Materialize the backend; further configuration must now be refused.
26864    let _backend = controller.backend();
26865
26866    let error = controller
26867      .configure(RuntimeOptions::default())
26868      .expect_err("configure after the backend started must be rejected");
26869    assert_eq!(
26870      error.to_string(),
26871      "the async runtime configuration is frozen; configure it before the first async call"
26872    );
26873  }
26874
26875  #[test]
26876  fn partial_configuration_after_backend_started_is_rejected() {
26877    let controller = current_thread_controller("partial-config-frozen");
26878    let _backend = controller.backend();
26879
26880    let error = controller
26881      .configure_partial(RuntimeOptionsPatch {
26882        worker_threads: Some(2),
26883        ..RuntimeOptionsPatch::default()
26884      })
26885      .expect_err("partial configuration after the backend started must be rejected");
26886    assert_eq!(
26887      error.to_string(),
26888      "the async runtime configuration is frozen; configure it before the first async call"
26889    );
26890  }
26891
26892  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
26893    let controller = RuntimeController::new();
26894    controller
26895      .configure(RuntimeOptions {
26896        flavor: RuntimeFlavor::CurrentThread,
26897        worker_threads: 1,
26898        max_blocking_tasks: 1,
26899        thread_name_prefix: thread_name_prefix.to_string(),
26900        park_deadline: None,
26901        drain_linger: DEFAULT_DRAIN_LINGER,
26902      })
26903      .expect("CurrentThread test configuration must be accepted");
26904    controller
26905  }
26906
26907  #[cfg(napi_runtime_os_threads)]
26908  fn multi_thread_controller(
26909    thread_name_prefix: &str,
26910    worker_threads: usize,
26911    max_blocking_tasks: usize,
26912  ) -> RuntimeController {
26913    let controller = RuntimeController::new();
26914    controller
26915      .configure(RuntimeOptions {
26916        flavor: RuntimeFlavor::MultiThread,
26917        worker_threads,
26918        max_blocking_tasks,
26919        thread_name_prefix: thread_name_prefix.to_string(),
26920        park_deadline: None,
26921        drain_linger: DEFAULT_DRAIN_LINGER,
26922      })
26923      .expect("MultiThread test configuration must be accepted");
26924    controller
26925  }
26926
26927  #[test]
26928  fn blocking_backend_creation_failure_preserves_diagnostic() {
26929    let controller = current_thread_controller("blocking-backend-creation-failure");
26930    let ran = Arc::new(AtomicBool::new(false));
26931    let work_ran = Arc::clone(&ran);
26932
26933    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26934    let Err((error, work)) = controller.try_spawn_blocking(move || {
26935      work_ran.store(true, Ordering::SeqCst);
26936      17usize
26937    }) else {
26938      panic!("the injected backend creation failure must reject blocking work");
26939    };
26940    assert_eq!(
26941      error.to_string(),
26942      "injected async runtime backend creation failure"
26943    );
26944    assert!(
26945      !ran.load(Ordering::SeqCst),
26946      "rejected blocking work must not run"
26947    );
26948    assert_eq!(
26949      work(),
26950      17,
26951      "the rejected blocking closure must remain usable"
26952    );
26953    assert!(ran.load(Ordering::SeqCst));
26954
26955    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26956    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
26957      .expect_err("infallible blocking submission must return a failed join handle");
26958    assert_eq!(
26959      error.to_string(),
26960      "injected async runtime backend creation failure"
26961    );
26962
26963    let handle = controller
26964      .try_spawn_blocking(|| 31usize)
26965      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
26966    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26967    controller.shutdown().unwrap();
26968  }
26969
26970  #[cfg(not(target_family = "wasm"))]
26971  #[test]
26972  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
26973    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
26974
26975    if std::env::var_os(CHILD_ENV).is_some() {
26976      struct PollProbe {
26977        polls: Arc<AtomicUsize>,
26978      }
26979
26980      impl Future for PollProbe {
26981        type Output = ();
26982
26983        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26984          self.polls.fetch_add(1, Ordering::SeqCst);
26985          Poll::Ready(())
26986        }
26987      }
26988
26989      shutdown().expect("the isolated runtime must stop before block_on admission");
26990      let polls = Arc::new(AtomicUsize::new(0));
26991      let mut future = std::pin::pin!(PollProbe {
26992        polls: Arc::clone(&polls)
26993      });
26994      let error = try_block_on_dyn(future.as_mut())
26995        .expect_err("the stopped runtime must reject the borrowed future");
26996      assert_eq!(
26997        error.to_string(),
26998        "the async runtime is stopped; call start before submitting work"
26999      );
27000      assert_eq!(
27001        polls.load(Ordering::SeqCst),
27002        0,
27003        "rejected block_on must not poll its future"
27004      );
27005
27006      start().expect("the isolated runtime must restart");
27007      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
27008      assert_eq!(polls.load(Ordering::SeqCst), 1);
27009      shutdown().expect("the restarted isolated runtime must stop");
27010      return;
27011    }
27012
27013    let output = std::process::Command::new(std::env::current_exe().unwrap())
27014      .arg("--exact")
27015      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
27016      .arg("--nocapture")
27017      .env(CHILD_ENV, "1")
27018      .output()
27019      .expect("the stopped block_on subprocess must start");
27020    assert!(
27021      output.status.success(),
27022      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
27023      output.status.code(),
27024      String::from_utf8_lossy(&output.stdout),
27025      String::from_utf8_lossy(&output.stderr)
27026    );
27027  }
27028
27029  #[cfg(not(target_family = "wasm"))]
27030  #[test]
27031  fn try_block_on_dyn_reports_concurrent_shutdown() {
27032    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
27033
27034    if std::env::var_os(CHILD_ENV).is_some() {
27035      use std::sync::mpsc;
27036
27037      struct PendingProbe {
27038        entered: Option<mpsc::Sender<()>>,
27039        complete: Arc<AtomicBool>,
27040      }
27041
27042      impl Future for PendingProbe {
27043        type Output = ();
27044
27045        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27046          if let Some(entered) = self.entered.take() {
27047            entered.send(()).unwrap();
27048          }
27049          if self.complete.load(Ordering::SeqCst) {
27050            Poll::Ready(())
27051          } else {
27052            Poll::Pending
27053          }
27054        }
27055      }
27056
27057      configure(RuntimeOptions {
27058        flavor: RuntimeFlavor::CurrentThread,
27059        worker_threads: 1,
27060        max_blocking_tasks: 1,
27061        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
27062        park_deadline: None,
27063        drain_linger: DEFAULT_DRAIN_LINGER,
27064      })
27065      .expect("the isolated runtime must accept CurrentThread configuration");
27066      start().expect("the isolated runtime must start");
27067
27068      let (entered_tx, entered_rx) = mpsc::channel();
27069      let (error_tx, error_rx) = mpsc::channel();
27070      let (retry_tx, retry_rx) = mpsc::channel();
27071      let complete = Arc::new(AtomicBool::new(false));
27072      let future_complete = Arc::clone(&complete);
27073      let runner = std::thread::spawn(move || {
27074        let mut future = std::pin::pin!(PendingProbe {
27075          entered: Some(entered_tx),
27076          complete: future_complete
27077        });
27078        let error = try_block_on_dyn(future.as_mut())
27079          .expect_err("concurrent shutdown must interrupt the borrowed future")
27080          .to_string();
27081        error_tx.send(error).unwrap();
27082        retry_rx.recv().unwrap();
27083        try_block_on_dyn(future.as_mut())
27084          .expect("restart must accept the same interrupted borrowed future");
27085      });
27086      entered_rx
27087        .recv_timeout(Duration::from_secs(2))
27088        .expect("the borrowed future must be polled before shutdown");
27089      shutdown().expect("shutdown must wait for the active block_on scope to retire");
27090      assert_eq!(
27091        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27092        "the async runtime stopped before block_on completed its future"
27093      );
27094      start().expect("the isolated runtime must restart after the interrupted drive");
27095      complete.store(true, Ordering::SeqCst);
27096      retry_tx.send(()).unwrap();
27097      runner.join().unwrap();
27098      shutdown().expect("the retried isolated runtime must stop");
27099      return;
27100    }
27101
27102    let output = std::process::Command::new(std::env::current_exe().unwrap())
27103      .arg("--exact")
27104      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
27105      .arg("--nocapture")
27106      .env(CHILD_ENV, "1")
27107      .output()
27108      .expect("the concurrent block_on shutdown subprocess must start");
27109    assert!(
27110      output.status.success(),
27111      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
27112      output.status.code(),
27113      String::from_utf8_lossy(&output.stdout),
27114      String::from_utf8_lossy(&output.stderr)
27115    );
27116  }
27117
27118  #[test]
27119  fn initial_lifecycle_start_preserves_lazy_configuration() {
27120    let controller = RuntimeController::new();
27121    controller
27122      .start()
27123      .expect("initial lifecycle start must succeed");
27124    controller
27125      .configure(RuntimeOptions {
27126        flavor: RuntimeFlavor::CurrentThread,
27127        worker_threads: 1,
27128        max_blocking_tasks: 1,
27129        thread_name_prefix: "lifecycle-initial-start".to_string(),
27130        park_deadline: None,
27131        drain_linger: DEFAULT_DRAIN_LINGER,
27132      })
27133      .expect("module-registration start must not consume the configuration window");
27134
27135    let state = controller
27136      .state
27137      .lock()
27138      .unwrap_or_else(std::sync::PoisonError::into_inner);
27139    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27140  }
27141
27142  #[test]
27143  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
27144    let controller = RuntimeController::new();
27145
27146    for _ in 0..3 {
27147      controller
27148        .start()
27149        .expect("zero-work lifecycle start must succeed");
27150      assert!(matches!(
27151        &controller
27152          .state
27153          .lock()
27154          .unwrap_or_else(std::sync::PoisonError::into_inner)
27155          .lifecycle,
27156        RuntimeLifecycle::Initial
27157      ));
27158
27159      controller
27160        .shutdown()
27161        .expect("zero-work lifecycle shutdown must succeed");
27162      assert!(matches!(
27163        &controller
27164          .state
27165          .lock()
27166          .unwrap_or_else(std::sync::PoisonError::into_inner)
27167          .lifecycle,
27168        RuntimeLifecycle::StoppedBeforeFirstUse
27169      ));
27170    }
27171
27172    controller
27173      .start()
27174      .expect("a later zero-work restart must remain lazy");
27175    controller
27176      .configure(RuntimeOptions {
27177        flavor: RuntimeFlavor::CurrentThread,
27178        worker_threads: 1,
27179        max_blocking_tasks: 1,
27180        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
27181        park_deadline: None,
27182        drain_linger: DEFAULT_DRAIN_LINGER,
27183      })
27184      .expect("zero-work lifecycle cycles must not consume the configuration window");
27185
27186    let state = controller
27187      .state
27188      .lock()
27189      .unwrap_or_else(std::sync::PoisonError::into_inner);
27190    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27191  }
27192
27193  #[cfg(napi_runtime_os_threads)]
27194  #[test]
27195  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
27196    use std::sync::mpsc;
27197
27198    struct BlockingDropFuture {
27199      entered: mpsc::Sender<()>,
27200      release: mpsc::Receiver<()>,
27201    }
27202
27203    impl Future for BlockingDropFuture {
27204      type Output = ();
27205
27206      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27207        Poll::Pending
27208      }
27209    }
27210
27211    impl Drop for BlockingDropFuture {
27212      fn drop(&mut self) {
27213        self.entered.send(()).unwrap();
27214        self.release.recv().unwrap();
27215      }
27216    }
27217
27218    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
27219    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27220    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27221    let submit_controller = Arc::clone(&controller);
27222    let submitter = std::thread::spawn(move || {
27223      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27224      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27225        entered: drop_entered_tx,
27226        release: release_drop_rx,
27227      }));
27228      assert!(
27229        result.is_err(),
27230        "the injected backend creation failure must reject the submission"
27231      );
27232    });
27233    drop_entered_rx
27234      .recv_timeout(Duration::from_secs(2))
27235      .expect("the rejected future destructor must start");
27236
27237    let retry_ran = Arc::new(AtomicBool::new(false));
27238    let retry_ran_task = Arc::clone(&retry_ran);
27239    let Err(retry) = controller.try_spawn_detached(async move {
27240      retry_ran_task.store(true, Ordering::SeqCst);
27241    }) else {
27242      panic!("backend creation must remain closed during rejected destruction");
27243    };
27244    drop(retry);
27245    assert!(!retry_ran.load(Ordering::SeqCst));
27246
27247    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27248    let shutdown_controller = Arc::clone(&controller);
27249    let shutdown = std::thread::spawn(move || {
27250      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27251    });
27252    wait_until("initial shutdown to close runtime admission", || {
27253      matches!(
27254        controller
27255          .state
27256          .lock()
27257          .unwrap_or_else(std::sync::PoisonError::into_inner)
27258          .lifecycle,
27259        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
27260      )
27261    });
27262    assert!(
27263      shutdown_rx
27264        .recv_timeout(Duration::from_millis(200))
27265        .is_err(),
27266      "initial shutdown must wait for rejected user state destruction"
27267    );
27268    assert!(
27269      controller.try_spawn_detached(async {}).is_err(),
27270      "shutdown must keep first-generation admission closed while it waits"
27271    );
27272
27273    let (start_tx, start_rx) = mpsc::channel();
27274    let start_controller = Arc::clone(&controller);
27275    let starter = std::thread::spawn(move || {
27276      start_tx.send(start_controller.start()).unwrap();
27277    });
27278    assert!(
27279      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27280      "start must wait for the zero-backend shutdown transition to finish"
27281    );
27282
27283    release_drop_tx.send(()).unwrap();
27284    submitter.join().unwrap();
27285    shutdown_rx
27286      .recv_timeout(Duration::from_secs(2))
27287      .unwrap()
27288      .unwrap();
27289    shutdown.join().unwrap();
27290    start_rx
27291      .recv_timeout(Duration::from_secs(2))
27292      .unwrap()
27293      .unwrap();
27294    starter.join().unwrap();
27295
27296    let ran = Arc::new(AtomicBool::new(false));
27297    let ran_task = Arc::clone(&ran);
27298    controller
27299      .try_spawn_detached(async move {
27300        ran_task.store(true, Ordering::SeqCst);
27301      })
27302      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
27303    let dispatch = controller_current_thread_dispatch(&controller);
27304    controller.drive_current_thread_dispatch(dispatch, false);
27305    assert!(ran.load(Ordering::SeqCst));
27306    controller.shutdown().unwrap();
27307  }
27308
27309  #[cfg(napi_runtime_os_threads)]
27310  #[test]
27311  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
27312    use std::sync::mpsc;
27313
27314    struct BlockingDropFuture {
27315      entered: mpsc::Sender<()>,
27316      release: mpsc::Receiver<()>,
27317    }
27318
27319    impl Future for BlockingDropFuture {
27320      type Output = ();
27321
27322      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27323        Poll::Pending
27324      }
27325    }
27326
27327    impl Drop for BlockingDropFuture {
27328      fn drop(&mut self) {
27329        self.entered.send(()).unwrap();
27330        self.release.recv().unwrap();
27331      }
27332    }
27333
27334    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
27335    let old_generation = controller.backend().generation();
27336    controller.shutdown().unwrap();
27337
27338    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27339    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27340    let submit_controller = Arc::clone(&controller);
27341    let submitter = std::thread::spawn(move || {
27342      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
27343        entered: drop_entered_tx,
27344        release: release_drop_rx,
27345      }));
27346      assert!(
27347        result.is_err(),
27348        "the stopped runtime must reject the submission"
27349      );
27350    });
27351    drop_entered_rx
27352      .recv_timeout(Duration::from_secs(2))
27353      .unwrap();
27354
27355    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27356    let shutdown_controller = Arc::clone(&controller);
27357    let shutdown = std::thread::spawn(move || {
27358      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27359    });
27360    wait_until(
27361      "stopped shutdown to enter its zero-backend transition",
27362      || {
27363        matches!(
27364          controller
27365            .state
27366            .lock()
27367            .unwrap_or_else(std::sync::PoisonError::into_inner)
27368            .lifecycle,
27369          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
27370        )
27371      },
27372    );
27373
27374    let (start_tx, start_rx) = mpsc::channel();
27375    let start_controller = Arc::clone(&controller);
27376    let starter = std::thread::spawn(move || {
27377      start_tx.send(start_controller.start()).unwrap();
27378    });
27379    assert!(
27380      shutdown_rx
27381        .recv_timeout(Duration::from_millis(200))
27382        .is_err(),
27383      "stopped shutdown must wait for rejected destruction"
27384    );
27385    assert!(
27386      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
27387      "restart must wait for the stopped zero-backend transition"
27388    );
27389
27390    release_drop_tx.send(()).unwrap();
27391    submitter.join().unwrap();
27392    shutdown_rx
27393      .recv_timeout(Duration::from_secs(2))
27394      .unwrap()
27395      .unwrap();
27396    shutdown.join().unwrap();
27397    start_rx
27398      .recv_timeout(Duration::from_secs(2))
27399      .unwrap()
27400      .unwrap();
27401    starter.join().unwrap();
27402
27403    let new_generation = controller.backend().generation();
27404    assert_ne!(new_generation, old_generation);
27405    controller.shutdown().unwrap();
27406  }
27407
27408  #[test]
27409  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
27410    use std::sync::mpsc;
27411
27412    struct ShutdownOnDrop {
27413      controller: Arc<RuntimeController>,
27414      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27415    }
27416
27417    impl Future for ShutdownOnDrop {
27418      type Output = ();
27419
27420      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27421        Poll::Pending
27422      }
27423    }
27424
27425    impl Drop for ShutdownOnDrop {
27426      fn drop(&mut self) {
27427        self.result.send(self.controller.shutdown()).unwrap();
27428      }
27429    }
27430
27431    let controller = Arc::new(current_thread_controller(
27432      "failed-initial-reentrant-shutdown",
27433    ));
27434    let (result_tx, result_rx) = mpsc::channel();
27435    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27436    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
27437      controller: Arc::clone(&controller),
27438      result: result_tx,
27439    }));
27440    assert!(rejected.is_err());
27441
27442    let error = result_rx
27443      .recv_timeout(Duration::from_secs(1))
27444      .expect("the rejected destructor must not self-deadlock")
27445      .expect_err("rejected destruction cannot wait for its own shutdown");
27446    assert!(error.to_string().contains("rejected submission"));
27447    {
27448      let state = controller
27449        .state
27450        .lock()
27451        .unwrap_or_else(std::sync::PoisonError::into_inner);
27452      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27453      assert_eq!(state.rejected_drops, 0);
27454    }
27455
27456    let handle = controller
27457      .try_spawn(async { 23usize })
27458      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27459    let dispatch = controller_current_thread_dispatch(&controller);
27460    controller.drive_current_thread_dispatch(dispatch, false);
27461    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
27462    controller.shutdown().unwrap();
27463  }
27464
27465  #[cfg(napi_runtime_os_threads)]
27466  #[test]
27467  fn failed_initial_submission_destructor_cannot_reenter_start() {
27468    use std::sync::mpsc;
27469
27470    struct StartOnDrop {
27471      controller: Arc<RuntimeController>,
27472      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
27473      release: mpsc::Receiver<()>,
27474    }
27475
27476    impl Future for StartOnDrop {
27477      type Output = ();
27478
27479      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27480        Poll::Pending
27481      }
27482    }
27483
27484    impl Drop for StartOnDrop {
27485      fn drop(&mut self) {
27486        self.result.send(self.controller.start()).unwrap();
27487        self.release.recv().unwrap();
27488      }
27489    }
27490
27491    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
27492    let (result_tx, result_rx) = mpsc::channel();
27493    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27494    let (submission_tx, submission_rx) = mpsc::channel();
27495    let submit_controller = Arc::clone(&controller);
27496    let submitter = std::thread::spawn(move || {
27497      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27498      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
27499        controller: Arc::clone(&submit_controller),
27500        result: result_tx,
27501        release: release_drop_rx,
27502      }));
27503      submission_tx.send(rejected).unwrap();
27504    });
27505
27506    let error = result_rx
27507      .recv_timeout(Duration::from_secs(1))
27508      .expect("the rejected destructor must not self-deadlock")
27509      .expect_err("rejected destruction must not report a successful runtime start");
27510    assert!(error.to_string().contains("rejected submission"));
27511    {
27512      let state = controller
27513        .state
27514        .lock()
27515        .unwrap_or_else(std::sync::PoisonError::into_inner);
27516      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27517      assert_eq!(state.rejected_drops, 1);
27518    }
27519
27520    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
27521    let (external_start_tx, external_start_rx) = mpsc::channel();
27522    let start_controller = Arc::clone(&controller);
27523    let starter = std::thread::spawn(move || {
27524      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
27525        *slot.borrow_mut() = Some(Box::new(move || {
27526          wait_entered_tx.send(()).unwrap();
27527        }));
27528      });
27529      external_start_tx.send(start_controller.start()).unwrap();
27530    });
27531    wait_entered_rx
27532      .recv_timeout(Duration::from_secs(1))
27533      .expect("external start must reach the Initial rejected-destruction wait");
27534    assert!(
27535      external_start_rx.try_recv().is_err(),
27536      "external start must not return while rejected destruction remains active"
27537    );
27538
27539    release_drop_tx.send(()).unwrap();
27540    assert!(
27541      submission_rx
27542        .recv_timeout(Duration::from_secs(2))
27543        .unwrap()
27544        .is_err()
27545    );
27546    submitter.join().unwrap();
27547    external_start_rx
27548      .recv_timeout(Duration::from_secs(2))
27549      .unwrap()
27550      .unwrap();
27551    starter.join().unwrap();
27552
27553    {
27554      let state = controller
27555        .state
27556        .lock()
27557        .unwrap_or_else(std::sync::PoisonError::into_inner);
27558      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
27559      assert_eq!(state.rejected_drops, 0);
27560    }
27561
27562    let handle = controller
27563      .try_spawn(async { 31usize })
27564      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
27565    let dispatch = controller_current_thread_dispatch(&controller);
27566    controller.drive_current_thread_dispatch(dispatch, false);
27567    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
27568    controller.shutdown().unwrap();
27569  }
27570
27571  #[cfg(panic = "unwind")]
27572  #[test]
27573  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
27574    use std::sync::mpsc;
27575
27576    struct BlockingDropFuture {
27577      controller: Arc<RuntimeController>,
27578      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27579      release: mpsc::Receiver<()>,
27580    }
27581
27582    impl Future for BlockingDropFuture {
27583      type Output = ();
27584
27585      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27586        Poll::Pending
27587      }
27588    }
27589
27590    impl Drop for BlockingDropFuture {
27591      fn drop(&mut self) {
27592        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
27593        self
27594          .entered
27595          .send((generation, self.controller.shutdown()))
27596          .unwrap();
27597        self.release.recv().unwrap();
27598      }
27599    }
27600
27601    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
27602    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27603    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27604    let (runner_done_tx, runner_done_rx) = mpsc::channel();
27605    let runner_controller = Arc::clone(&controller);
27606    let runner = std::thread::spawn(move || {
27607      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
27608      let result = catch_unwind(AssertUnwindSafe(|| {
27609        block_on_with_controller(
27610          &runner_controller,
27611          BlockingDropFuture {
27612            controller: Arc::clone(&runner_controller),
27613            entered: drop_entered_tx,
27614            release: release_drop_rx,
27615          },
27616        );
27617      }));
27618      runner_done_tx.send(result.is_err()).unwrap();
27619    });
27620
27621    let (generation, reentrant_shutdown) = drop_entered_rx
27622      .recv_timeout(Duration::from_secs(2))
27623      .expect("failed block_on acquisition must destroy its owned future");
27624    assert_eq!(generation, None, "no runtime generation was created");
27625    let error = reentrant_shutdown
27626      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
27627    assert!(error.to_string().contains("rejected submission"));
27628
27629    let Err(retry_error) = controller.try_backend() else {
27630      panic!("backend retry must remain closed during rejected block_on destruction");
27631    };
27632    assert!(retry_error.to_string().contains("rejected submission"));
27633
27634    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27635    let shutdown_controller = Arc::clone(&controller);
27636    let shutdown = std::thread::spawn(move || {
27637      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27638    });
27639    assert!(
27640      shutdown_rx
27641        .recv_timeout(Duration::from_millis(200))
27642        .is_err(),
27643      "initial shutdown must wait for rejected block_on destruction"
27644    );
27645
27646    release_drop_tx.send(()).unwrap();
27647    assert!(
27648      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
27649      "block_on must preserve its infallible panic contract after contained cleanup"
27650    );
27651    runner.join().unwrap();
27652    shutdown_rx
27653      .recv_timeout(Duration::from_secs(2))
27654      .unwrap()
27655      .unwrap();
27656    shutdown.join().unwrap();
27657
27658    controller
27659      .start()
27660      .expect("zero-generation shutdown must remain restartable");
27661    let ran = Arc::new(AtomicBool::new(false));
27662    let ran_task = Arc::clone(&ran);
27663    controller
27664      .try_spawn_detached(async move {
27665        ran_task.store(true, Ordering::SeqCst);
27666      })
27667      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
27668    let dispatch = controller_current_thread_dispatch(&controller);
27669    controller.drive_current_thread_dispatch(dispatch, false);
27670    assert!(ran.load(Ordering::SeqCst));
27671    controller.shutdown().unwrap();
27672  }
27673
27674  #[cfg(not(target_family = "wasm"))]
27675  #[test]
27676  fn public_block_on_contains_poll_and_drop_double_panic() {
27677    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
27678
27679    if std::env::var_os(CHILD_ENV).is_some() {
27680      struct PanicInPollAndDrop;
27681
27682      impl Future for PanicInPollAndDrop {
27683        type Output = ();
27684
27685        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27686          panic!("intentional block_on poll panic");
27687        }
27688      }
27689
27690      impl Drop for PanicInPollAndDrop {
27691        fn drop(&mut self) {
27692          panic!("intentional block_on future destructor panic");
27693        }
27694      }
27695
27696      let controller = current_thread_controller("block-on-double-panic");
27697      assert!(
27698        catch_unwind(AssertUnwindSafe(|| {
27699          block_on_with_controller(&controller, PanicInPollAndDrop);
27700        }))
27701        .is_err(),
27702        "the original poll panic must still reach the caller"
27703      );
27704      controller
27705        .shutdown()
27706        .expect("contained future destruction must not strand the runtime");
27707      return;
27708    }
27709
27710    let output = std::process::Command::new(std::env::current_exe().unwrap())
27711      .arg("--exact")
27712      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27713      .arg("--nocapture")
27714      .env(CHILD_ENV, "1")
27715      .output()
27716      .expect("the subprocess test must start");
27717    assert!(
27718      output.status.success(),
27719      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27720      output.status.code(),
27721      String::from_utf8_lossy(&output.stdout),
27722      String::from_utf8_lossy(&output.stderr)
27723    );
27724  }
27725
27726  #[test]
27727  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27728    let controller = RuntimeController::new();
27729    controller
27730      .start()
27731      .expect("initial lifecycle start must succeed");
27732    controller
27733      .shutdown()
27734      .expect("zero-work lifecycle shutdown must succeed");
27735    controller
27736      .start()
27737      .expect("zero-work lifecycle restart must remain lazy");
27738    controller
27739      .configure(RuntimeOptions {
27740        flavor: RuntimeFlavor::CurrentThread,
27741        worker_threads: 1,
27742        max_blocking_tasks: 1,
27743        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27744        park_deadline: None,
27745        drain_linger: DEFAULT_DRAIN_LINGER,
27746      })
27747      .expect("configuration before first real async use must succeed");
27748
27749    let first_generation = controller.backend().generation();
27750    controller
27751      .shutdown()
27752      .expect("the first real generation must stop");
27753    assert!(matches!(
27754      &controller
27755        .state
27756        .lock()
27757        .unwrap_or_else(std::sync::PoisonError::into_inner)
27758        .lifecycle,
27759      RuntimeLifecycle::Stopped
27760    ));
27761    let error = controller
27762      .configure(RuntimeOptions::default())
27763      .expect_err("a real generation must permanently freeze configuration");
27764    assert_eq!(
27765      error.to_string(),
27766      "the async runtime configuration is frozen; configure it before the first async call"
27767    );
27768
27769    controller
27770      .start()
27771      .expect("the stopped real generation must restart");
27772    let restarted_generation = {
27773      let state = controller
27774        .state
27775        .lock()
27776        .unwrap_or_else(std::sync::PoisonError::into_inner);
27777      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27778        panic!("a real generation restart must eagerly create its replacement backend");
27779      };
27780      backend.generation()
27781    };
27782    assert_ne!(
27783      restarted_generation, first_generation,
27784      "a real generation restart must not reuse the retired backend"
27785    );
27786    controller
27787      .shutdown()
27788      .expect("the replacement generation must stop");
27789  }
27790
27791  #[test]
27792  fn runtime_controller_rejects_submissions_after_shutdown() {
27793    let controller = current_thread_controller("lifecycle-reject");
27794    controller.start().expect("runtime must start");
27795    drop(controller.backend());
27796    controller.shutdown().expect("runtime must stop");
27797
27798    let async_ran = Arc::new(AtomicBool::new(false));
27799    let async_ran_task = Arc::clone(&async_ran);
27800    let Err(rejected_future) = controller.try_spawn_detached(async move {
27801      async_ran_task.store(true, Ordering::SeqCst);
27802    }) else {
27803      panic!("shutdown must reject async work");
27804    };
27805    assert!(
27806      !async_ran.load(Ordering::SeqCst),
27807      "a rejected future must not be polled"
27808    );
27809    futures::executor::block_on(rejected_future);
27810    assert!(
27811      async_ran.load(Ordering::SeqCst),
27812      "the original future must remain usable"
27813    );
27814
27815    let blocking_ran = Arc::new(AtomicBool::new(false));
27816    let blocking_ran_task = Arc::clone(&blocking_ran);
27817    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
27818      blocking_ran_task.store(true, Ordering::SeqCst);
27819      7
27820    }) else {
27821      panic!("shutdown must reject blocking work");
27822    };
27823    assert_eq!(
27824      blocking_error.to_string(),
27825      "the async runtime is stopped; call start before submitting work"
27826    );
27827    assert!(
27828      !blocking_ran.load(Ordering::SeqCst),
27829      "rejected blocking work must not run"
27830    );
27831    assert_eq!(
27832      rejected_work(),
27833      7,
27834      "the original closure must remain usable"
27835    );
27836
27837    let config_error = controller
27838      .configure(RuntimeOptions::default())
27839      .expect_err("shutdown must not make runtime configuration mutable again");
27840    assert_eq!(
27841      config_error.to_string(),
27842      "the async runtime configuration is frozen; configure it before the first async call"
27843    );
27844
27845    let state = controller
27846      .state
27847      .lock()
27848      .unwrap_or_else(std::sync::PoisonError::into_inner);
27849    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
27850  }
27851
27852  #[test]
27853  fn rejected_future_can_be_resubmitted_after_restart() {
27854    let controller = current_thread_controller("lifecycle-retry-future");
27855    controller.start().expect("runtime must start");
27856    drop(controller.backend());
27857    controller.shutdown().expect("runtime must stop");
27858
27859    let runs = Arc::new(AtomicUsize::new(0));
27860    let runs_task = Arc::clone(&runs);
27861    let future = async move {
27862      runs_task.fetch_add(1, Ordering::SeqCst);
27863      23
27864    };
27865    let Err((error, future)) = controller.try_spawn(future) else {
27866      panic!("stopped runtime must reject the future");
27867    };
27868    assert!(error.to_string().contains("runtime is stopped"));
27869    assert_eq!(runs.load(Ordering::SeqCst), 0);
27870
27871    controller.start().expect("runtime must restart");
27872    let handle = controller
27873      .try_spawn(future)
27874      .unwrap_or_else(|_| panic!("restart must accept retained future"));
27875    let dispatch = controller_current_thread_dispatch(&controller);
27876    controller.drive_current_thread_dispatch(dispatch, false);
27877    assert_eq!(
27878      futures::executor::block_on(handle).expect("retained future must complete"),
27879      23
27880    );
27881    assert_eq!(runs.load(Ordering::SeqCst), 1);
27882    controller
27883      .shutdown()
27884      .expect("replacement runtime must stop");
27885  }
27886
27887  #[test]
27888  fn runtime_controller_start_after_shutdown_accepts_work() {
27889    let controller = current_thread_controller("lifecycle-restart");
27890    controller.start().expect("runtime must start");
27891    drop(controller.backend());
27892    controller.shutdown().expect("runtime must stop");
27893    controller.start().expect("runtime must restart");
27894
27895    let async_ran = Arc::new(AtomicBool::new(false));
27896    let async_ran_task = Arc::clone(&async_ran);
27897    controller
27898      .try_spawn_detached(async move {
27899        async_ran_task.store(true, Ordering::SeqCst);
27900      })
27901      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
27902    assert!(
27903      !async_ran.load(Ordering::SeqCst),
27904      "hostless CurrentThread submission must remain enqueue-only"
27905    );
27906    let dispatch = controller_current_thread_dispatch(&controller);
27907    controller.drive_current_thread_dispatch(dispatch, false);
27908    assert!(
27909      async_ran.load(Ordering::SeqCst),
27910      "restarted runtime must poll submitted work"
27911    );
27912
27913    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
27914      panic!("restarted runtime must accept blocking work");
27915    };
27916    assert_eq!(
27917      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
27918      11
27919    );
27920  }
27921
27922  #[test]
27923  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
27924    let controller = current_thread_controller("stale-host-generation");
27925    let old_generation = controller.backend().generation();
27926    let old_dispatch = controller_current_thread_dispatch(&controller);
27927    controller.shutdown().unwrap();
27928    controller.start().unwrap();
27929    let new_generation = controller.backend().generation();
27930    assert_ne!(new_generation, old_generation);
27931
27932    let ran = Arc::new(AtomicBool::new(false));
27933    let ran_task = Arc::clone(&ran);
27934    controller
27935      .try_spawn_detached(async move {
27936        ran_task.store(true, Ordering::SeqCst);
27937      })
27938      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
27939    let new_dispatch = controller_current_thread_dispatch(&controller);
27940    assert_ne!(new_dispatch, old_dispatch);
27941
27942    controller.drive_current_thread_dispatch(old_dispatch, false);
27943    assert!(
27944      !ran.load(Ordering::SeqCst),
27945      "a callback from the retired generation must not touch the restarted executor"
27946    );
27947    controller.drive_current_thread_dispatch(new_dispatch, false);
27948    assert!(ran.load(Ordering::SeqCst));
27949    controller.shutdown().unwrap();
27950  }
27951
27952  #[test]
27953  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
27954    let controller = current_thread_controller("stale-host-cancellation");
27955    let old_generation = controller.backend().generation();
27956    let old_dispatch = controller_current_thread_dispatch(&controller);
27957    controller.shutdown().unwrap();
27958    controller.start().unwrap();
27959    let backend = controller.backend();
27960    let new_generation = backend.generation();
27961    assert_ne!(new_generation, old_generation);
27962    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27963      panic!("configured CurrentThread backend must create a CurrentThread executor");
27964    };
27965    let new_dispatch = current_thread_dispatch(executor);
27966    assert_ne!(new_dispatch, old_dispatch);
27967
27968    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
27969    assert_eq!(
27970      executor.dispatch_pending.load(Ordering::Acquire),
27971      new_dispatch,
27972      "a delayed failure from the retired generation must not release the replacement latch"
27973    );
27974
27975    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
27976    assert_eq!(
27977      executor.dispatch_pending.load(Ordering::Acquire),
27978      0,
27979      "the live generation must be able to cancel its accepted dispatch"
27980    );
27981    drop(backend);
27982    controller.shutdown().unwrap();
27983  }
27984
27985  #[cfg(napi_runtime_os_threads)]
27986  #[test]
27987  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
27988    use std::sync::mpsc;
27989
27990    type ProbeResult = (
27991      &'static str,
27992      Option<u64>,
27993      Result<(), String>,
27994      Result<(), String>,
27995      Result<(), String>,
27996    );
27997
27998    struct StaleGenerationProbe {
27999      label: &'static str,
28000      controller: Arc<RuntimeController>,
28001      result: mpsc::Sender<ProbeResult>,
28002    }
28003
28004    impl Drop for StaleGenerationProbe {
28005      fn drop(&mut self) {
28006        let generation = active_runtime_generation();
28007        let submission = match self.controller.try_spawn(async {}) {
28008          Ok(handle) => {
28009            drop(handle);
28010            Ok(())
28011          }
28012          Err((error, future)) => {
28013            drop(future);
28014            Err(error.to_string())
28015          }
28016        };
28017        let start = self.controller.start().map_err(|error| error.to_string());
28018        let shutdown = self
28019          .controller
28020          .shutdown()
28021          .map_err(|error| error.to_string());
28022        self
28023          .result
28024          .send((self.label, generation, submission, start, shutdown))
28025          .unwrap();
28026      }
28027    }
28028
28029    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
28030    let old_generation = controller.backend().generation();
28031    let (result_tx, result_rx) = mpsc::channel();
28032
28033    let async_controller = Arc::clone(&controller);
28034    let async_result = result_tx.clone();
28035    let async_handle = controller
28036      .try_spawn(async move {
28037        StaleGenerationProbe {
28038          label: "async",
28039          controller: async_controller,
28040          result: async_result,
28041        }
28042      })
28043      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
28044    let old_dispatch = controller_current_thread_dispatch(&controller);
28045    controller.drive_current_thread_dispatch(old_dispatch, false);
28046
28047    let ready_controller = Arc::clone(&controller);
28048    let ready_handle = controller
28049      .try_spawn_blocking(move || StaleGenerationProbe {
28050        label: "ready",
28051        controller: ready_controller,
28052        result: result_tx,
28053      })
28054      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
28055
28056    controller.shutdown().unwrap();
28057    controller.start().unwrap();
28058    let new_generation = controller.backend().generation();
28059    assert_ne!(new_generation, old_generation);
28060
28061    drop(async_handle);
28062    let async_result = result_rx
28063      .recv_timeout(Duration::from_secs(2))
28064      .expect("the retained async output must be destroyed without waiting on the replacement");
28065    drop(ready_handle);
28066    let ready_result = result_rx
28067      .recv_timeout(Duration::from_secs(2))
28068      .expect("the retained immediate output must be destroyed without touching the replacement");
28069
28070    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
28071      let (label, generation, submission, start, shutdown) = result;
28072      assert_eq!(label, expected_label);
28073      assert_eq!(generation, Some(old_generation));
28074      for error in [submission, start, shutdown] {
28075        assert!(
28076          error
28077            .expect_err("retired-generation output destruction must be rejected")
28078            .contains("retired generation")
28079        );
28080      }
28081    }
28082
28083    let survivor = controller
28084      .try_spawn(async { 29usize })
28085      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
28086    let new_dispatch = controller_current_thread_dispatch(&controller);
28087    controller.drive_current_thread_dispatch(new_dispatch, false);
28088    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
28089    controller.shutdown().unwrap();
28090  }
28091
28092  #[cfg(napi_runtime_os_threads)]
28093  #[test]
28094  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
28095    use std::sync::mpsc;
28096
28097    struct ShutdownOnDrop {
28098      controller: Arc<RuntimeController>,
28099      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
28100    }
28101
28102    impl Drop for ShutdownOnDrop {
28103      fn drop(&mut self) {
28104        self
28105          .result
28106          .send((active_runtime_generation(), self.controller.shutdown()))
28107          .unwrap();
28108      }
28109    }
28110
28111    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
28112    let old_generation = controller.backend().generation();
28113    let (result_tx, result_rx) = mpsc::channel();
28114    let old_handle = controller
28115      .try_spawn_blocking({
28116        let controller = Arc::clone(&controller);
28117        move || ShutdownOnDrop {
28118          controller,
28119          result: result_tx,
28120        }
28121      })
28122      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
28123    wait_until(
28124      "the first-generation blocking result to be buffered",
28125      || {
28126        controller
28127          .metrics
28128          .blocking_tasks_completed
28129          .load(Ordering::Acquire)
28130          == 1
28131      },
28132    );
28133    controller.shutdown().unwrap();
28134    controller.start().unwrap();
28135    let new_generation = controller.backend().generation();
28136    assert_ne!(new_generation, old_generation);
28137
28138    let (holder_started_tx, holder_started_rx) = mpsc::channel();
28139    let (release_holder_tx, release_holder_rx) = mpsc::channel();
28140    let holder = controller
28141      .try_spawn_blocking(move || {
28142        holder_started_tx.send(()).unwrap();
28143        release_holder_rx.recv().unwrap();
28144      })
28145      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
28146    holder_started_rx
28147      .recv_timeout(Duration::from_secs(2))
28148      .unwrap();
28149
28150    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28151    let shutdown_controller = Arc::clone(&controller);
28152    let shutdown = std::thread::spawn(move || {
28153      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28154    });
28155    wait_until("the replacement generation to enter Stopping", || {
28156      matches!(
28157        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
28158        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
28159      )
28160    });
28161
28162    let dropper = std::thread::spawn(move || drop(old_handle));
28163    let (observed_generation, reentrant_shutdown) =
28164      match result_rx.recv_timeout(Duration::from_secs(2)) {
28165        Ok(result) => result,
28166        Err(error) => {
28167          release_holder_tx.send(()).unwrap();
28168          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
28169          let _ = dropper.join();
28170          let _ = shutdown.join();
28171          panic!("retained first-generation output waited on replacement shutdown: {error}");
28172        }
28173      };
28174    assert_eq!(observed_generation, Some(old_generation));
28175    let error = reentrant_shutdown
28176      .expect_err("a retained first-generation output must not control replacement shutdown");
28177    assert!(error.to_string().contains("retired generation"));
28178
28179    release_holder_tx.send(()).unwrap();
28180    shutdown_rx
28181      .recv_timeout(Duration::from_secs(2))
28182      .unwrap()
28183      .unwrap();
28184    dropper.join().unwrap();
28185    shutdown.join().unwrap();
28186    futures::executor::block_on(holder).unwrap();
28187  }
28188
28189  #[cfg(napi_runtime_os_threads)]
28190  #[test]
28191  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
28192    use std::sync::mpsc;
28193
28194    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
28195    let generation = controller.backend().generation();
28196    let dispatch = controller_current_thread_dispatch(&controller);
28197    let (admitted_tx, admitted_rx) = mpsc::channel();
28198    let (release_tx, release_rx) = mpsc::channel();
28199    let driver_controller = Arc::clone(&controller);
28200    let driver = std::thread::spawn(move || {
28201      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
28202        *slot.borrow_mut() = Some(Box::new(move || {
28203          admitted_tx.send(()).unwrap();
28204          release_rx.recv().unwrap();
28205        }));
28206      });
28207      driver_controller.drive_current_thread_dispatch(dispatch, false);
28208    });
28209    admitted_rx
28210      .recv_timeout(Duration::from_secs(2))
28211      .expect("the host turn must claim its scheduler role before execution");
28212
28213    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28214    let shutdown_controller = Arc::clone(&controller);
28215    let shutdown = std::thread::spawn(move || {
28216      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28217    });
28218    wait_until(
28219      "shutdown to close the admitted CurrentThread generation",
28220      || {
28221        let state = controller
28222          .state
28223          .lock()
28224          .unwrap_or_else(std::sync::PoisonError::into_inner);
28225        matches!(
28226          &state.lifecycle,
28227          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
28228        )
28229      },
28230    );
28231    assert!(
28232      shutdown_rx
28233        .recv_timeout(Duration::from_millis(200))
28234        .is_err(),
28235      "shutdown must wait for an admitted host callback even before it starts draining"
28236    );
28237
28238    release_tx.send(()).unwrap();
28239    driver.join().unwrap();
28240    shutdown_rx
28241      .recv_timeout(Duration::from_secs(2))
28242      .unwrap()
28243      .unwrap();
28244    shutdown.join().unwrap();
28245  }
28246
28247  #[cfg(napi_runtime_os_threads)]
28248  #[test]
28249  fn spawn_racing_shutdown_cannot_recreate_backend() {
28250    use std::sync::mpsc;
28251
28252    let controller = Arc::new(current_thread_controller("lifecycle-race"));
28253    controller.start().expect("runtime must start");
28254    drop(controller.backend());
28255
28256    let (at_hook_tx, at_hook_rx) = mpsc::channel();
28257    let (release_tx, release_rx) = mpsc::channel();
28258    let ran = Arc::new(AtomicBool::new(false));
28259    let ran_task = Arc::clone(&ran);
28260    let worker_controller = Arc::clone(&controller);
28261    let worker = std::thread::spawn(move || {
28262      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
28263        *slot.borrow_mut() = Some(Box::new(move || {
28264          at_hook_tx
28265            .send(())
28266            .expect("test coordinator must still be listening");
28267          release_rx
28268            .recv()
28269            .expect("test coordinator must release the submission");
28270        }));
28271      });
28272
28273      let future = async move {
28274        ran_task.store(true, Ordering::SeqCst);
28275      };
28276      match worker_controller.try_spawn_detached(future) {
28277        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
28278        Err(future) => drop(future),
28279      }
28280    });
28281
28282    at_hook_rx
28283      .recv_timeout(Duration::from_secs(5))
28284      .expect("submitting thread must stop before taking the controller lock");
28285    controller.shutdown().expect("runtime must stop");
28286    release_tx
28287      .send(())
28288      .expect("submitting thread must still be waiting");
28289    worker.join().expect("submitting thread must not panic");
28290
28291    assert!(
28292      !ran.load(Ordering::SeqCst),
28293      "the rejected task must never run"
28294    );
28295    let state = controller
28296      .state
28297      .lock()
28298      .unwrap_or_else(std::sync::PoisonError::into_inner);
28299    assert!(
28300      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
28301      "the rejected submission must not lazily recreate a backend"
28302    );
28303  }
28304
28305  #[cfg(napi_runtime_os_threads)]
28306  #[test]
28307  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
28308    use std::sync::mpsc;
28309
28310    struct BlockingDropFuture {
28311      work: Arc<GenerationWork>,
28312      entered: mpsc::Sender<(Option<u64>, usize)>,
28313      release: mpsc::Receiver<()>,
28314    }
28315
28316    impl Future for BlockingDropFuture {
28317      type Output = ();
28318
28319      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28320        Poll::Pending
28321      }
28322    }
28323
28324    impl Drop for BlockingDropFuture {
28325      fn drop(&mut self) {
28326        let active = self
28327          .work
28328          .state
28329          .lock()
28330          .unwrap_or_else(std::sync::PoisonError::into_inner)
28331          .active;
28332        self
28333          .entered
28334          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28335          .unwrap();
28336        self.release.recv().unwrap();
28337      }
28338    }
28339
28340    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
28341    let backend = controller.backend();
28342    let old_generation = backend.generation();
28343    let work = Arc::clone(&backend.work);
28344    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
28345      panic!("configured CurrentThread backend must create a CurrentThread executor");
28346    };
28347    let executor = Arc::clone(executor);
28348    drop(backend);
28349
28350    let (registered_tx, registered_rx) = mpsc::channel();
28351    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28352    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28353    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28354    let submit_controller = Arc::clone(&controller);
28355    let submit_work = Arc::clone(&work);
28356    let submitter = std::thread::spawn(move || {
28357      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28358        *slot.borrow_mut() = Some(Box::new(move || {
28359          registered_tx.send(()).unwrap();
28360          release_submission_rx.recv().unwrap();
28361        }));
28362      });
28363      submit_controller
28364        .try_spawn_detached(BlockingDropFuture {
28365          work: submit_work,
28366          entered: drop_entered_tx,
28367          release: release_drop_rx,
28368        })
28369        .unwrap_or_else(|_| {
28370          panic!("the submission linearized before shutdown and must be accepted")
28371        });
28372    });
28373    registered_rx
28374      .recv_timeout(Duration::from_secs(2))
28375      .expect("the submitting thread must register generation work before shutdown");
28376
28377    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28378    let shutdown_controller = Arc::clone(&controller);
28379    let shutdown = std::thread::spawn(move || {
28380      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28381    });
28382    wait_until("CurrentThread shutdown to close the runnable queue", || {
28383      executor
28384        .queue
28385        .lock()
28386        .unwrap_or_else(std::sync::PoisonError::into_inner)
28387        .closed
28388    });
28389    release_submission_tx.send(()).unwrap();
28390
28391    let (drop_generation, active_during_drop) = drop_entered_rx
28392      .recv_timeout(Duration::from_secs(2))
28393      .expect("the closed queue must destroy the accepted unpolled future");
28394    assert_eq!(drop_generation, Some(old_generation));
28395    assert_eq!(
28396      active_during_drop, 1,
28397      "accepted generation work must remain registered through user future destruction"
28398    );
28399
28400    let (start_tx, start_rx) = mpsc::channel();
28401    let start_controller = Arc::clone(&controller);
28402    let starter = std::thread::spawn(move || {
28403      start_tx.send(start_controller.start()).unwrap();
28404    });
28405    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28406    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28407    let shutdown_overlapped = early_shutdown.is_some();
28408    let restart_overlapped = early_start.is_some();
28409
28410    release_drop_tx.send(()).unwrap();
28411    submitter.join().unwrap();
28412    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28413      shutdown_rx
28414        .recv_timeout(Duration::from_secs(2))
28415        .expect("shutdown must finish after drop")
28416    });
28417    shutdown_result.unwrap();
28418    shutdown.join().unwrap();
28419    let start_result = early_start.unwrap_or_else(|| {
28420      start_rx
28421        .recv_timeout(Duration::from_secs(2))
28422        .expect("restart must follow shutdown")
28423    });
28424    start_result.unwrap();
28425    starter.join().unwrap();
28426
28427    assert!(
28428      !shutdown_overlapped,
28429      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
28430    );
28431    assert!(
28432      !restart_overlapped,
28433      "restart must not create a new generation while an accepted old-generation future is being destroyed"
28434    );
28435    assert_ne!(controller.backend().generation(), old_generation);
28436    controller.shutdown().unwrap();
28437  }
28438
28439  #[cfg(napi_runtime_os_threads)]
28440  #[test]
28441  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
28442    use std::sync::mpsc;
28443
28444    struct BlockingDrop {
28445      work: Arc<GenerationWork>,
28446      entered: mpsc::Sender<(Option<u64>, usize)>,
28447      release: mpsc::Receiver<()>,
28448    }
28449
28450    impl Drop for BlockingDrop {
28451      fn drop(&mut self) {
28452        let active = self
28453          .work
28454          .state
28455          .lock()
28456          .unwrap_or_else(std::sync::PoisonError::into_inner)
28457          .active;
28458        self
28459          .entered
28460          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
28461          .unwrap();
28462        self.release.recv().unwrap();
28463      }
28464    }
28465
28466    let controller = Arc::new(multi_thread_controller(
28467      "accepted-blocking-submission-drop",
28468      2,
28469      1,
28470    ));
28471    let backend = controller.backend();
28472    let old_generation = backend.generation();
28473    let work = Arc::clone(&backend.work);
28474    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28475      panic!("configured MultiThread backend must create a MultiThread executor");
28476    };
28477    let executor = Arc::downgrade(executor);
28478    drop(backend);
28479
28480    let (registered_tx, registered_rx) = mpsc::channel();
28481    let (release_submission_tx, release_submission_rx) = mpsc::channel();
28482    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28483    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28484    let submit_controller = Arc::clone(&controller);
28485    let submitter = std::thread::spawn(move || {
28486      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
28487        *slot.borrow_mut() = Some(Box::new(move || {
28488          registered_tx.send(()).unwrap();
28489          release_submission_rx.recv().unwrap();
28490        }));
28491      });
28492      let drop_signal = BlockingDrop {
28493        work,
28494        entered: drop_entered_tx,
28495        release: release_drop_rx,
28496      };
28497      let handle = submit_controller
28498        .try_spawn_blocking(move || {
28499          let _drop_signal = drop_signal;
28500        })
28501        .unwrap_or_else(|_| {
28502          panic!("the submission linearized before shutdown and must be accepted")
28503        });
28504      assert!(
28505        futures::executor::block_on(handle).is_err(),
28506        "the executor must cancel work that reaches its queue after shutdown closed it"
28507      );
28508    });
28509    registered_rx
28510      .recv_timeout(Duration::from_secs(2))
28511      .expect("the submitting thread must register generation work before shutdown");
28512
28513    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28514    let shutdown_controller = Arc::clone(&controller);
28515    let shutdown = std::thread::spawn(move || {
28516      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28517    });
28518    wait_until("MultiThread shutdown to close the blocking queue", || {
28519      executor
28520        .upgrade()
28521        .expect("the retiring executor must remain alive until shutdown completes")
28522        .blocking_queue
28523        .lock()
28524        .unwrap_or_else(std::sync::PoisonError::into_inner)
28525        .closed
28526    });
28527    release_submission_tx.send(()).unwrap();
28528
28529    let (drop_generation, active_during_drop) = drop_entered_rx
28530      .recv_timeout(Duration::from_secs(2))
28531      .expect("the closed queue must destroy the accepted blocking closure");
28532    assert_eq!(drop_generation, Some(old_generation));
28533    assert_eq!(
28534      active_during_drop, 1,
28535      "accepted generation work must remain registered through blocking closure destruction"
28536    );
28537
28538    let (start_tx, start_rx) = mpsc::channel();
28539    let start_controller = Arc::clone(&controller);
28540    let starter = std::thread::spawn(move || {
28541      start_tx.send(start_controller.start()).unwrap();
28542    });
28543    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28544    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
28545    let shutdown_overlapped = early_shutdown.is_some();
28546    let restart_overlapped = early_start.is_some();
28547
28548    release_drop_tx.send(()).unwrap();
28549    submitter.join().unwrap();
28550    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28551      shutdown_rx
28552        .recv_timeout(Duration::from_secs(2))
28553        .expect("shutdown must finish after drop")
28554    });
28555    shutdown_result.unwrap();
28556    shutdown.join().unwrap();
28557    let start_result = early_start.unwrap_or_else(|| {
28558      start_rx
28559        .recv_timeout(Duration::from_secs(2))
28560        .expect("restart must follow shutdown")
28561    });
28562    start_result.unwrap();
28563    starter.join().unwrap();
28564
28565    assert!(
28566      !shutdown_overlapped,
28567      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
28568    );
28569    assert!(
28570      !restart_overlapped,
28571      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
28572    );
28573    assert_ne!(controller.backend().generation(), old_generation);
28574    controller.shutdown().unwrap();
28575  }
28576
28577  #[cfg(napi_runtime_os_threads)]
28578  #[test]
28579  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
28580    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
28581    let options = controller.options();
28582    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
28583
28584    let backend = controller.backend();
28585    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28586      panic!("configured MultiThread backend must create a Rayon executor");
28587    };
28588    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
28589    let lifecycle = Arc::clone(&executor.worker_lifecycle);
28590    drop(backend);
28591
28592    controller.shutdown().expect("runtime must stop");
28593    assert_eq!(
28594      lifecycle.remaining(),
28595      0,
28596      "shutdown must observe every physical worker exit"
28597    );
28598  }
28599
28600  #[cfg(napi_runtime_os_threads)]
28601  #[test]
28602  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
28603    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
28604    // includes worker TLS-destructor completion -- `wait_for_all_workers`
28605    // joins the OS threads, and an OS thread only becomes joinable after its
28606    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
28607    // worker's destructor and sampling the counter on the shutdown thread at
28608    // the very instant `shutdown()` returns: a barrier that excluded the
28609    // destructors would race that sample and fail loudly.
28610    //
28611    // The destructor must never do more than record. The previous shape
28612    // parked it on a condvar until the harness released it -- on Windows TLS
28613    // destructors run under the process loader lock, so the first parked
28614    // worker blocked the second from even ENTERING its destructor, the
28615    // release condition (both entered) became unsatisfiable, and the parked
28616    // destructor froze every thread spawn/exit in the process: the
28617    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
28618    // `WorkerTlsDropAction`).
28619    use std::sync::mpsc;
28620
28621    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
28622    let backend = controller.backend();
28623    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28624      panic!("configured MultiThread backend must create a Rayon executor");
28625    };
28626    let worker_count = executor.pool.current_num_threads();
28627    let completed = Arc::new(AtomicUsize::new(0));
28628    let probe_completed = Arc::clone(&completed);
28629    executor.pool.broadcast(move |_| {
28630      WORKER_TLS_DROP_PROBE.with(|slot| {
28631        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28632          action: Some(WorkerTlsDropAction::Record {
28633            completed: Arc::clone(&probe_completed),
28634          }),
28635        });
28636        assert!(
28637          previous.is_none(),
28638          "each worker must install exactly one TLS drop probe"
28639        );
28640      });
28641    });
28642    drop(backend);
28643
28644    assert_eq!(
28645      completed.load(Ordering::SeqCst),
28646      0,
28647      "no worker TLS destructor may run before shutdown retires the workers"
28648    );
28649
28650    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28651    let shutdown_controller = Arc::clone(&controller);
28652    let sample_completed = Arc::clone(&completed);
28653    let shutdown = std::thread::spawn(move || {
28654      let result = shutdown_controller.shutdown();
28655      // Sample on the SAME thread, before any further synchronization could
28656      // mask a barrier gap: at this instant every destructor must already
28657      // have recorded itself.
28658      let seen_at_return = sample_completed.load(Ordering::SeqCst);
28659      shutdown_tx.send((result, seen_at_return)).unwrap();
28660    });
28661    let (shutdown_result, seen_at_return) = shutdown_rx
28662      .recv_timeout(Duration::from_secs(10))
28663      .expect("shutdown must finish once worker TLS destructors have run");
28664    shutdown_result.unwrap();
28665    assert_eq!(
28666      seen_at_return, worker_count,
28667      "every worker TLS destructor must have completed before shutdown() returned: \
28668       the worker-join barrier must include TLS-destructor completion"
28669    );
28670    join_within(
28671      "the shutdown thread exits after teardown",
28672      shutdown,
28673      Duration::from_secs(30),
28674    );
28675  }
28676
28677  #[cfg(not(target_family = "wasm"))]
28678  #[test]
28679  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
28680    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
28681
28682    if std::env::var_os(CHILD_ENV).is_some() {
28683      use std::sync::mpsc;
28684
28685      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
28686      let backend = controller.backend();
28687      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28688        panic!("configured MultiThread backend must create a Rayon executor");
28689      };
28690      let installed = Arc::new(AtomicBool::new(false));
28691      let (completed_tx, completed_rx) = mpsc::channel();
28692      let probe_controller = Arc::clone(&controller);
28693      let probe_installed = Arc::clone(&installed);
28694      executor.pool.broadcast(move |_| {
28695        if probe_installed.swap(true, Ordering::SeqCst) {
28696          return;
28697        }
28698        WORKER_TLS_DROP_PROBE.with(|slot| {
28699          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
28700            action: Some(WorkerTlsDropAction::ReenterLifecycle {
28701              controller: Arc::clone(&probe_controller),
28702              completed: completed_tx.clone(),
28703            }),
28704          });
28705          assert!(
28706            previous.is_none(),
28707            "the selected worker must have no prior TLS probe"
28708          );
28709        });
28710      });
28711      assert!(
28712        installed.load(Ordering::SeqCst),
28713        "one worker must install the lifecycle probe"
28714      );
28715      drop(backend);
28716
28717      controller
28718        .shutdown()
28719        .expect("external shutdown must join the retiring workers");
28720      let (start, shutdown) = completed_rx
28721        .recv_timeout(Duration::from_secs(2))
28722        .expect("the TLS destructor must complete both lifecycle reentry attempts");
28723      assert_eq!(
28724        start.unwrap_err(),
28725        "cannot restart the async runtime from work in the generation being stopped"
28726      );
28727      assert_eq!(
28728        shutdown.unwrap_err(),
28729        "cannot wait for async runtime shutdown from work in the generation being stopped"
28730      );
28731      return;
28732    }
28733
28734    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28735      .arg("--exact")
28736      .arg(
28737        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28738      )
28739      .arg("--nocapture")
28740      .env(CHILD_ENV, "1")
28741      .stdout(std::process::Stdio::piped())
28742      .stderr(std::process::Stdio::piped())
28743      .spawn()
28744      .expect("the TLS lifecycle-reentry subprocess must start");
28745    let deadline = Instant::now() + Duration::from_secs(5);
28746    let status = loop {
28747      if let Some(status) = child
28748        .try_wait()
28749        .expect("the subprocess status must be readable")
28750      {
28751        break status;
28752      }
28753      if Instant::now() >= deadline {
28754        let _ = child.kill();
28755        let output = child
28756          .wait_with_output()
28757          .expect("the timed-out subprocess must be reaped");
28758        panic!(
28759          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28760          String::from_utf8_lossy(&output.stdout),
28761          String::from_utf8_lossy(&output.stderr)
28762        );
28763      }
28764      std::thread::sleep(Duration::from_millis(10));
28765    };
28766    let output = child
28767      .wait_with_output()
28768      .expect("the completed subprocess output must be read");
28769    assert!(
28770      status.success(),
28771      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28772      status.code(),
28773      String::from_utf8_lossy(&output.stdout),
28774      String::from_utf8_lossy(&output.stderr)
28775    );
28776  }
28777
28778  #[cfg(napi_runtime_os_threads)]
28779  #[test]
28780  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28781    use std::sync::mpsc;
28782
28783    struct DropSignal(mpsc::Sender<()>);
28784    impl Drop for DropSignal {
28785      fn drop(&mut self) {
28786        let _ = self.0.send(());
28787      }
28788    }
28789
28790    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
28791    let (polled_tx, polled_rx) = mpsc::channel();
28792    let (dropped_tx, dropped_rx) = mpsc::channel();
28793    controller
28794      .try_spawn_detached(async move {
28795        let _drop_signal = DropSignal(dropped_tx);
28796        polled_tx.send(()).unwrap();
28797        std::future::pending::<()>().await;
28798      })
28799      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
28800    polled_rx
28801      .recv_timeout(Duration::from_secs(5))
28802      .expect("the pending task must be polled before shutdown");
28803
28804    controller
28805      .shutdown()
28806      .expect("shutdown must cancel accepted async work");
28807    dropped_rx
28808      .recv_timeout(Duration::from_secs(5))
28809      .expect("shutdown must drop the accepted task future before returning");
28810  }
28811
28812  #[cfg(napi_runtime_os_threads)]
28813  #[test]
28814  fn current_thread_shutdown_outranks_perpetual_self_wake() {
28815    use std::sync::mpsc;
28816
28817    struct SelfWakingPending {
28818      entered: Option<mpsc::Sender<()>>,
28819    }
28820
28821    impl Future for SelfWakingPending {
28822      type Output = ();
28823
28824      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28825        if let Some(entered) = self.entered.take() {
28826          entered.send(()).unwrap();
28827        }
28828        cx.waker().wake_by_ref();
28829        Poll::Pending
28830      }
28831    }
28832
28833    let controller = Arc::new(current_thread_controller(
28834      "current-thread-self-wake-shutdown",
28835    ));
28836    let (entered_tx, entered_rx) = mpsc::channel();
28837    let (outcome_tx, outcome_rx) = mpsc::channel();
28838    let block_on_controller = Arc::clone(&controller);
28839    let runner = std::thread::spawn(move || {
28840      let mut future = std::pin::pin!(SelfWakingPending {
28841        entered: Some(entered_tx)
28842      });
28843      outcome_tx
28844        .send(block_on_controller.block_on(future.as_mut()))
28845        .unwrap();
28846    });
28847    entered_rx
28848      .recv_timeout(Duration::from_secs(2))
28849      .expect("the self-waking future must be polled before shutdown");
28850
28851    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28852    let shutdown_controller = Arc::clone(&controller);
28853    let shutdown = std::thread::spawn(move || {
28854      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28855    });
28856    shutdown_rx
28857      .recv_timeout(Duration::from_secs(2))
28858      .expect("shutdown must outrank a stored self-wake permit")
28859      .unwrap();
28860    assert_eq!(
28861      outcome_rx
28862        .recv_timeout(Duration::from_secs(2))
28863        .expect("the raw block_on driver must report shutdown"),
28864      BlockOnOutcome::Stopped
28865    );
28866    runner.join().unwrap();
28867    shutdown.join().unwrap();
28868  }
28869
28870  #[cfg(napi_runtime_os_threads)]
28871  #[test]
28872  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
28873    use std::sync::mpsc;
28874
28875    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
28876    let backend = controller.backend();
28877    let executor = match &backend.executor {
28878      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28879      RuntimeExecutor::CurrentThread(_) => unreachable!(),
28880    };
28881    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
28882    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
28883    for _ in 0..2 {
28884      let worker_ready_tx = worker_ready_tx.clone();
28885      let release_workers = Arc::clone(&release_workers);
28886      executor.pool.spawn(move || {
28887        worker_ready_tx.send(()).unwrap();
28888        let (released, changed) = &*release_workers;
28889        let mut released = released
28890          .lock()
28891          .unwrap_or_else(std::sync::PoisonError::into_inner);
28892        while !*released {
28893          released = changed
28894            .wait(released)
28895            .unwrap_or_else(std::sync::PoisonError::into_inner);
28896        }
28897      });
28898    }
28899    drop(worker_ready_tx);
28900    for _ in 0..2 {
28901      worker_ready_rx
28902        .recv_timeout(Duration::from_secs(5))
28903        .expect("both workers must be occupied before the FIFO runnable is submitted");
28904    }
28905    drop(executor);
28906    drop(backend);
28907
28908    let polls = Arc::new(AtomicUsize::new(0));
28909    let handle = controller.spawn(StopPollProbe {
28910      polls: Arc::clone(&polls),
28911      first_poll: None,
28912    });
28913    wait_until("the FIFO runnable to be queued", || {
28914      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
28915    });
28916
28917    let observed_queue = Arc::new(Mutex::new(None));
28918    let hook_observed_queue = Arc::clone(&observed_queue);
28919    let hook_metrics = Arc::clone(&controller.metrics);
28920    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28921      *slot.borrow_mut() = Some(Box::new(move || {
28922        let (released, changed) = &*release_workers;
28923        *released
28924          .lock()
28925          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28926        changed.notify_all();
28927        wait_until("the stopped FIFO runnable claim", || {
28928          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
28929        });
28930        *hook_observed_queue
28931          .lock()
28932          .unwrap_or_else(std::sync::PoisonError::into_inner) =
28933          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
28934      }));
28935    });
28936
28937    controller
28938      .shutdown()
28939      .expect("shutdown must cancel the stopped FIFO claim");
28940    assert_eq!(polls.load(Ordering::SeqCst), 0);
28941    assert_eq!(
28942      *observed_queue
28943        .lock()
28944        .unwrap_or_else(std::sync::PoisonError::into_inner),
28945      Some(0)
28946    );
28947    assert_eq!(
28948      controller.metrics.queued_runnables.load(Ordering::SeqCst),
28949      0
28950    );
28951    drop(handle);
28952  }
28953
28954  #[cfg(napi_runtime_os_threads)]
28955  #[test]
28956  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
28957    use std::sync::mpsc;
28958
28959    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
28960    let backend = controller.backend();
28961    let executor = match &backend.executor {
28962      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28963      RuntimeExecutor::CurrentThread(_) => unreachable!(),
28964    };
28965    let polls = Arc::new(AtomicUsize::new(0));
28966    let scheduler = Arc::downgrade(&executor);
28967    let (runnable, task) = async_task::spawn(
28968      StopPollProbe {
28969        polls: Arc::clone(&polls),
28970        first_poll: None,
28971      },
28972      move |runnable| {
28973        if let Some(executor) = scheduler.upgrade() {
28974          executor.schedule(runnable);
28975        }
28976      },
28977    );
28978    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
28979    let (release_slot_tx, release_slot_rx) = mpsc::channel();
28980    let (claim_done_tx, claim_done_rx) = mpsc::channel();
28981    let worker_executor = Arc::clone(&executor);
28982    let worker_metrics = Arc::clone(&controller.metrics);
28983    executor.pool.spawn(move || {
28984      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
28985      worker_executor.schedule(runnable);
28986      slot_ready_tx.send(()).unwrap();
28987      release_slot_rx.recv().unwrap();
28988      let claimed = worker_executor.run_one_runnable();
28989      claim_done_tx
28990        .send((
28991          claimed,
28992          worker_metrics.queued_runnables.load(Ordering::SeqCst),
28993        ))
28994        .unwrap();
28995    });
28996    slot_ready_rx
28997      .recv_timeout(Duration::from_secs(5))
28998      .expect("the worker must publish the LIFO runnable before shutdown");
28999    assert_eq!(
29000      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29001      1
29002    );
29003    drop(executor);
29004    drop(backend);
29005
29006    let observed_claim = Arc::new(Mutex::new(None));
29007    let hook_observed_claim = Arc::clone(&observed_claim);
29008    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
29009      *slot.borrow_mut() = Some(Box::new(move || {
29010        release_slot_tx.send(()).unwrap();
29011        let result = claim_done_rx
29012          .recv_timeout(Duration::from_secs(5))
29013          .expect("the stopped LIFO runnable claim must complete before abort");
29014        *hook_observed_claim
29015          .lock()
29016          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
29017      }));
29018    });
29019
29020    controller
29021      .shutdown()
29022      .expect("shutdown must cancel the stopped LIFO claim");
29023    assert_eq!(polls.load(Ordering::SeqCst), 0);
29024    assert_eq!(
29025      *observed_claim
29026        .lock()
29027        .unwrap_or_else(std::sync::PoisonError::into_inner),
29028      Some((true, 0))
29029    );
29030    assert_eq!(
29031      controller.metrics.queued_runnables.load(Ordering::SeqCst),
29032      0
29033    );
29034    drop(task);
29035  }
29036
29037  #[cfg(napi_runtime_os_threads)]
29038  #[test]
29039  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
29040    use std::sync::mpsc;
29041
29042    struct BlockingDrop {
29043      entered: mpsc::Sender<()>,
29044      release: mpsc::Receiver<()>,
29045    }
29046
29047    impl Drop for BlockingDrop {
29048      fn drop(&mut self) {
29049        self.entered.send(()).unwrap();
29050        self.release.recv().unwrap();
29051      }
29052    }
29053
29054    struct TwoPollProbe {
29055      first_poll: Option<mpsc::Sender<()>>,
29056      release_first_poll: mpsc::Receiver<()>,
29057      second_poll: mpsc::Sender<()>,
29058    }
29059
29060    impl Future for TwoPollProbe {
29061      type Output = ();
29062
29063      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
29064        if let Some(first_poll) = self.first_poll.take() {
29065          first_poll.send(()).unwrap();
29066          self.release_first_poll.recv().unwrap();
29067        } else {
29068          self.second_poll.send(()).unwrap();
29069        }
29070        cx.waker().wake_by_ref();
29071        Poll::Pending
29072      }
29073    }
29074
29075    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
29076    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29077    let (release_drop_tx, release_drop_rx) = mpsc::channel();
29078    let blocking_drop = BlockingDrop {
29079      entered: drop_entered_tx,
29080      release: release_drop_rx,
29081    };
29082    controller
29083      .try_spawn_detached(async move {
29084        let _drop = blocking_drop;
29085        std::future::pending::<()>().await;
29086      })
29087      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
29088
29089    let (first_poll_tx, first_poll_rx) = mpsc::channel();
29090    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
29091    let (second_poll_tx, second_poll_rx) = mpsc::channel();
29092    let (outcome_tx, outcome_rx) = mpsc::channel();
29093    let runner_controller = Arc::clone(&controller);
29094    let runner = std::thread::spawn(move || {
29095      let mut future = std::pin::pin!(TwoPollProbe {
29096        first_poll: Some(first_poll_tx),
29097        release_first_poll: release_first_poll_rx,
29098        second_poll: second_poll_tx,
29099      });
29100      outcome_tx
29101        .send(runner_controller.block_on(future.as_mut()))
29102        .unwrap();
29103    });
29104    first_poll_rx
29105      .recv_timeout(Duration::from_secs(2))
29106      .expect("the active block_on poll must enter before shutdown");
29107
29108    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29109    let shutdown_controller = Arc::clone(&controller);
29110    let shutdown = std::thread::spawn(move || {
29111      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29112    });
29113    drop_entered_rx
29114      .recv_timeout(Duration::from_secs(2))
29115      .expect("shutdown must begin destroying queued work");
29116
29117    release_first_poll_tx.send(()).unwrap();
29118    let polled_again = second_poll_rx
29119      .recv_timeout(Duration::from_millis(200))
29120      .is_ok();
29121    release_drop_tx.send(()).unwrap();
29122
29123    assert!(
29124      !polled_again,
29125      "an active block_on driver must observe generation stop before queued user cleanup blocks"
29126    );
29127    assert_eq!(
29128      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29129      BlockOnOutcome::Stopped
29130    );
29131    shutdown_rx
29132      .recv_timeout(Duration::from_secs(2))
29133      .unwrap()
29134      .unwrap();
29135    runner.join().unwrap();
29136    shutdown.join().unwrap();
29137  }
29138
29139  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29140  #[test]
29141  fn shutdown_cancels_nested_block_on_within_deadline() {
29142    use std::sync::mpsc;
29143
29144    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
29145    let task_controller = Arc::clone(&controller);
29146    let (entered_tx, entered_rx) = mpsc::channel();
29147    let task = controller
29148      .try_spawn(async move {
29149        entered_tx.send(()).unwrap();
29150        block_on_with_controller(&task_controller, std::future::pending::<()>());
29151      })
29152      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
29153    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29154
29155    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29156    let shutdown_controller = Arc::clone(&controller);
29157    let shutdown = std::thread::spawn(move || {
29158      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29159    });
29160    shutdown_rx
29161      .recv_timeout(Duration::from_secs(2))
29162      .expect("shutdown must wake and cancel an active nested block_on")
29163      .unwrap();
29164    shutdown.join().unwrap();
29165    assert!(
29166      futures::executor::block_on(task).is_err(),
29167      "the cancelled nested block_on task must resolve as stopped"
29168    );
29169  }
29170
29171  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29172  #[test]
29173  fn stopped_public_block_on_drops_future_before_generation_retirement() {
29174    use std::sync::mpsc;
29175
29176    struct PendingDrop {
29177      entered: Option<mpsc::Sender<()>>,
29178      dropped: mpsc::Sender<Option<u64>>,
29179      release: mpsc::Receiver<()>,
29180    }
29181
29182    impl Future for PendingDrop {
29183      type Output = ();
29184
29185      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29186        if let Some(entered) = self.entered.take() {
29187          entered.send(()).unwrap();
29188        }
29189        Poll::Pending
29190      }
29191    }
29192
29193    impl Drop for PendingDrop {
29194      fn drop(&mut self) {
29195        self
29196          .dropped
29197          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29198          .unwrap();
29199        self.release.recv().unwrap();
29200      }
29201    }
29202
29203    let controller = Arc::new(current_thread_controller(
29204      "public-block-on-generation-destruction",
29205    ));
29206    let generation = controller.backend().generation();
29207    let (entered_tx, entered_rx) = mpsc::channel();
29208    let (dropped_tx, dropped_rx) = mpsc::channel();
29209    let (release_tx, release_rx) = mpsc::channel();
29210    let (runner_done_tx, runner_done_rx) = mpsc::channel();
29211    let block_controller = Arc::clone(&controller);
29212    let runner = std::thread::spawn(move || {
29213      let result = catch_unwind(AssertUnwindSafe(|| {
29214        block_on_with_controller(
29215          &block_controller,
29216          PendingDrop {
29217            entered: Some(entered_tx),
29218            dropped: dropped_tx,
29219            release: release_rx,
29220          },
29221        );
29222      }));
29223      runner_done_tx.send(result.is_err()).unwrap();
29224    });
29225    entered_rx
29226      .recv_timeout(Duration::from_secs(2))
29227      .expect("the public block_on future must be polled before shutdown");
29228
29229    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29230    let shutdown_controller = Arc::clone(&controller);
29231    let shutdown = std::thread::spawn(move || {
29232      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29233    });
29234
29235    assert_eq!(
29236      dropped_rx
29237        .recv_timeout(Duration::from_secs(2))
29238        .expect("shutdown must wake block_on and begin destroying its future"),
29239      Some(generation),
29240      "the stopped future must be destroyed under its retiring generation identity"
29241    );
29242    assert!(
29243      shutdown_rx
29244        .recv_timeout(Duration::from_millis(200))
29245        .is_err(),
29246      "shutdown must not publish Stopped while the public block_on future destructor is running"
29247    );
29248
29249    release_tx.send(()).unwrap();
29250    assert!(
29251      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
29252      "the public helper must retain its existing early-stop panic behavior"
29253    );
29254    shutdown_rx
29255      .recv_timeout(Duration::from_secs(2))
29256      .unwrap()
29257      .unwrap();
29258    runner.join().unwrap();
29259    shutdown.join().unwrap();
29260  }
29261
29262  #[cfg(not(target_family = "wasm"))]
29263  #[test]
29264  fn shutdown_contains_panicking_nested_block_on_future_drop() {
29265    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
29266
29267    if std::env::var_os(CHILD_ENV).is_some() {
29268      use std::sync::mpsc;
29269
29270      struct PendingPanicOnDrop {
29271        entered: Option<mpsc::Sender<()>>,
29272      }
29273
29274      impl Future for PendingPanicOnDrop {
29275        type Output = ();
29276
29277        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29278          if let Some(entered) = self.entered.take() {
29279            entered.send(()).unwrap();
29280          }
29281          Poll::Pending
29282        }
29283      }
29284
29285      impl Drop for PendingPanicOnDrop {
29286        fn drop(&mut self) {
29287          panic!("intentional nested block_on future destructor panic");
29288        }
29289      }
29290
29291      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
29292      let task_controller = Arc::clone(&controller);
29293      let (entered_tx, entered_rx) = mpsc::channel();
29294      let task = controller
29295        .try_spawn(async move {
29296          block_on_with_controller(
29297            &task_controller,
29298            PendingPanicOnDrop {
29299              entered: Some(entered_tx),
29300            },
29301          );
29302        })
29303        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
29304      entered_rx
29305        .recv_timeout(Duration::from_secs(2))
29306        .expect("the nested block_on future must be polled before shutdown");
29307
29308      controller
29309        .shutdown()
29310        .expect("shutdown must contain nested future destruction");
29311      assert!(
29312        futures::executor::block_on(task).is_err(),
29313        "shutdown cancellation must still surface as a task error"
29314      );
29315      return;
29316    }
29317
29318    let output = std::process::Command::new(std::env::current_exe().unwrap())
29319      .arg("--exact")
29320      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
29321      .arg("--nocapture")
29322      .env(CHILD_ENV, "1")
29323      .output()
29324      .expect("the subprocess test must start");
29325    assert!(
29326      output.status.success(),
29327      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
29328      output.status.code(),
29329      String::from_utf8_lossy(&output.stdout),
29330      String::from_utf8_lossy(&output.stderr)
29331    );
29332  }
29333
29334  #[cfg(not(target_family = "wasm"))]
29335  #[test]
29336  fn panicking_future_drop_is_contained_before_async_task_drop() {
29337    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
29338
29339    if std::env::var_os(CHILD_ENV).is_some() {
29340      struct PanicOnDrop;
29341      impl Drop for PanicOnDrop {
29342        fn drop(&mut self) {
29343          panic!("intentional future destructor panic");
29344        }
29345      }
29346
29347      let controller = current_thread_controller("future-drop-subprocess");
29348      let bomb = PanicOnDrop;
29349      controller
29350        .try_spawn_detached(async move {
29351          let _bomb = bomb;
29352          std::future::pending::<()>().await;
29353        })
29354        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
29355      controller
29356        .shutdown()
29357        .expect("shutdown must contain the future destructor panic");
29358      return;
29359    }
29360
29361    let output = std::process::Command::new(std::env::current_exe().unwrap())
29362      .arg("--exact")
29363      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
29364      .arg("--nocapture")
29365      .env(CHILD_ENV, "1")
29366      .output()
29367      .expect("the subprocess test must start");
29368    assert!(
29369      output.status.success(),
29370      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29371      output.status.code(),
29372      String::from_utf8_lossy(&output.stdout),
29373      String::from_utf8_lossy(&output.stderr)
29374    );
29375  }
29376
29377  #[cfg(not(target_family = "wasm"))]
29378  #[test]
29379  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
29380    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
29381
29382    if std::env::var_os(CHILD_ENV).is_some() {
29383      struct PanicOnDrop;
29384      impl Drop for PanicOnDrop {
29385        fn drop(&mut self) {
29386          panic!("intentional detached output destructor panic");
29387        }
29388      }
29389
29390      let controller = current_thread_controller("output-drop-subprocess");
29391      let handle = controller
29392        .try_spawn(async { PanicOnDrop })
29393        .unwrap_or_else(|_| panic!("runtime must accept the task"));
29394      drop(handle);
29395      let dispatch = controller_current_thread_dispatch(&controller);
29396      controller.drive_current_thread_dispatch(dispatch, false);
29397      controller.shutdown().unwrap();
29398      return;
29399    }
29400
29401    let output = std::process::Command::new(std::env::current_exe().unwrap())
29402      .arg("--exact")
29403      .arg(
29404        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
29405      )
29406      .arg("--nocapture")
29407      .env(CHILD_ENV, "1")
29408      .output()
29409      .expect("the subprocess test must start");
29410    assert!(
29411      output.status.success(),
29412      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
29413      output.status.code(),
29414      String::from_utf8_lossy(&output.stdout),
29415      String::from_utf8_lossy(&output.stderr)
29416    );
29417  }
29418
29419  #[cfg(napi_runtime_os_threads)]
29420  #[test]
29421  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
29422    use std::sync::mpsc;
29423
29424    struct BlockingDrop {
29425      controller: Arc<RuntimeController>,
29426      entered: mpsc::Sender<Option<u64>>,
29427      release: mpsc::Receiver<()>,
29428      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
29429    }
29430
29431    impl Drop for BlockingDrop {
29432      fn drop(&mut self) {
29433        self
29434          .entered
29435          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
29436          .unwrap();
29437        self.release.recv().unwrap();
29438        self
29439          .reentrant_shutdown
29440          .send(self.controller.shutdown())
29441          .unwrap();
29442      }
29443    }
29444
29445    let controller = Arc::new(current_thread_controller(
29446      "current-thread-host-drain-output-retirement",
29447    ));
29448    let old_generation = controller.backend().generation();
29449    let (entered_tx, entered_rx) = mpsc::channel();
29450    let (release_tx, release_rx) = mpsc::channel();
29451    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29452    let output_controller = Arc::clone(&controller);
29453    let handle = controller
29454      .try_spawn(async move {
29455        BlockingDrop {
29456          controller: output_controller,
29457          entered: entered_tx,
29458          release: release_rx,
29459          reentrant_shutdown: reentrant_tx,
29460        }
29461      })
29462      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
29463    drop(handle);
29464    let dispatch = controller_current_thread_dispatch(&controller);
29465
29466    let driver_controller = Arc::clone(&controller);
29467    let driver =
29468      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
29469    assert_eq!(
29470      entered_rx
29471        .recv_timeout(Duration::from_secs(2))
29472        .expect("the host turn must begin destroying the detached output"),
29473      Some(old_generation),
29474      "detached output destruction must retain the generation that produced it"
29475    );
29476
29477    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29478    let shutdown_controller = Arc::clone(&controller);
29479    let shutdown = std::thread::spawn(move || {
29480      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29481    });
29482    wait_until(
29483      "CurrentThread shutdown to enter the old generation's stopping state",
29484      || {
29485        let state = controller
29486          .state
29487          .lock()
29488          .unwrap_or_else(std::sync::PoisonError::into_inner);
29489        matches!(
29490          &state.lifecycle,
29491          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
29492        )
29493      },
29494    );
29495
29496    let (restart_tx, restart_rx) = mpsc::channel();
29497    let restart_controller = Arc::clone(&controller);
29498    let restart = std::thread::spawn(move || {
29499      restart_controller.start().unwrap();
29500      restart_tx
29501        .send(restart_controller.backend().generation())
29502        .unwrap();
29503    });
29504
29505    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
29506    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
29507    let shutdown_overlapped = early_shutdown.is_some();
29508    let restart_overlapped = early_restart.is_some();
29509    release_tx.send(()).unwrap();
29510
29511    let reentrant_error = reentrant_rx
29512      .recv_timeout(Duration::from_secs(2))
29513      .expect("the retiring output destructor must finish its lifecycle check")
29514      .expect_err("old-generation output destruction must not shut down a replacement runtime");
29515    assert!(
29516      reentrant_error
29517        .to_string()
29518        .contains("generation being stopped"),
29519      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
29520    );
29521
29522    let shutdown_result = early_shutdown.unwrap_or_else(|| {
29523      shutdown_rx
29524        .recv_timeout(Duration::from_secs(2))
29525        .expect("shutdown must finish after detached output destruction retires")
29526    });
29527    shutdown_result.unwrap();
29528    let new_generation = early_restart.unwrap_or_else(|| {
29529      restart_rx
29530        .recv_timeout(Duration::from_secs(2))
29531        .expect("restart must finish after the old host turn retires")
29532    });
29533
29534    driver.join().unwrap();
29535    shutdown.join().unwrap();
29536    restart.join().unwrap();
29537    assert!(
29538      !shutdown_overlapped,
29539      "shutdown must not publish Stopped while a host turn is destroying detached output"
29540    );
29541    assert!(
29542      !restart_overlapped,
29543      "restart must not overlap a host turn from the retiring generation"
29544    );
29545    assert_ne!(new_generation, old_generation);
29546    controller.shutdown().unwrap();
29547  }
29548
29549  #[cfg(napi_runtime_os_threads)]
29550  #[test]
29551  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
29552    use std::sync::mpsc;
29553
29554    struct ShutdownOnDrop {
29555      controller: Arc<RuntimeController>,
29556      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29557    }
29558
29559    impl Drop for ShutdownOnDrop {
29560      fn drop(&mut self) {
29561        let _ = self.result.send(self.controller.shutdown());
29562      }
29563    }
29564
29565    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
29566    let (running_tx, running_rx) = mpsc::channel();
29567    let (release_tx, release_rx) = mpsc::channel();
29568    let running = controller
29569      .try_spawn_blocking(move || {
29570        running_tx.send(()).unwrap();
29571        release_rx.recv().unwrap();
29572      })
29573      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29574    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29575
29576    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29577    let bomb = ShutdownOnDrop {
29578      controller: Arc::clone(&controller),
29579      result: reentrant_tx,
29580    };
29581    let queued = controller
29582      .try_spawn_blocking(move || {
29583        let _bomb = &bomb;
29584      })
29585      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29586
29587    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29588    let shutdown_controller = Arc::clone(&controller);
29589    let shutdown = std::thread::spawn(move || {
29590      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29591    });
29592    let error = reentrant_rx
29593      .recv_timeout(Duration::from_secs(2))
29594      .expect("queued destruction must not self-deadlock")
29595      .expect_err("the retiring generation must reject reentrant shutdown");
29596    assert!(error.to_string().contains("generation being stopped"));
29597    release_tx.send(()).unwrap();
29598    shutdown_rx
29599      .recv_timeout(Duration::from_secs(2))
29600      .unwrap()
29601      .unwrap();
29602    shutdown.join().unwrap();
29603    futures::executor::block_on(running).unwrap();
29604    assert!(futures::executor::block_on(queued).is_err());
29605  }
29606
29607  #[cfg(napi_runtime_os_threads)]
29608  #[test]
29609  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
29610    use std::sync::mpsc;
29611
29612    struct ShutdownAfterRelease {
29613      controller: Arc<RuntimeController>,
29614      entered: mpsc::Sender<()>,
29615      release: mpsc::Receiver<()>,
29616      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
29617    }
29618
29619    impl Drop for ShutdownAfterRelease {
29620      fn drop(&mut self) {
29621        self.entered.send(()).unwrap();
29622        self.release.recv().unwrap();
29623        self.result.send(self.controller.shutdown()).unwrap();
29624      }
29625    }
29626
29627    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
29628    let (holder_started_tx, holder_started_rx) = mpsc::channel();
29629    let (release_holder_tx, release_holder_rx) = mpsc::channel();
29630    let holder = controller
29631      .try_spawn_blocking(move || {
29632        holder_started_tx.send(()).unwrap();
29633        release_holder_rx.recv().unwrap();
29634      })
29635      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29636    holder_started_rx
29637      .recv_timeout(Duration::from_secs(2))
29638      .unwrap();
29639
29640    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29641    let shutdown_controller = Arc::clone(&controller);
29642    let shutdown = std::thread::spawn(move || {
29643      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29644    });
29645    wait_until("the runtime entered Stopping", || {
29646      matches!(
29647        controller
29648          .state
29649          .lock()
29650          .unwrap_or_else(std::sync::PoisonError::into_inner)
29651          .lifecycle,
29652        RuntimeLifecycle::Stopping(_)
29653      )
29654    });
29655
29656    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
29657    let (release_drop_tx, release_drop_rx) = mpsc::channel();
29658    let (reentrant_tx, reentrant_rx) = mpsc::channel();
29659    let rejected_controller = Arc::clone(&controller);
29660    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
29661    let rejected = std::thread::spawn(move || {
29662      let bomb = ShutdownAfterRelease {
29663        controller: Arc::clone(&rejected_controller),
29664        entered: drop_entered_tx,
29665        release: release_drop_rx,
29666        result: reentrant_tx,
29667      };
29668      let handle = rejected_controller.spawn(async move {
29669        let _bomb = bomb;
29670        std::future::pending::<()>().await;
29671      });
29672      rejected_done_tx
29673        .send(futures::executor::block_on(handle))
29674        .unwrap();
29675    });
29676    drop_entered_rx
29677      .recv_timeout(Duration::from_secs(2))
29678      .unwrap();
29679
29680    release_holder_tx.send(()).unwrap();
29681    assert!(
29682      shutdown_rx
29683        .recv_timeout(Duration::from_millis(200))
29684        .is_err(),
29685      "shutdown crossed into Stopped before rejected destruction finished"
29686    );
29687
29688    let (start_tx, start_rx) = mpsc::channel();
29689    let start_controller = Arc::clone(&controller);
29690    let starter = std::thread::spawn(move || {
29691      start_tx.send(start_controller.start()).unwrap();
29692    });
29693    assert!(
29694      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
29695      "restart crossed into a new generation before rejected destruction finished"
29696    );
29697
29698    release_drop_tx.send(()).unwrap();
29699    let error = reentrant_rx
29700      .recv_timeout(Duration::from_secs(2))
29701      .expect("the rejected destructor must complete")
29702      .expect_err("the retiring generation must reject the destructor's shutdown");
29703    assert!(error.to_string().contains("generation being stopped"));
29704    assert!(
29705      rejected_done_rx
29706        .recv_timeout(Duration::from_secs(2))
29707        .unwrap()
29708        .is_err()
29709    );
29710    rejected.join().unwrap();
29711
29712    shutdown_rx
29713      .recv_timeout(Duration::from_secs(2))
29714      .unwrap()
29715      .unwrap();
29716    shutdown.join().unwrap();
29717    start_rx
29718      .recv_timeout(Duration::from_secs(2))
29719      .unwrap()
29720      .unwrap();
29721    starter.join().unwrap();
29722    futures::executor::block_on(holder).unwrap();
29723    controller.shutdown().unwrap();
29724  }
29725
29726  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29727  #[test]
29728  fn shutdown_contains_hostile_caught_panic_payload() {
29729    use std::{panic::panic_any, sync::mpsc};
29730
29731    struct PanicAgain;
29732    impl Drop for PanicAgain {
29733      fn drop(&mut self) {
29734        panic!("caught panic payload destructor escaped");
29735      }
29736    }
29737
29738    struct PanicPayloadOnDrop(mpsc::Sender<()>);
29739    impl Drop for PanicPayloadOnDrop {
29740      fn drop(&mut self) {
29741        let _ = self.0.send(());
29742        panic_any(PanicAgain);
29743      }
29744    }
29745
29746    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29747    let (running_tx, running_rx) = mpsc::channel();
29748    let (release_tx, release_rx) = mpsc::channel();
29749    let running = controller
29750      .try_spawn_blocking(move || {
29751        running_tx.send(()).unwrap();
29752        release_rx.recv().unwrap();
29753      })
29754      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29755    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29756
29757    let (drop_tx, drop_rx) = mpsc::channel();
29758    let payload = PanicPayloadOnDrop(drop_tx);
29759    let queued = controller
29760      .try_spawn_blocking(move || {
29761        let _payload = &payload;
29762      })
29763      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29764
29765    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29766    let shutdown_controller = Arc::clone(&controller);
29767    let shutdown = std::thread::spawn(move || {
29768      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29769    });
29770    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29771    release_tx.send(()).unwrap();
29772    shutdown_rx
29773      .recv_timeout(Duration::from_secs(2))
29774      .expect("hostile panic payload destruction must remain contained")
29775      .unwrap();
29776    shutdown
29777      .join()
29778      .expect("the caught payload must not panic during destruction");
29779    futures::executor::block_on(running).unwrap();
29780    assert!(futures::executor::block_on(queued).is_err());
29781  }
29782
29783  #[cfg(napi_runtime_os_threads)]
29784  #[test]
29785  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
29786    use std::sync::mpsc;
29787
29788    struct DropSignal(mpsc::Sender<()>);
29789    impl Drop for DropSignal {
29790      fn drop(&mut self) {
29791        let _ = self.0.send(());
29792      }
29793    }
29794
29795    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
29796    let (running_tx, running_rx) = mpsc::channel();
29797    let (release_tx, release_rx) = mpsc::channel();
29798    let running = controller
29799      .try_spawn_blocking(move || {
29800        running_tx.send(()).unwrap();
29801        release_rx.recv().unwrap();
29802        7usize
29803      })
29804      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29805    running_rx
29806      .recv_timeout(Duration::from_secs(5))
29807      .expect("the first blocking job must hold the only blocking slot");
29808
29809    let queued_ran = Arc::new(AtomicBool::new(false));
29810    let queued_ran_work = Arc::clone(&queued_ran);
29811    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
29812    let queued_drop_signal = DropSignal(queued_dropped_tx);
29813    let queued = controller
29814      .try_spawn_blocking(move || {
29815        let _drop_signal = queued_drop_signal;
29816        queued_ran_work.store(true, Ordering::SeqCst);
29817        11usize
29818      })
29819      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29820
29821    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29822    let shutdown_controller = Arc::clone(&controller);
29823    let shutdown = std::thread::spawn(move || {
29824      shutdown_controller.shutdown().unwrap();
29825      shutdown_done_tx.send(()).unwrap();
29826    });
29827
29828    queued_dropped_rx
29829      .recv_timeout(Duration::from_secs(5))
29830      .expect("shutdown must drop queued blocking work");
29831    assert!(
29832      !queued_ran.load(Ordering::SeqCst),
29833      "queued blocking work must not execute"
29834    );
29835    assert!(
29836      shutdown_done_rx.try_recv().is_err(),
29837      "shutdown must wait while an accepted blocking closure is still running"
29838    );
29839
29840    release_tx.send(()).unwrap();
29841    shutdown_done_rx
29842      .recv_timeout(Duration::from_secs(5))
29843      .expect("shutdown must finish after running blocking work retires");
29844    shutdown.join().unwrap();
29845    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
29846    assert!(
29847      futures::executor::block_on(queued).is_err(),
29848      "the queued blocking handle must resolve as cancelled"
29849    );
29850  }
29851
29852  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29853  #[test]
29854  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
29855    use std::sync::mpsc;
29856
29857    struct PanicOnDrop(mpsc::Sender<()>);
29858    impl Drop for PanicOnDrop {
29859      fn drop(&mut self) {
29860        let _ = self.0.send(());
29861        panic!("intentional queued blocking destructor panic");
29862      }
29863    }
29864
29865    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
29866    let (running_tx, running_rx) = mpsc::channel();
29867    let (release_tx, release_rx) = mpsc::channel();
29868    let running = controller
29869      .try_spawn_blocking(move || {
29870        running_tx.send(()).unwrap();
29871        release_rx.recv().unwrap();
29872      })
29873      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29874    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29875
29876    let (drop_tx, drop_rx) = mpsc::channel();
29877    let panic_on_drop = PanicOnDrop(drop_tx);
29878    let queued = controller
29879      .try_spawn_blocking(move || {
29880        let _keep_captured_until_run = &panic_on_drop;
29881      })
29882      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29883
29884    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29885    let shutdown_controller = Arc::clone(&controller);
29886    let shutdown = std::thread::spawn(move || {
29887      shutdown_controller.shutdown().unwrap();
29888      shutdown_done_tx.send(()).unwrap();
29889    });
29890
29891    drop_rx
29892      .recv_timeout(Duration::from_secs(5))
29893      .expect("shutdown must attempt to drop the queued closure");
29894    release_tx.send(()).unwrap();
29895    shutdown_done_rx
29896      .recv_timeout(Duration::from_secs(5))
29897      .expect("a panicking queued destructor must not strand shutdown");
29898    shutdown
29899      .join()
29900      .expect("shutdown must contain the destructor panic");
29901    futures::executor::block_on(running).unwrap();
29902    assert!(futures::executor::block_on(queued).is_err());
29903
29904    controller
29905      .start()
29906      .expect("the stopped controller must restart");
29907    let restarted = controller
29908      .try_spawn_blocking(|| 17usize)
29909      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29910    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
29911    controller.shutdown().unwrap();
29912  }
29913
29914  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29915  #[test]
29916  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
29917    use std::{panic::panic_any, sync::mpsc};
29918
29919    struct ReentrantPayload {
29920      controller: Arc<RuntimeController>,
29921      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
29922    }
29923
29924    impl Drop for ReentrantPayload {
29925      fn drop(&mut self) {
29926        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
29927        let shutdown = self.controller.shutdown();
29928        self.result.send((generation, shutdown)).unwrap();
29929      }
29930    }
29931
29932    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
29933
29934    impl Drop for PanicPayloadOnDrop {
29935      fn drop(&mut self) {
29936        panic_any(self.0.take().expect("the result must panic only once"));
29937      }
29938    }
29939
29940    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
29941    let generation = controller.backend().generation();
29942    let (started_tx, started_rx) = mpsc::channel();
29943    let (release_tx, release_rx) = mpsc::channel();
29944    let (result_tx, result_rx) = mpsc::channel();
29945    let payload_controller = Arc::clone(&controller);
29946    let handle = controller
29947      .try_spawn_blocking(move || {
29948        started_tx.send(()).unwrap();
29949        release_rx.recv().unwrap();
29950        PanicPayloadOnDrop(Some(ReentrantPayload {
29951          controller: payload_controller,
29952          result: result_tx,
29953        }))
29954      })
29955      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29956    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29957
29958    // Detach the receiver before the result is delivered. Sending then drops
29959    // the result on the worker, whose destructor intentionally panics.
29960    drop(handle);
29961    release_tx.send(()).unwrap();
29962    let (observed_generation, reentrant_shutdown) = result_rx
29963      .recv_timeout(Duration::from_secs(5))
29964      .expect("the dropped result panic payload must not self-deadlock");
29965    assert_eq!(observed_generation, Some(generation));
29966    let error =
29967      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
29968    assert!(error.to_string().contains("work running on that runtime"));
29969
29970    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29971    let shutdown_controller = Arc::clone(&controller);
29972    let shutdown = std::thread::spawn(move || {
29973      shutdown_controller.shutdown().unwrap();
29974      shutdown_done_tx.send(()).unwrap();
29975    });
29976    shutdown_done_rx
29977      .recv_timeout(Duration::from_secs(5))
29978      .expect("a dropped-result destructor panic must not strand shutdown");
29979    shutdown
29980      .join()
29981      .expect("shutdown must contain the destructor panic");
29982
29983    controller
29984      .start()
29985      .expect("the stopped controller must restart");
29986    let restarted = controller
29987      .try_spawn_blocking(|| 19usize)
29988      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29989    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
29990    controller.shutdown().unwrap();
29991  }
29992
29993  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
29994  #[test]
29995  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
29996    use std::sync::mpsc;
29997
29998    struct PanicOnDrop(mpsc::Sender<()>);
29999
30000    impl Drop for PanicOnDrop {
30001      fn drop(&mut self) {
30002        self.0.send(()).unwrap();
30003        panic!("intentional buffered blocking result destructor panic");
30004      }
30005    }
30006
30007    let options = RuntimeOptions {
30008      flavor: RuntimeFlavor::MultiThread,
30009      worker_threads: 2,
30010      max_blocking_tasks: 1,
30011      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
30012      park_deadline: None,
30013      drain_linger: DEFAULT_DRAIN_LINGER,
30014    };
30015    let executor =
30016      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30017    let (started_tx, started_rx) = mpsc::channel();
30018    let (dropped_tx, dropped_rx) = mpsc::channel();
30019    let handle = executor.schedule_blocking(move || {
30020      started_tx.send(()).unwrap();
30021      PanicOnDrop(dropped_tx)
30022    });
30023    started_rx
30024      .recv_timeout(Duration::from_secs(2))
30025      .expect("the blocking closure must run");
30026    wait_until("the completed blocking result to be buffered", || {
30027      executor.active_blocking.load(Ordering::Acquire) == 0
30028    });
30029    assert!(
30030      dropped_rx.try_recv().is_err(),
30031      "the buffered result must remain owned by the live receiver"
30032    );
30033
30034    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
30035    assert!(
30036      dropped.is_ok(),
30037      "dropping the buffered result must contain its destructor panic"
30038    );
30039    dropped_rx
30040      .recv_timeout(Duration::from_secs(2))
30041      .expect("dropping the receiver must reclaim the buffered result");
30042  }
30043
30044  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30045  #[test]
30046  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
30047    use std::sync::mpsc;
30048
30049    struct PanicOnDrop(mpsc::Sender<()>);
30050    impl Drop for PanicOnDrop {
30051      fn drop(&mut self) {
30052        let _ = self.0.send(());
30053        panic!("intentional late blocking destructor panic");
30054      }
30055    }
30056
30057    let executor = multi_thread_executor(2, None, "closed-queue-drop");
30058    executor.begin_shutdown();
30059
30060    let (drop_tx, drop_rx) = mpsc::channel();
30061    let panic_on_drop = PanicOnDrop(drop_tx);
30062    let handle = executor.schedule_blocking(move || {
30063      let _keep_captured_until_run = &panic_on_drop;
30064    });
30065
30066    drop_rx
30067      .recv_timeout(Duration::from_secs(5))
30068      .expect("the closed queue must retire the rejected closure");
30069    assert!(
30070      futures::executor::block_on(handle).is_err(),
30071      "the rejected blocking handle must resolve as cancelled"
30072    );
30073  }
30074
30075  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
30076  #[test]
30077  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
30078    struct PanicWake;
30079    impl std::task::Wake for PanicWake {
30080      fn wake(self: Arc<Self>) {
30081        panic!("intentional timer waker panic");
30082      }
30083    }
30084
30085    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
30086    let backend = controller.backend();
30087    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30088      panic!("configured MultiThread backend must create a Rayon executor");
30089    };
30090    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
30091    let waker = Waker::from(Arc::new(PanicWake));
30092    let mut cx = Context::from_waker(&waker);
30093    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30094    drop(backend);
30095
30096    controller
30097      .shutdown()
30098      .expect("shutdown must contain the timer waker panic");
30099    controller
30100      .start()
30101      .expect("the stopped controller must restart");
30102    let restarted = controller
30103      .try_spawn_blocking(|| 23usize)
30104      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
30105    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
30106    controller.shutdown().unwrap();
30107  }
30108
30109  #[cfg(napi_runtime_os_threads)]
30110  #[test]
30111  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
30112    use std::sync::mpsc;
30113
30114    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
30115    let backend = controller.backend();
30116    let old_generation = backend.generation();
30117    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30118      panic!("configured MultiThread backend must create a Rayon executor");
30119    };
30120    let old_workers = Arc::clone(&executor.worker_lifecycle);
30121    drop(backend);
30122
30123    let (running_tx, running_rx) = mpsc::channel();
30124    let (release_tx, release_rx) = mpsc::channel();
30125    let running = controller
30126      .try_spawn_blocking(move || {
30127        running_tx.send(()).unwrap();
30128        release_rx.recv().unwrap();
30129      })
30130      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
30131    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30132
30133    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
30134    let shutdown_controller = Arc::clone(&controller);
30135    let shutdown = std::thread::spawn(move || {
30136      shutdown_controller.shutdown().unwrap();
30137      shutdown_done_tx.send(()).unwrap();
30138    });
30139    wait_until("controller entered Stopping", || {
30140      let state = controller
30141        .state
30142        .lock()
30143        .unwrap_or_else(std::sync::PoisonError::into_inner);
30144      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
30145    });
30146
30147    let (start_done_tx, start_done_rx) = mpsc::channel();
30148    let start_controller = Arc::clone(&controller);
30149    let starter = std::thread::spawn(move || {
30150      start_controller.start().unwrap();
30151      start_done_tx.send(()).unwrap();
30152    });
30153    assert!(
30154      start_done_rx.try_recv().is_err(),
30155      "start must wait while shutdown is quiescing"
30156    );
30157    assert!(
30158      old_workers.remaining() > 0,
30159      "the held old generation must still own workers"
30160    );
30161
30162    release_tx.send(()).unwrap();
30163    futures::executor::block_on(running).unwrap();
30164    shutdown_done_rx
30165      .recv_timeout(Duration::from_secs(5))
30166      .unwrap();
30167    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
30168    shutdown.join().unwrap();
30169    starter.join().unwrap();
30170    assert_eq!(
30171      old_workers.remaining(),
30172      0,
30173      "restart may begin only after every old worker exits"
30174    );
30175
30176    let backend = controller.backend();
30177    assert_ne!(backend.generation(), old_generation);
30178    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30179      panic!("restarted backend must remain MultiThread");
30180    };
30181    assert_eq!(
30182      executor.pool.current_num_threads(),
30183      controller.options().worker_threads
30184    );
30185    drop(backend);
30186    controller.shutdown().unwrap();
30187  }
30188
30189  #[cfg(napi_runtime_os_threads)]
30190  #[test]
30191  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
30192    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
30193    let task_controller = Arc::clone(&controller);
30194    let handle = controller
30195      .try_spawn(async move {
30196        task_controller
30197          .shutdown()
30198          .expect_err("runtime work must not wait for its own generation")
30199          .to_string()
30200      })
30201      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
30202    let message = futures::executor::block_on(handle).unwrap();
30203    assert!(message.contains("cannot shut down"));
30204    controller.shutdown().unwrap();
30205  }
30206
30207  // ---- two-phase shutdown handshake ----------------------------------------
30208  // `shutdown` is `begin_shutdown` + `finish_shutdown`, so every existing
30209  // shutdown test above also covers the composition. These cover the window
30210  // the split opens: phase 1 must return while work is still running, the
30211  // poll must eventually answer `false`, a re-entered phase 1 must never
30212  // wait, and every OPENED phase 1 must pair with exactly one
30213  // `finish_shutdown` -- no more (the extra one is the orphan error) and no
30214  // fewer (a racing `start` in the window must not turn a completed shutdown
30215  // into an error).
30216
30217  #[cfg(napi_runtime_os_threads)]
30218  #[test]
30219  fn two_phase_shutdown_matches_single_call() {
30220    // The threaded WASI artifact enters shutdown from the JS thread, so phase
30221    // 1 has to come back while a blocking closure is still running; phase 2
30222    // then does exactly what the single call does once the closure retires.
30223    let controller = multi_thread_controller("two-phase-shutdown", 2, 1);
30224    let entered = Arc::new(AtomicBool::new(false));
30225    let release = Arc::new(AtomicBool::new(false));
30226    let job_entered = Arc::clone(&entered);
30227    let job_release = Arc::clone(&release);
30228    let handle = controller
30229      .try_spawn_blocking(move || {
30230        job_entered.store(true, Ordering::SeqCst);
30231        while !job_release.load(Ordering::SeqCst) {
30232          std::hint::spin_loop();
30233        }
30234        7usize
30235      })
30236      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30237    while !entered.load(Ordering::SeqCst) {
30238      std::thread::yield_now();
30239    }
30240
30241    assert!(
30242      controller
30243        .begin_shutdown()
30244        .expect("phase 1 on a running generation must be accepted"),
30245      "phase 1 must report the still-running blocking closure instead of waiting for it"
30246    );
30247    assert!(
30248      controller.runtime_work_pending(),
30249      "the parked closure holds its generation guard"
30250    );
30251    // Admission is closed by `backend_locked` the moment `Stopping` is
30252    // published; phase 1 adds no admission code of its own.
30253    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30254      panic!("a submission between the phases must be rejected");
30255    };
30256    assert_eq!(
30257      error.to_string(),
30258      "the async runtime is stopped; call start before submitting work"
30259    );
30260
30261    release.store(true, Ordering::SeqCst);
30262    let deadline = Instant::now() + Duration::from_secs(5);
30263    while controller.runtime_work_pending() {
30264      assert!(
30265        Instant::now() < deadline,
30266        "the released closure must retire and the poll must answer false"
30267      );
30268      std::thread::yield_now();
30269    }
30270    assert_eq!(
30271      futures::executor::block_on(handle).unwrap(),
30272      7usize,
30273      "the closure that phase 1 refused to wait for still delivers its result"
30274    );
30275
30276    controller
30277      .finish_shutdown()
30278      .expect("phase 2 must complete the outstanding phase 1");
30279    {
30280      let state = controller
30281        .state
30282        .lock()
30283        .unwrap_or_else(std::sync::PoisonError::into_inner);
30284      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30285      assert!(
30286        state.draining.is_none(),
30287        "phase 2 must consume the phase-1 handoff"
30288      );
30289    }
30290
30291    // The split leaves the restart path exactly as the single call does.
30292    controller.start().expect("a stopped runtime must restart");
30293    drop(controller.backend());
30294    controller
30295      .shutdown()
30296      .expect("the single call must still shut the restarted runtime down");
30297  }
30298
30299  #[cfg(napi_runtime_os_threads)]
30300  #[test]
30301  fn concurrent_shutdown_during_two_phase_still_reports_success() {
30302    // A `shutdown` from a second thread must still answer `Ok(())` while a
30303    // handshake is in flight, and neither thread may wedge the other. THIS
30304    // test pins the interleaving where the phase-1 OWNER drains: the
30305    // concurrent call is started from the owner's own
30306    // `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e. after the handoff is out
30307    // of the state and before the owner waits, so the second thread provably
30308    // arrives at an already-taken handshake and must report the owner's
30309    // publication. The mirror image (the concurrent call drains, the owner
30310    // finishes afterwards) is
30311    // `phase_one_owner_finishes_after_a_concurrent_shutdown_drains`.
30312    let controller = Arc::new(multi_thread_controller("two-phase-concurrent", 2, 1));
30313    let entered = Arc::new(AtomicBool::new(false));
30314    let committed = Arc::new(AtomicBool::new(false));
30315    let job_entered = Arc::clone(&entered);
30316    let job_committed = Arc::clone(&committed);
30317    // The closure retires only once the concurrent call has committed, so the
30318    // owner's drain cannot run to completion before the second thread is in
30319    // flight: both are always overlapped, as before.
30320    let handle = controller
30321      .try_spawn_blocking(move || {
30322        job_entered.store(true, Ordering::SeqCst);
30323        while !job_committed.load(Ordering::SeqCst) {
30324          std::hint::spin_loop();
30325        }
30326        3usize
30327      })
30328      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30329    wait_until("the blocking closure to park", || {
30330      entered.load(Ordering::SeqCst)
30331    });
30332    assert!(
30333      controller
30334        .begin_shutdown()
30335        .expect("phase 1 must be accepted")
30336    );
30337
30338    let (concurrent_tx, concurrent_rx) = std::sync::mpsc::channel();
30339    let hook_controller = Arc::clone(&controller);
30340    let hook_committed = Arc::clone(&committed);
30341    let owner_took_the_drain = Arc::new(AtomicBool::new(false));
30342    let hook_took_the_drain = Arc::clone(&owner_took_the_drain);
30343    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30344      *slot.borrow_mut() = Some(Box::new(move || {
30345        hook_took_the_drain.store(true, Ordering::SeqCst);
30346        let concurrent = std::thread::spawn(move || {
30347          hook_committed.store(true, Ordering::SeqCst);
30348          hook_controller.shutdown()
30349        });
30350        concurrent_tx
30351          .send(concurrent)
30352          .expect("the test thread must still be waiting for the concurrent handle");
30353      }));
30354    });
30355
30356    controller
30357      .finish_shutdown()
30358      .expect("phase 2 must complete the handshake");
30359    assert!(
30360      owner_took_the_drain.load(Ordering::SeqCst),
30361      "the phase-1 owner must be the thread that took the handoff"
30362    );
30363    concurrent_rx
30364      .recv()
30365      .expect("the hook must have started the concurrent shutdown")
30366      .join()
30367      .expect("the concurrent shutdown thread must not panic")
30368      .expect("a concurrent single-call shutdown must still succeed");
30369
30370    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30371    let state = controller
30372      .state
30373      .lock()
30374      .unwrap_or_else(std::sync::PoisonError::into_inner);
30375    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30376    assert!(state.draining.is_none());
30377  }
30378
30379  #[cfg(napi_runtime_os_threads)]
30380  #[test]
30381  fn a_phase_one_during_the_in_flight_drain_answers_without_waiting() {
30382    // The handoff slot must never go empty while the join is still running.
30383    // `finish_shutdown` takes the backend out of the state and only then
30384    // waits; for the whole of that window the lifecycle still reads
30385    // `Stopping`. If the slot were left `None` there, a re-entering phase 1
30386    // would fall through to the lifecycle loop and PARK on `lifecycle_changed`
30387    // until the join it is racing publishes `Stopped` -- breaking the
30388    // documented "a `begin_shutdown` while one is outstanding never waits"
30389    // contract -- and `runtime_work_pending` would answer `false` about work
30390    // that is demonstrably still parked. On a host that drives both calls from
30391    // the same thread pool that the drained work needs, that park is a
30392    // deadlock.
30393    //
30394    // The probe is started from `AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK`, i.e.
30395    // provably after the handoff has left the slot and before the finisher
30396    // waits, and the closure it must see is released only once the probe has
30397    // answered (or a bounded watchdog gives up), so a parking probe FAILS the
30398    // test instead of hanging it.
30399    let controller = Arc::new(multi_thread_controller("two-phase-inflight-drain", 2, 1));
30400    let entered = Arc::new(AtomicBool::new(false));
30401    let release = Arc::new(AtomicBool::new(false));
30402    let job_entered = Arc::clone(&entered);
30403    let job_release = Arc::clone(&release);
30404    let handle = controller
30405      .try_spawn_blocking(move || {
30406        job_entered.store(true, Ordering::SeqCst);
30407        while !job_release.load(Ordering::SeqCst) {
30408          std::hint::spin_loop();
30409        }
30410        11usize
30411      })
30412      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30413    wait_until("the blocking closure to park", || {
30414      entered.load(Ordering::SeqCst)
30415    });
30416    assert!(
30417      controller
30418        .begin_shutdown()
30419        .expect("phase 1 must be accepted"),
30420      "the parked closure must be reported as live"
30421    );
30422
30423    let (probe_tx, probe_rx) = std::sync::mpsc::channel();
30424    let hook_controller = Arc::clone(&controller);
30425    let hook_release = Arc::clone(&release);
30426    AFTER_SHUTDOWN_DRAIN_TAKEN_TEST_HOOK.with(|slot| {
30427      *slot.borrow_mut() = Some(Box::new(move || {
30428        let probe_controller = Arc::clone(&hook_controller);
30429        let probe = std::thread::spawn(move || {
30430          let pending = probe_controller.runtime_work_pending();
30431          (pending, probe_controller.begin_shutdown())
30432        });
30433        let watchdog = std::thread::spawn(move || {
30434          let deadline = Instant::now() + Duration::from_secs(5);
30435          while !probe.is_finished() && Instant::now() < deadline {
30436            std::thread::yield_now();
30437          }
30438          // Whether the probe answered BEFORE the drained closure retired is
30439          // the whole verdict: releasing afterwards would let a parked probe
30440          // be woken by the `Stopped` publication and look healthy.
30441          let answered_during_the_drain = probe.is_finished();
30442          hook_release.store(true, Ordering::SeqCst);
30443          (answered_during_the_drain, probe.join())
30444        });
30445        probe_tx
30446          .send(watchdog)
30447          .expect("the test thread must still be waiting for the probe handle");
30448      }));
30449    });
30450
30451    controller
30452      .finish_shutdown()
30453      .expect("phase 2 must complete the outstanding phase 1");
30454    let (answered_during_the_drain, probe) = probe_rx
30455      .recv()
30456      .expect("the hook must have started the probe")
30457      .join()
30458      .expect("the watchdog thread must not panic");
30459    let (pending, verdict) = probe.expect("the probe thread must not panic");
30460    assert!(
30461      answered_during_the_drain,
30462      "a phase 1 re-entered during the in-flight drain must answer without waiting for the join"
30463    );
30464    assert!(
30465      pending,
30466      "the poll must still see the parked closure while the drain that is waiting for it runs"
30467    );
30468    assert!(
30469      verdict.expect("a phase 1 during the in-flight drain must be accepted"),
30470      "the re-entering phase 1 must report the parked closure as live"
30471    );
30472
30473    assert_eq!(futures::executor::block_on(handle).unwrap(), 11usize);
30474    {
30475      let state = controller
30476        .state
30477        .lock()
30478        .unwrap_or_else(std::sync::PoisonError::into_inner);
30479      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30480      assert!(
30481        state.draining.is_none(),
30482        "publishing `Stopped` must retire the in-flight drain marker too"
30483      );
30484    }
30485    assert!(
30486      !controller.runtime_work_pending(),
30487      "a stopped runtime has no pending work"
30488    );
30489
30490    controller.start().expect("a stopped runtime must restart");
30491    drop(controller.backend());
30492    controller
30493      .shutdown()
30494      .expect("the single call must still shut the restarted runtime down");
30495  }
30496
30497  #[cfg(napi_runtime_os_threads)]
30498  #[test]
30499  fn phase_one_owner_finishes_after_a_concurrent_shutdown_drains() {
30500    // The other half of the race above, hand-sequenced by a join: a
30501    // concurrent single-call `shutdown` takes the outstanding handoff AND
30502    // completes the whole shutdown, and only then does the thread that opened
30503    // phase 1 call its own phase 2. The runtime is already `Stopped`, so the
30504    // join that caller is owed has happened -- phase 2 owes it `Ok(())`, not
30505    // "shutdown was not started", whoever performed that join.
30506    let controller = Arc::new(multi_thread_controller("two-phase-owner-finish", 2, 1));
30507    let entered = Arc::new(AtomicBool::new(false));
30508    let release = Arc::new(AtomicBool::new(false));
30509    let job_entered = Arc::clone(&entered);
30510    let job_release = Arc::clone(&release);
30511    let handle = controller
30512      .try_spawn_blocking(move || {
30513        job_entered.store(true, Ordering::SeqCst);
30514        while !job_release.load(Ordering::SeqCst) {
30515          std::hint::spin_loop();
30516        }
30517        3usize
30518      })
30519      .unwrap_or_else(|_| panic!("running runtime must accept the blocking job"));
30520    wait_until("the blocking closure to park", || {
30521      entered.load(Ordering::SeqCst)
30522    });
30523    assert!(
30524      controller
30525        .begin_shutdown()
30526        .expect("phase 1 must be accepted"),
30527      "the parked closure must be reported as live"
30528    );
30529
30530    let single_call = Arc::clone(&controller);
30531    let concurrent = std::thread::spawn(move || single_call.shutdown());
30532    release.store(true, Ordering::SeqCst);
30533    concurrent
30534      .join()
30535      .expect("the concurrent shutdown thread must not panic")
30536      .expect("a concurrent single-call shutdown must still succeed");
30537    {
30538      let state = controller
30539        .state
30540        .lock()
30541        .unwrap_or_else(std::sync::PoisonError::into_inner);
30542      assert!(
30543        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30544        "the concurrent call must have published the stop"
30545      );
30546      assert!(
30547        state.draining.is_none(),
30548        "the concurrent call must have consumed the phase-1 handoff"
30549      );
30550    }
30551
30552    controller
30553      .finish_shutdown()
30554      .expect("the thread that began phase 1 must still be told its shutdown completed");
30555    assert_eq!(futures::executor::block_on(handle).unwrap(), 3usize);
30556
30557    // Flipped with the idempotent model (it used to expect the orphan error
30558    // here): a phase 2 after a restart finds nothing outstanding, so it is a
30559    // no-op `Ok(())` -- and it must NOT adopt the generation the restart
30560    // published.
30561    controller.start().expect("a stopped runtime must restart");
30562    drop(controller.backend());
30563    controller
30564      .finish_shutdown()
30565      .expect("phase 2 after a restart is a no-op, not an error");
30566    {
30567      let state = controller
30568        .state
30569        .lock()
30570        .unwrap_or_else(std::sync::PoisonError::into_inner);
30571      assert!(
30572        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30573        "the no-op phase 2 must not stop the restarted generation"
30574      );
30575    }
30576    controller
30577      .shutdown()
30578      .expect("the single call must still shut the restarted runtime down");
30579  }
30580
30581  #[test]
30582  fn begin_shutdown_twice_does_not_block() {
30583    // The host thread may re-enter the handshake from a turn it took between
30584    // the phases. A second phase 1 must answer from the outstanding one --
30585    // taking the `Stopping` arm instead would park this thread on the condvar
30586    // forever, so a regression hangs this test rather than failing it. It
30587    // also OPENS nothing -- and, flipped with the idempotent model, the second
30588    // phase 2 is no longer an orphan error: the stop it was asked to complete
30589    // has been completed, so it answers `Ok(())`.
30590    let controller = current_thread_controller("two-phase-reentrant-begin");
30591    drop(controller.backend());
30592
30593    assert!(
30594      !controller
30595        .begin_shutdown()
30596        .expect("phase 1 on an idle generation must be accepted"),
30597      "an idle generation has nothing for the host to wait for"
30598    );
30599    {
30600      let state = controller
30601        .state
30602        .lock()
30603        .unwrap_or_else(std::sync::PoisonError::into_inner);
30604      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_)));
30605    }
30606    assert!(
30607      !controller
30608        .begin_shutdown()
30609        .expect("a second phase 1 must answer immediately"),
30610      "the second phase 1 must report the same verdict"
30611    );
30612
30613    controller
30614      .finish_shutdown()
30615      .expect("one phase 2 completes the single outstanding phase 1");
30616    {
30617      let state = controller
30618        .state
30619        .lock()
30620        .unwrap_or_else(std::sync::PoisonError::into_inner);
30621      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30622      assert!(state.draining.is_none());
30623    }
30624
30625    controller
30626      .finish_shutdown()
30627      .expect("a second phase 2 repeats the completed stop instead of erroring");
30628    {
30629      let state = controller
30630        .state
30631        .lock()
30632        .unwrap_or_else(std::sync::PoisonError::into_inner);
30633      assert!(
30634        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30635        "the repeated phase 2 must change nothing"
30636      );
30637    }
30638  }
30639
30640  #[test]
30641  fn current_thread_two_phase_is_idle_immediately() {
30642    // No cfg gate: this is the threadless `wasm32-wasip1` shape, where the
30643    // handshake must degrade to two cheap calls.
30644    let controller = current_thread_controller("two-phase-current-thread");
30645    drop(controller.backend());
30646    assert!(
30647      !controller.runtime_work_pending(),
30648      "an idle running generation has no pending work"
30649    );
30650
30651    assert!(
30652      !controller
30653        .begin_shutdown()
30654        .expect("phase 1 must be accepted"),
30655      "phase 1 on an idle generation must report nothing to wait for"
30656    );
30657    assert!(
30658      !controller.runtime_work_pending(),
30659      "the poll must stay false across the window"
30660    );
30661    controller.finish_shutdown().expect("phase 2 must complete");
30662    {
30663      let state = controller
30664        .state
30665        .lock()
30666        .unwrap_or_else(std::sync::PoisonError::into_inner);
30667      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30668    }
30669
30670    controller.start().expect("a stopped runtime must restart");
30671    drop(controller.backend());
30672    controller
30673      .shutdown()
30674      .expect("the single call must still work");
30675  }
30676
30677  #[test]
30678  fn finish_shutdown_without_begin_is_an_error() {
30679    let controller = current_thread_controller("two-phase-orphan-finish");
30680    drop(controller.backend());
30681    let error = controller
30682      .finish_shutdown()
30683      .expect_err("phase 2 without an outstanding phase 1 must be rejected");
30684    assert_eq!(
30685      error.to_string(),
30686      "the async runtime shutdown was not started; call begin_shutdown before finish_shutdown"
30687    );
30688    controller
30689      .shutdown()
30690      .expect("the rejected phase 2 must leave the runtime shuttable");
30691  }
30692
30693  #[test]
30694  fn a_restart_does_not_inherit_a_settled_shutdown_handoff() {
30695    // A phase 1 on an already stopped runtime settles with no backend. The
30696    // `start` that follows publishes a FRESH generation, which owes nothing
30697    // to a handshake opened against the previous one. If the settled handoff
30698    // survived that `start`, the next `shutdown` would short-circuit on it --
30699    // phase 1 answering from the stale handoff, phase 2 consuming it -- and
30700    // report success while this generation was still `Running`, still holding
30701    // its backend and still accepting work.
30702    let controller = current_thread_controller("two-phase-stale-settled");
30703    drop(controller.backend());
30704    controller
30705      .shutdown()
30706      .expect("the first shutdown must complete");
30707
30708    assert!(
30709      !controller
30710        .begin_shutdown()
30711        .expect("phase 1 on a stopped runtime must be accepted"),
30712      "a stopped runtime has no work for the host to wait for"
30713    );
30714    controller.start().expect("a stopped runtime must restart");
30715    {
30716      let state = controller
30717        .state
30718        .lock()
30719        .unwrap_or_else(std::sync::PoisonError::into_inner);
30720      assert!(
30721        state.draining.is_none(),
30722        "a fresh generation must not inherit the settled handoff"
30723      );
30724    }
30725    drop(controller.backend());
30726
30727    controller
30728      .shutdown()
30729      .expect("the second shutdown must stop the restarted generation");
30730    {
30731      let state = controller
30732        .state
30733        .lock()
30734        .unwrap_or_else(std::sync::PoisonError::into_inner);
30735      assert!(
30736        matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30737        "the restarted generation must really be stopped, not reported stopped"
30738      );
30739      assert!(state.draining.is_none());
30740    }
30741    assert!(
30742      controller.current_backend.load().is_none(),
30743      "the stopped generation's backend must be gone"
30744    );
30745    assert!(
30746      !controller.runtime_work_pending(),
30747      "a stopped runtime has no pending work"
30748    );
30749    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
30750      panic!("a submission after the second shutdown must be rejected");
30751    };
30752    assert_eq!(
30753      error.to_string(),
30754      "the async runtime is stopped; call start before submitting work"
30755    );
30756  }
30757
30758  #[test]
30759  fn a_settled_phase_one_is_reported_by_every_finish_across_a_restart() {
30760    // Renamed and flipped again with the idempotent model (it counted phases
30761    // before, and errored before that): a `start` between the phases DROPS the
30762    // settled handoff -- so it can never answer for the new generation -- but
30763    // the stop that was announced still happened, so EVERY phase 2 that
30764    // follows reports it, and none of them touches the restarted generation.
30765    // Without this, a single-call `shutdown` racing a `start` reports `Err`
30766    // for a shutdown it performed.
30767    let controller = current_thread_controller("two-phase-settled-idempotent");
30768    drop(controller.backend());
30769    controller
30770      .shutdown()
30771      .expect("the first shutdown must complete");
30772    assert!(
30773      !controller
30774        .begin_shutdown()
30775        .expect("phase 1 on a stopped runtime must be accepted")
30776    );
30777    controller.start().expect("a stopped runtime must restart");
30778    {
30779      let state = controller
30780        .state
30781        .lock()
30782        .unwrap_or_else(std::sync::PoisonError::into_inner);
30783      assert!(
30784        state.draining.is_none(),
30785        "the restart must drop the settled handoff"
30786      );
30787      assert!(
30788        state.phase_ever_opened,
30789        "the restart must not erase the fact that a stop was announced"
30790      );
30791    }
30792
30793    controller
30794      .finish_shutdown()
30795      .expect("the announced stop must be reported to the caller that opened it");
30796    controller
30797      .finish_shutdown()
30798      .expect("and to every phase 2 after it -- the call is idempotent");
30799    {
30800      let state = controller
30801        .state
30802        .lock()
30803        .unwrap_or_else(std::sync::PoisonError::into_inner);
30804      assert!(
30805        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30806        "neither phase 2 may stop the generation the restart published"
30807      );
30808    }
30809    drop(controller.backend());
30810    controller
30811      .shutdown()
30812      .expect("the no-op phase 2s must leave the runtime shuttable");
30813  }
30814
30815  #[test]
30816  fn shutdown_racing_a_start_still_reports_ok() {
30817    // The defect this whole rule exists for. The single call takes
30818    // the lifecycle lock twice; on an already stopped runtime phase 1 settles
30819    // with no backend and publishes no `Stopping` for a `start` to wait in,
30820    // so a `start` can land in the window and drop the parked handoff. Phase 2
30821    // then finds an empty slot and a `Running` lifecycle. `phase_ever_opened`
30822    // is what lets it answer `Ok(())` for the shutdown this call really did
30823    // perform, instead of "shutdown was not started".
30824    //
30825    // Deterministic, not stressed: the hook runs the `start` inside the window
30826    // itself. In the wild the same interleaving shows up as a flaky
30827    // `NO_PHASE_ERROR` from a `shutdown` racing a restart on another thread.
30828    let controller = Arc::new(current_thread_controller("shutdown-racing-start"));
30829    drop(controller.backend());
30830    controller
30831      .shutdown()
30832      .expect("the first shutdown must complete");
30833
30834    let start_controller = Arc::clone(&controller);
30835    let started = Arc::new(AtomicBool::new(false));
30836    let hook_started = Arc::clone(&started);
30837    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
30838      *slot.borrow_mut() = Some(Box::new(move || {
30839        start_controller
30840          .start()
30841          .expect("a stopped runtime must restart");
30842        hook_started.store(true, Ordering::SeqCst);
30843      }));
30844    });
30845    controller
30846      .shutdown()
30847      .expect("a shutdown must report the phase it opened, even across a racing start");
30848    assert!(
30849      started.load(Ordering::SeqCst),
30850      "the hook must have run the start inside the window"
30851    );
30852
30853    {
30854      let state = controller
30855        .state
30856        .lock()
30857        .unwrap_or_else(std::sync::PoisonError::into_inner);
30858      assert!(
30859        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30860        "the racing start wins the window: its generation is live, not stopped"
30861      );
30862      assert!(
30863        state.draining.is_none(),
30864        "the start must have dropped the settled handoff"
30865      );
30866    }
30867
30868    // The generation the start published is a real one: it accepts work, and
30869    // the next shutdown really stops it.
30870    let ran = Arc::new(AtomicBool::new(false));
30871    let ran_task = Arc::clone(&ran);
30872    controller
30873      .try_spawn_detached(async move {
30874        ran_task.store(true, Ordering::SeqCst);
30875      })
30876      .unwrap_or_else(|_| panic!("the generation the start published must accept work"));
30877    let dispatch = controller_current_thread_dispatch(&controller);
30878    controller.drive_current_thread_dispatch(dispatch, false);
30879    assert!(ran.load(Ordering::SeqCst));
30880    controller
30881      .shutdown()
30882      .expect("the restarted generation must still shut down");
30883    let state = controller
30884      .state
30885      .lock()
30886      .unwrap_or_else(std::sync::PoisonError::into_inner);
30887    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
30888    assert!(state.draining.is_none());
30889  }
30890
30891  // The runtime FLAVOR under test is CurrentThread -- that is where the race
30892  // this pins was 8.2% -- but the probe itself needs a second OS thread, so it
30893  // cannot run on the threadless `wasm32-wasip1` lane.
30894  #[cfg(napi_runtime_os_threads)]
30895  #[test]
30896  fn concurrent_split_finish_racing_a_single_call_shutdown() {
30897    // The interleaving the counting model got wrong (8.2% on CurrentThread in
30898    // a 100k-iteration probe, and the reason phases are no longer counted): a
30899    // single call opens the phase and a SECOND thread, using the documented
30900    // split pair, completes it. With a count, the split thread's finish spent
30901    // the opener's credit and the single call was then told "shutdown was not
30902    // started" -- for a stop it had already published.
30903    //
30904    // Deterministic, not stressed: the hook parks the single call between its
30905    // halves and runs the split pair to completion there.
30906    let controller = Arc::new(current_thread_controller("split-finish-vs-single"));
30907    drop(controller.backend());
30908
30909    let split_controller = Arc::clone(&controller);
30910    let (tx, rx) = std::sync::mpsc::channel();
30911    BETWEEN_SHUTDOWN_PHASES_TEST_HOOK.with(|slot| {
30912      *slot.borrow_mut() = Some(Box::new(move || {
30913        let split = std::thread::spawn(move || {
30914          let pending = split_controller
30915            .begin_shutdown()
30916            .expect("a second phase 1 must re-report the outstanding handoff");
30917          split_controller
30918            .finish_shutdown()
30919            .expect("an in-protocol phase 2 must complete the outstanding stop");
30920          pending
30921        });
30922        tx.send(
30923          split
30924            .join()
30925            .expect("the split shutdown thread must not panic"),
30926        )
30927        .expect("the test thread must still be waiting for the split result");
30928      }));
30929    });
30930
30931    controller
30932      .shutdown()
30933      .expect("the single call must report the stop it announced, whoever joined it");
30934    assert!(
30935      !rx
30936        .try_recv()
30937        .expect("the hook must have run the split pair inside the window"),
30938      "an idle generation has nothing for the host to wait for"
30939    );
30940
30941    let state = controller
30942      .state
30943      .lock()
30944      .unwrap_or_else(std::sync::PoisonError::into_inner);
30945    assert!(
30946      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
30947      "the generation must really be stopped, not reported stopped"
30948    );
30949    assert!(state.draining.is_none());
30950  }
30951
30952  #[test]
30953  fn repeated_begin_start_pairs_leave_no_residue() {
30954    // The other half of what the count got wrong: `k x (begin, start)` parked
30955    // `k` credits that nothing ever drained, so the next `k` stray phase 2s
30956    // reported success off a leaked count. A sticky bool cannot accumulate --
30957    // the behaviour is the same at every `k`.
30958    const PAIRS: usize = 50;
30959    let controller = current_thread_controller("begin-start-residue");
30960    drop(controller.backend());
30961    for _ in 0..PAIRS {
30962      controller
30963        .shutdown()
30964        .expect("each shutdown must stop the live generation");
30965      assert!(
30966        !controller
30967          .begin_shutdown()
30968          .expect("phase 1 on a stopped runtime must be accepted"),
30969        "a stopped runtime has no work for the host to wait for"
30970      );
30971      controller
30972        .start()
30973        .expect("a zero-backend window must let a restart through");
30974      let state = controller
30975        .state
30976        .lock()
30977        .unwrap_or_else(std::sync::PoisonError::into_inner);
30978      assert!(
30979        state.draining.is_none(),
30980        "the restart must drop the settled handoff"
30981      );
30982      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Running(_)));
30983    }
30984
30985    controller
30986      .finish_shutdown()
30987      .expect("a phase 2 with nothing outstanding is a no-op Ok");
30988    {
30989      let state = controller
30990        .state
30991        .lock()
30992        .unwrap_or_else(std::sync::PoisonError::into_inner);
30993      assert!(
30994        matches!(&state.lifecycle, RuntimeLifecycle::Running(_)),
30995        "the no-op phase 2 must not stop the generation the last restart published"
30996      );
30997    }
30998
30999    controller
31000      .shutdown()
31001      .expect("a full shutdown after the begin/start pairs must still work");
31002    let state = controller
31003      .state
31004      .lock()
31005      .unwrap_or_else(std::sync::PoisonError::into_inner);
31006    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31007    assert!(state.draining.is_none());
31008  }
31009
31010  #[cfg(napi_runtime_os_threads)]
31011  #[test]
31012  fn a_restart_after_a_settled_phase_one_still_joins_its_workers() {
31013    // The MultiThread half of the same rule: the worker join lives in phase
31014    // 2, so a stale settled handoff would let `shutdown` return while this
31015    // generation's workers were still alive.
31016    let controller = multi_thread_controller("two-phase-stale-settled-mt", 2, 1);
31017    drop(controller.backend());
31018    controller
31019      .shutdown()
31020      .expect("the first shutdown must complete");
31021    assert!(
31022      !controller
31023        .begin_shutdown()
31024        .expect("phase 1 on a stopped runtime must be accepted")
31025    );
31026    controller.start().expect("a stopped runtime must restart");
31027
31028    let backend = controller.backend();
31029    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31030      panic!("configured MultiThread backend must create a Rayon executor");
31031    };
31032    let workers = Arc::clone(&executor.worker_lifecycle);
31033    drop(backend);
31034    let handle = controller
31035      .try_spawn_blocking(|| 5usize)
31036      .unwrap_or_else(|_| panic!("the restarted generation must accept work"));
31037    assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
31038
31039    controller
31040      .shutdown()
31041      .expect("the second shutdown must stop the restarted generation");
31042    assert_eq!(
31043      workers.remaining(),
31044      0,
31045      "phase 2 must join the restarted generation's workers"
31046    );
31047    {
31048      let state = controller
31049        .state
31050        .lock()
31051        .unwrap_or_else(std::sync::PoisonError::into_inner);
31052      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31053      assert!(state.draining.is_none());
31054    }
31055    let Err((error, _work)) = controller.try_spawn_blocking(|| 0usize) else {
31056      panic!("a submission after the second shutdown must be rejected");
31057    };
31058    assert_eq!(
31059      error.to_string(),
31060      "the async runtime is stopped; call start before submitting work"
31061    );
31062  }
31063
31064  /// A rejected submission whose destructor is parked until `release` is sent.
31065  /// The counted window (`rejected_drops`) stays open for the whole of that
31066  /// `Drop`, which is the state phase 1 must refuse to wait on.
31067  #[cfg(napi_runtime_os_threads)]
31068  struct ParkedRejectedDropFuture {
31069    entered: std::sync::mpsc::Sender<()>,
31070    release: std::sync::mpsc::Receiver<()>,
31071  }
31072
31073  #[cfg(napi_runtime_os_threads)]
31074  impl Future for ParkedRejectedDropFuture {
31075    type Output = ();
31076
31077    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31078      Poll::Pending
31079    }
31080  }
31081
31082  #[cfg(napi_runtime_os_threads)]
31083  impl Drop for ParkedRejectedDropFuture {
31084    fn drop(&mut self) {
31085      self.entered.send(()).unwrap();
31086      self.release.recv().unwrap();
31087    }
31088  }
31089
31090  /// Reject one submission on a spawned thread and return once its destructor
31091  /// has been entered, so the caller runs with `rejected_drops != 0` held open
31092  /// by a thread that is not it.
31093  #[cfg(napi_runtime_os_threads)]
31094  fn park_a_rejected_destructor(
31095    controller: &Arc<RuntimeController>,
31096    fail_backend_creation: bool,
31097  ) -> (std::thread::JoinHandle<()>, std::sync::mpsc::Sender<()>) {
31098    use std::sync::mpsc;
31099
31100    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
31101    let (release_drop_tx, release_drop_rx) = mpsc::channel();
31102    let submit_controller = Arc::clone(controller);
31103    let submitter = std::thread::spawn(move || {
31104      if fail_backend_creation {
31105        FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
31106      }
31107      let result = futures::executor::block_on(submit_controller.spawn(ParkedRejectedDropFuture {
31108        entered: drop_entered_tx,
31109        release: release_drop_rx,
31110      }));
31111      assert!(
31112        result.is_err(),
31113        "the submission must be rejected, not accepted"
31114      );
31115    });
31116    drop_entered_rx
31117      .recv_timeout(Duration::from_secs(2))
31118      .expect("the rejected future destructor must start");
31119    (submitter, release_drop_tx)
31120  }
31121
31122  /// Run phase 1 on a spawned thread and answer within `timeout`.
31123  ///
31124  /// A regression HANGS inside `begin_shutdown` instead of returning a wrong
31125  /// value, so the call cannot be made inline: the `recv_timeout` is what
31126  /// turns the hang into a failure.
31127  #[cfg(napi_runtime_os_threads)]
31128  fn begin_shutdown_within(
31129    controller: &Arc<RuntimeController>,
31130    what: &str,
31131    timeout: Duration,
31132  ) -> Result<bool, RuntimeConfigError> {
31133    use std::sync::mpsc;
31134
31135    let (tx, rx) = mpsc::channel();
31136    let begin_controller = Arc::clone(controller);
31137    let beginner = std::thread::spawn(move || {
31138      tx.send(begin_controller.begin_shutdown()).unwrap();
31139    });
31140    let answer = rx
31141      .recv_timeout(timeout)
31142      .unwrap_or_else(|_| panic!("phase 1 must not wait: {what}"));
31143    join_within(what, beginner, Duration::from_secs(2));
31144    answer
31145  }
31146
31147  #[cfg(napi_runtime_os_threads)]
31148  #[test]
31149  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_a_stopped_runtime() {
31150    // Phase 1 never waits -- including for a rejected submission whose
31151    // destructor is running on ANOTHER thread. That destructor is arbitrary
31152    // user `Drop` code; on the threaded WASI artifact the thread running it
31153    // may be waiting for the very host turn the phase-1 caller can only give
31154    // by returning, so waiting here is the deadlock the split API exists to
31155    // avoid. The wait belongs to phase 2, and the window must report itself
31156    // as pending work so the host keeps turning its loop.
31157    use std::sync::mpsc;
31158
31159    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-stopped"));
31160    drop(controller.backend());
31161    controller
31162      .shutdown()
31163      .expect("the first shutdown must complete");
31164
31165    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31166
31167    assert!(
31168      begin_shutdown_within(
31169        &controller,
31170        "phase 1 on a stopped runtime with a rejected destructor outstanding",
31171        Duration::from_secs(2),
31172      )
31173      .expect("phase 1 on a stopped runtime must be accepted"),
31174      "the outstanding rejected destruction is a wait phase 2 still owes"
31175    );
31176    assert!(
31177      controller.runtime_work_pending(),
31178      "the poll must report the destruction, or the host stops turning the loop that frees it"
31179    );
31180    {
31181      let state = controller
31182        .state
31183        .lock()
31184        .unwrap_or_else(std::sync::PoisonError::into_inner);
31185      assert!(matches!(
31186        &state.lifecycle,
31187        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
31188      ));
31189      assert!(matches!(
31190        &state.draining,
31191        Some(ShutdownDrain::ZeroBackend(
31192          ZeroBackendShutdownTarget::Stopped
31193        ))
31194      ));
31195    }
31196
31197    assert!(
31198      begin_shutdown_within(
31199        &controller,
31200        "a second phase 1 inside the zero-backend window",
31201        Duration::from_secs(2),
31202      )
31203      .expect("a second phase 1 must answer from the outstanding handoff"),
31204      "the second phase 1 must repeat the same verdict"
31205    );
31206
31207    let (finish_tx, finish_rx) = mpsc::channel();
31208    let finish_controller = Arc::clone(&controller);
31209    let finisher = std::thread::spawn(move || {
31210      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31211    });
31212    assert!(
31213      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31214      "phase 2 owns the wait phase 1 refused"
31215    );
31216
31217    release_drop_tx.send(()).unwrap();
31218    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31219    finish_rx
31220      .recv_timeout(Duration::from_secs(2))
31221      .expect("phase 2 must return once the destructor retires")
31222      .expect("phase 2 must complete the announced stop");
31223    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31224
31225    {
31226      let state = controller
31227        .state
31228        .lock()
31229        .unwrap_or_else(std::sync::PoisonError::into_inner);
31230      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31231      assert!(state.draining.is_none());
31232    }
31233  }
31234
31235  #[cfg(napi_runtime_os_threads)]
31236  #[test]
31237  fn begin_shutdown_does_not_wait_for_rejected_destruction_on_an_initial_runtime() {
31238    // The `Initial` twin of the test above: a backend creation that fails
31239    // leaves the lifecycle `Initial` with the destructor of the rejected
31240    // submission still running, and phase 1 must publish and return there
31241    // too. The target it parks is `StoppedBeforeFirstUse`, and phase 2 is
31242    // what publishes it.
31243    use std::sync::mpsc;
31244
31245    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-initial"));
31246    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, true);
31247
31248    assert!(
31249      begin_shutdown_within(
31250        &controller,
31251        "phase 1 on an initial runtime with a rejected destructor outstanding",
31252        Duration::from_secs(2),
31253      )
31254      .expect("phase 1 on an initial runtime must be accepted"),
31255      "the outstanding rejected destruction is a wait phase 2 still owes"
31256    );
31257    assert!(controller.runtime_work_pending());
31258    {
31259      let state = controller
31260        .state
31261        .lock()
31262        .unwrap_or_else(std::sync::PoisonError::into_inner);
31263      assert!(matches!(
31264        &state.lifecycle,
31265        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
31266      ));
31267      assert!(matches!(
31268        &state.draining,
31269        Some(ShutdownDrain::ZeroBackend(
31270          ZeroBackendShutdownTarget::StoppedBeforeFirstUse
31271        ))
31272      ));
31273    }
31274    assert!(
31275      controller.try_spawn_detached(async {}).is_err(),
31276      "the announced stop must keep first-generation admission closed"
31277    );
31278
31279    let (finish_tx, finish_rx) = mpsc::channel();
31280    let finish_controller = Arc::clone(&controller);
31281    let finisher = std::thread::spawn(move || {
31282      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31283    });
31284    assert!(
31285      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31286      "phase 2 owns the wait phase 1 refused"
31287    );
31288
31289    release_drop_tx.send(()).unwrap();
31290    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31291    finish_rx
31292      .recv_timeout(Duration::from_secs(2))
31293      .expect("phase 2 must return once the destructor retires")
31294      .expect("phase 2 must complete the announced stop");
31295    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31296
31297    {
31298      let state = controller
31299        .state
31300        .lock()
31301        .unwrap_or_else(std::sync::PoisonError::into_inner);
31302      assert!(
31303        matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse),
31304        "a generation that never had a backend must stop before first use"
31305      );
31306      assert!(state.draining.is_none());
31307    }
31308  }
31309
31310  #[cfg(napi_runtime_os_threads)]
31311  #[test]
31312  fn start_waits_out_a_zero_backend_phase_one() {
31313    // Phase 1 no longer waits, so the `StoppingWithoutBackend` window now
31314    // lasts until phase 2 publishes instead of only for the length of phase
31315    // 1's internal wait. `start` must wait that whole window out: the
31316    // destructor still runs under the RETIRED generation's guard, so a
31317    // restart admitted before phase 2 would publish a fresh backend on top of
31318    // it. The existing coverage only drives the single-call `shutdown`; this
31319    // pins the invariant on the split API.
31320    use std::sync::mpsc;
31321
31322    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-start"));
31323    let old_generation = controller.backend().generation();
31324    controller
31325      .shutdown()
31326      .expect("the first shutdown must complete");
31327
31328    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31329    assert!(
31330      begin_shutdown_within(
31331        &controller,
31332        "phase 1 before the restart",
31333        Duration::from_secs(2),
31334      )
31335      .expect("phase 1 on a stopped runtime must be accepted")
31336    );
31337
31338    let (start_tx, start_rx) = mpsc::channel();
31339    let start_controller = Arc::clone(&controller);
31340    let starter = std::thread::spawn(move || {
31341      start_tx.send(start_controller.start()).unwrap();
31342    });
31343    assert!(
31344      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31345      "a restart must wait for the announced zero-backend stop to be published"
31346    );
31347
31348    let (finish_tx, finish_rx) = mpsc::channel();
31349    let finish_controller = Arc::clone(&controller);
31350    let finisher = std::thread::spawn(move || {
31351      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31352    });
31353    assert!(
31354      finish_rx.recv_timeout(Duration::from_millis(200)).is_err(),
31355      "phase 2 must still be waiting on the destructor"
31356    );
31357
31358    release_drop_tx.send(()).unwrap();
31359    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31360    finish_rx
31361      .recv_timeout(Duration::from_secs(2))
31362      .expect("phase 2 must return once the destructor retires")
31363      .expect("phase 2 must complete the announced stop");
31364    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31365    start_rx
31366      .recv_timeout(Duration::from_secs(2))
31367      .expect("the restart must be admitted once phase 2 has published")
31368      .expect("a stopped runtime must restart after phase 2");
31369    join_within("the restart thread", starter, Duration::from_secs(2));
31370
31371    let new_generation = controller.backend().generation();
31372    assert_ne!(
31373      new_generation, old_generation,
31374      "the restart must publish a fresh generation"
31375    );
31376    controller
31377      .shutdown()
31378      .expect("the restarted generation must still shut down");
31379  }
31380
31381  #[cfg(napi_runtime_os_threads)]
31382  #[test]
31383  fn zero_backend_phase_one_settles_through_the_poll_before_finish() {
31384    // The order the production WASI loader actually runs, which the three
31385    // tests above never do: `begin` -> turn the host loop, polling
31386    // `runtime_work_pending` until it answers `false` -> only THEN call
31387    // `finish`. They park phase 2 on a thread BEFORE releasing the
31388    // destructor, so they prove the wait; this proves the way out of it. The
31389    // poll must stop reporting work once the rejected destructor retires, and
31390    // the `finish` entered after that must not block -- a loader that polled
31391    // to zero and then found phase 2 parked would wedge the disposal on the
31392    // JavaScript thread, which is the whole failure the split API avoids.
31393    use std::sync::mpsc;
31394
31395    let controller = Arc::new(current_thread_controller("two-phase-zero-backend-poll"));
31396    drop(controller.backend());
31397    controller
31398      .shutdown()
31399      .expect("the first shutdown must complete");
31400
31401    let (submitter, release_drop_tx) = park_a_rejected_destructor(&controller, false);
31402    assert!(
31403      begin_shutdown_within(
31404        &controller,
31405        "phase 1 before the loader's poll loop",
31406        Duration::from_secs(2),
31407      )
31408      .expect("phase 1 on a stopped runtime must be accepted"),
31409      "the outstanding rejected destruction is a wait phase 2 still owes"
31410    );
31411    assert!(
31412      controller.runtime_work_pending(),
31413      "the loader gates its poll loop on this, so it must report the destruction first"
31414    );
31415
31416    release_drop_tx.send(()).unwrap();
31417    join_within("the rejected submitter", submitter, Duration::from_secs(2));
31418
31419    // The host's turns, bounded: a handoff that never clears fails here
31420    // instead of spinning for the length of the CI step.
31421    let deadline = std::time::Instant::now() + Duration::from_secs(5);
31422    while controller.runtime_work_pending() {
31423      assert!(
31424        std::time::Instant::now() < deadline,
31425        "the poll must stop reporting work once the rejected destructor retires"
31426      );
31427      std::thread::sleep(Duration::from_millis(1));
31428    }
31429
31430    let (finish_tx, finish_rx) = mpsc::channel();
31431    let finish_controller = Arc::clone(&controller);
31432    let finisher = std::thread::spawn(move || {
31433      finish_tx.send(finish_controller.finish_shutdown()).unwrap();
31434    });
31435    finish_rx
31436      .recv_timeout(Duration::from_millis(200))
31437      .expect("phase 2 entered after the poll cleared must not block")
31438      .expect("phase 2 must complete the announced stop");
31439    join_within("the phase 2 thread", finisher, Duration::from_secs(2));
31440
31441    {
31442      let state = controller
31443        .state
31444        .lock()
31445        .unwrap_or_else(std::sync::PoisonError::into_inner);
31446      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
31447      assert!(state.draining.is_none());
31448    }
31449  }
31450
31451  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
31452  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
31453  // global `RUNTIME`) and run the workload on a child thread guarded by a
31454  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
31455  // wedging the whole suite.
31456
31457  #[cfg(napi_runtime_os_threads)]
31458  #[test]
31459  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
31460    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
31461    // blocking job while `active_blocking >= max_blocking`. The guard is
31462    // exercised deterministically (no sleeps, no incidental worker overlap):
31463    //
31464    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
31465    //      occupies a counted slot (`take_blocking` increments `active_blocking`
31466    //      BEFORE running the closure), signals `held`, then parks on a release
31467    //      barrier -- so all slots are provably occupied before we proceed.
31468    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
31469    //      With the cap intact `take_blocking` returns `None` for them, so the
31470    //      idle workers cannot pop them: no extra may signal `extra_started`.
31471    //   3. We release the holders, then assert EVERY job (holders + extras)
31472    //      completes with the right result and the recorded peak never exceeded
31473    //      the cap.
31474    //
31475    // Decisive property: if the `active >= self.max_blocking` guard were removed,
31476    // the two idle workers would pop the extras while both holders are still
31477    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
31478    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
31479    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
31480    // can never self-deadlock on the over-cap extras.
31481    use std::sync::{Barrier, mpsc};
31482    use std::time::Duration;
31483
31484    let (done_tx, done_rx) = mpsc::channel();
31485    let peak_out = Arc::new(Mutex::new(None));
31486    let peak_slot = Arc::clone(&peak_out);
31487    let runner = std::thread::spawn(move || {
31488      let metrics = Arc::new(RuntimeMetrics::default());
31489      let options = RuntimeOptions {
31490        flavor: RuntimeFlavor::MultiThread,
31491        worker_threads: 4,
31492        max_blocking_tasks: 2,
31493        thread_name_prefix: "rd9-blk-cap".to_string(),
31494        park_deadline: None,
31495        drain_linger: DEFAULT_DRAIN_LINGER,
31496      };
31497      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31498
31499      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
31500      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
31501
31502      // Holders: signal once they hold a counted slot, then park until released.
31503      let (held_tx, held_rx) = mpsc::channel();
31504      let release = Arc::new(Barrier::new(holder_count + 1));
31505      let mut holder_handles = Vec::new();
31506      for _ in 0..holder_count {
31507        let held_tx = held_tx.clone();
31508        let release = Arc::clone(&release);
31509        holder_handles.push(executor.schedule_blocking(move || {
31510          held_tx.send(()).unwrap();
31511          release.wait();
31512          0usize
31513        }));
31514      }
31515      // Wait until every slot is provably occupied (cap fully saturated).
31516      for _ in 0..holder_count {
31517        held_rx.recv().unwrap();
31518      }
31519
31520      // Queue the beyond-cap extras. Each signals the instant it begins running.
31521      let (extra_started_tx, extra_started_rx) = mpsc::channel();
31522      let mut extra_handles = Vec::new();
31523      for i in 0..extra_count {
31524        let extra_started_tx = extra_started_tx.clone();
31525        extra_handles.push(executor.schedule_blocking(move || {
31526          extra_started_tx.send(i).unwrap();
31527          i * 2
31528        }));
31529      }
31530
31531      // With the cap intact, `take_blocking` returns `None` while both slots are
31532      // held, so no extra can have started running yet.
31533      assert!(
31534        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
31535        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
31536      );
31537
31538      // Release the holders so the queued extras can run within the cap.
31539      release.wait();
31540
31541      for handle in holder_handles {
31542        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
31543      }
31544      // Each handle owns its own oneshot receiver, so awaiting in order returns
31545      // each extra's own result regardless of the order the pool ran them.
31546      for (i, handle) in extra_handles.into_iter().enumerate() {
31547        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
31548      }
31549
31550      // `blocking_tasks_started` is incremented at job entry, so once every result
31551      // is in, all holder + extra starts are recorded.
31552      assert_eq!(
31553        metrics.blocking_tasks_started.load(Ordering::Relaxed),
31554        (holder_count + extra_count) as u64
31555      );
31556
31557      *peak_slot
31558        .lock()
31559        .unwrap_or_else(std::sync::PoisonError::into_inner) =
31560        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
31561      let _ = done_tx.send(());
31562    });
31563
31564    match done_rx.recv_timeout(Duration::from_secs(10)) {
31565      Ok(()) => runner.join().unwrap(),
31566      Err(error) => panic!("blocking-cap test did not complete ({error})"),
31567    }
31568    let peak = peak_out
31569      .lock()
31570      .unwrap_or_else(std::sync::PoisonError::into_inner)
31571      .expect("peak captured");
31572    assert!(
31573      peak >= 1,
31574      "at least one blocking job must have run (peak {peak})"
31575    );
31576    assert!(
31577      peak <= 2,
31578      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
31579    );
31580  }
31581
31582  #[cfg(napi_runtime_os_threads)]
31583  #[test]
31584  fn multi_thread_drain_budget_completes_large_runnable_batch() {
31585    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
31586    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
31587    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
31588    // still complete every task, with the runnable counters settling back to 0.
31589    use std::sync::mpsc;
31590    use std::time::{Duration, Instant};
31591
31592    let (done_tx, done_rx) = mpsc::channel();
31593    let runner = std::thread::spawn(move || {
31594      let metrics = Arc::new(RuntimeMetrics::default());
31595      let options = RuntimeOptions {
31596        flavor: RuntimeFlavor::MultiThread,
31597        worker_threads: 2,
31598        max_blocking_tasks: 2,
31599        thread_name_prefix: "rd9-budget".to_string(),
31600        park_deadline: None,
31601        drain_linger: DEFAULT_DRAIN_LINGER,
31602      };
31603      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
31604
31605      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
31606      let completed = Arc::new(AtomicU64::new(0));
31607      let mut tasks = Vec::new();
31608      for _ in 0..task_count {
31609        let completed = Arc::clone(&completed);
31610        let scheduler = Arc::clone(&executor);
31611        let (runnable, task) = async_task::spawn(
31612          async move {
31613            completed.fetch_add(1, Ordering::Relaxed);
31614          },
31615          move |r| scheduler.schedule(r),
31616        );
31617        executor.schedule(runnable);
31618        tasks.push(task);
31619      }
31620      for task in tasks {
31621        futures::executor::block_on(task);
31622      }
31623
31624      // Every immediately-ready future is polled exactly once; `runnable_polls`
31625      // (incremented at poll entry, before completion) is therefore exact here.
31626      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
31627      assert_eq!(
31628        metrics.runnable_polls.load(Ordering::Relaxed),
31629        task_count as u64
31630      );
31631
31632      // The active-runnable guard for the LAST task drops just after its Task
31633      // resolves (i.e. just after `block_on` returns), so give the counters a
31634      // brief, bounded moment to settle to 0 rather than asserting them racily.
31635      let deadline = Instant::now() + Duration::from_secs(5);
31636      loop {
31637        let active = metrics.active_runnables.load(Ordering::Relaxed);
31638        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
31639        if active == 0 && queued == 0 {
31640          break;
31641        }
31642        assert!(
31643          Instant::now() < deadline,
31644          "runnable counters did not settle to 0: active={active} queued={queued}"
31645        );
31646        std::thread::yield_now();
31647      }
31648      let _ = done_tx.send(());
31649    });
31650
31651    match done_rx.recv_timeout(Duration::from_secs(15)) {
31652      Ok(()) => runner.join().unwrap(),
31653      Err(error) => panic!("drain-budget test did not complete ({error})"),
31654    }
31655  }
31656
31657  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
31658  #[test]
31659  fn multi_thread_spawned_panic_surfaces_as_join_error() {
31660    // A panicking spawned future must surface as `Err(JoinError)` carrying the
31661    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
31662    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
31663    // drive the resulting `JoinHandle` on this executor -- without touching the
31664    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
31665    // `panic!("x")` message round-trips as `"x"`.
31666    use std::sync::mpsc;
31667    use std::time::Duration;
31668
31669    let (done_tx, done_rx) = mpsc::channel();
31670    let runner = std::thread::spawn(move || {
31671      let metrics = Arc::new(RuntimeMetrics::default());
31672      let options = RuntimeOptions {
31673        flavor: RuntimeFlavor::MultiThread,
31674        worker_threads: 2,
31675        max_blocking_tasks: 2,
31676        thread_name_prefix: "rd9-panic".to_string(),
31677        park_deadline: None,
31678        drain_linger: DEFAULT_DRAIN_LINGER,
31679      };
31680      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
31681
31682      let scheduler = Arc::clone(&executor);
31683      let wrapped = async {
31684        AssertUnwindSafe(async { panic!("x") })
31685          .catch_unwind()
31686          .await
31687          .map(|()| ContainedTaskOutput::new((), u64::MAX))
31688          .map_err(panic_payload_to_join_error)
31689      };
31690      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
31691      executor.schedule(runnable);
31692
31693      let result: Result<(), JoinError> =
31694        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
31695          task: task.fallible(),
31696          awaiter: None,
31697          dependency: Arc::new(TaskDependency::default()),
31698        }));
31699      let _ = done_tx.send(result);
31700    });
31701
31702    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
31703      Ok(result) => {
31704        runner.join().unwrap();
31705        result
31706      }
31707      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
31708    };
31709    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
31710    assert_eq!(error.to_string(), "x");
31711  }
31712
31713  #[cfg(napi_runtime_os_threads)]
31714  #[test]
31715  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
31716    use std::sync::mpsc;
31717
31718    let metrics = Arc::new(RuntimeMetrics::default());
31719    metrics.tasks_spawned.store(1, Ordering::Relaxed);
31720    metrics.tasks_completed.store(2, Ordering::Relaxed);
31721    metrics.tasks_panicked.store(3, Ordering::Relaxed);
31722    metrics.runnable_schedules.store(4, Ordering::Relaxed);
31723    metrics.runnable_polls.store(5, Ordering::Relaxed);
31724    metrics
31725      .blocking_tasks_scheduled
31726      .store(14, Ordering::Relaxed);
31727    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
31728    metrics
31729      .blocking_tasks_completed
31730      .store(11, Ordering::Relaxed);
31731    metrics.runnable_scheduled();
31732    let (guards_live_tx, guards_live_rx) = mpsc::channel();
31733    let (release_tx, release_rx) = mpsc::channel();
31734    let guard_metrics = Arc::clone(&metrics);
31735    let guard_thread = std::thread::spawn(move || {
31736      let runnable_guard = guard_metrics.runnable_started();
31737      let blocking_guard = guard_metrics.blocking_started(true);
31738      guards_live_tx.send(()).unwrap();
31739      release_rx.recv().unwrap();
31740      drop(runnable_guard);
31741      drop(blocking_guard);
31742    });
31743    guards_live_rx.recv().unwrap();
31744    let fingerprint_before_reset = metrics.progress_fingerprint();
31745
31746    metrics.reset();
31747
31748    assert!(
31749      metrics.progress_fingerprint() != fingerprint_before_reset,
31750      "a reset must advance the deadlock detector generation even if counters later repeat"
31751    );
31752    assert_eq!(
31753      metrics.tasks_spawned.load(Ordering::Relaxed),
31754      0,
31755      "tasks_spawned"
31756    );
31757    assert_eq!(
31758      metrics.tasks_completed.load(Ordering::Relaxed),
31759      0,
31760      "tasks_completed"
31761    );
31762    assert_eq!(
31763      metrics.tasks_panicked.load(Ordering::Relaxed),
31764      0,
31765      "tasks_panicked"
31766    );
31767    assert_eq!(
31768      metrics.runnable_schedules.load(Ordering::Relaxed),
31769      0,
31770      "runnable_schedules"
31771    );
31772    assert_eq!(
31773      metrics.runnable_polls.load(Ordering::Relaxed),
31774      0,
31775      "runnable_polls"
31776    );
31777    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
31778    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
31779    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
31780    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
31781    assert_eq!(
31782      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
31783      0,
31784      "blocking_tasks_scheduled"
31785    );
31786    assert_eq!(
31787      metrics.blocking_tasks_started.load(Ordering::Relaxed),
31788      0,
31789      "blocking_tasks_started"
31790    );
31791    assert_eq!(
31792      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
31793      0,
31794      "blocking_tasks_completed"
31795    );
31796    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
31797    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
31798
31799    release_tx.send(()).unwrap();
31800    guard_thread.join().unwrap();
31801    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
31802    assert_eq!(
31803      metrics.active_blocking_tasks.load(Ordering::Relaxed),
31804      0,
31805      "a live blocking guard completed after reset without unsigned underflow"
31806    );
31807    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
31808  }
31809
31810  #[cfg(napi_runtime_os_threads)]
31811  #[test]
31812  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
31813    use std::sync::mpsc;
31814
31815    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
31816      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
31817        *slot.borrow_mut() = Some(Box::new(move || {
31818          entered.send(()).unwrap();
31819          release.recv().unwrap();
31820        }));
31821      });
31822    }
31823
31824    let options = RuntimeOptions {
31825      flavor: RuntimeFlavor::CurrentThread,
31826      worker_threads: 1,
31827      max_blocking_tasks: 1,
31828      thread_name_prefix: "metrics-snapshot".to_string(),
31829      park_deadline: None,
31830      drain_linger: DEFAULT_DRAIN_LINGER,
31831    };
31832
31833    let queued_metrics = Arc::new(RuntimeMetrics::default());
31834    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
31835    let (queued_release_tx, queued_release_rx) = mpsc::channel();
31836    let queued_writer_metrics = Arc::clone(&queued_metrics);
31837    let queued_writer = std::thread::spawn(move || {
31838      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
31839      queued_writer_metrics.runnable_scheduled();
31840    });
31841    queued_entered_rx.recv().unwrap();
31842    assert_eq!(
31843      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
31844      0
31845    );
31846    let snapshot = queued_metrics.snapshot(&options);
31847    assert_eq!(snapshot.queued_runnables, 1);
31848    assert_eq!(snapshot.max_queued_runnables, 1);
31849    queued_release_tx.send(()).unwrap();
31850    queued_writer.join().unwrap();
31851    queued_metrics.runnable_cancelled();
31852
31853    let active_metrics = Arc::new(RuntimeMetrics::default());
31854    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
31855    let (active_entered_tx, active_entered_rx) = mpsc::channel();
31856    let (active_release_tx, active_release_rx) = mpsc::channel();
31857    let active_writer_metrics = Arc::clone(&active_metrics);
31858    let active_writer = std::thread::spawn(move || {
31859      pause_next_high_water_update(active_entered_tx, active_release_rx);
31860      let _active = active_writer_metrics.runnable_started();
31861    });
31862    active_entered_rx.recv().unwrap();
31863    assert_eq!(
31864      active_metrics.max_active_runnables.load(Ordering::Relaxed),
31865      0
31866    );
31867    let snapshot = active_metrics.snapshot(&options);
31868    assert_eq!(snapshot.active_runnables, 1);
31869    assert_eq!(snapshot.max_active_runnables, 1);
31870    active_release_tx.send(()).unwrap();
31871    active_writer.join().unwrap();
31872
31873    let blocking_metrics = Arc::new(RuntimeMetrics::default());
31874    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
31875    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
31876    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
31877    let blocking_writer = std::thread::spawn(move || {
31878      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
31879      let _active = blocking_writer_metrics.blocking_started(true);
31880    });
31881    blocking_entered_rx.recv().unwrap();
31882    assert_eq!(
31883      blocking_metrics
31884        .max_active_blocking_tasks
31885        .load(Ordering::Relaxed),
31886      0
31887    );
31888    let snapshot = blocking_metrics.snapshot(&options);
31889    assert_eq!(snapshot.active_blocking_tasks, 1);
31890    assert_eq!(snapshot.max_active_blocking_tasks, 1);
31891    blocking_release_tx.send(()).unwrap();
31892    blocking_writer.join().unwrap();
31893  }
31894
31895  #[cfg(panic = "unwind")]
31896  #[test]
31897  fn runtime_metrics_reset_fails_before_generation_wraparound() {
31898    let metrics = RuntimeMetrics::default();
31899    metrics
31900      .reset_generation
31901      .store(u64::MAX - 1, Ordering::SeqCst);
31902    metrics.tasks_spawned.store(7, Ordering::Relaxed);
31903
31904    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
31905    assert!(exhausted.is_err());
31906    assert!(!metrics.reset_lock.is_poisoned());
31907    assert_eq!(
31908      metrics.reset_generation.load(Ordering::SeqCst),
31909      u64::MAX - 1
31910    );
31911    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
31912  }
31913
31914  // ---- Self-detecting block_on deadlocks -------------------------------------
31915  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
31916  // park decision with no pending wake panics with the typed diagnostic; (2) a
31917  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
31918  // lives at the PARK DECISION, not at Pending-return; (3) an
31919  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
31920  // progress RESETS the deadline instead of firing; (5) the
31921  // foreign/napi whole-build park is excluded from the deadline entirely.
31922
31923  /// A future that never completes and never wakes anyone: once its driver
31924  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
31925  struct NeverReady;
31926  impl Future for NeverReady {
31927    type Output = ();
31928    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31929      Poll::Pending
31930    }
31931  }
31932
31933  #[cfg(napi_runtime_os_threads)]
31934  struct ReadyAfterOwnerLanePublication {
31935    dependency: BlockingDependency,
31936    ready: Arc<AtomicBool>,
31937  }
31938
31939  #[cfg(napi_runtime_os_threads)]
31940  impl Future for ReadyAfterOwnerLanePublication {
31941    type Output = ();
31942
31943    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
31944      if self.ready.load(Ordering::SeqCst) {
31945        Poll::Ready(())
31946      } else {
31947        // This shape publishes on every pending poll and never retires; the
31948        // owner-lane assertions read the state, not the token.
31949        let mut published = PublishedBlockingDependencies::default();
31950        record_blocking_dependency(&mut published, self.dependency);
31951        Poll::Pending
31952      }
31953    }
31954  }
31955
31956  #[cfg(panic = "unwind")]
31957  #[test]
31958  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
31959    // Shape (1): on a threadless build a CT park decision with an empty queue
31960    // and no wake token pending is a PROVABLE deadlock (no other thread exists
31961    // to deliver the wake) -- `block_on` must panic immediately with the typed
31962    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
31963    // runs on a child thread bounded by recv_timeout so a missing detection
31964    // (a hang) fails the test rather than wedging the suite.
31965    use std::sync::mpsc;
31966
31967    let (done_tx, done_rx) = mpsc::channel();
31968    let runner = std::thread::spawn(move || {
31969      let metrics = Arc::new(RuntimeMetrics::default());
31970      // Native stand-in for the threadless wasm build; no deadline because the
31971      // certain case is not timing-based.
31972      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31973
31974      let payload = catch_unwind(AssertUnwindSafe(|| {
31975        let mut future = std::pin::pin!(NeverReady);
31976        executor.block_on(future.as_mut());
31977      }))
31978      .expect_err("a threadless park with an empty queue and no pending wake must panic");
31979      let diagnostic = payload
31980        .downcast_ref::<BlockOnDeadlock>()
31981        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31982      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31983      assert_eq!(
31984        diagnostic.park_deadline, None,
31985        "the certain case carries no deadline"
31986      );
31987      let message = diagnostic.to_string();
31988      assert!(
31989        message.contains("block_on") && message.contains("JS"),
31990        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
31991      );
31992      let _ = done_tx.send(());
31993    });
31994
31995    match done_rx.recv_timeout(Duration::from_secs(10)) {
31996      Ok(()) => runner.join().unwrap(),
31997      Err(error) => panic!(
31998        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
31999      ),
32000    }
32001  }
32002
32003  #[cfg(napi_runtime_os_threads)]
32004  #[test]
32005  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
32006    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
32007    // `Pending` but wakes itself synchronously during the poll leaves
32008    // a wake token pending, so the loop must consume the token and re-poll --
32009    // NOT panic -- even on a threadless build. "Pending returned" alone is
32010    // never a deadlock.
32011    use std::sync::mpsc;
32012
32013    struct PendThenReady {
32014      polls: usize,
32015    }
32016    impl Future for PendThenReady {
32017      type Output = ();
32018      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
32019        self.polls += 1;
32020        if self.polls >= 2 {
32021          Poll::Ready(())
32022        } else {
32023          cx.waker().wake_by_ref();
32024          Poll::Pending
32025        }
32026      }
32027    }
32028
32029    let (done_tx, done_rx) = mpsc::channel();
32030    let runner = std::thread::spawn(move || {
32031      let metrics = Arc::new(RuntimeMetrics::default());
32032      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
32033      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
32034      executor.block_on(future.as_mut());
32035      assert_eq!(
32036        future.polls, 2,
32037        "the self-woken future must have been re-polled to completion"
32038      );
32039      let _ = done_tx.send(());
32040    });
32041
32042    match done_rx.recv_timeout(Duration::from_secs(10)) {
32043      Ok(()) => runner.join().unwrap(),
32044      Err(error) => panic!(
32045        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
32046      ),
32047    }
32048  }
32049
32050  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32051  #[test]
32052  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
32053    // Threaded CT is NOT provably dead at the park decision (another OS thread
32054    // could deliver a wake later), so detection there is deadline-based and
32055    // opt-in. A park that outlives the armed deadline with ZERO runtime
32056    // progress must panic with the typed diagnostic.
32057    use std::sync::mpsc;
32058
32059    let (done_tx, done_rx) = mpsc::channel();
32060    let runner = std::thread::spawn(move || {
32061      let metrics = Arc::new(RuntimeMetrics::default());
32062      let executor = Arc::new(CurrentThreadExecutor::with_detection(
32063        metrics,
32064        false,
32065        Some(Duration::from_millis(50)),
32066      ));
32067
32068      let payload = catch_unwind(AssertUnwindSafe(|| {
32069        let mut future = std::pin::pin!(NeverReady);
32070        executor.block_on(future.as_mut());
32071      }))
32072      .expect_err("a dead native CT park must fire the armed deadline");
32073      let diagnostic = payload
32074        .downcast_ref::<BlockOnDeadlock>()
32075        .expect("typed payload");
32076      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
32077      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32078      // Teardown inside the bounded window: a wedged executor drop must fail
32079      // the recv below loudly, not hang the final join silently.
32080      drop(executor);
32081      let _ = done_tx.send(());
32082    });
32083
32084    match done_rx.recv_timeout(Duration::from_secs(10)) {
32085      Ok(()) => join_within(
32086        "the CT deadline-fire runner exits after teardown",
32087        runner,
32088        Duration::from_secs(30),
32089      ),
32090      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
32091    }
32092  }
32093
32094  #[cfg(napi_runtime_os_threads)]
32095  #[test]
32096  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
32097    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
32098    // runtime is alive (e.g. a slow JS plugin with other
32099    // work proceeding), so the deadline must re-arm instead of firing.
32100    // Progress is injected by advancing the very counter the reset reads --
32101    // the exact seam, isolated from wake delivery (production advances it via
32102    // real runnable polls).
32103    //
32104    // Deadline/cadence ratio: the reset is only exercised when at least one
32105    // tick lands inside EVERY armed window, so the window must dwarf the
32106    // injector thread's worst-case scheduling stall. On starved CI hosts
32107    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
32108    // can overshoot past a 60ms window, leaving one window with zero
32109    // progress -- which IS a deadline fire by design. Ticks every ~25ms
32110    // against a 1s window tolerate a ~975ms stall while the park is still
32111    // held across multiple full windows, so the repeated RE-ARM (not merely
32112    // the first arm) is what keeps the park alive.
32113    use std::sync::mpsc;
32114
32115    let deadline = Duration::from_millis(1000);
32116    let metrics = Arc::new(RuntimeMetrics::default());
32117    let executor = Arc::new(CurrentThreadExecutor::with_detection(
32118      Arc::clone(&metrics),
32119      false,
32120      Some(deadline),
32121    ));
32122
32123    let (value_tx, value_rx) = oneshot::channel::<usize>();
32124    let (done_tx, done_rx) = mpsc::channel();
32125    let runner = std::thread::spawn(move || {
32126      let mut output = None;
32127      {
32128        let mut future = std::pin::pin!(async {
32129          output = Some(value_rx.await.unwrap());
32130        });
32131        executor.block_on(future.as_mut());
32132      }
32133      assert_eq!(output, Some(9));
32134      // Teardown inside the bounded window (see `join_within`).
32135      drop(executor);
32136      let _ = done_tx.send(());
32137    });
32138
32139    // Hold the park across several full deadline windows, every one
32140    // containing many injected progress ticks, then deliver the (legitimately
32141    // late) wake. The loop is elapsed-based so an oversleeping iteration
32142    // extends the hold (more windows) instead of thinning the tick supply,
32143    // and the final tick lands at most one iteration before the wake.
32144    let hold_until = Instant::now() + deadline * 5 / 2;
32145    while Instant::now() < hold_until {
32146      std::thread::sleep(Duration::from_millis(25));
32147      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32148    }
32149    value_tx.send(9).unwrap();
32150
32151    match done_rx.recv_timeout(Duration::from_secs(30)) {
32152      Ok(()) => join_within(
32153        "the CT deadline-reset runner exits after teardown",
32154        runner,
32155        Duration::from_secs(30),
32156      ),
32157      Err(error) => panic!(
32158        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
32159      ),
32160    }
32161  }
32162
32163  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
32164  #[test]
32165  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
32166    // Shape (3): the MT deadline applies to the DriverParker inside
32167    // `cooperative_block_on`. Run the cooperative branch directly on this
32168    // thread with the test-only on-pool marker so the panic is observable as
32169    // a typed payload without rayon in between.
32170    use std::sync::mpsc;
32171
32172    let (done_tx, done_rx) = mpsc::channel();
32173    let runner = std::thread::spawn(move || {
32174      let options = RuntimeOptions {
32175        flavor: RuntimeFlavor::MultiThread,
32176        worker_threads: 1,
32177        max_blocking_tasks: 1,
32178        thread_name_prefix: "deadline-fire".to_string(),
32179        park_deadline: Some(Duration::from_millis(50)),
32180        drain_linger: DEFAULT_DRAIN_LINGER,
32181      };
32182      let executor =
32183        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32184
32185      let payload = {
32186        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32187        catch_unwind(AssertUnwindSafe(|| {
32188          let mut future = std::pin::pin!(NeverReady);
32189          executor.block_on(future.as_mut());
32190        }))
32191        .expect_err("a dead cooperative park must fire the armed deadline")
32192      };
32193      let diagnostic = payload
32194        .downcast_ref::<BlockOnDeadlock>()
32195        .expect("typed payload");
32196      assert_eq!(
32197        diagnostic.kind,
32198        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
32199      );
32200      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
32201      let message = diagnostic.to_string();
32202      assert!(
32203        message.contains("block_on"),
32204        "diagnostic must name the hazard, got: {message}"
32205      );
32206      assert_eq!(
32207        executor.parked_drivers.count.load(Ordering::SeqCst),
32208        0,
32209        "the firing driver must deregister itself before panicking"
32210      );
32211      // Teardown inside the bounded window (see `join_within`).
32212      drop(executor);
32213      let _ = done_tx.send(());
32214    });
32215
32216    match done_rx.recv_timeout(Duration::from_secs(10)) {
32217      Ok(()) => join_within(
32218        "the MT deadline-fire runner exits after executor teardown",
32219        runner,
32220        Duration::from_secs(30),
32221      ),
32222      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
32223    }
32224  }
32225
32226  #[cfg(napi_runtime_os_threads)]
32227  #[test]
32228  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
32229    // Shape (4), MT side of the progress reset: a legitimately slow wake under
32230    // an armed deadline must NOT fire while the executor's
32231    // metrics keep advancing. Topology: the sole worker is parked as a
32232    // cooperative driver (real pool thread, real DriverParker park); the test
32233    // injects progress ticks across several full deadline windows, then
32234    // delivers the late wake.
32235    //
32236    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
32237    // so the window must dwarf the injector thread's worst-case scheduling
32238    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
32239    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
32240    // and a full window with zero progress IS a deadline fire by design.
32241    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
32242    // still holding the park across multiple full windows, proving the
32243    // repeated RE-ARM rather than a single lucky first window.
32244    use std::sync::mpsc;
32245
32246    let (done_tx, done_rx) = mpsc::channel();
32247    let runner = std::thread::spawn(move || {
32248      let deadline = Duration::from_millis(1000);
32249      let metrics = Arc::new(RuntimeMetrics::default());
32250      let options = RuntimeOptions {
32251        flavor: RuntimeFlavor::MultiThread,
32252        worker_threads: 1,
32253        max_blocking_tasks: 1,
32254        thread_name_prefix: "deadline-reset".to_string(),
32255        park_deadline: Some(deadline),
32256        drain_linger: DEFAULT_DRAIN_LINGER,
32257      };
32258      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
32259
32260      // Park the sole worker as a cooperative driver awaiting a gate (same
32261      // topology as the miss-compensation test).
32262      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32263      let td_exec = Arc::clone(&executor);
32264      let completed = Arc::new(AtomicBool::new(false));
32265      let td_flag = Arc::clone(&completed);
32266      let td_body = async move {
32267        let mut inner = std::pin::pin!(async move {
32268          assert_eq!(gate_rx.await.unwrap(), 9);
32269        });
32270        td_exec.block_on(inner.as_mut());
32271        td_flag.store(true, Ordering::SeqCst);
32272      };
32273      let sched = Arc::clone(&executor);
32274      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
32275      executor.schedule(td_runnable);
32276      wait_until("the sole driver parked", || {
32277        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
32278      });
32279
32280      // Outlive the armed deadline several times over, with every window
32281      // containing many injected progress ticks (the counter the reset
32282      // reads). Elapsed-based so an oversleeping iteration extends the hold
32283      // instead of thinning the tick supply, and the final tick lands at
32284      // most one iteration before the late wake below.
32285      let hold_until = Instant::now() + deadline * 5 / 2;
32286      while Instant::now() < hold_until {
32287        std::thread::sleep(Duration::from_millis(25));
32288        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
32289      }
32290      assert!(
32291        !completed.load(Ordering::SeqCst),
32292        "the driver must still be parked awaiting its late wake"
32293      );
32294      gate_tx.send(9).unwrap();
32295      futures::executor::block_on(td_task);
32296      assert!(
32297        completed.load(Ordering::SeqCst),
32298        "the late wake must complete the parked driver"
32299      );
32300      // Teardown inside the bounded window (see `join_within`).
32301      drop(executor);
32302      let _ = done_tx.send(());
32303    });
32304
32305    match done_rx.recv_timeout(Duration::from_secs(30)) {
32306      Ok(()) => join_within(
32307        "the MT deadline-reset runner exits after executor teardown",
32308        runner,
32309        Duration::from_secs(30),
32310      ),
32311      Err(error) => panic!(
32312        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
32313      ),
32314    }
32315  }
32316
32317  #[cfg(napi_runtime_os_threads)]
32318  #[test]
32319  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
32320    // Shape (5): the foreign/napi caller thread parks in
32321    // `futures::executor::block_on` for the ENTIRE build -- the normal
32322    // production shape. Even with an aggressively short deadline armed and
32323    // ZERO executor progress for many windows, that park must never fire.
32324    use std::sync::mpsc;
32325
32326    let (done_tx, done_rx) = mpsc::channel();
32327    let runner = std::thread::spawn(move || {
32328      let options = RuntimeOptions {
32329        flavor: RuntimeFlavor::MultiThread,
32330        worker_threads: 2,
32331        max_blocking_tasks: 2,
32332        thread_name_prefix: "foreign-exempt".to_string(),
32333        park_deadline: Some(Duration::from_millis(40)),
32334        drain_linger: DEFAULT_DRAIN_LINGER,
32335      };
32336      let executor =
32337        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32338
32339      let (value_tx, value_rx) = oneshot::channel::<usize>();
32340      let waker_thread = std::thread::spawn(move || {
32341        // Well past several deadline windows, with zero executor progress.
32342        std::thread::sleep(Duration::from_millis(300));
32343        let _ = value_tx.send(7);
32344      });
32345
32346      let started = Instant::now();
32347      let mut output = None;
32348      {
32349        let mut future = std::pin::pin!(async {
32350          output = Some(value_rx.await.unwrap());
32351        });
32352        // This (non-pool) thread takes the parking branch: exempt by design.
32353        executor.block_on(future.as_mut());
32354      }
32355      assert_eq!(output, Some(7));
32356      assert!(
32357        started.elapsed() >= Duration::from_millis(250),
32358        "the park must genuinely have outlived the 40ms deadline many times over"
32359      );
32360      waker_thread.join().unwrap();
32361      // Teardown inside the bounded window: this test was one of the silent
32362      // wedges on the Windows CI runners -- every assertion above had
32363      // passed, so the hang lived in the unbounded residue (executor drop /
32364      // thread teardown). Keep that phase inside the loud bounds.
32365      drop(executor);
32366      let _ = done_tx.send(());
32367    });
32368
32369    match done_rx.recv_timeout(Duration::from_secs(10)) {
32370      Ok(()) => join_within(
32371        "the exempt foreign-park runner exits after executor teardown",
32372        runner,
32373        Duration::from_secs(30),
32374      ),
32375      Err(error) => {
32376        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
32377      }
32378    }
32379  }
32380
32381  #[cfg(napi_runtime_os_threads)]
32382  #[test]
32383  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
32384    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
32385    // hook instead of a wall-clock lottery. Interleaving:
32386    //   (1) the cooperative park's deadline expires with genuinely zero
32387    //       progress (nothing else touches the executor before the park);
32388    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
32389    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
32390    //       pops this parker, stores the permit and reports the wake as
32391    //       DELIVERED (so no drainer is spawned). The wake was counted as
32392    //       delivered TO US, so the post-deregister permit re-check is the
32393    //       first-line catch; queued work and the enqueue counters in the
32394    //       fingerprint are further backstops;
32395    //   (3) the expiry decision runs. It must treat the permit as a wake, run
32396    //       the job (which sends the gate), and complete the awaited future
32397    //       instead of reporting `BlockOnDeadlock`.
32398    use std::sync::mpsc;
32399
32400    let (done_tx, done_rx) = mpsc::channel();
32401    let runner = std::thread::spawn(move || {
32402      let options = RuntimeOptions {
32403        flavor: RuntimeFlavor::MultiThread,
32404        worker_threads: 1,
32405        max_blocking_tasks: 1,
32406        thread_name_prefix: "edge-wake".to_string(),
32407        park_deadline: Some(Duration::from_millis(40)),
32408        drain_linger: DEFAULT_DRAIN_LINGER,
32409      };
32410      let executor =
32411        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32412
32413      // Until the racing job sends this gate, the awaited future is Pending.
32414      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32415      // (registered_before_wake, registered_after_wake) observed by the hook.
32416      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
32417
32418      let hook_exec = Arc::clone(&executor);
32419      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32420        *slot.borrow_mut() = Some(Box::new(move || {
32421          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32422          // The racing schedule: pushes the job, then `wake_one` pops the
32423          // still-registered parker (permit stored, wake counted as
32424          // delivered, no drainer).
32425          let handle = hook_exec.schedule_blocking(move || {
32426            let _ = gate_tx.send(5usize);
32427            0usize
32428          });
32429          drop(handle); // result unobserved; the gate send is the signal
32430          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
32431          hook_saw_tx.send((before, after)).unwrap();
32432        }));
32433      });
32434
32435      // The test-only on-pool marker runs the cooperative branch and its
32436      // thread-local hook on THIS thread.
32437      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32438      let mut output = None;
32439      {
32440        let mut future = std::pin::pin!(async {
32441          output = Some(gate_rx.await.unwrap());
32442        });
32443        executor.block_on(future.as_mut());
32444      }
32445      // Clear the hook slot BEFORE any assert can panic: a hook left
32446      // installed captures an executor Arc, and dropping the last executor
32447      // Arc from a TLS destructor at thread exit is loader-lock hostile on
32448      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
32449      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32450      assert_eq!(
32451        output,
32452        Some(5),
32453        "the edge-delivered wake's job must have run and completed the future"
32454      );
32455
32456      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
32457      assert_eq!(
32458        before, 1,
32459        "the hook must observe the parker STILL registered (race window)"
32460      );
32461      assert_eq!(
32462        after, 0,
32463        "the racing wake_one must have popped the parker (wake DELIVERED)"
32464      );
32465      // Teardown inside the bounded window (see `join_within`).
32466      drop(executor);
32467      let _ = done_tx.send(());
32468    });
32469
32470    match done_rx.recv_timeout(Duration::from_secs(10)) {
32471      Ok(()) => join_within(
32472        "the expiry-edge-wake runner exits after executor teardown",
32473        runner,
32474        Duration::from_secs(30),
32475      ),
32476      Err(error) => panic!(
32477        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
32478      ),
32479    }
32480  }
32481
32482  #[cfg(napi_runtime_os_threads)]
32483  #[test]
32484  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
32485    // Enqueue/fingerprint invariant: both runnable and blocking submissions
32486    // must advance the progress fingerprint at ENQUEUE. A blocking submission
32487    // landing after `has_queued_work()` but before the final fingerprint read
32488    // has already pushed deliverable work, while its wake targets an EMPTY
32489    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
32490    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
32491    // fires in this window on the driver thread:
32492    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
32493    //     job on the real worker -- `blocking_tasks_started` provably cannot
32494    //     advance behind our back;
32495    //   * the enqueue counter must therefore save the healthy park.
32496    use std::sync::mpsc;
32497
32498    let (done_tx, done_rx) = mpsc::channel();
32499    let runner = std::thread::spawn(move || {
32500      let options = RuntimeOptions {
32501        flavor: RuntimeFlavor::MultiThread,
32502        worker_threads: 1,
32503        max_blocking_tasks: 1,
32504        thread_name_prefix: "verdict-blocking".to_string(),
32505        park_deadline: Some(Duration::from_millis(40)),
32506        drain_linger: DEFAULT_DRAIN_LINGER,
32507      };
32508      let executor =
32509        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32510      executor
32511        .active_drainers
32512        .store(executor.max_drainers, Ordering::SeqCst);
32513
32514      // Until the racing job sends this gate, the awaited future is Pending.
32515      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32516      // Registry size observed by the hook (must be 0: already deregistered,
32517      // so no permit can possibly be delivered for this enqueue).
32518      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
32519
32520      let hook_exec = Arc::clone(&executor);
32521      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32522        *slot.borrow_mut() = Some(Box::new(move || {
32523          let handle = hook_exec.schedule_blocking(move || {
32524            let _ = gate_tx.send(11usize);
32525            0usize
32526          });
32527          drop(handle); // result unobserved; the gate send is the signal
32528          hook_saw_tx
32529            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
32530            .unwrap();
32531        }));
32532      });
32533
32534      // The test-only on-pool marker runs the cooperative branch and its
32535      // thread-local hook on THIS thread.
32536      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32537      let mut output = None;
32538      {
32539        let mut future = std::pin::pin!(async {
32540          output = Some(gate_rx.await.unwrap());
32541        });
32542        executor.block_on(future.as_mut());
32543      }
32544      assert_eq!(
32545        output,
32546        Some(11),
32547        "the enqueued job must have been run by the surviving driver via run_one"
32548      );
32549
32550      let registered_at_hook = hook_saw_rx
32551        .recv()
32552        .expect("the verdict hook must have fired");
32553      assert_eq!(
32554        registered_at_hook, 0,
32555        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
32556      );
32557      executor.active_drainers.store(0, Ordering::SeqCst);
32558      let _ = done_tx.send(());
32559    });
32560
32561    match done_rx.recv_timeout(Duration::from_secs(10)) {
32562      Ok(()) => runner.join().unwrap(),
32563      Err(error) => panic!(
32564        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
32565      ),
32566    }
32567  }
32568
32569  #[cfg(napi_runtime_os_threads)]
32570  #[test]
32571  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
32572    // Publish only through the deadlock-admission protocol after every legacy
32573    // final check. No runnable, blocking job, permit, timer, or metrics
32574    // fingerprint changes, so only the synchronized publication epoch can
32575    // prevent the false verdict. The future observes a separate test flag on
32576    // its next poll; that flag is deliberately not a scheduler wake source.
32577    use std::sync::mpsc;
32578
32579    struct ReadyAfterPublication(Arc<AtomicBool>);
32580
32581    impl Future for ReadyAfterPublication {
32582      type Output = ();
32583
32584      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32585        if self.0.load(Ordering::SeqCst) {
32586          Poll::Ready(())
32587        } else {
32588          Poll::Pending
32589        }
32590      }
32591    }
32592
32593    let (done_tx, done_rx) = mpsc::channel();
32594    let runner = std::thread::spawn(move || {
32595      let options = RuntimeOptions {
32596        flavor: RuntimeFlavor::MultiThread,
32597        worker_threads: 1,
32598        max_blocking_tasks: 1,
32599        thread_name_prefix: "final-verdict-publication".to_string(),
32600        park_deadline: Some(Duration::from_millis(40)),
32601        drain_linger: DEFAULT_DRAIN_LINGER,
32602      };
32603      let executor =
32604        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32605
32606      let ready = Arc::new(AtomicBool::new(false));
32607      let baseline_fingerprint = executor.metrics.progress_fingerprint();
32608      let baseline_publications = executor.deadlock_state.publications();
32609      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
32610      let hook_exec = Arc::clone(&executor);
32611      let hook_ready = Arc::clone(&ready);
32612      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
32613        *slot.borrow_mut() = Some(Box::new(move || {
32614          let publication_executor = Arc::clone(&hook_exec);
32615          std::thread::spawn(move || {
32616            let mut admission = publication_executor
32617              .begin_deadlock_admission()
32618              .expect("the deadline detector must enable admission accounting");
32619            hook_ready.store(true, Ordering::SeqCst);
32620            admission.mark_published();
32621          })
32622          .join()
32623          .unwrap();
32624          hook_saw_tx
32625            .send((
32626              hook_exec.deadlock_state.publications(),
32627              !hook_exec.has_queued_runnable(),
32628              hook_exec
32629                .blocking_queue
32630                .lock()
32631                .unwrap_or_else(std::sync::PoisonError::into_inner)
32632                .is_empty(),
32633              hook_exec.next_timer_deadline().is_none(),
32634              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
32635              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
32636            ))
32637            .unwrap();
32638        }));
32639      });
32640
32641      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32642      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
32643      assert_eq!(
32644        executor.block_on(future.as_mut()),
32645        BlockOnOutcome::Completed,
32646        "the publication epoch must veto the first verdict and permit the next poll"
32647      );
32648      let (
32649        publications,
32650        runnable_queue_empty,
32651        blocking_queue_empty,
32652        timer_heap_empty,
32653        fingerprint_unchanged,
32654        parked_drivers,
32655      ) = hook_saw_rx
32656        .recv()
32657        .expect("the final-verdict hook must have fired");
32658      assert_eq!(
32659        publications,
32660        baseline_publications
32661          .checked_add(1)
32662          .expect("test publication epoch must not overflow"),
32663        "the cross-thread admission must publish exactly one epoch"
32664      );
32665      assert!(
32666        runnable_queue_empty,
32667        "the publication test must not queue a runnable"
32668      );
32669      assert!(
32670        blocking_queue_empty,
32671        "the publication test must not queue blocking work"
32672      );
32673      assert!(
32674        timer_heap_empty,
32675        "the publication test must not register a timer"
32676      );
32677      assert!(
32678        fingerprint_unchanged,
32679        "ordinary metrics progress must not veto the publication-only verdict"
32680      );
32681      assert_eq!(
32682        parked_drivers, 0,
32683        "the publication must land after the cooperative parker was deregistered"
32684      );
32685      let _ = done_tx.send(());
32686    });
32687
32688    match done_rx.recv_timeout(Duration::from_secs(10)) {
32689      Ok(()) => runner.join().unwrap(),
32690      Err(error) => panic!(
32691        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
32692      ),
32693    }
32694  }
32695
32696  #[cfg(napi_runtime_os_threads)]
32697  #[test]
32698  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
32699    // The exact owner lane is saturated and reserved while the cooperative
32700    // driver parks. At expiry, release selects that driver and publishes its
32701    // handoff, then pauses before unpark. The driver reaches the gated verdict
32702    // with no queue, timer, metric, or ordinary publication change. The
32703    // exact-owner publication boundary must serialize the final check. The
32704    // hook then consumes the ordinary permit and renews the lane reservation,
32705    // leaving only the pending-handoff predicate to veto the false deadlock.
32706    use std::sync::mpsc;
32707
32708    let executor = multi_thread_executor(
32709      1,
32710      Some(Duration::from_millis(40)),
32711      "deadline-selected-handoff",
32712    );
32713    let owner = executor.fresh_owner_token();
32714    executor.active_blocking_owners.register(owner);
32715    executor
32716      .active_blocking
32717      .store(executor.max_blocking, Ordering::SeqCst);
32718
32719    let ready = Arc::new(AtomicBool::new(false));
32720    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
32721    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
32722    let (selected_tx, selected_rx) = mpsc::channel();
32723    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
32724    let (released_tx, released_rx) = mpsc::channel();
32725    let (renewed_tx, renewed_rx) = mpsc::channel();
32726    let release_executor = Arc::clone(&executor);
32727    let renewal_executor = Arc::clone(&executor);
32728    let release_ready = Arc::clone(&ready);
32729    let releaser = std::thread::spawn(move || {
32730      let reservation =
32731        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
32732          .expect("the synthetic exact-owner lane must start reserved");
32733      reservation_ready_tx.send(()).unwrap();
32734      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
32735        *slot.borrow_mut() = Some(Box::new(move || {
32736          selected_tx.send(()).unwrap();
32737          allow_unpark_rx.recv().unwrap();
32738          let renewed = renewal_executor
32739            .active_blocking_owners
32740            .reserve(owner)
32741            .expect("the released exact-owner lane must be reservable before unpark");
32742          release_ready.store(true, Ordering::SeqCst);
32743          renewed_tx.send(renewed).unwrap();
32744        }));
32745      });
32746      release_reservation_rx.recv().unwrap();
32747      released_tx
32748        .send(release_executor.release_owner_lane_and_wake(reservation))
32749        .unwrap();
32750    });
32751    reservation_ready_rx
32752      .recv_timeout(Duration::from_secs(2))
32753      .expect("the exact-owner reservation must be established before the park");
32754
32755    let baseline_fingerprint = executor.metrics.progress_fingerprint();
32756    let baseline_publications = executor.deadlock_state.publications();
32757    let expiry_executor = Arc::clone(&executor);
32758    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
32759      *slot.borrow_mut() = Some(Box::new(move || {
32760        release_reservation_tx.send(()).unwrap();
32761        selected_rx
32762          .recv_timeout(Duration::from_secs(2))
32763          .expect("reservation release must select the still-registered exact-owner driver");
32764        assert_eq!(
32765          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
32766          0,
32767          "handoff selection must remove the driver before pausing ahead of unpark"
32768        );
32769      }));
32770    });
32771
32772    let verdict_executor = Arc::clone(&executor);
32773    let verdict_ready = Arc::clone(&ready);
32774    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
32775      *slot.borrow_mut() = Some(Box::new(move || {
32776        assert!(
32777          verdict_executor.active_blocking_owners.is_available(owner),
32778          "reservation release must publish lane availability before handoff selection"
32779        );
32780        assert!(!verdict_executor.has_queued_runnable());
32781        assert!(
32782          verdict_executor
32783            .blocking_queue
32784            .lock()
32785            .unwrap_or_else(std::sync::PoisonError::into_inner)
32786            .is_empty()
32787        );
32788        assert!(verdict_executor.next_timer_deadline().is_none());
32789        assert!(
32790          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
32791          "ordinary metrics progress must not save the selected-handoff verdict"
32792        );
32793        assert_eq!(
32794          verdict_executor.deadlock_state.publications(),
32795          baseline_publications
32796        );
32797        assert!(
32798          !verdict_ready.load(Ordering::SeqCst),
32799          "the selected handoff must still be paused before unpark at the gated verdict"
32800        );
32801        allow_unpark_tx.send(()).unwrap();
32802      }));
32803    });
32804    let exact_owner_executor = Arc::clone(&executor);
32805    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
32806      *slot.borrow_mut() = Some(Box::new(move |parker| {
32807        assert!(
32808          parker.consume_permit(),
32809          "the selected handoff must unpark before exact-owner final predicates run"
32810        );
32811        assert!(
32812          parker.has_owner_handoff(),
32813          "consuming the ordinary permit must leave the exact-owner handoff pending"
32814        );
32815        assert!(
32816          !exact_owner_executor
32817            .active_blocking_owners
32818            .is_available(owner),
32819          "the renewed reservation must prevent lane availability from saving the verdict"
32820        );
32821      }));
32822    });
32823
32824    {
32825      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32826      let _owner = BlockingOwnerGuard::enter(Some(owner));
32827      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
32828        dependency: BlockingDependency {
32829          job: BlockingJobId(u64::MAX - 80),
32830          owner: Some(owner)
32831        },
32832        ready: Arc::clone(&ready),
32833      });
32834      assert_eq!(
32835        executor.block_on(future.as_mut()),
32836        BlockOnOutcome::Completed
32837      );
32838    }
32839    assert!(
32840      released_rx
32841        .recv_timeout(Duration::from_secs(2))
32842        .expect("the selected handoff publication must finish"),
32843      "reservation release must have selected the cooperative driver"
32844    );
32845    let renewed = renewed_rx
32846      .recv_timeout(Duration::from_secs(2))
32847      .expect("the selected handoff must renew the owner reservation before unpark");
32848    releaser.join().unwrap();
32849
32850    executor.active_blocking_owners.release(owner, renewed);
32851    executor.active_blocking.store(0, Ordering::SeqCst);
32852    executor.active_blocking_owners.unregister(owner);
32853    executor.begin_shutdown();
32854    executor.wait_until_scheduler_idle();
32855  }
32856
32857  #[cfg(napi_runtime_os_threads)]
32858  #[test]
32859  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
32860    // Release begins only after the deadline path has deregistered its parker
32861    // and completed every legacy final check. No targeted wake or ordinary
32862    // scheduler publication can save the verdict; the synchronized exact-owner
32863    // availability predicate must observe the released lane.
32864    use std::sync::mpsc;
32865
32866    let executor = multi_thread_executor(
32867      1,
32868      Some(Duration::from_millis(40)),
32869      "deadline-owner-no-parker",
32870    );
32871    let owner = executor.fresh_owner_token();
32872    executor.active_blocking_owners.register(owner);
32873    executor
32874      .active_blocking
32875      .store(executor.max_blocking, Ordering::SeqCst);
32876
32877    let ready = Arc::new(AtomicBool::new(false));
32878    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
32879    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
32880    let (released_tx, released_rx) = mpsc::channel();
32881    let release_executor = Arc::clone(&executor);
32882    let release_ready = Arc::clone(&ready);
32883    let releaser = std::thread::spawn(move || {
32884      let reservation =
32885        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
32886          .expect("the synthetic exact-owner lane must start reserved");
32887      reservation_ready_tx.send(()).unwrap();
32888      release_reservation_rx.recv().unwrap();
32889      let woke = release_executor.release_owner_lane_and_wake(reservation);
32890      release_ready.store(true, Ordering::SeqCst);
32891      released_tx.send(woke).unwrap();
32892    });
32893    reservation_ready_rx
32894      .recv_timeout(Duration::from_secs(2))
32895      .expect("the exact-owner reservation must be established before the park");
32896
32897    let baseline_fingerprint = executor.metrics.progress_fingerprint();
32898    let baseline_publications = executor.deadlock_state.publications();
32899    let verdict_executor = Arc::clone(&executor);
32900    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
32901      *slot.borrow_mut() = Some(Box::new(move || {
32902        assert_eq!(
32903          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
32904          0,
32905          "the release must begin only after deadline deregistration"
32906        );
32907        release_reservation_tx.send(()).unwrap();
32908        assert!(
32909          !released_rx
32910            .recv_timeout(Duration::from_secs(2))
32911            .expect("the no-parker owner release must finish"),
32912          "no registered parker may receive the released owner lane"
32913        );
32914        assert!(verdict_executor.active_blocking_owners.is_available(owner));
32915        assert!(!verdict_executor.has_queued_runnable());
32916        assert!(
32917          verdict_executor
32918            .blocking_queue
32919            .lock()
32920            .unwrap_or_else(std::sync::PoisonError::into_inner)
32921            .is_empty()
32922        );
32923        assert!(verdict_executor.next_timer_deadline().is_none());
32924        assert!(
32925          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
32926          "ordinary metrics progress must not save the no-parker release verdict"
32927        );
32928        assert_eq!(
32929          verdict_executor.deadlock_state.publications(),
32930          baseline_publications
32931        );
32932      }));
32933    });
32934
32935    {
32936      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32937      let _owner = BlockingOwnerGuard::enter(Some(owner));
32938      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
32939        dependency: BlockingDependency {
32940          job: BlockingJobId(u64::MAX - 81),
32941          owner: Some(owner)
32942        },
32943        ready: Arc::clone(&ready),
32944      });
32945      assert_eq!(
32946        executor.block_on(future.as_mut()),
32947        BlockOnOutcome::Completed
32948      );
32949    }
32950    releaser.join().unwrap();
32951
32952    executor.active_blocking.store(0, Ordering::SeqCst);
32953    executor.active_blocking_owners.unregister(owner);
32954    executor.begin_shutdown();
32955    executor.wait_until_scheduler_idle();
32956  }
32957
32958  #[cfg(napi_runtime_os_threads)]
32959  #[test]
32960  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
32961    // Pin the shutdown race after the final verdict gate has closed:
32962    //   1. an accepted task is pending, so shutdown abort must wake it;
32963    //   2. a cooperative block_on reaches its deadline and pauses while
32964    //      holding the closed verdict gate;
32965    //   3. shutdown publishes generation stop, transitions
32966    //      Running -> Stopping, then its abort wake blocks entering scheduler
32967    //      admission behind that gate;
32968    //   4. the gated final verdict must observe stop and return Stopped,
32969    //      reopening the gate so the abort wake and shutdown can finish.
32970    //
32971    // Before stop was published by the controller transition, step 3 blocked
32972    // inside abort before executor shutdown could set it. The driver then saw
32973    // empty queues and emitted a false BlockOnDeadlock.
32974    use std::sync::mpsc;
32975
32976    let controller = Arc::new(RuntimeController::new());
32977    controller
32978      .configure(RuntimeOptions {
32979        flavor: RuntimeFlavor::MultiThread,
32980        worker_threads: 2,
32981        max_blocking_tasks: 1,
32982        thread_name_prefix: "shutdown-gated-verdict".to_string(),
32983        park_deadline: Some(Duration::from_millis(40)),
32984        drain_linger: DEFAULT_DRAIN_LINGER,
32985      })
32986      .expect("the shutdown race test configuration must be accepted");
32987
32988    let backend = controller.backend();
32989    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
32990      panic!("the configured backend must be MultiThread");
32991    };
32992    let executor_id = executor.id;
32993    let weak_executor = Arc::downgrade(executor);
32994    drop(backend);
32995
32996    let (task_polled_tx, task_polled_rx) = mpsc::channel();
32997    let task = controller
32998      .try_spawn(async move {
32999        task_polled_tx.send(()).unwrap();
33000        std::future::pending::<()>().await;
33001      })
33002      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
33003    task_polled_rx
33004      .recv_timeout(Duration::from_secs(2))
33005      .expect("the accepted task must publish an abortable pending waker");
33006
33007    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
33008    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33009    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33010    let driver_controller = Arc::clone(&controller);
33011    let driver = std::thread::spawn(move || {
33012      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
33013        *slot.borrow_mut() = Some(Box::new(move || {
33014          gate_closed_tx.send(()).unwrap();
33015          release_verdict_rx.recv().unwrap();
33016        }));
33017      });
33018
33019      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33020      let mut pending = std::pin::pin!(NeverReady);
33021      let result = catch_unwind(AssertUnwindSafe(|| {
33022        driver_controller.block_on(pending.as_mut())
33023      }));
33024      let result = match result {
33025        Ok(outcome) => Ok(outcome),
33026        Err(payload) => {
33027          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33028          discard_panic_payload(payload);
33029          Err(was_deadlock)
33030        }
33031      };
33032      driver_result_tx.send(result).unwrap();
33033    });
33034
33035    gate_closed_rx
33036      .recv_timeout(Duration::from_secs(2))
33037      .expect("the cooperative driver must pause with the verdict gate closed");
33038
33039    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
33040    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33041    let shutdown_controller = Arc::clone(&controller);
33042    let shutdown = std::thread::spawn(move || {
33043      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
33044        *slot.borrow_mut() = Some(Box::new(move || {
33045          abort_blocked_tx.send(()).unwrap();
33046        }));
33047      });
33048      shutdown_result_tx
33049        .send(shutdown_controller.shutdown())
33050        .unwrap();
33051    });
33052
33053    abort_blocked_rx
33054      .recv_timeout(Duration::from_secs(2))
33055      .expect("the accepted task's abort wake must reach the closed verdict gate");
33056    let lifecycle_is_stopping = {
33057      let state = controller
33058        .state
33059        .lock()
33060        .unwrap_or_else(std::sync::PoisonError::into_inner);
33061      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
33062    };
33063    let stop_was_published = weak_executor
33064      .upgrade()
33065      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
33066
33067    release_verdict_tx.send(()).unwrap();
33068
33069    let driver_result = driver_result_rx
33070      .recv_timeout(Duration::from_secs(2))
33071      .expect("the gated driver must resolve after observing shutdown");
33072    let shutdown_result = shutdown_result_rx
33073      .recv_timeout(Duration::from_secs(5))
33074      .expect("shutdown must finish after the verdict gate reopens");
33075    // Both threads have sent their results; what remains is teardown (and on
33076    // the shutdown thread, the controller's `wait_for_all_workers` already
33077    // completed inside `shutdown()`). This test was one of the silent wedges
33078    // on the Windows CI runners -- bound the residue loudly.
33079    join_within(
33080      "the gated cooperative driver exits after teardown",
33081      driver,
33082      Duration::from_secs(30),
33083    );
33084    join_within(
33085      "the shutdown thread exits after teardown",
33086      shutdown,
33087      Duration::from_secs(30),
33088    );
33089
33090    assert!(
33091      lifecycle_is_stopping,
33092      "the abort wake must occur after Running -> Stopping"
33093    );
33094    assert!(
33095      stop_was_published,
33096      "generation stop must be visible before shutdown invokes accepted-task aborts"
33097    );
33098    assert_eq!(
33099      driver_result,
33100      Ok(BlockOnOutcome::Stopped),
33101      "shutdown must not be reported as a gated BlockOnDeadlock"
33102    );
33103    shutdown_result.unwrap();
33104    assert!(
33105      futures::executor::block_on(task).is_err(),
33106      "the accepted pending task must resolve as cancelled"
33107    );
33108  }
33109
33110  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
33111  #[test]
33112  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
33113    // Complement the publication-wins race above. The verdict owns both its
33114    // admission gate and the stop-publication mutex before shutdown starts.
33115    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
33116    // Running -> Stopping until the verdict finishes its synchronized checks.
33117    use std::sync::mpsc;
33118
33119    let controller = Arc::new(RuntimeController::new());
33120    controller
33121      .configure(RuntimeOptions {
33122        flavor: RuntimeFlavor::MultiThread,
33123        worker_threads: 2,
33124        max_blocking_tasks: 1,
33125        thread_name_prefix: "verdict-before-shutdown".to_string(),
33126        park_deadline: Some(Duration::from_millis(40)),
33127        drain_linger: DEFAULT_DRAIN_LINGER,
33128      })
33129      .expect("the verdict-first test configuration must be accepted");
33130
33131    let backend = controller.backend();
33132    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
33133      panic!("the configured backend must be MultiThread");
33134    };
33135    let executor_id = executor.id;
33136    drop(backend);
33137
33138    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
33139    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
33140    let (driver_result_tx, driver_result_rx) = mpsc::channel();
33141    let driver_controller = Arc::clone(&controller);
33142    let driver = std::thread::spawn(move || {
33143      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
33144        *slot.borrow_mut() = Some(Box::new(move || {
33145          verdict_locked_tx.send(()).unwrap();
33146          release_verdict_rx.recv().unwrap();
33147        }));
33148      });
33149
33150      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
33151      let mut pending = std::pin::pin!(NeverReady);
33152      let result = catch_unwind(AssertUnwindSafe(|| {
33153        driver_controller.block_on(pending.as_mut())
33154      }));
33155      let result = match result {
33156        Ok(outcome) => Ok(outcome),
33157        Err(payload) => {
33158          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
33159          discard_panic_payload(payload);
33160          Err(was_deadlock)
33161        }
33162      };
33163      driver_result_tx.send(result).unwrap();
33164    });
33165
33166    verdict_locked_rx
33167      .recv_timeout(Duration::from_secs(2))
33168      .expect("the verdict must hold the synchronized stop-publication guard");
33169
33170    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
33171    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
33172    let shutdown_controller = Arc::clone(&controller);
33173    let shutdown = std::thread::spawn(move || {
33174      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
33175        *slot.borrow_mut() = Some(Box::new(move || {
33176          shutdown_waiting_tx.send(()).unwrap();
33177        }));
33178      });
33179      shutdown_result_tx
33180        .send(shutdown_controller.shutdown())
33181        .unwrap();
33182    });
33183
33184    shutdown_waiting_rx
33185      .recv_timeout(Duration::from_secs(2))
33186      .expect("shutdown must reach the lifecycle-to-publication lock edge");
33187    let early_shutdown = shutdown_result_rx
33188      .recv_timeout(Duration::from_millis(100))
33189      .ok();
33190    let shutdown_was_blocked = early_shutdown.is_none();
33191    release_verdict_tx.send(()).unwrap();
33192
33193    let driver_result = driver_result_rx
33194      .recv_timeout(Duration::from_secs(2))
33195      .expect("the verdict-first driver must finish after release");
33196    let shutdown_result = early_shutdown.unwrap_or_else(|| {
33197      shutdown_result_rx
33198        .recv_timeout(Duration::from_secs(5))
33199        .expect("shutdown must finish after the verdict releases publication")
33200    });
33201    driver.join().unwrap();
33202    shutdown.join().unwrap();
33203
33204    assert!(
33205      shutdown_was_blocked,
33206      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
33207    );
33208    assert_eq!(
33209      driver_result,
33210      Err(true),
33211      "a verdict that linearizes before shutdown must retain the typed deadlock"
33212    );
33213    shutdown_result.unwrap();
33214  }
33215
33216  #[cfg(not(target_family = "wasm"))]
33217  #[test]
33218  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
33219    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
33220
33221    if std::env::var_os(CHILD_ENV).is_some() {
33222      let executor =
33223        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
33224      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
33225      // hook only needs to prove that scheduler admission returns.
33226      executor
33227        .active_drainers
33228        .store(executor.max_drainers, Ordering::SeqCst);
33229
33230      let hook_returned = Arc::new(AtomicBool::new(false));
33231      let hook_flag = Arc::clone(&hook_returned);
33232      let hook_executor = Arc::clone(&executor);
33233      let previous_hook = std::panic::take_hook();
33234      std::panic::set_hook(Box::new(move |_| {
33235        let handle = hook_executor.schedule_blocking(|| ());
33236        drop(handle);
33237        hook_flag.store(true, Ordering::SeqCst);
33238      }));
33239
33240      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
33241      let result = catch_unwind(AssertUnwindSafe(|| {
33242        let mut pending = std::pin::pin!(std::future::pending::<()>());
33243        executor.block_on(pending.as_mut());
33244      }));
33245      std::panic::set_hook(previous_hook);
33246
33247      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
33248      assert!(
33249        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
33250        "the deadline must retain its structured diagnostic"
33251      );
33252      assert!(
33253        hook_returned.load(Ordering::SeqCst),
33254        "the panic hook's scheduler submission must return before unwind"
33255      );
33256
33257      executor.active_drainers.store(0, Ordering::SeqCst);
33258      executor.begin_shutdown();
33259      executor.wait_until_scheduler_idle();
33260      return;
33261    }
33262
33263    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
33264      .arg("--exact")
33265      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
33266      .arg("--nocapture")
33267      .env(CHILD_ENV, "1")
33268      .stdout(std::process::Stdio::piped())
33269      .stderr(std::process::Stdio::piped())
33270      .spawn()
33271      .expect("the panic-hook subprocess must start");
33272    let deadline = Instant::now() + Duration::from_secs(5);
33273    let status = loop {
33274      if let Some(status) = child
33275        .try_wait()
33276        .expect("the subprocess status must be readable")
33277      {
33278        break status;
33279      }
33280      if Instant::now() >= deadline {
33281        let _ = child.kill();
33282        let output = child
33283          .wait_with_output()
33284          .expect("the timed-out subprocess must be reaped");
33285        panic!(
33286          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
33287          String::from_utf8_lossy(&output.stdout),
33288          String::from_utf8_lossy(&output.stderr)
33289        );
33290      }
33291      std::thread::sleep(Duration::from_millis(10));
33292    };
33293    let output = child
33294      .wait_with_output()
33295      .expect("the completed subprocess output must be read");
33296    assert!(
33297      status.success(),
33298      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
33299      status.code(),
33300      String::from_utf8_lossy(&output.stdout),
33301      String::from_utf8_lossy(&output.stderr)
33302    );
33303  }
33304
33305  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
33306  // the embedding host's env-resolution pipeline together with the read
33307  // itself; its tests live there.
33308
33309  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
33310  // The tests cover both deadlock-detection interactions. All tests build
33311  // executors LOCALLY (never the global RUNTIME or the process-global host
33312  // driver) and run hang-prone workloads on a child thread bounded by
33313  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
33314
33315  #[cfg(panic = "unwind")]
33316  #[test]
33317  fn contained_waker_separates_wake_and_drop_panics() {
33318    struct PanicWakeAndDrop;
33319
33320    impl std::task::Wake for PanicWakeAndDrop {
33321      fn wake(self: Arc<Self>) {
33322        panic!("intentional consuming wake panic");
33323      }
33324
33325      fn wake_by_ref(self: &Arc<Self>) {
33326        panic!("intentional borrowed wake panic");
33327      }
33328    }
33329
33330    impl Drop for PanicWakeAndDrop {
33331      fn drop(&mut self) {
33332        panic!("intentional RawWaker destructor panic");
33333      }
33334    }
33335
33336    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
33337  }
33338
33339  struct TestHostileRawWakerState {
33340    clones: AtomicUsize,
33341    drop_panics: AtomicUsize,
33342    panic_on_clone: Option<usize>,
33343  }
33344
33345  struct TestHostileRawWakerToken {
33346    state: Arc<TestHostileRawWakerState>,
33347    panic_on_drop: bool,
33348  }
33349
33350  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
33351    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
33352    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
33353    assert_ne!(
33354      token.state.panic_on_clone,
33355      Some(clone),
33356      "intentional RawWaker clone panic"
33357    );
33358    let clone = Box::new(TestHostileRawWakerToken {
33359      state: Arc::clone(&token.state),
33360      panic_on_drop: true,
33361    });
33362    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
33363  }
33364
33365  unsafe fn wake_test_hostile_waker(data: *const ()) {
33366    unsafe { drop_test_hostile_waker(data) };
33367  }
33368
33369  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
33370
33371  unsafe fn drop_test_hostile_waker(data: *const ()) {
33372    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
33373    if token.panic_on_drop {
33374      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
33375      panic!("intentional cloned RawWaker destructor panic");
33376    }
33377  }
33378
33379  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
33380    clone_test_hostile_waker,
33381    wake_test_hostile_waker,
33382    wake_test_hostile_waker_by_ref,
33383    drop_test_hostile_waker,
33384  );
33385
33386  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
33387    let token = Box::new(TestHostileRawWakerToken {
33388      state,
33389      panic_on_drop: false,
33390    });
33391    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
33392    unsafe { Waker::from_raw(raw) }
33393  }
33394
33395  /// Waker that only counts. For polling a raw `Sleep` without an executor.
33396  struct CountingWake(AtomicUsize);
33397  impl std::task::Wake for CountingWake {
33398    fn wake(self: Arc<Self>) {
33399      self.0.fetch_add(1, Ordering::SeqCst);
33400    }
33401  }
33402
33403  /// One armed stub timer: the latest waker plus a cancellation flag.
33404  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
33405
33406  /// Test-local host driver: fires each registered timer from a helper OS
33407  /// thread at its deadline (re-registration under the same id refreshes the
33408  /// waker without arming a second thread, per the `TimerDriver` contract).
33409  struct StubHostTimerDriver {
33410    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
33411  }
33412
33413  impl StubHostTimerDriver {
33414    fn new() -> Self {
33415      Self {
33416        timers: Mutex::new(FxHashMap::default()),
33417      }
33418    }
33419  }
33420
33421  impl TimerDriver for StubHostTimerDriver {
33422    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33423      let mut timers = self.timers.lock().unwrap();
33424      if let Some(cell) = timers.get(&id) {
33425        *cell.0.lock().unwrap() = waker;
33426        return;
33427      }
33428      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
33429      timers.insert(id, Arc::clone(&cell));
33430      std::thread::spawn(move || {
33431        let now = Instant::now();
33432        if deadline > now {
33433          std::thread::sleep(deadline - now);
33434        }
33435        if !cell.1.load(Ordering::SeqCst) {
33436          cell.0.lock().unwrap().wake_by_ref();
33437        }
33438      });
33439    }
33440
33441    fn cancel(&self, id: TimerId) {
33442      let removed = self.timers.lock().unwrap().remove(&id);
33443      if let Some(cell) = removed {
33444        cell.1.store(true, Ordering::SeqCst);
33445      }
33446    }
33447  }
33448
33449  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
33450  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
33451  /// per timer id), never fires on its own, and reports the controllable
33452  /// `live` flag (a killed stub plays a driver whose owning host context --
33453  /// napi env in the binding -- was torn down).
33454  struct ManualStubDriver {
33455    live: AtomicBool,
33456    registers: Mutex<Vec<(TimerId, Instant)>>,
33457    cancels: Mutex<Vec<TimerId>>,
33458    pending: Mutex<FxHashMap<TimerId, Waker>>,
33459    swept: AtomicBool,
33460  }
33461
33462  impl ManualStubDriver {
33463    fn new() -> Arc<Self> {
33464      Arc::new(Self {
33465        live: AtomicBool::new(true),
33466        registers: Mutex::new(Vec::new()),
33467        cancels: Mutex::new(Vec::new()),
33468        pending: Mutex::new(FxHashMap::default()),
33469        swept: AtomicBool::new(false),
33470      })
33471    }
33472
33473    fn kill(&self) {
33474      self.live.store(false, Ordering::SeqCst);
33475    }
33476  }
33477
33478  impl TimerDriver for ManualStubDriver {
33479    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
33480      self.registers.lock().unwrap().push((id, deadline));
33481      self.pending.lock().unwrap().insert(id, waker);
33482    }
33483
33484    fn cancel(&self, id: TimerId) {
33485      self.cancels.lock().unwrap().push(id);
33486      self.pending.lock().unwrap().remove(&id);
33487    }
33488
33489    fn is_live(&self) -> bool {
33490      self.live.load(Ordering::SeqCst)
33491    }
33492
33493    fn on_swept(&self) {
33494      // The binding shape: drain the pending map and wake everything armed
33495      // here so those sleeps re-poll onto the next live registrant.
33496      self.swept.store(true, Ordering::SeqCst);
33497      let wakers: Vec<Waker> = self
33498        .pending
33499        .lock()
33500        .unwrap()
33501        .drain()
33502        .map(|(_, w)| w)
33503        .collect();
33504      for waker in wakers {
33505        waker.wake();
33506      }
33507    }
33508  }
33509
33510  struct PanickingTimerDriver {
33511    swept: AtomicBool,
33512  }
33513
33514  impl TimerDriver for PanickingTimerDriver {
33515    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33516
33517    fn cancel(&self, _id: TimerId) {}
33518
33519    fn is_live(&self) -> bool {
33520      panic!("intentional timer-driver liveness panic");
33521    }
33522
33523    fn on_swept(&self) {
33524      self.swept.store(true, Ordering::SeqCst);
33525      panic!("intentional timer-driver sweep panic");
33526    }
33527  }
33528
33529  struct PanickingRegisterTimerDriver {
33530    registers: AtomicUsize,
33531    cancels: Mutex<Vec<TimerId>>,
33532    pending: Mutex<FxHashMap<TimerId, Waker>>,
33533  }
33534
33535  impl PanickingRegisterTimerDriver {
33536    fn new() -> Arc<Self> {
33537      Arc::new(Self {
33538        registers: AtomicUsize::new(0),
33539        cancels: Mutex::new(Vec::new()),
33540        pending: Mutex::new(FxHashMap::default()),
33541      })
33542    }
33543  }
33544
33545  impl TimerDriver for PanickingRegisterTimerDriver {
33546    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
33547      self.registers.fetch_add(1, Ordering::SeqCst);
33548      self.pending.lock().unwrap().insert(id, waker);
33549      panic!("intentional timer-driver register panic after arming");
33550    }
33551
33552    fn cancel(&self, id: TimerId) {
33553      self.cancels.lock().unwrap().push(id);
33554      self.pending.lock().unwrap().remove(&id);
33555    }
33556  }
33557
33558  struct PanicBeforeRetainingTimerDriver {
33559    registers: AtomicUsize,
33560  }
33561
33562  impl TimerDriver for PanicBeforeRetainingTimerDriver {
33563    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
33564      self.registers.fetch_add(1, Ordering::SeqCst);
33565      panic!("intentional timer-driver register panic before retaining its waker");
33566    }
33567
33568    fn cancel(&self, _id: TimerId) {}
33569  }
33570
33571  struct PanickingCancelDropTimerDriver {
33572    cancels: Arc<AtomicUsize>,
33573    drops: Arc<AtomicUsize>,
33574  }
33575
33576  impl TimerDriver for PanickingCancelDropTimerDriver {
33577    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33578
33579    fn cancel(&self, _id: TimerId) {
33580      self.cancels.fetch_add(1, Ordering::SeqCst);
33581      panic!("intentional timer-driver cancel panic");
33582    }
33583  }
33584
33585  impl Drop for PanickingCancelDropTimerDriver {
33586    fn drop(&mut self) {
33587      self.drops.fetch_add(1, Ordering::SeqCst);
33588      panic!("intentional timer-driver destructor panic");
33589    }
33590  }
33591
33592  struct UnregisterLaterThenPanicTimerDriver {
33593    registry: Weak<TimerDriverRegistry>,
33594    later: Mutex<Option<TimerDriverId>>,
33595    registers: AtomicUsize,
33596  }
33597
33598  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
33599    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
33600      self.registers.fetch_add(1, Ordering::SeqCst);
33601      let later = *self.later.lock().unwrap();
33602      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
33603        registry.unregister(later);
33604      }
33605      panic!("intentional earlier timer-driver panic after unregistering a later driver");
33606    }
33607
33608    fn cancel(&self, _id: TimerId) {}
33609  }
33610
33611  struct ReentrantLivenessDriver {
33612    registry: std::sync::Weak<TimerDriverRegistry>,
33613    registration: Mutex<Option<TimerDriverId>>,
33614  }
33615
33616  impl TimerDriver for ReentrantLivenessDriver {
33617    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33618
33619    fn cancel(&self, _id: TimerId) {}
33620
33621    fn is_live(&self) -> bool {
33622      let registration = *self.registration.lock().unwrap();
33623      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
33624        registry.unregister(id);
33625      }
33626      false
33627    }
33628  }
33629
33630  struct ReentrantMultiUnregisterTimerDriver {
33631    registry: std::sync::Weak<TimerDriverRegistry>,
33632    registrations: Mutex<Vec<TimerDriverId>>,
33633  }
33634
33635  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
33636    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33637
33638    fn cancel(&self, _id: TimerId) {}
33639
33640    fn is_live(&self) -> bool {
33641      if let Some(registry) = self.registry.upgrade() {
33642        for registration in self.registrations.lock().unwrap().iter().copied() {
33643          registry.unregister(registration);
33644        }
33645      }
33646      false
33647    }
33648  }
33649
33650  struct ReentrantSweepTimerDriver {
33651    registry: std::sync::Weak<TimerDriverRegistry>,
33652    registration: Mutex<Option<TimerDriverId>>,
33653    sweeps: AtomicUsize,
33654    removed_before_sweep: AtomicBool,
33655  }
33656
33657  impl TimerDriver for ReentrantSweepTimerDriver {
33658    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33659
33660    fn cancel(&self, _id: TimerId) {}
33661
33662    fn is_live(&self) -> bool {
33663      false
33664    }
33665
33666    fn on_swept(&self) {
33667      self.sweeps.fetch_add(1, Ordering::SeqCst);
33668      let registration = self.registration.lock().unwrap().take();
33669      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
33670        let state = registry
33671          .state
33672          .try_lock()
33673          .expect("the sweep callback must run outside the timer-driver registry lock");
33674        assert!(
33675          !state.entries.contains_key(&registration),
33676          "the dead driver must be unlinked before its sweep callback runs"
33677        );
33678        self.removed_before_sweep.store(true, Ordering::SeqCst);
33679        drop(state);
33680        // Mirrors JsTimerHostInner::evict: the sweep already removed this
33681        // registration, so explicit host cleanup must remain a cheap no-op.
33682        registry.unregister(registration);
33683      }
33684    }
33685  }
33686
33687  struct ReentrantDropDriver {
33688    registry: std::sync::Weak<TimerDriverRegistry>,
33689    fallback: TimerDriverId,
33690  }
33691
33692  impl TimerDriver for ReentrantDropDriver {
33693    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
33694
33695    fn cancel(&self, _id: TimerId) {}
33696  }
33697
33698  impl Drop for ReentrantDropDriver {
33699    fn drop(&mut self) {
33700      if let Some(registry) = self.registry.upgrade() {
33701        registry.unregister(self.fallback);
33702      }
33703    }
33704  }
33705
33706  /// Waker whose `wake` just latches a flag -- observable evidence that a
33707  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
33708  #[derive(Default)]
33709  struct WakeFlag(AtomicBool);
33710
33711  impl std::task::Wake for WakeFlag {
33712    fn wake(self: Arc<Self>) {
33713      self.0.store(true, Ordering::SeqCst);
33714    }
33715  }
33716
33717  #[cfg(napi_runtime_os_threads)]
33718  fn multi_thread_executor(
33719    workers: usize,
33720    park_deadline: Option<Duration>,
33721    prefix: &str,
33722  ) -> Arc<MultiThreadExecutor> {
33723    let options = RuntimeOptions {
33724      flavor: RuntimeFlavor::MultiThread,
33725      worker_threads: workers,
33726      max_blocking_tasks: workers,
33727      thread_name_prefix: prefix.to_string(),
33728      park_deadline,
33729      drain_linger: DEFAULT_DRAIN_LINGER,
33730    };
33731    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
33732  }
33733
33734  /// Local driver registry holding exactly `driver` (the common single-host
33735  /// shape of the ported pre-registry tests).
33736  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
33737    let registry = Arc::new(TimerDriverRegistry::default());
33738    registry.register(driver);
33739    registry
33740  }
33741
33742  #[cfg(napi_runtime_os_threads)]
33743  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
33744    // The MultiThread arm never touches the driver registry; empty is fine.
33745    make_sleep(
33746      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
33747      &Arc::new(TimerDriverRegistry::default()),
33748      deadline,
33749    )
33750  }
33751
33752  #[cfg(napi_runtime_os_threads)]
33753  #[test]
33754  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
33755    use std::sync::mpsc;
33756
33757    let executor = multi_thread_executor(
33758      1,
33759      Some(Duration::from_secs(1)),
33760      "timer-registration-admission",
33761    );
33762    let (entered_tx, entered_rx) = mpsc::channel();
33763    let (release_tx, release_rx) = mpsc::channel();
33764    let registration_executor = Arc::clone(&executor);
33765    let registration = std::thread::spawn(move || {
33766      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
33767        *slot.borrow_mut() = Some(Box::new(move || {
33768          entered_tx.send(()).unwrap();
33769          release_rx.recv().unwrap();
33770        }));
33771      });
33772      registration_executor.register_timer(
33773        1,
33774        Instant::now() + Duration::from_secs(60),
33775        futures::task::noop_waker(),
33776        &Arc::new(AtomicBool::new(false)),
33777      );
33778    });
33779
33780    entered_rx
33781      .recv_timeout(Duration::from_secs(2))
33782      .expect("timer registration must pause after entering verdict admission");
33783    let verdict = executor.try_begin_deadlock_verdict();
33784    let admission_blocked_verdict = verdict.is_none();
33785    drop(verdict);
33786    release_tx.send(()).unwrap();
33787    registration.join().unwrap();
33788
33789    assert!(
33790      admission_blocked_verdict,
33791      "a timer registration must remain announced until its heap entry is visible"
33792    );
33793    assert!(
33794      executor.next_timer_deadline().is_some(),
33795      "the admitted registration must publish its timer after release"
33796    );
33797    executor.begin_shutdown();
33798    executor.wait_until_scheduler_idle();
33799  }
33800
33801  #[cfg(napi_runtime_os_threads)]
33802  #[derive(Clone, Copy, Debug)]
33803  enum TimerFireAdmissionPath {
33804    Timekeeper,
33805    Cooperative,
33806    Shutdown,
33807  }
33808
33809  #[cfg(napi_runtime_os_threads)]
33810  struct ScheduleAfterTimerWakeRelease {
33811    executor: Arc<MultiThreadExecutor>,
33812    runnable: Mutex<Option<Runnable>>,
33813    entered: std::sync::mpsc::Sender<()>,
33814    release: Mutex<std::sync::mpsc::Receiver<()>>,
33815    scheduled: std::sync::mpsc::Sender<()>,
33816  }
33817
33818  #[cfg(napi_runtime_os_threads)]
33819  impl ScheduleAfterTimerWakeRelease {
33820    fn publish(&self) {
33821      self.entered.send(()).unwrap();
33822      self
33823        .release
33824        .lock()
33825        .unwrap_or_else(std::sync::PoisonError::into_inner)
33826        .recv()
33827        .unwrap();
33828      let runnable = self
33829        .runnable
33830        .lock()
33831        .unwrap_or_else(std::sync::PoisonError::into_inner)
33832        .take();
33833      if let Some(runnable) = runnable {
33834        self.executor.schedule(runnable);
33835      }
33836      self.scheduled.send(()).unwrap();
33837    }
33838  }
33839
33840  #[cfg(napi_runtime_os_threads)]
33841  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
33842    fn wake(self: Arc<Self>) {
33843      self.publish();
33844    }
33845
33846    fn wake_by_ref(self: &Arc<Self>) {
33847      self.publish();
33848    }
33849  }
33850
33851  #[cfg(napi_runtime_os_threads)]
33852  fn insert_timer_for_fire_admission_test(
33853    executor: &MultiThreadExecutor,
33854    id: TimerId,
33855    deadline: Instant,
33856    waker: Waker,
33857    fired: Arc<AtomicBool>,
33858  ) {
33859    let mut inner = executor
33860      .timers
33861      .inner
33862      .lock()
33863      .unwrap_or_else(std::sync::PoisonError::into_inner);
33864    inner.entries.insert(id, HeapTimerEntry { waker, fired });
33865    inner.queue.push(deadline, id);
33866  }
33867
33868  #[cfg(napi_runtime_os_threads)]
33869  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
33870    use std::sync::mpsc;
33871
33872    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
33873    let (runnable, task) = async_task::spawn(async {}, |_| {});
33874    task.detach();
33875    let (entered_tx, entered_rx) = mpsc::channel();
33876    let (release_tx, release_rx) = mpsc::channel();
33877    let (scheduled_tx, scheduled_rx) = mpsc::channel();
33878    let fired = Arc::new(AtomicBool::new(false));
33879    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
33880      executor: Arc::clone(&executor),
33881      runnable: Mutex::new(Some(runnable)),
33882      entered: entered_tx,
33883      release: Mutex::new(release_rx),
33884      scheduled: scheduled_tx,
33885    }));
33886
33887    let fire_thread = match path {
33888      TimerFireAdmissionPath::Timekeeper => {
33889        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
33890        None
33891      }
33892      TimerFireAdmissionPath::Cooperative => {
33893        insert_timer_for_fire_admission_test(
33894          &executor,
33895          1,
33896          Instant::now(),
33897          waker,
33898          Arc::clone(&fired),
33899        );
33900        let fire_executor = Arc::clone(&executor);
33901        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
33902      }
33903      TimerFireAdmissionPath::Shutdown => {
33904        insert_timer_for_fire_admission_test(
33905          &executor,
33906          1,
33907          Instant::now() + Duration::from_secs(60),
33908          waker,
33909          Arc::clone(&fired),
33910        );
33911        let fire_executor = Arc::clone(&executor);
33912        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
33913      }
33914    };
33915
33916    // Hang detector on a guaranteed event that needs a freshly spawned
33917    // thread (timekeeper / fire thread) to get CPU -- generous for starved
33918    // CI runners.
33919    entered_rx
33920      .recv_timeout(Duration::from_secs(10))
33921      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
33922    assert!(
33923      executor.next_timer_deadline().is_none(),
33924      "{path:?} must exercise the remove-before-wake window"
33925    );
33926    assert!(
33927      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
33928      "{path:?} must retain admission while its waker is publishing"
33929    );
33930    let verdict = executor.try_begin_deadlock_verdict();
33931    let admission_blocked_verdict = verdict.is_none();
33932    drop(verdict);
33933
33934    release_tx.send(()).unwrap();
33935    scheduled_rx
33936      .recv_timeout(Duration::from_secs(10))
33937      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
33938    if let Some(fire_thread) = fire_thread {
33939      fire_thread.join().unwrap();
33940    }
33941    assert!(
33942      admission_blocked_verdict,
33943      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
33944    );
33945
33946    executor.begin_shutdown();
33947    executor.wait_until_scheduler_idle();
33948  }
33949
33950  #[cfg(napi_runtime_os_threads)]
33951  #[test]
33952  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
33953    for path in [
33954      TimerFireAdmissionPath::Timekeeper,
33955      TimerFireAdmissionPath::Cooperative,
33956      TimerFireAdmissionPath::Shutdown,
33957    ] {
33958      assert_timer_fire_path_holds_deadlock_admission(path);
33959    }
33960  }
33961
33962  #[cfg(napi_runtime_os_threads)]
33963  #[test]
33964  fn multi_thread_sleep_fires_from_foreign_block_on() {
33965    // The napi-caller shape: a NON-pool thread parks in
33966    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
33967    // the executor, so ONLY the dedicated timekeeper can fire the timer --
33968    // this is the timekeeper's existence test.
33969    use std::sync::mpsc;
33970
33971    let (done_tx, done_rx) = mpsc::channel();
33972    let runner = std::thread::spawn(move || {
33973      let executor = multi_thread_executor(2, None, "timer-foreign");
33974      let started = Instant::now();
33975      let mut future = std::pin::pin!(async {
33976        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
33977      });
33978      executor.block_on(future.as_mut());
33979      assert!(
33980        started.elapsed() >= Duration::from_millis(80),
33981        "the sleep must not resolve before its deadline"
33982      );
33983      let _ = done_tx.send(());
33984    });
33985
33986    match done_rx.recv_timeout(Duration::from_secs(10)) {
33987      Ok(()) => runner.join().unwrap(),
33988      Err(error) => {
33989        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
33990      }
33991    }
33992  }
33993
33994  #[cfg(napi_runtime_os_threads)]
33995  #[test]
33996  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
33997    // A long timer waits on the dedicated timer thread, leaving the sole
33998    // low-level test worker available for ordinary scheduler work.
33999    use std::sync::mpsc;
34000
34001    let (done_tx, done_rx) = mpsc::channel();
34002    let runner = std::thread::spawn(move || {
34003      let executor = multi_thread_executor(1, None, "timer-drainer");
34004
34005      // Arm a long timer through a real spawned task so the sole worker
34006      // returns to rayon afterwards (the watch-idle Debouncing shape).
34007      let sleep_exec = Arc::clone(&executor);
34008      let sleep_done = Arc::new(AtomicBool::new(false));
34009      let sleep_flag = Arc::clone(&sleep_done);
34010      let sched = Arc::clone(&executor);
34011      let (sleep_runnable, sleep_task) = async_task::spawn(
34012        async move {
34013          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
34014          sleep_flag.store(true, Ordering::SeqCst);
34015        },
34016        move |r| sched.schedule(r),
34017      );
34018      executor.schedule(sleep_runnable);
34019      wait_until("the timekeeper parked on the 60s deadline", || {
34020        executor
34021          .timers
34022          .inner
34023          .lock()
34024          .unwrap()
34025          .timekeeper_parker
34026          .is_some()
34027      });
34028
34029      // Queued work must run NOW, not in 60s.
34030      let counter = Arc::new(AtomicUsize::new(0));
34031      let mut tasks = Vec::new();
34032      for _ in 0..32 {
34033        let counter = Arc::clone(&counter);
34034        let sched = Arc::clone(&executor);
34035        let (runnable, task) = async_task::spawn(
34036          async move {
34037            counter.fetch_add(1, Ordering::SeqCst);
34038          },
34039          move |r| sched.schedule(r),
34040        );
34041        executor.schedule(runnable);
34042        tasks.push(task);
34043      }
34044      for task in tasks {
34045        futures::executor::block_on(task);
34046      }
34047      assert_eq!(counter.load(Ordering::SeqCst), 32);
34048      assert!(
34049        !sleep_done.load(Ordering::SeqCst),
34050        "the 60s timer must still be pending: the work ran DURING the timer wait"
34051      );
34052      // Cancel the long sleep so the executor tears down promptly.
34053      executor.shutdown_timers();
34054      futures::executor::block_on(sleep_task);
34055      let _ = done_tx.send(());
34056    });
34057
34058    match done_rx.recv_timeout(Duration::from_secs(15)) {
34059      Ok(()) => runner.join().unwrap(),
34060      Err(error) => {
34061        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
34062      }
34063    }
34064  }
34065
34066  #[cfg(napi_runtime_os_threads)]
34067  #[test]
34068  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
34069    let executor = multi_thread_executor(2, None, "timer-lazy-start");
34070    assert!(
34071      executor.timers.thread.lock().unwrap().is_none(),
34072      "constructing a runtime without timers must not create an extra OS thread"
34073    );
34074
34075    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34076    assert!(
34077      executor.timers.thread.lock().unwrap().is_none(),
34078      "an unpolled sleep has not registered a deadline"
34079    );
34080    let waker = futures::task::noop_waker();
34081    let mut cx = Context::from_waker(&waker);
34082    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34083    assert!(
34084      executor.timers.thread.lock().unwrap().is_some(),
34085      "the first timer registration must start the dedicated service"
34086    );
34087  }
34088
34089  #[cfg(napi_runtime_os_threads)]
34090  #[test]
34091  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
34092    use std::sync::mpsc;
34093
34094    let executor = multi_thread_executor(1, None, "timer-closed-start");
34095    executor.shutdown_timers();
34096    let handle_guard = executor.timers.thread.lock().unwrap();
34097    let ensure_executor = Arc::clone(&executor);
34098    let (completed_tx, completed_rx) = mpsc::channel();
34099    let ensure = std::thread::spawn(move || {
34100      ensure_executor.timers.ensure_started().unwrap();
34101      completed_tx.send(()).unwrap();
34102    });
34103
34104    // Hang detector on a freshly spawned thread getting CPU -- generous for
34105    // starved CI runners (a real regression blocks forever on the held lock).
34106    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
34107      "ensure_started consulted or joined the timer handle before observing the closed heap",
34108    );
34109    drop(handle_guard);
34110    ensure.join().unwrap();
34111  }
34112
34113  #[cfg(napi_runtime_os_threads)]
34114  #[test]
34115  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
34116    const TIMERS: u64 = 4096;
34117
34118    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
34119    let base = Instant::now() + Duration::from_secs(3600);
34120    for id in 0..TIMERS {
34121      executor.register_timer(
34122        id,
34123        base + Duration::from_secs(id),
34124        futures::task::noop_waker(),
34125        &Arc::new(AtomicBool::new(false)),
34126      );
34127    }
34128    {
34129      let inner = executor.timers.inner.lock().unwrap();
34130      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
34131      assert_eq!(inner.queue.len(), inner.entries.len());
34132    }
34133
34134    // Preserve the earliest node while removing every later node. A lazy
34135    // BinaryHeap would retain all 4095 tombstones until that first deadline.
34136    for id in 1..TIMERS {
34137      executor.cancel_timer(id);
34138    }
34139    {
34140      let inner = executor.timers.inner.lock().unwrap();
34141      assert_eq!(inner.entries.len(), 1);
34142      assert_eq!(inner.queue.len(), 1);
34143      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
34144    }
34145
34146    executor.cancel_timer(0);
34147    {
34148      let inner = executor.timers.inner.lock().unwrap();
34149      assert!(inner.entries.is_empty());
34150      assert!(inner.queue.is_empty());
34151    }
34152    executor.shutdown_timers();
34153    executor.timers.join();
34154  }
34155
34156  #[cfg(all(napi_runtime_os_threads, panic = "unwind"))]
34157  #[test]
34158  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
34159    let executor = multi_thread_executor(2, None, "timer-start-failure");
34160    executor
34161      .timers
34162      .fail_next_start
34163      .store(true, Ordering::SeqCst);
34164
34165    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34166    let waker = Waker::from(Arc::clone(&wake_count));
34167    let baseline_waker_refs = Arc::strong_count(&wake_count);
34168    let mut cx = Context::from_waker(&waker);
34169    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34170
34171    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
34172    assert!(
34173      failure.is_err(),
34174      "the injected timer-thread start failure must panic the poll"
34175    );
34176    {
34177      let inner = executor.timers.inner.lock().unwrap();
34178      assert!(
34179        inner.entries.is_empty(),
34180        "a failed start must not retain a timer entry"
34181      );
34182      assert!(
34183        inner.queue.is_empty(),
34184        "a failed start must not retain a stale heap node"
34185      );
34186      assert!(
34187        inner.timekeeper_parker.is_none(),
34188        "a failed start must not publish a timekeeper parker"
34189      );
34190    }
34191    assert!(
34192      executor.timers.thread.lock().unwrap().is_none(),
34193      "a failed start must not retain a thread handle"
34194    );
34195    assert_eq!(
34196      Arc::strong_count(&wake_count),
34197      baseline_waker_refs,
34198      "a failed start must release the cloned timer waker"
34199    );
34200
34201    assert!(
34202      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34203      "the same Sleep must be safely retryable after a transient start failure"
34204    );
34205    {
34206      let inner = executor.timers.inner.lock().unwrap();
34207      assert_eq!(
34208        inner.entries.len(),
34209        1,
34210        "the retry must publish exactly one timer entry"
34211      );
34212      assert_eq!(
34213        inner.queue.len(),
34214        1,
34215        "the retry must publish exactly one heap node"
34216      );
34217    }
34218    assert!(
34219      executor.timers.thread.lock().unwrap().is_some(),
34220      "the retry must start the dedicated timekeeper"
34221    );
34222
34223    drop(sleep);
34224    assert!(
34225      executor.timers.inner.lock().unwrap().entries.is_empty(),
34226      "dropping the retried Sleep must cancel its registration"
34227    );
34228    executor.shutdown_timers();
34229    executor.wait_until_scheduler_idle();
34230    assert!(
34231      executor.timers.thread.lock().unwrap().is_none(),
34232      "shutdown after a recovered start failure must join the timekeeper"
34233    );
34234  }
34235
34236  #[cfg(napi_runtime_os_threads)]
34237  #[test]
34238  fn pending_timer_preserves_every_configured_rayon_worker() {
34239    use std::sync::mpsc;
34240
34241    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
34242    let waker = futures::task::noop_waker();
34243    let mut cx = Context::from_waker(&waker);
34244    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34245    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34246    wait_until("the dedicated timer thread to park", || {
34247      executor
34248        .timers
34249        .inner
34250        .lock()
34251        .unwrap()
34252        .timekeeper_parker
34253        .is_some()
34254    });
34255
34256    let released = Arc::new((Mutex::new(false), Condvar::new()));
34257    let (started_tx, started_rx) = mpsc::channel();
34258    let (completed_tx, completed_rx) = mpsc::channel();
34259    for _ in 0..2 {
34260      let released = Arc::clone(&released);
34261      let started_tx = started_tx.clone();
34262      let completed_tx = completed_tx.clone();
34263      executor.pool.spawn(move || {
34264        started_tx.send(()).unwrap();
34265        let (lock, changed) = &*released;
34266        let mut released = lock
34267          .lock()
34268          .unwrap_or_else(std::sync::PoisonError::into_inner);
34269        while !*released {
34270          released = changed
34271            .wait(released)
34272            .unwrap_or_else(std::sync::PoisonError::into_inner);
34273        }
34274        completed_tx.send(()).unwrap();
34275      });
34276    }
34277    drop(started_tx);
34278    drop(completed_tx);
34279
34280    let first = started_rx.recv_timeout(Duration::from_secs(2));
34281    let second = started_rx.recv_timeout(Duration::from_secs(2));
34282    {
34283      let (lock, changed) = &*released;
34284      *lock
34285        .lock()
34286        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
34287      changed.notify_all();
34288    }
34289    drop(sleep);
34290    for _ in 0..2 {
34291      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34292    }
34293
34294    assert!(
34295      first.is_ok(),
34296      "the first configured Rayon worker did not start"
34297    );
34298    assert!(
34299      second.is_ok(),
34300      "a pending timer consumed one of the two configured Rayon workers"
34301    );
34302  }
34303
34304  #[cfg(napi_runtime_os_threads)]
34305  #[test]
34306  fn timekeeper_never_enters_a_blocking_closure() {
34307    use std::{
34308      sync::{Barrier, mpsc},
34309      time::Duration,
34310    };
34311
34312    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
34313    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34314    let waker = Waker::from(Arc::clone(&wake_count));
34315    let mut cx = Context::from_waker(&waker);
34316    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
34317    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34318    wait_until(
34319      "the timekeeper parked before blocking work was submitted",
34320      || {
34321        executor
34322          .timers
34323          .inner
34324          .lock()
34325          .unwrap()
34326          .timekeeper_parker
34327          .is_some()
34328      },
34329    );
34330
34331    let release = Arc::new(Barrier::new(2));
34332    let blocker_release = Arc::clone(&release);
34333    let (started_tx, started_rx) = mpsc::sync_channel(1);
34334    let blocking = executor.schedule_blocking(move || {
34335      started_tx.send(()).unwrap();
34336      blocker_release.wait();
34337    });
34338    // Hang detector (the closure start needs a worker to get CPU) --
34339    // generous for starved CI runners.
34340    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
34341
34342    wait_until(
34343      "the timer waker to fire while the Rayon worker is blocked",
34344      || wake_count.0.load(Ordering::SeqCst) != 0,
34345    );
34346    release.wait();
34347    futures::executor::block_on(blocking).unwrap();
34348    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34349  }
34350
34351  #[cfg(napi_runtime_os_threads)]
34352  #[test]
34353  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
34354    use std::sync::mpsc;
34355
34356    struct PendingEntered(Option<mpsc::Sender<()>>);
34357
34358    impl Future for PendingEntered {
34359      type Output = ();
34360
34361      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
34362        if let Some(entered) = self.0.take() {
34363          entered.send(()).unwrap();
34364        }
34365        Poll::Pending
34366      }
34367    }
34368
34369    let options = RuntimeOptions {
34370      flavor: RuntimeFlavor::MultiThread,
34371      worker_threads: 2,
34372      max_blocking_tasks: 1,
34373      thread_name_prefix: "nested-timer-wake".to_string(),
34374      park_deadline: None,
34375      drain_linger: DEFAULT_DRAIN_LINGER,
34376    };
34377    let executor =
34378      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
34379
34380    let timer_exec = Arc::clone(&executor);
34381    let (timer_tx, timer_rx) = mpsc::channel();
34382    let timer_scheduler = Arc::clone(&executor);
34383    let (timer_runnable, timer_task) = async_task::spawn(
34384      async move {
34385        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
34386        timer_tx.send(()).unwrap();
34387      },
34388      move |runnable| timer_scheduler.schedule(runnable),
34389    );
34390    executor.schedule(timer_runnable);
34391    wait_until(
34392      "the dedicated timekeeper parked before nested block_on",
34393      || {
34394        executor
34395          .timers
34396          .inner
34397          .lock()
34398          .unwrap()
34399          .timekeeper_parker
34400          .is_some()
34401      },
34402    );
34403
34404    let (entered_tx, entered_rx) = mpsc::channel();
34405    let nested_exec = Arc::clone(&executor);
34406    let nested_scheduler = Arc::clone(&executor);
34407    let (nested_runnable, nested_task) = async_task::spawn(
34408      async move {
34409        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
34410        let outcome = nested_exec.block_on(pending.as_mut());
34411        drop(pending);
34412        assert_eq!(
34413          outcome,
34414          BlockOnOutcome::Stopped,
34415          "shutdown must stop the raw nested block_on driver"
34416        );
34417      },
34418      move |runnable| nested_scheduler.schedule(runnable),
34419    );
34420    executor.schedule(nested_runnable);
34421    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34422
34423    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
34424    let (release_tx, release_rx) = mpsc::channel();
34425    let blocking = executor.schedule_blocking(move || {
34426      blocking_started_tx.send(()).unwrap();
34427      release_rx.recv().unwrap();
34428    });
34429    blocking_started_rx
34430      .recv_timeout(Duration::from_secs(2))
34431      .expect("a general worker must service blocking work");
34432    timer_rx
34433      .recv_timeout(Duration::from_secs(2))
34434      .expect("the timer wake must reach the nested cooperative driver");
34435
34436    release_tx.send(()).unwrap();
34437    futures::executor::block_on(blocking).unwrap();
34438    executor.begin_shutdown();
34439    assert_eq!(
34440      futures::executor::block_on(nested_task.fallible()),
34441      Some(())
34442    );
34443    futures::executor::block_on(timer_task);
34444  }
34445
34446  #[cfg(napi_runtime_os_threads)]
34447  #[test]
34448  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
34449    // Task requirement: a NEW earliest deadline (heap peek changes) must
34450    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
34451    // timekeeper far in the future; the 60ms timer must still fire on time.
34452    use std::sync::mpsc;
34453
34454    let (done_tx, done_rx) = mpsc::channel();
34455    let runner = std::thread::spawn(move || {
34456      let executor = multi_thread_executor(2, None, "timer-rearm");
34457
34458      let long_exec = Arc::clone(&executor);
34459      let long_done = Arc::new(AtomicBool::new(false));
34460      let long_flag = Arc::clone(&long_done);
34461      let sched = Arc::clone(&executor);
34462      let (long_runnable, long_task) = async_task::spawn(
34463        async move {
34464          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
34465          long_flag.store(true, Ordering::SeqCst);
34466        },
34467        move |r| sched.schedule(r),
34468      );
34469      executor.schedule(long_runnable);
34470      wait_until("the timekeeper parked on the 60s sentinel", || {
34471        executor
34472          .timers
34473          .inner
34474          .lock()
34475          .unwrap()
34476          .timekeeper_parker
34477          .is_some()
34478      });
34479
34480      let started = Instant::now();
34481      let mut future = std::pin::pin!(async {
34482        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
34483      });
34484      executor.block_on(future.as_mut());
34485      let elapsed = started.elapsed();
34486      assert!(
34487        elapsed >= Duration::from_millis(60),
34488        "fired before its deadline: {elapsed:?}"
34489      );
34490      assert!(
34491        !long_done.load(Ordering::SeqCst),
34492        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
34493         timekeeper, not by the sentinel expiring"
34494      );
34495      executor.shutdown_timers();
34496      futures::executor::block_on(long_task);
34497      let _ = done_tx.send(());
34498    });
34499
34500    match done_rx.recv_timeout(Duration::from_secs(15)) {
34501      Ok(()) => runner.join().unwrap(),
34502      Err(error) => panic!(
34503        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
34504      ),
34505    }
34506  }
34507
34508  #[cfg(napi_runtime_os_threads)]
34509  #[test]
34510  fn multi_thread_sleep_cancels_registration_on_drop() {
34511    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
34512    // its heap registration (no waker fired later, heap drains empty).
34513    let executor = multi_thread_executor(1, None, "timer-cancel");
34514    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
34515    let waker = Waker::from(Arc::clone(&wake_count));
34516    let mut cx = Context::from_waker(&waker);
34517
34518    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
34519    // starved runner (a past-due Sleep would resolve inline instead of
34520    // registering, failing the assert below for the wrong reason).
34521    let deadline = Instant::now() + Duration::from_millis(250);
34522    let mut sleep = heap_sleep(&executor, deadline);
34523    assert!(
34524      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
34525      "must register, not resolve"
34526    );
34527    assert!(
34528      executor.next_timer_deadline().is_some(),
34529      "the registration must be in the heap"
34530    );
34531    drop(sleep);
34532    assert!(
34533      executor.next_timer_deadline().is_none(),
34534      "cancel-on-drop must retire the heap registration"
34535    );
34536    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
34537    // cancelled timer must not wake. The negative assert is structural once
34538    // the heap is empty; waiting until the wall clock is genuinely past the
34539    // deadline keeps the ghost-fire probe honest.
34540    while Instant::now() < deadline + Duration::from_millis(25) {
34541      std::thread::sleep(Duration::from_millis(10));
34542    }
34543    executor.fire_due_timers();
34544    assert_eq!(
34545      wake_count.0.load(Ordering::SeqCst),
34546      0,
34547      "a dropped Sleep's waker must never fire"
34548    );
34549  }
34550
34551  #[cfg(napi_runtime_os_threads)]
34552  #[test]
34553  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
34554    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
34555    let waker = futures::task::noop_waker();
34556    let mut cx = Context::from_waker(&waker);
34557    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
34558
34559    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34560    wait_until(
34561      "the timekeeper parked on the timer that will be cancelled",
34562      || {
34563        executor
34564          .timers
34565          .inner
34566          .lock()
34567          .unwrap()
34568          .timekeeper_parker
34569          .is_some()
34570      },
34571    );
34572
34573    drop(sleep);
34574    wait_until(
34575      "the cancelled timer's timekeeper returned to its idle wait",
34576      || {
34577        executor.next_timer_deadline().is_none()
34578          && executor
34579            .timers
34580            .inner
34581            .lock()
34582            .unwrap()
34583            .timekeeper_parker
34584            .is_none()
34585      },
34586    );
34587  }
34588
34589  #[cfg(napi_runtime_os_threads)]
34590  #[test]
34591  fn timer_waker_replacement_drops_outside_heap_lock() {
34592    use std::sync::mpsc;
34593
34594    struct DropOtherTimer(Mutex<Option<Sleep>>);
34595
34596    #[expect(
34597      clippy::manual_noop_waker,
34598      reason = "the no-op wake type has a reentrant Drop used by this regression"
34599    )]
34600    impl std::task::Wake for DropOtherTimer {
34601      fn wake(self: Arc<Self>) {}
34602    }
34603
34604    impl Drop for DropOtherTimer {
34605      fn drop(&mut self) {
34606        drop(self.0.lock().unwrap().take());
34607      }
34608    }
34609
34610    let (done_tx, done_rx) = mpsc::channel();
34611    let runner = std::thread::spawn(move || {
34612      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
34613      let deadline = Instant::now() + Duration::from_secs(120);
34614      let mut other = heap_sleep(&executor, deadline);
34615      let noop = futures::task::noop_waker();
34616      let mut noop_cx = Context::from_waker(&noop);
34617      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
34618
34619      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
34620      let mut primary = heap_sleep(&executor, deadline);
34621      let mut old_cx = Context::from_waker(&old);
34622      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
34623      drop(old);
34624
34625      let replacement = futures::task::noop_waker();
34626      let mut replacement_cx = Context::from_waker(&replacement);
34627      assert!(
34628        Pin::new(&mut primary)
34629          .poll(&mut replacement_cx)
34630          .is_pending()
34631      );
34632      drop(primary);
34633      executor.shutdown_timers();
34634      done_tx.send(()).unwrap();
34635    });
34636    // Hang detector on a freshly spawned runner thread -- generous for
34637    // starved CI runners (a real regression deadlocks forever).
34638    done_rx
34639      .recv_timeout(Duration::from_secs(10))
34640      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
34641    runner.join().unwrap();
34642  }
34643
34644  #[cfg(napi_runtime_os_threads)]
34645  #[test]
34646  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
34647    use std::sync::mpsc;
34648
34649    struct DropFinalExecutorOnWake {
34650      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
34651      completed: mpsc::Sender<()>,
34652    }
34653
34654    impl std::task::Wake for DropFinalExecutorOnWake {
34655      fn wake(self: Arc<Self>) {
34656        let executor = self
34657          .executor
34658          .lock()
34659          .unwrap_or_else(std::sync::PoisonError::into_inner)
34660          .take()
34661          .expect("the timer waker must own the final executor reference");
34662        assert_eq!(
34663          ON_TIMER_THREAD.with(std::cell::Cell::get),
34664          Some(executor.id),
34665          "the dedicated timer thread must deliver the timer wake"
34666        );
34667        drop(executor);
34668        self
34669          .completed
34670          .send(())
34671          .expect("the test receiver must outlive the timer-thread callback");
34672      }
34673    }
34674
34675    let executor = multi_thread_executor(1, None, "timer-final-arc");
34676    let (completed_tx, completed_rx) = mpsc::channel();
34677    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
34678    {
34679      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
34680        executor: Mutex::new(Some(Arc::clone(&executor))),
34681        completed: completed_tx,
34682      }));
34683      let mut cx = Context::from_waker(&waker);
34684      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34685    }
34686    wait_until(
34687      "the timer thread parked before owning the final executor Arc",
34688      || {
34689        executor
34690          .timers
34691          .inner
34692          .lock()
34693          .unwrap()
34694          .timekeeper_parker
34695          .is_some()
34696      },
34697    );
34698
34699    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
34700    // heap waker owns the final direct executor Arc and releases it from the
34701    // timer thread while `TimerHeap::drop` still owns that thread's handle.
34702    drop(executor);
34703    // Hang detector: the fire needs the (already spawned) timer thread to
34704    // reach its 80ms deadline -- generous for starved CI runners.
34705    completed_rx
34706      .recv_timeout(Duration::from_secs(10))
34707      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
34708  }
34709
34710  #[cfg(not(target_family = "wasm"))]
34711  #[test]
34712  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
34713    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
34714    // pending wakers -- a `watcher.close()` awaiting a
34715    // debounce sleep must complete, not hang. Also pins fire-on-register for
34716    // a Sleep polled after shutdown.
34717    use std::sync::mpsc;
34718
34719    let (done_tx, done_rx) = mpsc::channel();
34720    let runner = std::thread::spawn(move || {
34721      let controller = Arc::new(RuntimeController::new());
34722      // Default options: MultiThread on native.
34723      let backend = controller.backend();
34724      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
34725        panic!("default native backend must be MultiThread");
34726      };
34727      let executor = Arc::downgrade(executor);
34728      let mut late = heap_sleep(
34729        &executor
34730          .upgrade()
34731          .expect("the initial executor must be live"),
34732        Instant::now() + Duration::from_secs(120),
34733      );
34734      drop(backend);
34735
34736      let (parked_tx, parked_rx) = mpsc::channel();
34737      let block_controller = Arc::clone(&controller);
34738      let block_exec = Weak::clone(&executor);
34739      let blocker = std::thread::spawn(move || {
34740        let mut future = std::pin::pin!(async {
34741          let executor = block_exec
34742            .upgrade()
34743            .expect("the runtime must remain live while blocking");
34744          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
34745          drop(executor);
34746          parked_tx.send(()).unwrap();
34747          sleep.await;
34748        });
34749        block_controller.block_on(future.as_mut());
34750      });
34751      parked_rx.recv().unwrap();
34752      wait_until("the 120s sleep registered in the heap", || {
34753        executor
34754          .upgrade()
34755          .is_some_and(|executor| executor.next_timer_deadline().is_some())
34756      });
34757
34758      // The acceptance surface: controller shutdown while a sleep is armed.
34759      controller
34760        .shutdown()
34761        .expect("runtime shutdown must succeed");
34762      blocker
34763        .join()
34764        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
34765
34766      // A Sleep polled AFTER shutdown resolves immediately instead of
34767      // registering with a dead heap.
34768      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
34769      let mut cx = Context::from_waker(&waker);
34770      assert!(
34771        Pin::new(&mut late).poll(&mut cx).is_ready(),
34772        "a Sleep polled after shutdown must fire early, never park a closing runtime"
34773      );
34774      let _ = done_tx.send(());
34775    });
34776
34777    match done_rx.recv_timeout(Duration::from_secs(10)) {
34778      Ok(()) => runner.join().unwrap(),
34779      Err(error) => {
34780        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
34781      }
34782    }
34783  }
34784
34785  #[cfg(napi_runtime_os_threads)]
34786  #[test]
34787  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
34788    // Timer-wait/deadline invariant: the park deadline is armed
34789    // SHORTER than the next timer deadline. The cooperative driver awaiting
34790    // the sleep sits with ZERO
34791    // executor progress for several full deadline windows -- a timer wait is
34792    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
34793    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
34794    // also allows the parked driver to race the dedicated timekeeper and fire
34795    // the timer itself, pinning the cooperative timer-bounded park.
34796    use std::sync::mpsc;
34797
34798    let (done_tx, done_rx) = mpsc::channel();
34799    let runner = std::thread::spawn(move || {
34800      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
34801
34802      let task_exec = Arc::clone(&executor);
34803      let body = async move {
34804        let started = Instant::now();
34805        let mut inner = std::pin::pin!(async {
34806          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
34807        });
34808        // Re-entrant block_on from the pool worker: the cooperative park.
34809        task_exec.block_on(inner.as_mut());
34810        assert!(started.elapsed() >= Duration::from_millis(250));
34811      };
34812      let sched = Arc::clone(&executor);
34813      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
34814      executor.schedule(runnable);
34815
34816      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
34817      // by run_runnable's catch_unwind), so the task would never complete and
34818      // the bounded harness below would report the failure.
34819      futures::executor::block_on(task);
34820      // Teardown inside the bounded window: this test was one of the silent
34821      // wedges on the Windows CI runners with every assertion already
34822      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
34823      // (a timer was polled, so the timekeeper thread is live) -- exactly
34824      // the kind of phase that must fail loudly rather than hang.
34825      drop(executor);
34826      let _ = done_tx.send(());
34827    });
34828
34829    match done_rx.recv_timeout(Duration::from_secs(10)) {
34830      Ok(()) => join_within(
34831        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
34832        runner,
34833        Duration::from_secs(30),
34834      ),
34835      Err(error) => {
34836        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
34837      }
34838    }
34839  }
34840
34841  #[cfg(napi_runtime_os_threads)]
34842  #[test]
34843  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
34844    // Timer-registration/verdict race, deterministic via the verdict hook: a
34845    // timer registered AFTER a deadline park was committed bumps no progress
34846    // counter by design, lands after the permit and queued-work re-checks, and
34847    // is invisible to the fingerprint. The heap re-check must recognize that
34848    // guaranteed future wake, veto the panic, and let the driver re-park
34849    // timer-bounded until the fire's task-wake completes the future.
34850    use std::sync::mpsc;
34851
34852    // A raw Sleep the hook registers is woken by this waker, which releases
34853    // the awaited future's gate.
34854    struct GateWake {
34855      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
34856    }
34857    impl std::task::Wake for GateWake {
34858      fn wake(self: Arc<Self>) {
34859        let gate = self.gate.lock().unwrap().take();
34860        if let Some(gate) = gate {
34861          let _ = gate.send(23);
34862        }
34863      }
34864    }
34865
34866    let (done_tx, done_rx) = mpsc::channel();
34867    let runner = std::thread::spawn(move || {
34868      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
34869      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
34870      // replacement drainer behind our back; only the verdict's heap re-check
34871      // can save this park.
34872      executor
34873        .active_drainers
34874        .store(executor.max_drainers, Ordering::SeqCst);
34875
34876      // Completion condition: the timer registered inside the hook fires and
34877      // its waker (the gate) releases the awaited future.
34878      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
34879      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
34880
34881      let hook_exec = Arc::clone(&executor);
34882      let hook_gate = Arc::clone(&gate_tx);
34883      // The Sleep must outlive the hook (drop cancels): park it in a slot the
34884      // runner thread later cleans up.
34885      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
34886      let hook_sleep_slot = Arc::clone(&parked_sleep);
34887      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
34888        *slot.borrow_mut() = Some(Box::new(move || {
34889          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
34890          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
34891          let mut cx = Context::from_waker(&waker);
34892          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34893          *hook_sleep_slot.lock().unwrap() = Some(sleep);
34894        }));
34895      });
34896
34897      // The test-only on-pool marker runs the cooperative branch and its
34898      // thread-local hook on THIS thread.
34899      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
34900      let mut output = None;
34901      {
34902        let mut future = std::pin::pin!(async {
34903          output = Some(gate_rx.await.unwrap());
34904        });
34905        executor.block_on(future.as_mut());
34906      }
34907      // Clear the hook slot BEFORE any assert can panic: this hook captures
34908      // an executor Arc and THIS executor's timekeeper is live (a timer was
34909      // polled), so a leftover hook dropped by a TLS destructor would join
34910      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
34911      // RULE at `WorkerTlsDropAction`).
34912      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
34913      assert_eq!(
34914        output,
34915        Some(23),
34916        "the edge-registered timer must have fired and released the gate"
34917      );
34918      executor.active_drainers.store(0, Ordering::SeqCst);
34919      // Teardown inside the bounded window; includes the timekeeper join
34920      // (a timer was polled). See `join_within`.
34921      drop(executor);
34922      let _ = done_tx.send(());
34923    });
34924
34925    match done_rx.recv_timeout(Duration::from_secs(10)) {
34926      Ok(()) => join_within(
34927        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
34928        runner,
34929        Duration::from_secs(30),
34930      ),
34931      Err(error) => panic!(
34932        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
34933      ),
34934    }
34935  }
34936
34937  #[cfg(panic = "unwind")]
34938  #[test]
34939  fn current_thread_sleep_without_host_driver_fails_loud() {
34940    // Missing-driver invariant: a CT runtime without a registered host driver
34941    // must fail LOUD with this exact diagnostic -- never a silent never-firing
34942    // debounce or a misleading certain-deadlock panic later.
34943    let metrics = Arc::new(RuntimeMetrics::default());
34944    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34945    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
34946
34947    let payload = catch_unwind(AssertUnwindSafe(|| {
34948      let _sleep = make_sleep(
34949        &backend,
34950        &Arc::new(TimerDriverRegistry::default()),
34951        Instant::now() + Duration::from_millis(10),
34952      );
34953    }))
34954    .expect_err("sleep_until on CurrentThread without a driver must panic");
34955    let message = payload
34956      .downcast_ref::<String>()
34957      .cloned()
34958      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
34959      .expect("panic payload must be a message");
34960    assert!(
34961      message.contains("CurrentThread runtime has no live timer driver registered"),
34962      "the diagnostic must name the missing driver, got: {message}"
34963    );
34964  }
34965
34966  #[test]
34967  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
34968    // Registry semantics: all-live snapshots with newest-live compatibility
34969    // selection, dead entries swept on contact, explicit unregister, and a
34970    // dead-only registry reading as NO driver.
34971    let registry = TimerDriverRegistry::default();
34972    let a = ManualStubDriver::new();
34973    let b = ManualStubDriver::new();
34974    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
34975    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
34976    assert_ne!(a_id, b_id, "registration handles must be distinct");
34977    assert_eq!(
34978      registry.current().map(|(id, _)| id),
34979      Some(b_id),
34980      "newest live registrant serves"
34981    );
34982    assert!(registry.has_live_driver());
34983
34984    // A dead newest (stub for: its owning env torn down) is skipped AND
34985    // swept; selection falls back to the older live registrant.
34986    b.kill();
34987    assert_eq!(
34988      registry.current().map(|(id, _)| id),
34989      Some(a_id),
34990      "dead newest must be skipped"
34991    );
34992    // The sweep is permanent: resurrecting the flag must not bring the
34993    // evicted registration back.
34994    b.live.store(true, Ordering::SeqCst);
34995    assert_eq!(
34996      registry.current().map(|(id, _)| id),
34997      Some(a_id),
34998      "swept entries stay gone"
34999    );
35000
35001    // Explicit unregister (the binding's env-cleanup path) empties the
35002    // registry; empty and dead-only both read as NO live driver.
35003    registry.unregister(a_id);
35004    assert!(registry.current().is_none());
35005    assert!(
35006      !registry.has_live_driver(),
35007      "an emptied registry must not report a live driver"
35008    );
35009  }
35010
35011  #[cfg(panic = "unwind")]
35012  #[test]
35013  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
35014    let registry = TimerDriverRegistry::default();
35015    let fallback = ManualStubDriver::new();
35016    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35017    let panicking = Arc::new(PanickingTimerDriver {
35018      swept: AtomicBool::new(false),
35019    });
35020    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35021
35022    assert_eq!(
35023      registry.current().map(|(id, _)| id),
35024      Some(fallback_id),
35025      "a panicking liveness callback must be treated as a dead driver"
35026    );
35027    assert!(
35028      panicking.swept.load(Ordering::SeqCst),
35029      "a panicking liveness callback must still run its contained sweep hook"
35030    );
35031    assert!(
35032      registry.has_live_driver(),
35033      "the healthy fallback must remain selectable"
35034    );
35035  }
35036
35037  #[cfg(napi_runtime_os_threads)]
35038  #[test]
35039  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
35040    use std::{sync::mpsc, time::Duration};
35041
35042    let registry = Arc::new(TimerDriverRegistry::default());
35043    let fallback = ManualStubDriver::new();
35044    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35045    let reentrant = Arc::new(ReentrantLivenessDriver {
35046      registry: Arc::downgrade(&registry),
35047      registration: Mutex::new(None),
35048    });
35049    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35050    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
35051
35052    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
35053    let selection_registry = Arc::clone(&registry);
35054    let selection_thread = std::thread::spawn(move || {
35055      selection_tx
35056        .send(selection_registry.current().map(|(id, _)| id))
35057        .unwrap();
35058    });
35059    assert_eq!(
35060      selection_rx
35061        .recv_timeout(Duration::from_secs(10))
35062        .expect("is_live re-entry deadlocked"),
35063      Some(fallback_id)
35064    );
35065    selection_thread.join().unwrap();
35066
35067    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
35068      registry: Arc::downgrade(&registry),
35069      fallback: fallback_id,
35070    });
35071    let drop_id = registry.register(Arc::clone(&drop_driver));
35072    drop(drop_driver);
35073
35074    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
35075    let drop_registry = Arc::clone(&registry);
35076    let drop_thread = std::thread::spawn(move || {
35077      drop_registry.unregister(drop_id);
35078      drop_tx.send(()).unwrap();
35079    });
35080    drop_rx
35081      .recv_timeout(Duration::from_secs(10))
35082      .expect("driver destructor re-entry deadlocked");
35083    drop_thread.join().unwrap();
35084    assert!(
35085      registry.current().is_none(),
35086      "the destructor must be able to unregister the fallback"
35087    );
35088  }
35089
35090  #[test]
35091  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
35092    const DRIVER_PAIRS: usize = 2_048;
35093
35094    let registry = Arc::new(TimerDriverRegistry::default());
35095    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
35096    let mut live = Vec::with_capacity(DRIVER_PAIRS);
35097    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
35098    for _ in 0..DRIVER_PAIRS {
35099      let driver = Arc::new(ReentrantSweepTimerDriver {
35100        registry: Arc::downgrade(&registry),
35101        registration: Mutex::new(None),
35102        sweeps: AtomicUsize::new(0),
35103        removed_before_sweep: AtomicBool::new(false),
35104      });
35105      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35106      *driver.registration.lock().unwrap() = Some(id);
35107      dead.push(driver);
35108
35109      let driver = ManualStubDriver::new();
35110      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
35111      live.push(driver);
35112    }
35113    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
35114      registry: Arc::downgrade(&registry),
35115      registration: Mutex::new(None),
35116      sweeps: AtomicUsize::new(0),
35117      removed_before_sweep: AtomicBool::new(false),
35118    });
35119    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
35120    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
35121    dead.push(dead_tail);
35122
35123    let selection = registry.live();
35124    assert_eq!(
35125      selection
35126        .as_slice()
35127        .iter()
35128        .map(|(id, _)| *id)
35129        .collect::<Vec<_>>(),
35130      live_ids,
35131      "sweeping every interleaved dead host must retain live registration order"
35132    );
35133    drop(selection);
35134    assert!(
35135      dead.iter().all(|driver| {
35136        driver.sweeps.load(Ordering::SeqCst) == 1
35137          && driver.removed_before_sweep.load(Ordering::SeqCst)
35138          && driver.registration.lock().unwrap().is_none()
35139      }),
35140      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
35141    );
35142
35143    {
35144      let state = registry
35145        .state
35146        .lock()
35147        .unwrap_or_else(std::sync::PoisonError::into_inner);
35148      assert_eq!(state.entries.len(), DRIVER_PAIRS);
35149      assert_eq!(state.oldest, live_ids.first().copied());
35150      assert_eq!(state.newest, live_ids.last().copied());
35151      assert_eq!(
35152        state
35153          .ordered_snapshot()
35154          .iter()
35155          .map(|(id, _)| *id)
35156          .collect::<Vec<_>>(),
35157        live_ids,
35158        "keyed removals must preserve the linked order of every live host"
35159      );
35160    }
35161    let second_selection = registry.live();
35162    assert_eq!(
35163      second_selection
35164        .as_slice()
35165        .iter()
35166        .map(|(id, _)| *id)
35167        .collect::<Vec<_>>(),
35168      live_ids,
35169      "a second selection must retain the same live order"
35170    );
35171    drop(second_selection);
35172    assert!(
35173      dead
35174        .iter()
35175        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
35176      "permanently swept hosts must not receive repeated eviction callbacks"
35177    );
35178    assert_eq!(
35179      registry.current().map(|(id, _)| id),
35180      live_ids.last().copied(),
35181      "newest-live compatibility selection must survive the mass sweep"
35182    );
35183
35184    drop(live);
35185    drop(dead);
35186  }
35187
35188  #[cfg(not(target_family = "wasm"))]
35189  #[test]
35190  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
35191    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
35192
35193    if std::env::var_os(CHILD_ENV).is_some() {
35194      let registry = Arc::new(TimerDriverRegistry::default());
35195      let fallback = ManualStubDriver::new();
35196      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
35197      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
35198        registry: Arc::downgrade(&registry),
35199        registrations: Mutex::new(Vec::new()),
35200      });
35201      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
35202
35203      let first_cancels = Arc::new(AtomicUsize::new(0));
35204      let first_drops = Arc::new(AtomicUsize::new(0));
35205      let first = Arc::new(PanickingCancelDropTimerDriver {
35206        cancels: Arc::clone(&first_cancels),
35207        drops: Arc::clone(&first_drops),
35208      });
35209      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35210      drop(first);
35211
35212      let second_cancels = Arc::new(AtomicUsize::new(0));
35213      let second_drops = Arc::new(AtomicUsize::new(0));
35214      let second = Arc::new(PanickingCancelDropTimerDriver {
35215        cancels: Arc::clone(&second_cancels),
35216        drops: Arc::clone(&second_drops),
35217      });
35218      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35219      drop(second);
35220      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
35221
35222      assert_eq!(
35223        registry.current().map(|(id, _)| id),
35224        Some(fallback_id),
35225        "selection must retry after reentrant removals and preserve the healthy fallback"
35226      );
35227      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35228      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35229      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35230      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35231      assert_eq!(
35232        registry
35233          .state
35234          .lock()
35235          .unwrap_or_else(std::sync::PoisonError::into_inner)
35236          .entries
35237          .len(),
35238        1
35239      );
35240      return;
35241    }
35242
35243    let output = std::process::Command::new(std::env::current_exe().unwrap())
35244      .arg("--exact")
35245      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
35246      .arg("--nocapture")
35247      .env(CHILD_ENV, "1")
35248      .output()
35249      .expect("the hostile timer-driver snapshot subprocess test must start");
35250    assert!(
35251      output.status.success(),
35252      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35253      output.status.code(),
35254      String::from_utf8_lossy(&output.stdout),
35255      String::from_utf8_lossy(&output.stderr)
35256    );
35257  }
35258
35259  #[cfg(not(target_family = "wasm"))]
35260  #[test]
35261  fn timer_driver_registry_live_result_contains_each_driver_drop() {
35262    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
35263
35264    if std::env::var_os(CHILD_ENV).is_some() {
35265      let registry = TimerDriverRegistry::default();
35266
35267      let first_cancels = Arc::new(AtomicUsize::new(0));
35268      let first_drops = Arc::new(AtomicUsize::new(0));
35269      let first = Arc::new(PanickingCancelDropTimerDriver {
35270        cancels: Arc::clone(&first_cancels),
35271        drops: Arc::clone(&first_drops),
35272      });
35273      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35274      drop(first);
35275
35276      let second_cancels = Arc::new(AtomicUsize::new(0));
35277      let second_drops = Arc::new(AtomicUsize::new(0));
35278      let second = Arc::new(PanickingCancelDropTimerDriver {
35279        cancels: Arc::clone(&second_cancels),
35280        drops: Arc::clone(&second_drops),
35281      });
35282      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35283      drop(second);
35284
35285      let live = registry.live();
35286      assert_eq!(live.len(), 2);
35287      registry.unregister(first_id);
35288      registry.unregister(second_id);
35289      drop(live);
35290
35291      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
35292      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
35293      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35294      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35295      assert!(
35296        registry
35297          .state
35298          .lock()
35299          .unwrap_or_else(std::sync::PoisonError::into_inner)
35300          .entries
35301          .is_empty()
35302      );
35303      return;
35304    }
35305
35306    let output = std::process::Command::new(std::env::current_exe().unwrap())
35307      .arg("--exact")
35308      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
35309      .arg("--nocapture")
35310      .env(CHILD_ENV, "1")
35311      .output()
35312      .expect("the hostile live timer-driver snapshot subprocess test must start");
35313    assert!(
35314      output.status.success(),
35315      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
35316      output.status.code(),
35317      String::from_utf8_lossy(&output.stdout),
35318      String::from_utf8_lossy(&output.stderr)
35319    );
35320  }
35321
35322  #[cfg(not(target_family = "wasm"))]
35323  #[test]
35324  fn timer_driver_registry_drop_contains_each_driver_destructor() {
35325    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
35326
35327    if std::env::var_os(CHILD_ENV).is_some() {
35328      let first_drops = Arc::new(AtomicUsize::new(0));
35329      let second_drops = Arc::new(AtomicUsize::new(0));
35330      let registry = TimerDriverRegistry::default();
35331      let first = Arc::new(PanickingCancelDropTimerDriver {
35332        cancels: Arc::new(AtomicUsize::new(0)),
35333        drops: Arc::clone(&first_drops),
35334      });
35335      let second = Arc::new(PanickingCancelDropTimerDriver {
35336        cancels: Arc::new(AtomicUsize::new(0)),
35337        drops: Arc::clone(&second_drops),
35338      });
35339      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
35340      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
35341      drop(first);
35342      drop(second);
35343
35344      drop(registry);
35345
35346      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
35347      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
35348      return;
35349    }
35350
35351    let output = std::process::Command::new(std::env::current_exe().unwrap())
35352      .arg("--exact")
35353      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
35354      .arg("--nocapture")
35355      .env(CHILD_ENV, "1")
35356      .output()
35357      .expect("the hostile timer-driver registry drop subprocess test must start");
35358    assert!(
35359      output.status.success(),
35360      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
35361      output.status.code(),
35362      String::from_utf8_lossy(&output.stdout),
35363      String::from_utf8_lossy(&output.stderr)
35364    );
35365  }
35366
35367  #[test]
35368  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
35369    const DRIVERS: usize = 512;
35370
35371    let metrics = Arc::new(RuntimeMetrics::default());
35372    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35373    let backend =
35374      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35375    let registry = Arc::new(TimerDriverRegistry::default());
35376    let drivers = (0..DRIVERS)
35377      .map(|_| {
35378        let driver = ManualStubDriver::new();
35379        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35380        driver
35381      })
35382      .collect::<Vec<_>>();
35383    let mut sleep = make_sleep(
35384      &backend,
35385      &registry,
35386      Instant::now() + Duration::from_secs(60),
35387    );
35388    let mut cx = Context::from_waker(Waker::noop());
35389
35390    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35391    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35392    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
35393    match &sleep.inner {
35394      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
35395      #[cfg(napi_runtime_os_threads)]
35396      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35397    }
35398    for driver in &drivers {
35399      assert_eq!(
35400        driver.registers.lock().unwrap().len(),
35401        2,
35402        "a re-poll must refresh each live driver exactly once"
35403      );
35404    }
35405
35406    for driver in drivers.iter().step_by(2) {
35407      driver.kill();
35408    }
35409    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35410    match &sleep.inner {
35411      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
35412      #[cfg(napi_runtime_os_threads)]
35413      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
35414    }
35415    for (index, driver) in drivers.iter().enumerate() {
35416      let registers = driver.registers.lock().unwrap().len();
35417      let cancels = driver.cancels.lock().unwrap().clone();
35418      if index % 2 == 0 {
35419        assert_eq!(registers, 2);
35420        assert_eq!(cancels, vec![timer_id]);
35421      } else {
35422        assert_eq!(registers, 3);
35423        assert!(cancels.is_empty());
35424      }
35425    }
35426
35427    drop(sleep);
35428    for driver in drivers {
35429      assert_eq!(
35430        driver.cancels.lock().unwrap().as_slice(),
35431        &[timer_id],
35432        "every physical arm must be cancelled exactly once"
35433      );
35434    }
35435    assert_eq!(registry.pending_len(), 0);
35436    assert!(!executor.host_timers.has_pending());
35437  }
35438
35439  #[cfg(panic = "unwind")]
35440  #[test]
35441  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
35442    let metrics = Arc::new(RuntimeMetrics::default());
35443    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35444    let backend =
35445      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35446    let healthy = ManualStubDriver::new();
35447    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35448    let deadline = Instant::now() + Duration::from_secs(60);
35449    let mut sleep = make_sleep(&backend, &registry, deadline);
35450    let mut cx = Context::from_waker(Waker::noop());
35451
35452    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35453    let timer_id = healthy.registers.lock().unwrap()[0].0;
35454
35455    let panicking = PanickingRegisterTimerDriver::new();
35456    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
35457    let result = catch_unwind(AssertUnwindSafe(|| {
35458      let _ = Pin::new(&mut sleep).poll(&mut cx);
35459    }));
35460    assert!(
35461      result.is_err(),
35462      "the hostile register panic must retain its normal propagation"
35463    );
35464    assert_eq!(
35465      healthy.registers.lock().unwrap().len(),
35466      2,
35467      "the previously armed driver must have completed its refresh before the later panic"
35468    );
35469    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
35470    assert!(
35471      panicking.pending.lock().unwrap().contains_key(&timer_id),
35472      "the hostile driver must model an arm committed before it panics"
35473    );
35474
35475    drop(sleep);
35476    assert_eq!(
35477      healthy.cancels.lock().unwrap().as_slice(),
35478      &[timer_id],
35479      "unwind must preserve cancellation ownership for the previously armed driver"
35480    );
35481    assert_eq!(
35482      panicking.cancels.lock().unwrap().as_slice(),
35483      &[timer_id],
35484      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
35485    );
35486    assert!(panicking.pending.lock().unwrap().is_empty());
35487    assert_eq!(registry.pending_len(), 0);
35488    assert!(!executor.host_timers.has_pending());
35489  }
35490
35491  #[cfg(not(target_family = "wasm"))]
35492  #[test]
35493  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
35494    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
35495
35496    if std::env::var_os(CHILD_ENV).is_some() {
35497      let metrics = Arc::new(RuntimeMetrics::default());
35498      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35499      let backend =
35500        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35501      let registry = Arc::new(TimerDriverRegistry::default());
35502      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
35503        registry: Arc::downgrade(&registry),
35504        later: Mutex::new(None),
35505        registers: AtomicUsize::new(0),
35506      });
35507      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
35508
35509      let later_cancels = Arc::new(AtomicUsize::new(0));
35510      let later_drops = Arc::new(AtomicUsize::new(0));
35511      let later = Arc::new(PanickingCancelDropTimerDriver {
35512        cancels: Arc::clone(&later_cancels),
35513        drops: Arc::clone(&later_drops),
35514      });
35515      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
35516      *earlier.later.lock().unwrap() = Some(later_id);
35517      drop(later);
35518
35519      let mut sleep = make_sleep(
35520        &backend,
35521        &registry,
35522        Instant::now() + Duration::from_secs(60),
35523      );
35524      let mut cx = Context::from_waker(Waker::noop());
35525      let result = catch_unwind(AssertUnwindSafe(|| {
35526        let _ = Pin::new(&mut sleep).poll(&mut cx);
35527      }));
35528
35529      assert!(
35530        result.is_err(),
35531        "the earlier driver's register panic must still propagate"
35532      );
35533      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
35534      assert_eq!(
35535        later_drops.load(Ordering::SeqCst),
35536        0,
35537        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
35538      );
35539
35540      drop(sleep);
35541      assert_eq!(
35542        later_cancels.load(Ordering::SeqCst),
35543        1,
35544        "the later driver must remain owned for cancellation despite its registry removal"
35545      );
35546      assert_eq!(
35547        later_drops.load(Ordering::SeqCst),
35548        1,
35549        "the later driver's final destructor must run once behind containment"
35550      );
35551      assert_eq!(registry.pending_len(), 0);
35552      assert!(!executor.host_timers.has_pending());
35553      return;
35554    }
35555
35556    let output = std::process::Command::new(std::env::current_exe().unwrap())
35557      .arg("--exact")
35558      .arg(
35559        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
35560      )
35561      .arg("--nocapture")
35562      .env(CHILD_ENV, "1")
35563      .output()
35564      .expect("the hostile host timer snapshot subprocess test must start");
35565    assert!(
35566      output.status.success(),
35567      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
35568      output.status.code(),
35569      String::from_utf8_lossy(&output.stdout),
35570      String::from_utf8_lossy(&output.stderr)
35571    );
35572  }
35573
35574  #[cfg(not(target_family = "wasm"))]
35575  #[test]
35576  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
35577    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
35578
35579    if std::env::var_os(CHILD_ENV).is_some() {
35580      let metrics = Arc::new(RuntimeMetrics::default());
35581      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35582      let backend =
35583        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35584      let cancels = Arc::new(AtomicUsize::new(0));
35585      let drops = Arc::new(AtomicUsize::new(0));
35586      let hostile = Arc::new(PanickingCancelDropTimerDriver {
35587        cancels: Arc::clone(&cancels),
35588        drops: Arc::clone(&drops),
35589      });
35590      let healthy = ManualStubDriver::new();
35591      let registry = Arc::new(TimerDriverRegistry::default());
35592      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
35593      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
35594      let mut sleep = make_sleep(
35595        &backend,
35596        &registry,
35597        Instant::now() + Duration::from_secs(60),
35598      );
35599      let mut cx = Context::from_waker(Waker::noop());
35600
35601      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35602      let timer_id = healthy.registers.lock().unwrap()[0].0;
35603
35604      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
35605      // independently contain both its cancel panic and its destructor panic.
35606      registry.unregister(hostile_id);
35607      drop(hostile);
35608      drop(sleep);
35609      assert_eq!(cancels.load(Ordering::SeqCst), 1);
35610      assert_eq!(drops.load(Ordering::SeqCst), 1);
35611      assert_eq!(
35612        healthy.cancels.lock().unwrap().as_slice(),
35613        &[timer_id],
35614        "one hostile cancellation must not skip later armed drivers"
35615      );
35616      assert_eq!(registry.pending_len(), 0);
35617      assert!(!executor.host_timers.has_pending());
35618      return;
35619    }
35620
35621    let output = std::process::Command::new(std::env::current_exe().unwrap())
35622      .arg("--exact")
35623      .arg(
35624        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
35625      )
35626      .arg("--nocapture")
35627      .env(CHILD_ENV, "1")
35628      .output()
35629      .expect("the hostile timer-driver cancellation subprocess test must start");
35630    assert!(
35631      output.status.success(),
35632      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
35633      output.status.code(),
35634      String::from_utf8_lossy(&output.stdout),
35635      String::from_utf8_lossy(&output.stderr)
35636    );
35637  }
35638
35639  #[cfg(not(target_family = "wasm"))]
35640  #[test]
35641  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
35642    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
35643
35644    if std::env::var_os(CHILD_ENV).is_some() {
35645      let metrics = Arc::new(RuntimeMetrics::default());
35646      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35647      let backend =
35648        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35649      let driver = ManualStubDriver::new();
35650      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35651      let state = Arc::new(TestHostileRawWakerState {
35652        clones: AtomicUsize::new(0),
35653        drop_panics: AtomicUsize::new(0),
35654        panic_on_clone: Some(2),
35655      });
35656
35657      let result = catch_unwind(AssertUnwindSafe(|| {
35658        let mut sleep = make_sleep(
35659          &backend,
35660          &registry,
35661          Instant::now() + Duration::from_secs(60),
35662        );
35663        let waker = test_hostile_waker(Arc::clone(&state));
35664        let mut cx = Context::from_waker(&waker);
35665        let _ = Pin::new(&mut sleep).poll(&mut cx);
35666      }));
35667      assert!(
35668        result.is_err(),
35669        "the original RawWaker clone panic must still propagate"
35670      );
35671      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
35672      assert_eq!(
35673        state.drop_panics.load(Ordering::SeqCst),
35674        1,
35675        "the first clone must be destroyed once behind a contained boundary"
35676      );
35677      assert!(driver.registers.lock().unwrap().is_empty());
35678      assert_eq!(registry.pending_len(), 0);
35679      assert!(!executor.host_timers.has_pending());
35680      return;
35681    }
35682
35683    let output = std::process::Command::new(std::env::current_exe().unwrap())
35684      .arg("--exact")
35685      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
35686      .arg("--nocapture")
35687      .env(CHILD_ENV, "1")
35688      .output()
35689      .expect("the hostile RawWaker subprocess test must start");
35690    assert!(
35691      output.status.success(),
35692      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
35693      output.status.code(),
35694      String::from_utf8_lossy(&output.stdout),
35695      String::from_utf8_lossy(&output.stderr)
35696    );
35697  }
35698
35699  #[cfg(not(target_family = "wasm"))]
35700  #[test]
35701  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
35702    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
35703
35704    if std::env::var_os(CHILD_ENV).is_some() {
35705      let metrics = Arc::new(RuntimeMetrics::default());
35706      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35707      let backend =
35708        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35709      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
35710        registers: AtomicUsize::new(0),
35711      });
35712      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
35713      let state = Arc::new(TestHostileRawWakerState {
35714        clones: AtomicUsize::new(0),
35715        drop_panics: AtomicUsize::new(0),
35716        panic_on_clone: None,
35717      });
35718      let mut sleep = make_sleep(
35719        &backend,
35720        &registry,
35721        Instant::now() + Duration::from_secs(60),
35722      );
35723      let waker = test_hostile_waker(Arc::clone(&state));
35724      let mut cx = Context::from_waker(&waker);
35725
35726      let result = catch_unwind(AssertUnwindSafe(|| {
35727        let _ = Pin::new(&mut sleep).poll(&mut cx);
35728      }));
35729      assert!(
35730        result.is_err(),
35731        "the timer-driver register panic must still propagate"
35732      );
35733      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
35734      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
35735      assert_eq!(
35736        state.drop_panics.load(Ordering::SeqCst),
35737        1,
35738        "the unretained register argument must be destroyed once behind the proxy boundary"
35739      );
35740      assert_eq!(registry.pending_len(), 1);
35741      assert!(executor.host_timers.has_pending());
35742
35743      drop(sleep);
35744      assert_eq!(
35745        state.drop_panics.load(Ordering::SeqCst),
35746        3,
35747        "repoll and registry wakers must retain their existing contained cleanup"
35748      );
35749      assert_eq!(registry.pending_len(), 0);
35750      assert!(!executor.host_timers.has_pending());
35751      return;
35752    }
35753
35754    let output = std::process::Command::new(std::env::current_exe().unwrap())
35755      .arg("--exact")
35756      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
35757      .arg("--nocapture")
35758      .env(CHILD_ENV, "1")
35759      .output()
35760      .expect("the hostile register-waker subprocess test must start");
35761    assert!(
35762      output.status.success(),
35763      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
35764      output.status.code(),
35765      String::from_utf8_lossy(&output.stdout),
35766      String::from_utf8_lossy(&output.stderr)
35767    );
35768  }
35769
35770  #[test]
35771  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
35772    // A sleep armed on a driver whose host dies must keep every remaining live
35773    // registrant armed with its remaining time preserved
35774    // (absolute deadline), keep the executor's wake-token accounting at
35775    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
35776    // armed on when dropped.
35777    let metrics = Arc::new(RuntimeMetrics::default());
35778    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35779    let backend =
35780      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35781
35782    let a = ManualStubDriver::new();
35783    let b = ManualStubDriver::new();
35784    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
35785    let deadline = Instant::now() + Duration::from_secs(60);
35786
35787    let (timer_id, b_id) = {
35788      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
35789      let mut cx = Context::from_waker(Waker::noop());
35790
35791      // First poll arms on A (the only registrant) and takes the wake-token.
35792      assert!(sleep.as_mut().poll(&mut cx).is_pending());
35793      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
35794      assert_eq!(armed_deadline, deadline);
35795      assert!(
35796        executor.host_timers.has_pending(),
35797        "first poll must arm the wake-token"
35798      );
35799
35800      // A's host dies; a newer live registrant appears (the binding shape:
35801      // eviction wakes every pending sleep, which then re-polls -- here the
35802      // re-poll is driven manually).
35803      a.kill();
35804      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
35805      assert!(sleep.as_mut().poll(&mut cx).is_pending());
35806      assert_eq!(
35807        b.registers.lock().unwrap().as_slice(),
35808        &[(timer_id, deadline)],
35809        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
35810      );
35811      assert_eq!(
35812        a.cancels.lock().unwrap().as_slice(),
35813        &[timer_id],
35814        "the dead driver gets a best-effort cancel for the old arm"
35815      );
35816      assert_eq!(
35817        registry.current().map(|(id, _)| id),
35818        Some(b_id),
35819        "the dead driver was evicted"
35820      );
35821      assert!(
35822        executor.host_timers.has_pending(),
35823        "the wake-token survives the re-arm (still exactly this sleep's entry)"
35824      );
35825      (timer_id, b_id)
35826    };
35827
35828    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
35829    // wake-token retires.
35830    let _ = b_id;
35831    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
35832    assert!(
35833      !executor.host_timers.has_pending(),
35834      "drop must retire the wake-token"
35835    );
35836  }
35837
35838  #[cfg(napi_runtime_os_threads)]
35839  #[test]
35840  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
35841    let metrics = Arc::new(RuntimeMetrics::default());
35842    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35843    let backend =
35844      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35845    let responsive = Arc::new(StubHostTimerDriver::new());
35846    let starved = ManualStubDriver::new();
35847    let registry = Arc::new(TimerDriverRegistry::default());
35848    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
35849    // Newest-live-only selection would arm exclusively on this host and never
35850    // receive a wake. The responsive older host must be raced in parallel.
35851    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
35852
35853    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
35854    let waker = Waker::from(Arc::clone(&wake_count));
35855    let mut cx = Context::from_waker(&waker);
35856    // This deadline's pre-fire budget covers only `make_sleep` plus the very
35857    // next poll (a past-due Sleep completes inline and would bypass the
35858    // racing-arm machinery under test); 500ms dwarfs those two statements on
35859    // a starved runner without dragging the fire wait out.
35860    let deadline = Instant::now() + Duration::from_millis(500);
35861    let mut sleep = make_sleep(&backend, &registry, deadline);
35862    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
35863
35864    let responsive_timer = *responsive
35865      .timers
35866      .lock()
35867      .unwrap()
35868      .keys()
35869      .next()
35870      .expect("responsive timer must be armed");
35871    assert_eq!(
35872      starved.registers.lock().unwrap().as_slice(),
35873      &[(responsive_timer, deadline)],
35874      "every live host must receive the same timer identity and absolute deadline"
35875    );
35876
35877    // Event-based fire wait (this was a fixed 140ms sleep, which required
35878    // the responsive stub's freshly spawned fire thread to get CPU inside
35879    // ~80ms on a starved CI host). The wait is structurally sound: the
35880    // starved ManualStubDriver has NO fire path (it only records), so the
35881    // one-and-only wake can come from the responsive host -- the bound is a
35882    // pure hang detector.
35883    let fire_bound = Instant::now() + Duration::from_secs(30);
35884    while wake_count.0.load(Ordering::SeqCst) == 0 {
35885      assert!(
35886        Instant::now() < fire_bound,
35887        "the responsive host must wake a sleep whose newest host is live but starved"
35888      );
35889      std::thread::sleep(Duration::from_millis(10));
35890    }
35891    // The stub fires at (or after) the shared absolute deadline, so the wall
35892    // clock is past `deadline` here and the poll completes.
35893    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
35894    assert_eq!(
35895      starved.cancels.lock().unwrap().as_slice(),
35896      &[responsive_timer],
35897      "completion through the winning host must cancel the starved losing arm"
35898    );
35899  }
35900
35901  #[cfg(napi_runtime_os_threads)]
35902  #[test]
35903  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
35904    use std::sync::mpsc;
35905
35906    struct ReentrantTimerRegistrationWake {
35907      registry: Weak<TimerDriverRegistry>,
35908      wakes: AtomicUsize,
35909      reentered: AtomicBool,
35910    }
35911
35912    impl std::task::Wake for ReentrantTimerRegistrationWake {
35913      fn wake(self: Arc<Self>) {
35914        self.wakes.fetch_add(1, Ordering::SeqCst);
35915        if let Some(registry) = self.registry.upgrade() {
35916          let _ = registry.has_live_driver();
35917          self.reentered.store(true, Ordering::SeqCst);
35918        }
35919      }
35920    }
35921
35922    let metrics = Arc::new(RuntimeMetrics::default());
35923    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
35924    let backend =
35925      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
35926    let registry = Arc::new(TimerDriverRegistry::default());
35927    let starved = ManualStubDriver::new();
35928    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
35929
35930    // The whole pre-fire phase (four polls plus a registration-thread round
35931    // trip) must finish while the wall clock is still BEFORE this shared
35932    // deadline: a past-due `Sleep` completes inline at poll
35933    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
35934    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
35935    // a starved GitHub macOS runner) without dragging the fire wait out.
35936    let deadline = Instant::now() + Duration::from_secs(2);
35937    let mut first = make_sleep(&backend, &registry, deadline);
35938    let mut second = make_sleep(&backend, &registry, deadline);
35939    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
35940      registry: Arc::downgrade(&registry),
35941      wakes: AtomicUsize::new(0),
35942      reentered: AtomicBool::new(false),
35943    });
35944    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
35945      registry: Arc::downgrade(&registry),
35946      wakes: AtomicUsize::new(0),
35947      reentered: AtomicBool::new(false),
35948    });
35949    let first_waker = Waker::from(Arc::clone(&first_wake));
35950    let second_waker = Waker::from(Arc::clone(&second_wake));
35951    let mut first_cx = Context::from_waker(&first_waker);
35952    let mut second_cx = Context::from_waker(&second_waker);
35953
35954    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
35955    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
35956    let initial = starved.registers.lock().unwrap().clone();
35957    let first_id = initial[0].0;
35958    let second_id = initial[1].0;
35959    assert_ne!(first_id, second_id);
35960    assert_eq!(
35961      registry.pending_len(),
35962      2,
35963      "one stable repoll entry is retained per sleep"
35964    );
35965
35966    let responsive = Arc::new(StubHostTimerDriver::new());
35967    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
35968    let registration_registry = Arc::clone(&registry);
35969    let registration_driver = Arc::clone(&responsive);
35970    let registration = std::thread::spawn(move || {
35971      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
35972      registered_tx.send(id).unwrap();
35973    });
35974    // Hang detector only (the regression it pins is a PERMANENT deadlock:
35975    // `register` re-entering the repoll callback), so the bound is generous
35976    // rather than a latency assertion a starved runner could trip.
35977    registered_rx
35978      .recv_timeout(Duration::from_secs(10))
35979      .expect("late driver registration or repoll callback re-entry deadlocked");
35980    registration.join().unwrap();
35981
35982    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
35983    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
35984    assert!(first_wake.reentered.load(Ordering::SeqCst));
35985    assert!(second_wake.reentered.load(Ordering::SeqCst));
35986    assert!(
35987      responsive.timers.lock().unwrap().is_empty(),
35988      "registration requests bounded re-polls instead of invoking driver callbacks inline"
35989    );
35990
35991    // Both sleeps must still be PENDING when they re-arm on the late driver;
35992    // see the deadline-margin note above. Fail with a diagnosis instead of a
35993    // misleading Pending-assert failure if a pathological stall consumed the
35994    // whole margin anyway.
35995    assert!(
35996      Instant::now() < deadline,
35997      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
35998       this runner is starved beyond the margin this test budgets for"
35999    );
36000    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
36001    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
36002    let responsive_ids = responsive
36003      .timers
36004      .lock()
36005      .unwrap()
36006      .keys()
36007      .copied()
36008      .collect::<FxHashSet<_>>();
36009    assert_eq!(
36010      responsive_ids,
36011      FxHashSet::from_iter([first_id, second_id]),
36012      "every pending sleep must re-arm its existing identity on the new driver"
36013    );
36014    assert_eq!(
36015      starved
36016        .registers
36017        .lock()
36018        .unwrap()
36019        .iter()
36020        .filter(|(id, _)| *id == first_id)
36021        .count(),
36022      2,
36023      "the old host receives only the normal waker refresh on the one re-poll"
36024    );
36025    assert_eq!(
36026      starved
36027        .registers
36028        .lock()
36029        .unwrap()
36030        .iter()
36031        .filter(|(id, _)| *id == second_id)
36032        .count(),
36033      2,
36034      "the old host receives only the normal waker refresh on the one re-poll"
36035    );
36036    assert_eq!(
36037      registry.pending_len(),
36038      2,
36039      "late registration must not duplicate pending state"
36040    );
36041
36042    // Event-based fire wait instead of a fixed sleep: the starved host has
36043    // NO fire path by construction (ManualStubDriver only records), so the
36044    // second wake per sleep can only come from the late responsive host --
36045    // "while the original host remains starved" is structural, not a
36046    // wall-clock race. The bound is purely a hang detector, generous enough
36047    // for a starved runner to schedule the stub's two fire threads.
36048    let fire_bound = Instant::now() + Duration::from_secs(30);
36049    while first_wake.wakes.load(Ordering::SeqCst) < 2
36050      || second_wake.wakes.load(Ordering::SeqCst) < 2
36051    {
36052      assert!(
36053        Instant::now() < fire_bound,
36054        "the late responsive host must fire both timers while the original host remains starved"
36055      );
36056      std::thread::sleep(Duration::from_millis(10));
36057    }
36058    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
36059    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
36060
36061    let mut cancelled = starved.cancels.lock().unwrap().clone();
36062    cancelled.sort_unstable();
36063    let mut expected = vec![first_id, second_id];
36064    expected.sort_unstable();
36065    assert_eq!(
36066      cancelled, expected,
36067      "completion must cancel both losing starved-host arms"
36068    );
36069    assert_eq!(
36070      registry.pending_len(),
36071      0,
36072      "completion retires every repoll entry"
36073    );
36074    assert!(!executor.host_timers.has_pending());
36075  }
36076
36077  #[cfg(panic = "unwind")]
36078  #[test]
36079  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
36080    // With NO live registrant left mid-flight there is nothing to re-arm on:
36081    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
36082    // sleep silently.
36083    let metrics = Arc::new(RuntimeMetrics::default());
36084    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36085    let backend =
36086      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36087
36088    let a = ManualStubDriver::new();
36089    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36090    let mut sleep = std::pin::pin!(make_sleep(
36091      &backend,
36092      &registry,
36093      Instant::now() + Duration::from_secs(60)
36094    ));
36095    let mut cx = Context::from_waker(Waker::noop());
36096    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36097
36098    a.kill();
36099    let payload = catch_unwind(AssertUnwindSafe(|| {
36100      let _ = sleep.as_mut().poll(&mut cx);
36101    }))
36102    .expect_err("a sleep with every driver dead must panic, not pend silently");
36103    let message = payload
36104      .downcast_ref::<String>()
36105      .cloned()
36106      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
36107      .expect("panic payload must be a message");
36108    assert!(
36109      message.contains("lost every live timer driver mid-sleep"),
36110      "the diagnostic must name the mid-flight driver loss, got: {message}"
36111    );
36112  }
36113
36114  #[test]
36115  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
36116    // Sweep-eviction invariant: the registry's selection sweep can be the
36117    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
36118    // or any call failure. A retain-only sweep would strand every sleep whose
36119    // armed waker lives in the swept driver's pending map. The sweep must call
36120    // `on_swept` outside the registry lock so the driver wakes those sleeps
36121    // into re-selection.
36122    let metrics = Arc::new(RuntimeMetrics::default());
36123    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36124    let backend =
36125      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36126
36127    let a = ManualStubDriver::new();
36128    let b = ManualStubDriver::new();
36129    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
36130    let mut sleep = std::pin::pin!(make_sleep(
36131      &backend,
36132      &registry,
36133      Instant::now() + Duration::from_secs(60)
36134    ));
36135
36136    // Arm on A with an observable waker.
36137    let flag = Arc::new(WakeFlag::default());
36138    let waker = Waker::from(Arc::clone(&flag));
36139    let mut cx = Context::from_waker(&waker);
36140    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36141    assert!(!flag.0.load(Ordering::SeqCst));
36142
36143    // A's host dies; a DIFFERENT selection (any registry touch -- here a
36144    // liveness query, in production another sleep's poll) sweeps it.
36145    a.kill();
36146    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
36147    let _ = registry.has_live_driver();
36148
36149    assert!(
36150      a.swept.load(Ordering::SeqCst),
36151      "the sweep must notify the swept driver"
36152    );
36153    assert!(
36154      flag.0.load(Ordering::SeqCst),
36155      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
36156    );
36157
36158    // The woken sleep's re-poll lands on the live registrant.
36159    assert!(sleep.as_mut().poll(&mut cx).is_pending());
36160    assert_eq!(
36161      b.registers.lock().unwrap().len(),
36162      1,
36163      "the re-poll must re-arm on the live driver"
36164    );
36165  }
36166
36167  #[cfg(napi_runtime_os_threads)]
36168  #[test]
36169  fn current_thread_sleep_fires_through_stub_host_driver() {
36170    // CT delegation: a registered host driver's fire must complete a sleep
36171    // awaited through the CT `block_on` drive loop.
36172    use std::sync::mpsc;
36173
36174    let (done_tx, done_rx) = mpsc::channel();
36175    let runner = std::thread::spawn(move || {
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<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36181
36182      let started = Instant::now();
36183      let mut future = std::pin::pin!(async {
36184        make_sleep(
36185          &backend,
36186          &registry_with(driver),
36187          Instant::now() + Duration::from_millis(60),
36188        )
36189        .await;
36190      });
36191      executor.block_on(future.as_mut());
36192      assert!(started.elapsed() >= Duration::from_millis(60));
36193      assert!(
36194        !executor.host_timers.has_pending(),
36195        "a completed sleep must retire its pending-registry entry"
36196      );
36197      let _ = done_tx.send(());
36198    });
36199
36200    match done_rx.recv_timeout(Duration::from_secs(10)) {
36201      Ok(()) => runner.join().unwrap(),
36202      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
36203    }
36204  }
36205
36206  #[test]
36207  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
36208    let metrics = Arc::new(RuntimeMetrics::default());
36209    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
36210    let backend =
36211      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36212    let drivers = Arc::new(TimerDriverRegistry::default());
36213    let driver = ManualStubDriver::new();
36214    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36215    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
36216    let mut cx = Context::from_waker(Waker::noop());
36217
36218    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
36219    let timer_id = driver.registers.lock().unwrap()[0].0;
36220    assert_eq!(drivers.pending_len(), 1);
36221
36222    executor.host_timers.shutdown();
36223    assert_eq!(
36224      drivers.pending_len(),
36225      0,
36226      "shutdown must retire the registration-repoll index before the Sleep is dropped"
36227    );
36228    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
36229    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
36230  }
36231
36232  #[cfg(napi_runtime_os_threads)]
36233  #[test]
36234  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
36235    use std::sync::mpsc;
36236
36237    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
36238    let backend = controller.backend();
36239    let drivers = Arc::new(TimerDriverRegistry::default());
36240    let driver = ManualStubDriver::new();
36241    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
36242    let sleep = make_sleep(
36243      &backend,
36244      &drivers,
36245      Instant::now() + Duration::from_secs(120),
36246    );
36247    drop(backend);
36248
36249    let (polled_tx, polled_rx) = mpsc::channel();
36250    let block_controller = Arc::clone(&controller);
36251    let blocker = std::thread::spawn(move || {
36252      let mut announced = false;
36253      let mut sleep = std::pin::pin!(sleep);
36254      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
36255        let poll = sleep.as_mut().poll(cx);
36256        if !announced {
36257          announced = true;
36258          polled_tx.send(()).unwrap();
36259        }
36260        poll
36261      }));
36262      block_controller.block_on(future.as_mut());
36263    });
36264    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
36265
36266    let (shutdown_tx, shutdown_rx) = mpsc::channel();
36267    let shutdown_controller = Arc::clone(&controller);
36268    let shutdown = std::thread::spawn(move || {
36269      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
36270    });
36271    shutdown_rx
36272      .recv_timeout(Duration::from_secs(2))
36273      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
36274      .unwrap();
36275    shutdown.join().unwrap();
36276    blocker.join().unwrap();
36277    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
36278  }
36279
36280  #[cfg(panic = "unwind")]
36281  #[test]
36282  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
36283    // Threadless pending-timer invariant: on a
36284    // THREADLESS build a pending host timer is NOT a wake-token source -- the
36285    // host event loop's `setTimeout` relay can only run on the very thread
36286    // that is about to park, so it can never fire while `block_on` holds it.
36287    // The certain check must panic with the typed diagnostic anyway (the
36288    // timer-backed kind, so the message names the shape), never fall through
36289    // to the park -- on the real wasip1 target that park is an untyped
36290    // "condvar wait not supported" abort. A native helper thread can fire the
36291    // timer in this test harness, but that concurrency cannot exist on the
36292    // threadless target and must not rescue the park.
36293    use std::sync::mpsc;
36294
36295    let (done_tx, done_rx) = mpsc::channel();
36296    let runner = std::thread::spawn(move || {
36297      let metrics = Arc::new(RuntimeMetrics::default());
36298      // Native stand-in for the threadless wasm build; the stub driver would
36299      // play the host loop from a helper thread, but the panic must land at
36300      // the first park decision, long before its 60ms fire.
36301      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36302      let backend =
36303        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36304      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36305
36306      let payload = catch_unwind(AssertUnwindSafe(|| {
36307        let mut future = std::pin::pin!(async {
36308          make_sleep(
36309            &backend,
36310            &registry_with(driver),
36311            Instant::now() + Duration::from_millis(60),
36312          )
36313          .await;
36314        });
36315        executor.block_on(future.as_mut());
36316      }))
36317      .expect_err("a threadless park must self-detect even while a host timer is pending");
36318      let diagnostic = payload
36319        .downcast_ref::<BlockOnDeadlock>()
36320        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36321      assert_eq!(
36322        diagnostic.kind,
36323        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
36324      );
36325      assert_eq!(
36326        diagnostic.park_deadline, None,
36327        "the certain case carries no deadline"
36328      );
36329      let message = diagnostic.to_string();
36330      assert!(
36331        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
36332        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
36333      );
36334      let _ = done_tx.send(());
36335    });
36336
36337    match done_rx.recv_timeout(Duration::from_secs(10)) {
36338      Ok(()) => runner.join().unwrap(),
36339      Err(error) => panic!(
36340        "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})"
36341      ),
36342    }
36343  }
36344
36345  #[cfg(panic = "unwind")]
36346  #[test]
36347  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
36348    // Unpolled-sleep invariant: the registry entry only exists once the host
36349    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
36350    // creation. A created-but-never-polled Sleep has no host callback behind
36351    // it, and on a parked single thread nothing can ever poll it into
36352    // arming -- it is provably NOT a future wake source. Counting it would
36353    // mislabel the certain diagnostic as timer-backed. (The armed twin above
36354    // pins the polled side: a POLLED pending timer still certain-panics, just
36355    // with the timer-backed kind.)
36356    use std::sync::mpsc;
36357
36358    let (done_tx, done_rx) = mpsc::channel();
36359    let runner = std::thread::spawn(move || {
36360      let metrics = Arc::new(RuntimeMetrics::default());
36361      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
36362      let backend =
36363        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36364      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36365
36366      // Created and held across the park decision, but NEVER polled: no host
36367      // timer armed, no wake source.
36368      let _unpolled = make_sleep(
36369        &backend,
36370        &registry_with(driver),
36371        Instant::now() + Duration::from_secs(60),
36372      );
36373
36374      let payload = catch_unwind(AssertUnwindSafe(|| {
36375        let mut future = std::pin::pin!(NeverReady);
36376        executor.block_on(future.as_mut());
36377      }))
36378      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
36379      let diagnostic = payload
36380        .downcast_ref::<BlockOnDeadlock>()
36381        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
36382      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
36383      let _ = done_tx.send(());
36384    });
36385
36386    match done_rx.recv_timeout(Duration::from_secs(10)) {
36387      Ok(()) => runner.join().unwrap(),
36388      Err(error) => panic!(
36389        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
36390      ),
36391    }
36392  }
36393
36394  #[cfg(napi_runtime_os_threads)]
36395  #[test]
36396  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
36397    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
36398    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
36399    // legitimate (the wake is scheduled on the host loop); the deadline
36400    // verdict must not fire. Once the timer fires, the sleep completes.
36401    use std::sync::mpsc;
36402
36403    let (done_tx, done_rx) = mpsc::channel();
36404    let runner = std::thread::spawn(move || {
36405      let metrics = Arc::new(RuntimeMetrics::default());
36406      // Deadline/grace ratio: the armed park deadline doubles as the
36407      // `has_live_wait` GRACE -- once the host timer is past due by more
36408      // than one deadline window without firing, the veto stops holding BY
36409      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
36410      // the stub host's freshly spawned fire thread had to get CPU within
36411      // ~230ms of test start or the verdict fired -- the starved-CI failure.
36412      // A 2s window keeps the invariant identical (a LIVE host-timer wait
36413      // with zero progress must not be declared dead) while tolerating
36414      // multi-second scheduling stalls of the fire thread: the veto holds
36415      // until deadline+2s, and the expiry checks land on 2s boundaries, so
36416      // the fire has >4s before a verdict could stick.
36417      let executor = Arc::new(CurrentThreadExecutor::with_detection(
36418        metrics,
36419        false,
36420        Some(Duration::from_secs(2)),
36421      ));
36422      let backend =
36423        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
36424      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
36425
36426      let started = Instant::now();
36427      let mut future = std::pin::pin!(async {
36428        make_sleep(
36429          &backend,
36430          &registry_with(driver),
36431          Instant::now() + Duration::from_millis(200),
36432        )
36433        .await;
36434      });
36435      executor.block_on(future.as_mut());
36436      assert!(started.elapsed() >= Duration::from_millis(200));
36437      let _ = done_tx.send(());
36438    });
36439
36440    match done_rx.recv_timeout(Duration::from_secs(30)) {
36441      Ok(()) => runner.join().unwrap(),
36442      Err(error) => {
36443        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
36444      }
36445    }
36446  }
36447
36448  #[test]
36449  fn host_timer_unrepresentable_grace_remains_live() {
36450    let registry = HostTimerRegistry::default();
36451    let fired = Arc::new(AtomicBool::new(false));
36452    let drivers = Arc::new(TimerDriverRegistry::default());
36453    let repoll = Arc::new(HostTimerRepoll::default());
36454    assert!(
36455      Instant::now().checked_add(Duration::MAX).is_none(),
36456      "Duration::MAX must exercise the unrepresentable-grace path"
36457    );
36458    assert!(
36459      registry.register(
36460        1,
36461        Instant::now(),
36462        futures::task::noop_waker(),
36463        &fired,
36464        &drivers,
36465        &repoll,
36466      ),
36467      "the open registry must accept the timer"
36468    );
36469
36470    assert!(
36471      registry.has_live_wait(Duration::MAX),
36472      "an unrepresentable grace deadline is effectively unbounded"
36473    );
36474    registry.remove(1);
36475  }
36476
36477  // `BlockOnDeadlock::multi_thread_cooperative` only exists where the
36478  // MultiThread machinery is compiled in.
36479  #[cfg(napi_runtime_os_threads)]
36480  #[test]
36481  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
36482    // A deadline firing inside a spawned task's poll unwinds into the spawn
36483    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
36484    // message must survive that trip (JoinError::from_panic downcasts it), or
36485    // the build error would read "async runtime task panicked" with no clue.
36486    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
36487    let expected = diagnostic.to_string();
36488    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
36489    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
36490  }
36491  // --- wasm32-wasip1-threads ------------------------------------------------
36492  //
36493  // `rustc --print cfg` is identical for the two WASI targets, so these pin
36494  // what the build script's cfgs are supposed to mean. They run only where
36495  // `napi_runtime_wasi_threads` holds; the threadless complement is pinned by
36496  // `threadless_wasm_still_rejects_multi_thread` below.
36497
36498  #[cfg(napi_runtime_wasi_threads)]
36499  #[test]
36500  fn wasi_threads_reports_a_thread_capable_worker_ceiling() {
36501    // Rayon's `THREADS_BITS` is 8 on 32-bit, so its ceiling is 255 and the
36502    // production cap of 256 never binds here. The `>= 2` half is what
36503    // `validate()` needs to accept MultiThread at all.
36504    let ceiling = crate::max_async_runtime_worker_threads();
36505    assert_eq!(
36506      ceiling, 255,
36507      "the threaded WASI target must report Rayon's realizable ceiling"
36508    );
36509    assert!(
36510      ceiling >= 2,
36511      "a ceiling below two would make MultiThread unconfigurable"
36512    );
36513  }
36514
36515  #[cfg(napi_runtime_wasi_threads)]
36516  #[test]
36517  fn wasi_threads_default_flavor_is_still_current_thread() {
36518    // Availability is not a default. Selecting MultiThread on a wasm target
36519    // stays an explicit host act, so a host that changes nothing sees no
36520    // change at all.
36521    assert_eq!(
36522      RuntimeOptions::default().flavor,
36523      RuntimeFlavor::CurrentThread,
36524      "the threaded WASI target must not silently default to MultiThread"
36525    );
36526  }
36527
36528  #[cfg(napi_runtime_wasi_threads)]
36529  #[test]
36530  fn wasi_threads_multi_thread_validates_to_the_two_worker_minimum() {
36531    // `available_parallelism()` returns Ok(1) under a WASI host, so a host
36532    // that flips only the flavor gets MultiThread's truthful minimum -- two
36533    // workers, one blocking lane -- rather than an error or a pool worth
36534    // having. Hosts that want parallelism must pass a worker count they got
36535    // from JavaScript.
36536    let validated = RuntimeOptions {
36537      flavor: RuntimeFlavor::MultiThread,
36538      ..RuntimeOptions::default()
36539    }
36540    .validate()
36541    .expect("MultiThread must validate on the threaded WASI target");
36542    assert_eq!(validated.worker_threads, 2);
36543    assert_eq!(validated.max_blocking_tasks, 1);
36544  }
36545
36546  #[cfg(napi_runtime_wasi_threads)]
36547  #[test]
36548  fn threadless_build_is_false_on_the_threads_target() {
36549    // `THREADLESS_BUILD` and `napi_runtime_os_threads` are orthogonal cfgs
36550    // that must agree on this one target, or the CurrentThread park logic and
36551    // the MultiThread machinery would disagree about whether a second thread
36552    // can deliver a wake.
36553    assert!(
36554      !THREADLESS_BUILD,
36555      "the threaded WASI target has real OS threads"
36556    );
36557    assert!(
36558      cfg!(napi_runtime_os_threads),
36559      "the threaded WASI target must build the OS-thread machinery"
36560    );
36561  }
36562
36563  // `not(napi_runtime_os_threads)` is exactly "wasm and not the threaded WASI
36564  // target": the build script sets the cfg for every non-wasm target plus
36565  // `wasm32-wasip1-threads`.
36566  #[cfg(not(napi_runtime_os_threads))]
36567  #[test]
36568  fn threadless_wasm_still_rejects_multi_thread() {
36569    let error = RuntimeOptions {
36570      flavor: RuntimeFlavor::MultiThread,
36571      ..RuntimeOptions::default()
36572    }
36573    .validate()
36574    .expect_err("threadless WebAssembly cannot run the multi-thread runtime");
36575    let message = error.to_string();
36576    assert!(
36577      message.contains("threadless"),
36578      "the rejection must name the property that causes it, not the family: {message}"
36579    );
36580    assert!(
36581      message.contains("wasm32-wasip1-threads"),
36582      "the rejection must name the target that does work: {message}"
36583    );
36584  }
36585
36586  #[cfg(napi_runtime_os_threads)]
36587  #[test]
36588  fn multi_thread_pool_starts_and_joins_every_worker() {
36589    // The disposal order the WASI loader depends on, as a unit test: every
36590    // worker is joined BEFORE anything tears the environment down, so no
36591    // thread can still be running over memory that is about to disappear.
36592    let options = RuntimeOptions {
36593      flavor: RuntimeFlavor::MultiThread,
36594      worker_threads: 3,
36595      max_blocking_tasks: 2,
36596      thread_name_prefix: "rd-os-threads-join".to_string(),
36597      park_deadline: None,
36598      drain_linger: DEFAULT_DRAIN_LINGER,
36599    };
36600    let executor = Arc::new(
36601      MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default()))
36602        .expect("a three-worker pool must build"),
36603    );
36604    assert_eq!(executor.pool.current_num_threads(), 3);
36605
36606    let ran = Arc::new(AtomicBool::new(false));
36607    let task_ran = Arc::clone(&ran);
36608    let (runnable, task) = async_task::spawn(
36609      async move {
36610        task_ran.store(true, Ordering::SeqCst);
36611      },
36612      |_| {},
36613    );
36614    executor.schedule(runnable);
36615    futures::executor::block_on(task);
36616    assert!(
36617      ran.load(Ordering::SeqCst),
36618      "the pool must run scheduled work"
36619    );
36620
36621    let lifecycle = Arc::clone(&executor.worker_lifecycle);
36622    executor.begin_shutdown();
36623    executor.wait_until_scheduler_idle();
36624    // A drainer's Rayon job decrements `active_drainers` before its closure
36625    // is dropped, so the last reference can lag the idle barrier by a moment.
36626    // Spin for it under a deadline: taking the value out is what proves the
36627    // pool really goes away here, and turns a leaked reference into a
36628    // failure rather than a hang in the join below.
36629    let deadline = Instant::now() + Duration::from_secs(10);
36630    while Arc::strong_count(&executor) > 1 && Instant::now() < deadline {
36631      std::thread::yield_now();
36632    }
36633    let executor = Arc::into_inner(executor)
36634      .expect("no drainer may outlive `wait_until_scheduler_idle` for long");
36635    drop(executor);
36636    lifecycle.wait_for_all_workers();
36637    assert_eq!(
36638      lifecycle.remaining(),
36639      0,
36640      "every physical worker must have exited before the join returns"
36641    );
36642  }
36643
36644  #[cfg(napi_runtime_os_threads)]
36645  #[test]
36646  fn current_thread_queues_concurrent_blocking_admission() {
36647    // A second OS thread submitting `spawn_blocking` while the lane owner
36648    // runs, carrying no `ambient_owner`. The gate that decides this used to
36649    // read `target_family = "wasm"` while its comment argued "threadless", so
36650    // on a threaded wasm build the closure was dropped and the handle
36651    // resolved to "the async runtime stopped before the blocking task could
36652    // start". It must be QUEUED and serviced on a later host turn.
36653    use std::sync::mpsc;
36654
36655    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
36656      RuntimeMetrics::default(),
36657    )));
36658    let work = GenerationWork::new();
36659    let (outcome_tx, outcome_rx) = mpsc::channel();
36660
36661    let owner_registration = work
36662      .try_register_work()
36663      .expect("the lane owner's blocking job must be accepted");
36664    let concurrent_work = Arc::clone(&work);
36665    let concurrent_executor = Arc::clone(&executor);
36666    let task_executor = Arc::clone(&executor);
36667    let wake_executor = Arc::clone(&executor);
36668    let (runnable, task) = async_task::spawn(
36669      async move {
36670        let concurrent = task_executor
36671          .schedule_blocking(
36672            move || {
36673              std::thread::spawn(move || {
36674                let registration = concurrent_work
36675                  .try_register_work()
36676                  .expect("the concurrent blocking job must be accepted");
36677                concurrent_executor.schedule_blocking(|| 7usize, registration)
36678              })
36679              .join()
36680              .expect("the concurrent submitter thread must finish")
36681            },
36682            owner_registration,
36683          )
36684          .await
36685          .expect("the lane owner's blocking job must finish");
36686        let _ = outcome_tx.send(concurrent.await);
36687      },
36688      move |runnable| wake_executor.schedule(runnable),
36689    );
36690
36691    executor.schedule(runnable);
36692    let mut turns = 0usize;
36693    let outcome = loop {
36694      executor.drive_host_turn();
36695      if let Ok(outcome) = outcome_rx.try_recv() {
36696        break outcome;
36697      }
36698      turns += 1;
36699      assert!(
36700        turns <= 8,
36701        "queued blocking admission must make bounded host-turn progress"
36702      );
36703    };
36704    futures::executor::block_on(task);
36705    assert_eq!(
36706      outcome.expect("a concurrent blocking submission must be queued, not rejected"),
36707      7
36708    );
36709  }
36710}