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#[cfg(test)]
25use async_task::Task;
26use async_task::{FallibleTask, Runnable};
27use futures::{
28  FutureExt,
29  future::{AbortHandle, AbortRegistration, Abortable},
30};
31
32#[cfg(not(target_family = "wasm"))]
33use crossbeam_deque::{Injector, Steal};
34#[cfg(not(target_family = "wasm"))]
35use futures::channel::oneshot;
36#[cfg(all(test, not(target_family = "wasm")))]
37use rayon::max_num_threads;
38#[cfg(not(target_family = "wasm"))]
39use rayon::{ThreadPool, ThreadPoolBuilder};
40
41/// Stable scheduler identities are equality capabilities. Exhaustion must fail
42/// before reuse can authorize a stale handle or collide in an indexed queue.
43fn next_unique_id(counter: &AtomicU64, exhausted: &'static str) -> u64 {
44  counter
45    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
46    .expect(exhausted)
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum RuntimeFlavor {
51  CurrentThread,
52  MultiThread,
53}
54
55#[derive(Debug, Clone)]
56pub struct RuntimeOptions {
57  pub flavor: RuntimeFlavor,
58  pub worker_threads: usize,
59  pub max_blocking_tasks: usize,
60  pub thread_name_prefix: String,
61  /// Deadline-based `block_on` deadlock detection: bounds how long a
62  /// runtime-owned park (the CurrentThread `block_on` parker and
63  /// MultiThread COOPERATIVE driver parks -- never the foreign/napi
64  /// whole-build park) may sleep with ZERO runtime progress before
65  /// panicking with the typed [`BlockOnDeadlock`] diagnostic. `None` (the
66  /// default) disables deadline detection: a legitimately long park (e.g.
67  /// awaiting a slow JS plugin) must never panic a production build. The
68  /// runtime itself never reads the environment; the embedder resolves its
69  /// own configuration (conventionally a [`PARK_DEADLINE_ENV`]-style
70  /// variable) into this field at addon load. The
71  /// threadless-wasm CERTAIN deadlock check is independent of this knob and
72  /// always on.
73  pub park_deadline: Option<Duration>,
74  /// MultiThread drainer idle-linger budget (see `MultiThreadExecutor::drain`):
75  /// after running the runnable/blocking queues empty a drainer parks on its
76  /// own parker for up to this long before returning its thread to rayon, so
77  /// a wave landing within the budget resumes it with one targeted wake
78  /// instead of a `spawn_fifo` respawn through rayon's idle protocol.
79  /// `Duration::ZERO` disables lingering (legacy exit-and-respawn on every
80  /// empty queue). Validation clamps the budget to
81  /// [`MAX_DRAIN_LINGER_MICROS`]: a linger occupies a pool worker and an
82  /// `active_drainers` slot while idle, so an unbounded value would pin
83  /// workers indefinitely. As with `park_deadline`, the runtime itself never
84  /// reads the environment; the embedder resolves its configuration
85  /// (conventionally [`DRAIN_LINGER_ENV`], which the napi adapter's `install`
86  /// resolves for hosts that use it) into this field at addon load. Ignored
87  /// by the CurrentThread flavor, which has no pool drainers.
88  pub drain_linger: Duration,
89}
90
91/// A partial update to the runtime options exposed by the binding.
92///
93/// [`configure_partial`] merges these fields into the latest configured
94/// options, validates the merged candidate, and commits it while holding the
95/// runtime controller lock. Fields that are not exposed for partial updates,
96/// such as `thread_name_prefix` and `park_deadline`, remain unchanged.
97#[derive(Debug, Clone, Copy, Default)]
98pub struct RuntimeOptionsPatch {
99  pub flavor: Option<RuntimeFlavor>,
100  pub worker_threads: Option<usize>,
101  pub max_blocking_tasks: Option<usize>,
102  /// `Some(Duration::ZERO)` disables lingering; other values are clamped to
103  /// [`MAX_DRAIN_LINGER_MICROS`] by validation.
104  pub drain_linger: Option<Duration>,
105}
106
107impl RuntimeOptionsPatch {
108  fn apply_to(self, options: &mut RuntimeOptions) {
109    if let Some(flavor) = self.flavor {
110      options.flavor = flavor;
111    }
112    if let Some(worker_threads) = self.worker_threads {
113      options.worker_threads = worker_threads;
114    }
115    if let Some(max_blocking_tasks) = self.max_blocking_tasks {
116      options.max_blocking_tasks = max_blocking_tasks;
117    }
118    if let Some(drain_linger) = self.drain_linger {
119      options.drain_linger = drain_linger;
120    }
121  }
122}
123
124impl Default for RuntimeOptions {
125  fn default() -> Self {
126    let worker_threads = std::thread::available_parallelism()
127      .map_or(1, usize::from)
128      .min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
129    Self {
130      flavor: if cfg!(target_family = "wasm") {
131        RuntimeFlavor::CurrentThread
132      } else {
133        RuntimeFlavor::MultiThread
134      },
135      worker_threads,
136      max_blocking_tasks: worker_threads,
137      thread_name_prefix: "napi-async-runtime".to_string(),
138      park_deadline: None,
139      drain_linger: DEFAULT_DRAIN_LINGER,
140    }
141  }
142}
143
144impl RuntimeOptions {
145  fn validate(self) -> Result<Self, RuntimeConfigError> {
146    #[cfg(target_family = "wasm")]
147    let rayon_max_threads = None;
148    #[cfg(not(target_family = "wasm"))]
149    let rayon_max_threads = Some(crate::max_async_runtime_worker_threads());
150    self.validate_with_rayon_max_threads(rayon_max_threads)
151  }
152
153  fn validate_with_rayon_max_threads(
154    mut self,
155    rayon_max_threads: Option<usize>,
156  ) -> Result<Self, RuntimeConfigError> {
157    if self.worker_threads == 0 {
158      return Err(RuntimeConfigError(
159        "worker_threads must be greater than zero".to_string(),
160      ));
161    }
162    if self.max_blocking_tasks == 0 {
163      return Err(RuntimeConfigError(
164        "max_blocking_tasks must be greater than zero".to_string(),
165      ));
166    }
167    // A lingering drainer occupies a pool worker and an `active_drainers`
168    // slot while idle, so an unbounded budget (e.g. an env typo resolving to
169    // u64::MAX microseconds) would pin workers indefinitely and wedge every
170    // idle-wait. Clamp instead of erroring: like the thread-count clamps
171    // below, an out-of-range value is a tuning excess, not a topology error.
172    // ZERO passes through unchanged and means "lingering disabled".
173    self.drain_linger = self.drain_linger.min(MAX_DRAIN_LINGER);
174    if self.flavor == RuntimeFlavor::CurrentThread {
175      self.worker_threads = 1;
176      self.max_blocking_tasks = 1;
177    } else {
178      let Some(rayon_max_threads) = rayon_max_threads else {
179        return Err(RuntimeConfigError(
180          "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
181        ));
182      };
183      if rayon_max_threads < 2 {
184        return Err(RuntimeConfigError(
185          "Rayon must support at least two worker threads for the multi-thread runtime".to_string(),
186        ));
187      }
188      // Blocking closures execute on the shared Rayon pool. Keep one execution
189      // lane available for runnable futures and timer service even when every
190      // admitted blocking job is stalled. MultiThread therefore has a truthful
191      // minimum of two physical/configured workers rather than a hidden reserve.
192      // Apply both this crate's production ceiling and Rayon's platform cap
193      // before deriving the blocking allowance, so a silently reduced physical
194      // pool cannot consume that reserved lane.
195      self.worker_threads = self
196        .worker_threads
197        .clamp(2, MAX_ASYNC_RUNTIME_WORKER_THREADS)
198        .min(rayon_max_threads);
199      let blocking_capacity = self.worker_threads - 1;
200      self.max_blocking_tasks = self.max_blocking_tasks.min(blocking_capacity);
201    }
202    Ok(self)
203  }
204}
205
206/// Environment variable that arms deadline-based `block_on` deadlock
207/// detection: milliseconds a runtime-owned park may sleep with zero runtime
208/// progress before panicking (see [`RuntimeOptions::park_deadline`]).
209/// Missing, non-numeric or `0` all mean "disabled". The runtime does NOT
210/// read this variable itself: the embedding host's env-resolution parses it
211/// once at addon load and hands the result to [`configure`] through
212/// [`RuntimeOptions::park_deadline`]. The name is the conventional default;
213/// hosts may resolve their own variable into the same field.
214pub const PARK_DEADLINE_ENV: &str = "NAPI_RUNTIME_PARK_DEADLINE_MS";
215
216/// Environment variable overriding the MultiThread drainer idle-linger budget
217/// in MICROSECONDS ([`RuntimeOptions::drain_linger`], consumed by
218/// `MultiThreadExecutor::drain`). After a drainer runs the runnable/blocking
219/// queues empty it parks on its own [`DriverParker`] (registered with
220/// `parked_drivers`) for up to this long before returning its thread to
221/// rayon. A wave of work arriving within the budget is resumed by ONE
222/// targeted condvar wake instead of a `spawn_fifo` respawn through rayon's
223/// idle protocol -- whose per-transition cost (a wake cascade plus 33
224/// `sched_yield` rounds and a full steal sweep on EVERY worker that was
225/// woken) is what makes system CPU scale with the worker count under
226/// wave-shaped loads. `0` disables lingering (legacy exit-and-respawn on
227/// every empty queue); values above [`MAX_DRAIN_LINGER_MICROS`] are clamped.
228/// A frame's TOTAL lingering residence is additionally capped at
229/// [`DRAIN_LINGER_FRAME_FACTOR`] times this budget so periodic sub-budget
230/// work cannot pin a rayon worker inside one drain frame forever.
231///
232/// Like [`PARK_DEADLINE_ENV`], the runtime does NOT read this variable
233/// itself: it is resolved once at addon load into
234/// [`RuntimeOptions::drain_linger`] -- by the napi adapter's `install` (via
235/// `resolve_drain_linger`) for hosts that use it, while hosts owning their
236/// own env resolution may resolve their own variable into the same field. A
237/// present-but-non-numeric value keeps the host-provided field (which
238/// defaults to [`DEFAULT_DRAIN_LINGER_MICROS`]).
239pub const DRAIN_LINGER_ENV: &str = "NAPI_RUNTIME_DRAIN_LINGER_US";
240
241/// Default drainer idle-linger budget (µs): [`RuntimeOptions::drain_linger`]'s
242/// default, applied when no host configuration or [`DRAIN_LINGER_ENV`]
243/// override resolves a different value.
244pub const DEFAULT_DRAIN_LINGER_MICROS: u64 = 500;
245
246/// Ceiling on [`RuntimeOptions::drain_linger`] in MICROSECONDS (100ms).
247/// Validation (and, defensively, executor construction) clamps the budget
248/// here: lingering exists to bridge micro-scale gaps between scheduling
249/// waves, and a lingering drainer holds a pool worker plus an
250/// `active_drainers` slot while idle, so budgets beyond this bound stop
251/// tuning the wave optimization and start pinning workers.
252pub const MAX_DRAIN_LINGER_MICROS: u64 = 100_000;
253
254/// [`DEFAULT_DRAIN_LINGER_MICROS`] as a `Duration`.
255const DEFAULT_DRAIN_LINGER: Duration = Duration::from_micros(DEFAULT_DRAIN_LINGER_MICROS);
256
257/// [`MAX_DRAIN_LINGER_MICROS`] as a `Duration`.
258const MAX_DRAIN_LINGER: Duration = Duration::from_micros(MAX_DRAIN_LINGER_MICROS);
259
260/// Bound on a single drain frame's TOTAL lingering residence, as a multiple
261/// of the per-episode idle budget (default 16 x 500us = 8ms). The per-episode
262/// deadline alone does NOT bound how long a drain frame occupies its rayon
263/// worker: every executed work unit re-arms it, so periodic sub-budget work
264/// (e.g. a timer publishing one short task every <500us) would keep the frame
265/// resident FOREVER -- and a raw rayon job spawned onto the pool (which only
266/// a worker that returned to rayon's scheduler can run) would starve behind
267/// it indefinitely. Once cumulative residence past the frame's FIRST idle
268/// observation reaches this bound, the drainer exits through the unchanged
269/// `finish_draining` path even though more sub-budget work may be arriving;
270/// the exit-and-respawn through rayon's queue is exactly the interleaving
271/// point that lets pending raw rayon work (par_iter splits, `pool.spawn`
272/// jobs) take the worker before the re-armed FIFO drain.
273#[cfg(not(target_family = "wasm"))]
274const DRAIN_LINGER_FRAME_FACTOR: u32 = 16;
275
276/// Ceiling on the frame residence bound so an absurd [`DRAIN_LINGER_ENV`]
277/// value cannot arm a deadline beyond the platform's representable range.
278#[cfg(not(target_family = "wasm"))]
279const MAX_DRAIN_LINGER_FRAME_RESIDENCE: Duration = Duration::from_secs(60);
280
281/// True on builds where no second thread can EVER deliver a wake to a parked
282/// `block_on`: every wasm build except the threaded WASI target (i.e. the
283/// single-thread `wasm32-wasip1` build and `wasm32-unknown-unknown`). On such
284/// a build a CurrentThread park decision with an empty queue and no pending
285/// wake token is a PROVABLE deadlock. The threaded wasi build
286/// (`wasm32-wasip1-threads`) has real OS threads, so its parks are not
287/// provably dead and fall under the optional deadline detection instead, like
288/// native builds.
289///
290/// `napi_runtime_wasi_threads` is emitted by this crate's build.rs for the exact
291/// `wasm32-wasip1-threads` cargo TARGET. It is NOT derivable from built-in
292/// cfgs: on current rustc the two WASI targets expose identical cfg sets --
293/// `cfg!(target_feature = "atomics")` is false even on the threads target
294/// (verified empirically; embedders needing the same discrimination use the
295/// same build.rs mechanism).
296const THREADLESS_BUILD: bool = cfg!(all(target_family = "wasm", not(napi_runtime_wasi_threads)));
297
298#[derive(Debug, Clone)]
299pub struct RuntimeConfigError(String);
300
301impl fmt::Display for RuntimeConfigError {
302  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303    f.write_str(&self.0)
304  }
305}
306
307impl std::error::Error for RuntimeConfigError {}
308
309/// Which self-detected `block_on` deadlock class fired.
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum BlockOnDeadlockKind {
312  /// CurrentThread on a threadless build: the park decision found an empty
313  /// queue and no pending wake token, and no other thread exists that could
314  /// ever deliver one -- a PROVABLE deadlock, detected unconditionally (not
315  /// timing-based).
316  CurrentThreadCertain,
317  /// [`Self::CurrentThreadCertain`] detected while a HOST timer was pending.
318  /// The timer does not make the park any less dead on a threadless build:
319  /// its host callback (the JS `setTimeout` relay) can only run on the very
320  /// thread that is about to park, so it can never fire while `block_on`
321  /// holds that thread. A distinct kind only so the diagnostic can name the
322  /// shape the user actually hit.
323  CurrentThreadCertainTimerBacked,
324  /// Threaded CurrentThread: a `block_on` park outlived the configured
325  /// deadline with zero runtime progress.
326  CurrentThreadDeadline,
327  /// MultiThread: a cooperative pool driver's re-entrant `block_on` park
328  /// outlived the configured deadline with zero executor progress. The
329  /// foreign/napi whole-build park is exempt by design.
330  MultiThreadCooperativeDeadline,
331}
332
333/// Typed panic payload for a self-detected `block_on` deadlock. Thrown with
334/// `std::panic::panic_any` at the park DECISION (never merely on a `Pending`
335/// return, because a self-waking future is not a deadlock), so the
336/// runtime fails loudly instead of freezing the thread (and, in the
337/// CurrentThread-on-JS-thread case, the whole JS event loop, where not even
338/// test-harness timeouts can fire). When the panic unwinds out of a spawned
339/// task's poll, `JoinError::from_panic` preserves this diagnostic's message.
340#[derive(Debug, Clone)]
341pub struct BlockOnDeadlock {
342  pub kind: BlockOnDeadlockKind,
343  /// The armed deadline for the deadline-based kinds; `None` for the provable
344  /// threadless case.
345  pub park_deadline: Option<Duration>,
346}
347
348impl BlockOnDeadlock {
349  fn current_thread_certain() -> Self {
350    Self {
351      kind: BlockOnDeadlockKind::CurrentThreadCertain,
352      park_deadline: None,
353    }
354  }
355
356  fn current_thread_certain_timer_backed() -> Self {
357    Self {
358      kind: BlockOnDeadlockKind::CurrentThreadCertainTimerBacked,
359      park_deadline: None,
360    }
361  }
362
363  fn current_thread_deadline(deadline: Duration) -> Self {
364    Self {
365      kind: BlockOnDeadlockKind::CurrentThreadDeadline,
366      park_deadline: Some(deadline),
367    }
368  }
369
370  // The MultiThread executor (and thus this constructor's caller) does not
371  // exist on wasm builds.
372  #[cfg(not(target_family = "wasm"))]
373  fn multi_thread_cooperative(deadline: Duration) -> Self {
374    Self {
375      kind: BlockOnDeadlockKind::MultiThreadCooperativeDeadline,
376      park_deadline: Some(deadline),
377    }
378  }
379}
380
381impl fmt::Display for BlockOnDeadlock {
382  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383    let deadline_ms = self
384      .park_deadline
385      .map_or(0, |deadline| deadline.as_millis());
386    match self.kind {
387      BlockOnDeadlockKind::CurrentThreadCertain => f.write_str(
388        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
389         park with an empty task queue and no pending wake on a threadless build -- no other \
390         thread exists that could ever deliver the wake, so this park would never end. This is \
391         the block_on-awaiting-JS hazard: native code called `block_on` on a future that \
392         (transitively) awaits a JS continuation (e.g. a threadsafe-function callback, like the \
393         dynamic-import-vars plugin's `resolver`), but the only thread that could run that \
394         continuation is the one now parking.",
395      ),
396      BlockOnDeadlockKind::CurrentThreadCertainTimerBacked => f.write_str(
397        "provable async-runtime deadlock: `block_on` on the CurrentThread flavor is about to \
398         park with an empty task queue on a threadless build while a host timer is pending. \
399         The pending timer is no rescue: its host callback (the JS `setTimeout` relay) can \
400         only run on the very thread that is now parking, so it can never fire, and no other \
401         thread exists that could ever deliver a wake. This is the block_on-awaiting-JS \
402         hazard: native code called `block_on` on a future that (transitively) awaits a JS \
403         continuation, but the only thread that could run that continuation is the one now \
404         parking.",
405      ),
406      BlockOnDeadlockKind::CurrentThreadDeadline => write!(
407        f,
408        "suspected async-runtime deadlock: `block_on` on the CurrentThread flavor parked for \
409         {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with an empty \
410         task queue, no pending wake and ZERO runtime progress across the whole window. This is \
411         the signature of the block_on-awaiting-JS hazard: a `block_on` on the JS thread \
412         awaiting a JS continuation (e.g. a threadsafe-function callback) that can only run \
413         once this thread returns to the event loop. If this park is legitimate, raise or unset \
414         the deadline.",
415      ),
416      BlockOnDeadlockKind::MultiThreadCooperativeDeadline => write!(
417        f,
418        "suspected async-runtime deadlock: a cooperative pool driver's re-entrant `block_on` \
419         parked for {deadline_ms}ms (the host-configured park deadline / RuntimeOptions::park_deadline) with \
420         no runnable or blocking work available and ZERO executor progress across the whole \
421         window -- the awaited future's wake (often a JS continuation) can no longer arrive. \
422         The foreign-thread whole-build `block_on` park is exempt from this deadline by design. \
423         If this park is legitimate, raise or unset the deadline.",
424      ),
425    }
426  }
427}
428
429impl std::error::Error for BlockOnDeadlock {}
430
431#[derive(Debug)]
432pub struct JoinError {
433  message: String,
434}
435
436impl JoinError {
437  fn from_panic(panic: &(dyn Any + Send + 'static)) -> Self {
438    Self {
439      // The typed deadlock diagnostic first: a deadline firing inside a
440      // spawned task's poll unwinds into the spawn wrapper's catch_unwind and
441      // surfaces here -- the diagnostic text must survive the trip.
442      message: if let Some(deadlock) = panic.downcast_ref::<BlockOnDeadlock>() {
443        deadlock.to_string()
444      } else if let Some(message) = panic.downcast_ref::<&str>() {
445        (*message).to_string()
446      } else if let Some(message) = panic.downcast_ref::<String>() {
447        message.clone()
448      } else {
449        "async runtime task panicked".to_string()
450      },
451    }
452  }
453}
454
455impl fmt::Display for JoinError {
456  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457    f.write_str(&self.message)
458  }
459}
460
461impl std::error::Error for JoinError {}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464enum BlockOnOutcome {
465  Completed,
466  Stopped,
467}
468
469fn discard_panic_payload(payload: Box<dyn Any + Send + 'static>) {
470  // The original unwind is already caught, so ordinary payloads should be
471  // destroyed normally. A hostile payload destructor can panic again; catch
472  // that nested unwind and leak only its new payload, whose destructor is
473  // likewise untrusted.
474  if let Err(nested_payload) = catch_unwind(AssertUnwindSafe(|| drop(payload))) {
475    std::mem::forget(nested_payload);
476  }
477}
478
479#[derive(Default)]
480struct RetainedPanicPayload(Option<Box<dyn Any + Send + 'static>>);
481
482impl RetainedPanicPayload {
483  fn capture(&mut self, payload: Box<dyn Any + Send + 'static>) {
484    if self.0.is_some() {
485      discard_panic_payload(payload);
486    } else {
487      self.0 = Some(payload);
488    }
489  }
490
491  fn is_some(&self) -> bool {
492    self.0.is_some()
493  }
494
495  fn resume(mut self) -> ! {
496    resume_unwind(
497      self
498        .0
499        .take()
500        .expect("a retained panic payload must remain owned"),
501    )
502  }
503}
504
505impl Drop for RetainedPanicPayload {
506  fn drop(&mut self) {
507    if let Some(payload) = self.0.take() {
508      discard_panic_payload(payload);
509    }
510  }
511}
512
513fn catch_unwind_contained<T>(function: impl FnOnce() -> T) -> Option<T> {
514  match catch_unwind(AssertUnwindSafe(function)) {
515    Ok(value) => Some(value),
516    Err(payload) => {
517      discard_panic_payload(payload);
518      None
519    }
520  }
521}
522
523struct ContainedArc<T: ?Sized> {
524  value: Option<Arc<T>>,
525}
526
527impl<T: ?Sized> ContainedArc<T> {
528  fn new(value: Arc<T>) -> Self {
529    Self { value: Some(value) }
530  }
531
532  fn get(&self) -> &T {
533    self
534      .value
535      .as_deref()
536      .expect("the contained Arc must remain owned")
537  }
538
539  #[cfg(test)]
540  fn into_inner(mut self) -> Arc<T> {
541    self
542      .value
543      .take()
544      .expect("the contained Arc must remain owned")
545  }
546}
547
548impl<T: ?Sized> Drop for ContainedArc<T> {
549  fn drop(&mut self) {
550    if let Some(value) = self.value.take() {
551      let _ = catch_unwind_contained(|| drop(value));
552    }
553  }
554}
555
556struct ContainedArcSnapshot<I: Copy, T: ?Sized> {
557  values: Vec<(I, Arc<T>)>,
558}
559
560impl<I: Copy, T: ?Sized> ContainedArcSnapshot<I, T> {
561  fn new(values: Vec<(I, Arc<T>)>) -> Self {
562    Self { values }
563  }
564
565  fn as_slice(&self) -> &[(I, Arc<T>)] {
566    &self.values
567  }
568
569  fn len(&self) -> usize {
570    self.values.len()
571  }
572
573  fn is_empty(&self) -> bool {
574    self.values.is_empty()
575  }
576
577  fn pop(&mut self) -> Option<(I, ContainedArc<T>)> {
578    self
579      .values
580      .pop()
581      .map(|(id, value)| (id, ContainedArc::new(value)))
582  }
583}
584
585impl<I: Copy, T: ?Sized> Drop for ContainedArcSnapshot<I, T> {
586  fn drop(&mut self) {
587    while let Some((_, value)) = self.values.pop() {
588      let _ = catch_unwind_contained(|| drop(value));
589    }
590  }
591}
592
593struct PanicContainedWaker {
594  waker: ManuallyDrop<Waker>,
595  generation: Option<u64>,
596}
597
598impl std::task::Wake for PanicContainedWaker {
599  fn wake(self: Arc<Self>) {
600    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
601    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
602  }
603
604  fn wake_by_ref(self: &Arc<Self>) {
605    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
606    let _ = catch_unwind_contained(|| self.waker.wake_by_ref());
607  }
608}
609
610impl Drop for PanicContainedWaker {
611  fn drop(&mut self) {
612    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
613    let _ = catch_unwind_contained(|| {
614      // SAFETY: this is the sole destruction path for the underlying waker.
615      unsafe { ManuallyDrop::drop(&mut self.waker) };
616    });
617  }
618}
619
620fn panic_contained_waker(waker: Waker) -> Waker {
621  Waker::from(Arc::new(PanicContainedWaker {
622    waker: ManuallyDrop::new(waker),
623    generation: active_runtime_generation(),
624  }))
625}
626
627struct PanicContainedWakerSource {
628  waker: ManuallyDrop<Waker>,
629  generation: Option<u64>,
630}
631
632impl PanicContainedWakerSource {
633  fn new(waker: Waker) -> Self {
634    Self {
635      waker: ManuallyDrop::new(waker),
636      generation: active_runtime_generation(),
637    }
638  }
639
640  fn as_waker(&self) -> &Waker {
641    &self.waker
642  }
643}
644
645impl Drop for PanicContainedWakerSource {
646  fn drop(&mut self) {
647    let _generation = RuntimeGenerationGuard::enter_optional(self.generation);
648    let _ = catch_unwind_contained(|| {
649      // SAFETY: this is the sole destruction path for the source waker.
650      unsafe { ManuallyDrop::drop(&mut self.waker) };
651    });
652  }
653}
654
655struct PanicContainedWakerCache {
656  source: PanicContainedWakerSource,
657  contained: Waker,
658}
659
660impl PanicContainedWakerCache {
661  fn new(waker: &Waker) -> Self {
662    let generation = active_runtime_generation();
663    let _generation = RuntimeGenerationGuard::enter_optional(generation);
664    let source = PanicContainedWakerSource::new(waker.clone());
665    let contained = panic_contained_waker(source.as_waker().clone());
666    Self { source, contained }
667  }
668
669  fn update(&mut self, waker: &Waker) {
670    let will_wake = {
671      let _generation = RuntimeGenerationGuard::enter_optional(self.source.generation);
672      self.source.as_waker().will_wake(waker)
673    };
674    if !will_wake {
675      *self = Self::new(waker);
676    }
677  }
678
679  fn contained(&self) -> &Waker {
680    &self.contained
681  }
682}
683
684fn wake_waker_contained(waker: Waker) {
685  // `Waker::wake` consumes the waker, so a panicking wake followed by a
686  // panicking RawWaker destructor would double-panic before the outer catch
687  // could regain control. Borrow for the wake and contain destruction
688  // separately.
689  let _ = catch_unwind_contained(|| waker.wake_by_ref());
690  let _ = catch_unwind_contained(|| drop(waker));
691}
692
693fn catch_unwind_join_error<T>(function: impl FnOnce() -> T) -> Result<T, JoinError> {
694  match catch_unwind(AssertUnwindSafe(function)) {
695    Ok(value) => Ok(value),
696    Err(payload) => {
697      let error = JoinError::from_panic(&*payload);
698      discard_panic_payload(payload);
699      Err(error)
700    }
701  }
702}
703
704fn panic_payload_to_join_error(payload: Box<dyn Any + Send + 'static>) -> JoinError {
705  let error = JoinError::from_panic(&*payload);
706  discard_panic_payload(payload);
707  error
708}
709
710struct ContainedTaskOutput<T> {
711  output: ManuallyDrop<T>,
712  generation: u64,
713}
714
715impl<T> ContainedTaskOutput<T> {
716  fn new(output: T, generation: u64) -> Self {
717    Self {
718      output: ManuallyDrop::new(output),
719      generation,
720    }
721  }
722
723  fn into_inner(mut self) -> T {
724    // SAFETY: ownership moves to the JoinHandle caller and `self` is then
725    // forgotten so its Drop implementation cannot run a second time.
726    let output = unsafe { ManuallyDrop::take(&mut self.output) };
727    std::mem::forget(self);
728    output
729  }
730}
731
732impl<T> Drop for ContainedTaskOutput<T> {
733  fn drop(&mut self) {
734    let _generation = RuntimeGenerationGuard::enter(self.generation);
735    let _ = catch_unwind_contained(|| {
736      // SAFETY: this is the sole destruction path while the output remains
737      // retained by a JoinHandle.
738      unsafe { ManuallyDrop::drop(&mut self.output) };
739    });
740  }
741}
742
743#[cfg(not(target_family = "wasm"))]
744#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
745struct BlockingJobId(u64);
746
747#[cfg(not(target_family = "wasm"))]
748static NEXT_BLOCKING_JOB_ID: AtomicU64 = AtomicU64::new(1);
749
750#[cfg(all(test, not(target_family = "wasm")))]
751thread_local! {
752  static DEPENDENCY_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
753    const { std::cell::RefCell::new(None) };
754  static CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
755    const { std::cell::RefCell::new(None) };
756  static CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK:
757    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
758      const { std::cell::RefCell::new(None) };
759  static CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK:
760    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
761      const { std::cell::RefCell::new(None) };
762  static CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK:
763    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
764      const { std::cell::RefCell::new(None) };
765  static CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
766    const { std::cell::RefCell::new(None) };
767  static CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
768    const { std::cell::RefCell::new(None) };
769  static CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK:
770    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
771      const { std::cell::RefCell::new(None) };
772  static CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK:
773    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
774      const { std::cell::RefCell::new(None) };
775  static MULTI_THREAD_WORK_CLAIMED_TEST_HOOK:
776    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
777      const { std::cell::RefCell::new(None) };
778  static MULTI_THREAD_WORK_STARTED_TEST_HOOK:
779    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
780      const { std::cell::RefCell::new(None) };
781  static DEPENDENCY_PREDICATE_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
782    const { std::cell::RefCell::new(None) };
783  static BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK:
784    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
785      const { std::cell::RefCell::new(None) };
786  static WORKER_TLS_DROP_PROBE: std::cell::RefCell<Option<WorkerTlsDropProbe>> =
787    const { std::cell::RefCell::new(None) };
788}
789
790#[cfg(all(test, not(target_family = "wasm")))]
791struct WorkerTlsDropProbe {
792  action: Option<WorkerTlsDropAction>,
793}
794
795// LOADER-LOCK RULE for every probe action below: TLS destructors run at
796// thread exit -- on Windows inside `LdrShutdownThread`, which HOLDS the
797// process loader lock while walking TLS callbacks. Any cross-thread wait
798// here (condvar, channel recv, barrier, joining a thread) therefore freezes
799// every other `thread::spawn` and thread-exit in the process -- and, with
800// RUST_BACKTRACE=1, even panic reporting (dbghelp loads DLLs under the same
801// lock). A previous `Wait { entered, release }` action parked the FIRST
802// exiting worker's destructor on a condvar; the SECOND worker then could not
803// even enter its destructor (loader lock), the release condition (both
804// entered) became unsatisfiable, and the whole test suite went silent until
805// the CI step timeout (Windows x64 jobs, runs 29195160953 / 86662331487).
806// Destructor actions may only RECORD (atomic stores, non-blocking sends);
807// ordering invariants are asserted from OUTSIDE, after `shutdown()` returns.
808#[cfg(all(test, not(target_family = "wasm")))]
809enum WorkerTlsDropAction {
810  /// Record-only: bump the shared counter so the harness can assert -- the
811  /// instant `shutdown()` returns -- that every worker TLS destructor had
812  /// already completed (the join barrier includes destructor completion).
813  Record { completed: Arc<AtomicUsize> },
814  ReenterLifecycle {
815    controller: Arc<RuntimeController>,
816    completed: std::sync::mpsc::Sender<(Result<(), String>, Result<(), String>)>,
817  },
818}
819
820#[cfg(all(test, not(target_family = "wasm")))]
821impl Drop for WorkerTlsDropProbe {
822  fn drop(&mut self) {
823    match self
824      .action
825      .take()
826      .expect("worker TLS drop probe action already consumed")
827    {
828      WorkerTlsDropAction::Record { completed } => {
829        completed.fetch_add(1, Ordering::SeqCst);
830      }
831      // Both lifecycle re-entries FAIL FAST from a stopping-generation worker
832      // (short state-mutex acquisitions, no cross-thread wait), and the send
833      // is non-blocking -- loader-lock safe. The owning test additionally
834      // isolates itself in a watchdogged subprocess.
835      WorkerTlsDropAction::ReenterLifecycle {
836        controller,
837        completed,
838      } => {
839        let start = controller.start().map_err(|error| error.to_string());
840        let shutdown = controller.shutdown().map_err(|error| error.to_string());
841        let _ = completed.send((start, shutdown));
842      }
843    }
844  }
845}
846
847#[cfg(all(test, not(target_family = "wasm")))]
848fn run_dependency_claim_test_hook() {
849  if let Some(hook) = DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
850    hook();
851  }
852}
853
854#[cfg(all(test, not(target_family = "wasm")))]
855fn run_current_thread_dispatch_claim_test_hook() {
856  if let Some(hook) = CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
857  {
858    hook();
859  }
860}
861
862#[cfg(all(test, not(target_family = "wasm")))]
863fn run_current_thread_dispatch_publication_blocked_test_hook() {
864  if let Some(hook) =
865    CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
866  {
867    hook();
868  }
869}
870
871#[cfg(all(test, not(target_family = "wasm")))]
872fn run_current_thread_dispatch_recovery_test_hook() {
873  if let Some(hook) =
874    CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| slot.borrow_mut().take())
875  {
876    hook();
877  }
878}
879
880#[cfg(all(test, not(target_family = "wasm")))]
881fn run_current_thread_failed_publication_retired_test_hook() {
882  if let Some(hook) =
883    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
884  {
885    hook();
886  }
887}
888
889#[cfg(all(test, not(target_family = "wasm")))]
890fn run_current_thread_dispatch_call_test_hook() {
891  if let Some(hook) = CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
892    hook();
893  }
894}
895
896#[cfg(all(test, not(target_family = "wasm")))]
897fn run_current_thread_dispatch_returned_test_hook() {
898  if let Some(hook) =
899    CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| slot.borrow_mut().take())
900  {
901    hook();
902  }
903}
904
905#[cfg(all(test, not(target_family = "wasm")))]
906fn run_current_thread_blocking_claim_test_hook() {
907  if let Some(hook) = CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
908  {
909    hook();
910  }
911}
912
913#[cfg(all(test, not(target_family = "wasm")))]
914fn run_current_thread_runnable_claim_test_hook() {
915  if let Some(hook) = CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
916  {
917    hook();
918  }
919}
920
921#[cfg(all(test, not(target_family = "wasm")))]
922fn run_multi_thread_work_claimed_test_hook() {
923  if let Some(hook) = MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
924    hook();
925  }
926}
927
928#[cfg(all(test, not(target_family = "wasm")))]
929fn run_multi_thread_work_started_test_hook() {
930  if let Some(hook) = MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
931    hook();
932  }
933}
934
935#[cfg(all(test, not(target_family = "wasm")))]
936fn run_dependency_predicate_test_hook() {
937  if let Some(hook) = DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
938    hook();
939  }
940}
941
942#[cfg(all(test, not(target_family = "wasm")))]
943fn run_before_owned_blocking_queue_claim_test_hook() {
944  if let Some(hook) =
945    BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| slot.borrow_mut().take())
946  {
947    hook();
948  }
949}
950
951#[cfg(not(target_family = "wasm"))]
952#[derive(Clone, Copy, Debug, PartialEq, Eq)]
953struct BlockingDependency {
954  job: BlockingJobId,
955  owner: Option<BlockingOwnerToken>,
956}
957
958#[cfg(not(target_family = "wasm"))]
959#[derive(Debug)]
960struct DependencyClaim {
961  claimed: AtomicBool,
962  transition: Mutex<()>,
963}
964
965#[cfg(not(target_family = "wasm"))]
966impl DependencyClaim {
967  fn new() -> Self {
968    Self {
969      claimed: AtomicBool::new(false),
970      transition: Mutex::new(()),
971    }
972  }
973
974  fn is_live(&self) -> bool {
975    !self.claimed.load(Ordering::Acquire)
976  }
977
978  fn cancel_link(&self, link: &DependencyLink) {
979    let _transition = self
980      .transition
981      .lock()
982      .unwrap_or_else(std::sync::PoisonError::into_inner);
983    link.cancel();
984  }
985
986  fn try_claim(&self, link: &DependencyLink) -> bool {
987    let _transition = self
988      .transition
989      .lock()
990      .unwrap_or_else(std::sync::PoisonError::into_inner);
991    #[cfg(test)]
992    run_dependency_claim_test_hook();
993    if self.claimed.load(Ordering::Acquire) || !link.is_live() {
994      return false;
995    }
996    self.claimed.store(true, Ordering::Release);
997    true
998  }
999}
1000
1001#[cfg(not(target_family = "wasm"))]
1002#[derive(Debug)]
1003struct DependencyLink {
1004  live: AtomicBool,
1005  parent: Option<Weak<DependencyLink>>,
1006}
1007
1008#[cfg(not(target_family = "wasm"))]
1009impl DependencyLink {
1010  fn root() -> Arc<Self> {
1011    Arc::new(Self {
1012      live: AtomicBool::new(true),
1013      parent: None,
1014    })
1015  }
1016
1017  fn derived(parent: &Arc<Self>) -> Arc<Self> {
1018    Arc::new(Self {
1019      live: AtomicBool::new(true),
1020      parent: Some(Arc::downgrade(parent)),
1021    })
1022  }
1023
1024  fn is_live(&self) -> bool {
1025    if !self.live.load(Ordering::Acquire) {
1026      return false;
1027    }
1028    let Some(parent) = &self.parent else {
1029      return true;
1030    };
1031    let Some(mut current) = parent.upgrade() else {
1032      return false;
1033    };
1034    loop {
1035      if !current.live.load(Ordering::Acquire) {
1036        return false;
1037      }
1038      let Some(parent) = &current.parent else {
1039        return true;
1040      };
1041      let Some(next) = parent.upgrade() else {
1042        return false;
1043      };
1044      current = next;
1045    }
1046  }
1047
1048  fn cancel(&self) {
1049    self.live.store(false, Ordering::Release);
1050  }
1051}
1052
1053#[cfg(not(target_family = "wasm"))]
1054#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1055struct DependencyPublicationIdentity {
1056  job: BlockingJobId,
1057  claim: *const DependencyClaim,
1058  link: *const DependencyLink,
1059}
1060
1061#[cfg(not(target_family = "wasm"))]
1062#[derive(Clone, Debug)]
1063struct DependencyPublication {
1064  dependency: BlockingDependency,
1065  claim: Arc<DependencyClaim>,
1066  link: Arc<DependencyLink>,
1067}
1068
1069#[cfg(not(target_family = "wasm"))]
1070impl DependencyPublication {
1071  fn owned(dependency: BlockingDependency) -> Self {
1072    Self {
1073      dependency,
1074      claim: Arc::new(DependencyClaim::new()),
1075      link: DependencyLink::root(),
1076    }
1077  }
1078
1079  fn derived(&self, owner: Option<BlockingOwnerToken>) -> Self {
1080    Self {
1081      dependency: BlockingDependency {
1082        owner: self.dependency.owner.or(owner),
1083        ..self.dependency
1084      },
1085      claim: Arc::clone(&self.claim),
1086      link: DependencyLink::derived(&self.link),
1087    }
1088  }
1089
1090  fn with_owner_if_none(mut self, owner: Option<BlockingOwnerToken>) -> Self {
1091    self.dependency.owner = self.dependency.owner.or(owner);
1092    self
1093  }
1094
1095  fn same_as(&self, other: &Self) -> bool {
1096    self.dependency == other.dependency
1097      && Arc::ptr_eq(&self.claim, &other.claim)
1098      && Arc::ptr_eq(&self.link, &other.link)
1099  }
1100
1101  fn identity(&self) -> DependencyPublicationIdentity {
1102    DependencyPublicationIdentity {
1103      job: self.dependency.job,
1104      claim: Arc::as_ptr(&self.claim),
1105      link: Arc::as_ptr(&self.link),
1106    }
1107  }
1108
1109  fn immediate_source_identity(&self) -> Option<DependencyPublicationIdentity> {
1110    Some(DependencyPublicationIdentity {
1111      job: self.dependency.job,
1112      claim: Arc::as_ptr(&self.claim),
1113      link: self.link.parent.as_ref()?.as_ptr(),
1114    })
1115  }
1116
1117  fn is_derived_from(&self, source: &Self) -> bool {
1118    self.immediate_source_identity() == Some(source.identity())
1119  }
1120
1121  fn is_live(&self) -> bool {
1122    self.claim.is_live() && self.link.is_live()
1123  }
1124
1125  fn cancel(&self) {
1126    self.claim.cancel_link(&self.link);
1127  }
1128
1129  fn try_claim(&self) -> bool {
1130    self.claim.try_claim(&self.link)
1131  }
1132}
1133
1134#[cfg(not(target_family = "wasm"))]
1135struct TaskDependencyEntry {
1136  sequence: u64,
1137  publication: DependencyPublication,
1138}
1139
1140#[cfg(not(target_family = "wasm"))]
1141struct SelectedDependencyPublication {
1142  sequence: u64,
1143  publication: DependencyPublication,
1144}
1145
1146#[cfg(not(target_family = "wasm"))]
1147#[derive(Clone, Copy)]
1148enum OwnerDependencyMode {
1149  Available,
1150  Exact,
1151}
1152
1153#[cfg(not(target_family = "wasm"))]
1154#[derive(Default)]
1155struct TaskDependencyState {
1156  current: VecDeque<TaskDependencyEntry>,
1157  next_sequence: u64,
1158  unowned_sequences: VecDeque<u64>,
1159  owner_sequences: FxHashMap<BlockingOwnerToken, VecDeque<u64>>,
1160  // NOTE: there is deliberately no `job_sequences` index. It used to be a
1161  // `FxHashMap<BlockingJobId, FxHashSet<u64>>` costing TWO heap allocations
1162  // per blocking dispatch (the map's table plus an inner set that almost
1163  // always held exactly one sequence). Its only job was to let a dropped
1164  // blocking `JoinHandle` find the entry it had published, and the handle can
1165  // simply remember the `sequence` `append` gave it: `current` is positionally
1166  // indexed by sequence via `entry_in`, so the handle retires its own entry in
1167  // O(1) with no map, no hash, and no scan. `next_sequence` never rewinds, so
1168  // a remembered sequence can never alias a later entry.
1169  waiter: Option<Waker>,
1170}
1171
1172#[cfg(not(target_family = "wasm"))]
1173impl TaskDependencyState {
1174  /// Publish `publication` and return the sequence it was filed under, so the
1175  /// publisher can retire exactly this entry later in O(1).
1176  fn append(&mut self, publication: DependencyPublication) -> u64 {
1177    let sequence = self.next_sequence;
1178    self.next_sequence = self
1179      .next_sequence
1180      .checked_add(1)
1181      .expect("task dependency sequence space exhausted");
1182    match publication.dependency.owner {
1183      Some(owner) => self
1184        .owner_sequences
1185        .entry(owner)
1186        .or_default()
1187        .push_back(sequence),
1188      None => self.unowned_sequences.push_back(sequence),
1189    }
1190    self.current.push_back(TaskDependencyEntry {
1191      sequence,
1192      publication,
1193    });
1194    sequence
1195  }
1196
1197  fn entry_in(
1198    current: &VecDeque<TaskDependencyEntry>,
1199    sequence: u64,
1200  ) -> Option<&TaskDependencyEntry> {
1201    let front = current.front()?.sequence;
1202    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1203    let entry = current.get(index)?;
1204    (entry.sequence == sequence).then_some(entry)
1205  }
1206
1207  fn entry(&self, sequence: u64) -> Option<&TaskDependencyEntry> {
1208    Self::entry_in(&self.current, sequence)
1209  }
1210
1211  fn entry_mut(&mut self, sequence: u64) -> Option<&mut TaskDependencyEntry> {
1212    let front = self.current.front()?.sequence;
1213    let index = usize::try_from(sequence.checked_sub(front)?).ok()?;
1214    let entry = self.current.get_mut(index)?;
1215    (entry.sequence == sequence).then_some(entry)
1216  }
1217
1218  fn remove_sequence_indexes(&mut self, sequence: u64, dependency: BlockingDependency) {
1219    match dependency.owner {
1220      Some(owner) => {
1221        let remove_owner = self
1222          .owner_sequences
1223          .get_mut(&owner)
1224          .is_some_and(|sequences| {
1225            if sequences.front() == Some(&sequence) {
1226              sequences.pop_front();
1227            }
1228            sequences.is_empty()
1229          });
1230        if remove_owner {
1231          self.owner_sequences.remove(&owner);
1232        }
1233      }
1234      None => {
1235        if self.unowned_sequences.front() == Some(&sequence) {
1236          self.unowned_sequences.pop_front();
1237        }
1238      }
1239    }
1240  }
1241
1242  fn clear_indexes(&mut self) {
1243    self.unowned_sequences.clear();
1244    self.owner_sequences.clear();
1245  }
1246}
1247
1248#[cfg(not(target_family = "wasm"))]
1249struct TaskDependency {
1250  state: Mutex<TaskDependencyState>,
1251  owner_hint: Option<BlockingOwnerToken>,
1252  has_current: AtomicBool,
1253  generation: u64,
1254  #[cfg(test)]
1255  entry_visits: AtomicUsize,
1256}
1257
1258#[cfg(not(target_family = "wasm"))]
1259impl Default for TaskDependency {
1260  fn default() -> Self {
1261    Self::new(u64::MAX, None)
1262  }
1263}
1264
1265#[cfg(not(target_family = "wasm"))]
1266impl TaskDependency {
1267  fn new(generation: u64, owner_hint: Option<BlockingOwnerToken>) -> Self {
1268    Self {
1269      state: Mutex::new(TaskDependencyState::default()),
1270      owner_hint,
1271      has_current: AtomicBool::new(false),
1272      generation,
1273      #[cfg(test)]
1274      entry_visits: AtomicUsize::new(0),
1275    }
1276  }
1277
1278  fn new_task(generation: u64) -> Self {
1279    Self::new(generation, None)
1280  }
1281
1282  fn notify_waiter(&self, waiter: Option<Waker>) {
1283    if let Some(waiter) = waiter {
1284      let _generation = RuntimeGenerationGuard::enter(self.generation);
1285      wake_waker_contained(waiter);
1286    }
1287  }
1288
1289  fn clear_waiter(&self) {
1290    let waiter = self
1291      .state
1292      .lock()
1293      .unwrap_or_else(std::sync::PoisonError::into_inner)
1294      .waiter
1295      .take();
1296    if let Some(waiter) = waiter {
1297      let _generation = RuntimeGenerationGuard::enter(self.generation);
1298      let _ = catch_unwind_contained(|| drop(waiter));
1299    }
1300  }
1301
1302  fn clear(&self) {
1303    let waiter = {
1304      let mut state = self
1305        .state
1306        .lock()
1307        .unwrap_or_else(std::sync::PoisonError::into_inner);
1308      if state.current.is_empty() {
1309        None
1310      } else {
1311        for current in state.current.drain(..) {
1312          current.publication.cancel();
1313        }
1314        state.clear_indexes();
1315        self.has_current.store(false, Ordering::Release);
1316        state.waiter.take()
1317      }
1318    };
1319    self.notify_waiter(waiter);
1320  }
1321
1322  /// Retire the single entry filed under `sequence`, in O(1).
1323  ///
1324  /// This replaces a job-id lookup through a `job_sequences` index. `current`
1325  /// is positionally indexed by sequence, so the publisher that remembers the
1326  /// sequence `append` returned can cancel exactly its own entry without a
1327  /// map and without a scan. `entry_in` re-checks `entry.sequence == sequence`
1328  /// after the positional lookup and `next_sequence` never rewinds, so a
1329  /// sequence whose entry has already been drained (`begin_poll`) or pruned
1330  /// resolves to `None` rather than aliasing a later entry.
1331  fn retire(&self, sequence: u64) {
1332    let waiter = {
1333      let mut state = self
1334        .state
1335        .lock()
1336        .unwrap_or_else(std::sync::PoisonError::into_inner);
1337      let cancelled = match state.entry_mut(sequence) {
1338        Some(current) => {
1339          self.note_entry_visit();
1340          current.publication.cancel();
1341          true
1342        }
1343        None => false,
1344      };
1345      if cancelled {
1346        self.prune_stale_prefix(&mut state);
1347        state.waiter.take()
1348      } else {
1349        None
1350      }
1351    };
1352    self.notify_waiter(waiter);
1353  }
1354
1355  fn clear_if_publications(&self, publications: &[DependencyPublication]) {
1356    let Some(first_publication) = publications.first() else {
1357      return;
1358    };
1359    let publication_identities = (publications.len() > 1).then(|| {
1360      publications
1361        .iter()
1362        .map(DependencyPublication::identity)
1363        .collect::<FxHashSet<_>>()
1364    });
1365    let waiter = {
1366      let mut state = self
1367        .state
1368        .lock()
1369        .unwrap_or_else(std::sync::PoisonError::into_inner);
1370      let removed = state
1371        .current
1372        .iter_mut()
1373        .filter_map(|current| {
1374          self.note_entry_visit();
1375          let is_derived = publication_identities.as_ref().map_or_else(
1376            || current.publication.is_derived_from(first_publication),
1377            |identities| {
1378              current
1379                .publication
1380                .immediate_source_identity()
1381                .is_some_and(|identity| identities.contains(&identity))
1382            },
1383          );
1384          if !is_derived {
1385            return None;
1386          }
1387          current.publication.cancel();
1388          Some(current.sequence)
1389        })
1390        .collect::<Vec<_>>();
1391      // No `job_sequences` index to maintain; the cancelled entries stay in
1392      // `current` until `prune_stale_prefix` retires them, exactly as before.
1393      if removed.is_empty() {
1394        None
1395      } else {
1396        self.prune_stale_prefix(&mut state);
1397        state.waiter.take()
1398      }
1399    };
1400    self.notify_waiter(waiter);
1401  }
1402
1403  /// Publish an owned dependency and return the sequence it was filed under,
1404  /// so the caller can [`retire`](Self::retire) exactly this entry in O(1).
1405  fn set_owned(&self, dependency: BlockingDependency) -> u64 {
1406    let publication = DependencyPublication::owned(BlockingDependency {
1407      owner: dependency.owner.or(self.owner_hint),
1408      ..dependency
1409    });
1410    self.set_entry(publication)
1411  }
1412
1413  /// Whether `sequence` still resolves to a LIVE entry in this state.
1414  ///
1415  /// A publisher uses this to decide whether its earlier publication still
1416  /// stands. `false` means the entry was drained by `begin_poll`, pruned, or
1417  /// cancelled (by a lending claim, say), so a fresh publication is needed.
1418  fn holds_live(&self, sequence: u64) -> bool {
1419    let state = self
1420      .state
1421      .lock()
1422      .unwrap_or_else(std::sync::PoisonError::into_inner);
1423    TaskDependencyState::entry_in(&state.current, sequence).is_some_and(|entry| {
1424      self.note_entry_visit();
1425      entry.publication.is_live()
1426    })
1427  }
1428
1429  fn set_borrowed(&self, publication: &DependencyPublication) {
1430    let _sequence = self.set_entry(publication.derived(self.owner_hint));
1431  }
1432
1433  fn set_entry(&self, publication: DependencyPublication) -> u64 {
1434    let (sequence, waiter) = {
1435      let mut state = self
1436        .state
1437        .lock()
1438        .unwrap_or_else(std::sync::PoisonError::into_inner);
1439      // Owned publications and propagated publications both create a fresh
1440      // local liveness link, so identity cannot already exist in this state.
1441      let sequence = state.append(publication);
1442      self.has_current.store(true, Ordering::Release);
1443      (sequence, state.waiter.take())
1444    };
1445    self.notify_waiter(waiter);
1446    sequence
1447  }
1448
1449  fn note_entry_visit(&self) {
1450    #[cfg(test)]
1451    self.entry_visits.fetch_add(1, Ordering::Relaxed);
1452  }
1453
1454  fn prune_stale_prefix(&self, state: &mut TaskDependencyState) {
1455    while state.current.front().is_some_and(|current| {
1456      self.note_entry_visit();
1457      !current.publication.is_live()
1458    }) {
1459      let current = state
1460        .current
1461        .pop_front()
1462        .expect("the stale prefix entry must exist");
1463      state.remove_sequence_indexes(current.sequence, current.publication.dependency);
1464    }
1465    self
1466      .has_current
1467      .store(!state.current.is_empty(), Ordering::Release);
1468  }
1469
1470  fn first_live_sequence(
1471    &self,
1472    current: &VecDeque<TaskDependencyEntry>,
1473    sequences: &mut VecDeque<u64>,
1474  ) -> Option<u64> {
1475    loop {
1476      let sequence = *sequences.front()?;
1477      let Some(entry) = TaskDependencyState::entry_in(current, sequence) else {
1478        sequences.pop_front();
1479        continue;
1480      };
1481      self.note_entry_visit();
1482      if entry.publication.is_live() {
1483        return Some(sequence);
1484      }
1485      sequences.pop_front();
1486    }
1487  }
1488
1489  fn first_live_owner_sequence(
1490    &self,
1491    state: &mut TaskDependencyState,
1492    owner: BlockingOwnerToken,
1493  ) -> Option<u64> {
1494    let sequence = {
1495      let current = &state.current;
1496      state
1497        .owner_sequences
1498        .get_mut(&owner)
1499        .and_then(|sequences| self.first_live_sequence(current, sequences))
1500    };
1501    if sequence.is_none() {
1502      state.owner_sequences.remove(&owner);
1503    }
1504    sequence
1505  }
1506
1507  fn first_live_unowned_sequence(&self, state: &mut TaskDependencyState) -> Option<u64> {
1508    self.first_live_sequence(&state.current, &mut state.unowned_sequences)
1509  }
1510
1511  #[cfg(test)]
1512  fn get(&self) -> Option<DependencyPublication> {
1513    self
1514      .state
1515      .lock()
1516      .unwrap_or_else(std::sync::PoisonError::into_inner)
1517      .current
1518      .iter()
1519      .find(|current| current.publication.is_live())
1520      .map(|current| current.publication.clone())
1521  }
1522
1523  fn snapshot_all_live(&self) -> Vec<DependencyPublication> {
1524    self
1525      .state
1526      .lock()
1527      .unwrap_or_else(std::sync::PoisonError::into_inner)
1528      .current
1529      .iter()
1530      .filter(|current| {
1531        self.note_entry_visit();
1532        current.publication.is_live()
1533      })
1534      .map(|current| current.publication.clone())
1535      .collect()
1536  }
1537
1538  fn select_for_owner(&self, owner: BlockingOwnerToken) -> Option<SelectedDependencyPublication> {
1539    let mut state = self
1540      .state
1541      .lock()
1542      .unwrap_or_else(std::sync::PoisonError::into_inner);
1543    self.prune_stale_prefix(&mut state);
1544    let exact_sequence = self.first_live_owner_sequence(&mut state, owner);
1545    let unowned_sequence = self.first_live_unowned_sequence(&mut state);
1546    let (sequence, bind_owner) = match (exact_sequence, unowned_sequence) {
1547      (Some(exact), Some(unowned)) if exact <= unowned => (exact, false),
1548      (Some(_) | None, Some(unowned)) => (unowned, true),
1549      (Some(exact), None) => (exact, false),
1550      (None, None) => return None,
1551    };
1552    if bind_owner {
1553      let removed = state.unowned_sequences.pop_front();
1554      debug_assert_eq!(removed, Some(sequence));
1555      let current = state
1556        .entry_mut(sequence)
1557        .expect("the selected dependency must remain indexed");
1558      debug_assert_eq!(current.publication.dependency.owner, None);
1559      current.publication.dependency.owner = Some(owner);
1560      // A previous exact-owner query may have observed no entry. Publishing
1561      // this sequence at the front makes the newly bound dependency visible
1562      // immediately without restarting a global scan.
1563      state
1564        .owner_sequences
1565        .entry(owner)
1566        .or_default()
1567        .push_front(sequence);
1568    }
1569    let publication = state
1570      .entry(sequence)
1571      .expect("the selected dependency must remain indexed")
1572      .publication
1573      .clone();
1574    Some(SelectedDependencyPublication {
1575      sequence,
1576      publication,
1577    })
1578  }
1579
1580  fn has_live_owner_dependency(
1581    &self,
1582    owner: BlockingOwnerToken,
1583    mode: OwnerDependencyMode,
1584  ) -> bool {
1585    #[cfg(all(test, not(target_family = "wasm")))]
1586    run_dependency_predicate_test_hook();
1587    let mut state = self
1588      .state
1589      .lock()
1590      .unwrap_or_else(std::sync::PoisonError::into_inner);
1591    self.prune_stale_prefix(&mut state);
1592    if self.first_live_owner_sequence(&mut state, owner).is_some() {
1593      return true;
1594    }
1595    matches!(mode, OwnerDependencyMode::Available)
1596      && self.first_live_unowned_sequence(&mut state).is_some()
1597  }
1598
1599  #[cfg(test)]
1600  fn get_dependency(&self) -> Option<BlockingDependency> {
1601    self.get().map(|publication| publication.dependency)
1602  }
1603
1604  #[cfg(test)]
1605  fn get_dependencies(&self) -> Vec<BlockingDependency> {
1606    self
1607      .snapshot_all_live()
1608      .into_iter()
1609      .map(|publication| publication.dependency)
1610      .collect()
1611  }
1612
1613  #[cfg(test)]
1614  fn reset_entry_visits(&self) {
1615    self.entry_visits.store(0, Ordering::Relaxed);
1616  }
1617
1618  #[cfg(test)]
1619  fn entry_visits(&self) -> usize {
1620    self.entry_visits.load(Ordering::Relaxed)
1621  }
1622
1623  /// Atomically reserve `job` for exact-dependency execution. A dependency
1624  /// transition that wins this race makes the claim fail, so lending can never
1625  /// execute a stale job copied from an earlier poll.
1626  fn try_claim(&self, selected: &SelectedDependencyPublication) -> bool {
1627    let mut state = self
1628      .state
1629      .lock()
1630      .unwrap_or_else(std::sync::PoisonError::into_inner);
1631    let Some(current) = state.entry(selected.sequence) else {
1632      return false;
1633    };
1634    self.note_entry_visit();
1635    if !current.publication.same_as(&selected.publication) || !selected.publication.try_claim() {
1636      return false;
1637    }
1638    let dependency = current.publication.dependency;
1639    state
1640      .entry_mut(selected.sequence)
1641      .expect("the claimed dependency must remain indexed")
1642      .publication
1643      .cancel();
1644    state.remove_sequence_indexes(selected.sequence, dependency);
1645    self.prune_stale_prefix(&mut state);
1646    true
1647  }
1648
1649  fn register_waiter_and_get(&self, waker: &Waker) -> Vec<DependencyPublication> {
1650    let _generation = RuntimeGenerationGuard::enter(self.generation);
1651    let Some(waker) = catch_unwind_contained(|| waker.clone()) else {
1652      return Vec::new();
1653    };
1654    let (previous, unused, current) = {
1655      let mut state = self
1656        .state
1657        .lock()
1658        .unwrap_or_else(std::sync::PoisonError::into_inner);
1659      let current = state
1660        .current
1661        .iter()
1662        .filter(|current| current.publication.is_live())
1663        .map(|current| current.publication.clone())
1664        .collect();
1665      if state
1666        .waiter
1667        .as_ref()
1668        .is_some_and(|previous| previous.will_wake(&waker))
1669      {
1670        (None, Some(waker), current)
1671      } else {
1672        (state.waiter.replace(waker), None, current)
1673      }
1674    };
1675    if let Some(previous) = previous {
1676      let _ = catch_unwind_contained(|| drop(previous));
1677    }
1678    if let Some(unused) = unused {
1679      let _ = catch_unwind_contained(|| drop(unused));
1680    }
1681    current
1682  }
1683
1684  fn begin_poll(&self) {
1685    if !self.has_current.load(Ordering::Acquire) {
1686      return;
1687    }
1688    let waiter = {
1689      let mut state = self
1690        .state
1691        .lock()
1692        .unwrap_or_else(std::sync::PoisonError::into_inner);
1693      if state.current.is_empty() {
1694        self.has_current.store(false, Ordering::Release);
1695        None
1696      } else {
1697        for previous in state.current.drain(..) {
1698          previous.publication.cancel();
1699        }
1700        state.clear_indexes();
1701        self.has_current.store(false, Ordering::Release);
1702        state.waiter.take()
1703      }
1704    };
1705    self.notify_waiter(waiter);
1706  }
1707
1708  #[cfg(test)]
1709  fn register_waiter(&self, waker: &Waker) {
1710    let _ = self.register_waiter_and_get(waker);
1711  }
1712}
1713
1714#[cfg(not(target_family = "wasm"))]
1715impl Drop for TaskDependency {
1716  fn drop(&mut self) {
1717    let state = self
1718      .state
1719      .get_mut()
1720      .unwrap_or_else(std::sync::PoisonError::into_inner);
1721    for current in state.current.drain(..) {
1722      current.publication.cancel();
1723    }
1724    if let Some(waiter) = state.waiter.take() {
1725      let _generation = RuntimeGenerationGuard::enter(self.generation);
1726      let _ = catch_unwind_contained(|| drop(waiter));
1727    }
1728  }
1729}
1730
1731#[cfg(not(target_family = "wasm"))]
1732thread_local! {
1733  static CURRENT_DEPENDENCY_STACK: std::cell::RefCell<Vec<Arc<TaskDependency>>> =
1734    const { std::cell::RefCell::new(Vec::new()) };
1735}
1736
1737#[cfg(not(target_family = "wasm"))]
1738struct TaskDependencyGuard(Arc<TaskDependency>);
1739
1740#[cfg(not(target_family = "wasm"))]
1741impl TaskDependencyGuard {
1742  fn enter(dependency: &Arc<TaskDependency>) -> Self {
1743    let dependency = Arc::clone(dependency);
1744    CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow_mut().push(Arc::clone(&dependency)));
1745    Self(dependency)
1746  }
1747}
1748
1749#[cfg(not(target_family = "wasm"))]
1750impl Drop for TaskDependencyGuard {
1751  fn drop(&mut self) {
1752    CURRENT_DEPENDENCY_STACK.with(|stack| {
1753      let current = stack
1754        .borrow_mut()
1755        .pop()
1756        .expect("blocking dependency context underflow");
1757      debug_assert!(Arc::ptr_eq(&current, &self.0));
1758    });
1759  }
1760}
1761
1762#[cfg(not(target_family = "wasm"))]
1763struct BlockOnDependencyGuard {
1764  dependency: Arc<TaskDependency>,
1765  _context: TaskDependencyGuard,
1766}
1767
1768#[cfg(not(target_family = "wasm"))]
1769impl BlockOnDependencyGuard {
1770  fn enter(generation: u64) -> Self {
1771    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1772    let dependency = Arc::new(TaskDependency::new(generation, owner));
1773    let context = TaskDependencyGuard::enter(&dependency);
1774    Self {
1775      dependency,
1776      _context: context,
1777    }
1778  }
1779
1780  fn clear(&self) {
1781    self.dependency.clear();
1782  }
1783}
1784
1785/// A dependency this handle published, remembered well enough to retire it in
1786/// O(1).
1787///
1788/// The sequence alone is not enough: sequences are allocated per
1789/// `TaskDependency`, so applying one to a different state would cancel an
1790/// unrelated entry. The state's identity therefore travels with it, as a
1791/// `Weak` rather than a raw pointer so a freed state cannot be impersonated by
1792/// a later allocation reusing its address -- a `Weak` keeps the allocation
1793/// itself reserved, so the pointer stays unique even after the state is gone.
1794///
1795/// It is deliberately NOT a strong `Arc`: a handle can be polled under many
1796/// frames over its life, and holding each one alive to remember it would retain
1797/// one dead `TaskDependency`, with its deque and index maps, per frame the
1798/// handle has ever seen.
1799#[cfg(not(target_family = "wasm"))]
1800struct PublishedBlockingDependency {
1801  state: Weak<TaskDependency>,
1802  sequence: u64,
1803}
1804
1805/// Every publication a blocking handle has made, one per dependency frame it
1806/// has been polled under.
1807///
1808/// A handle publishes once per Pending poll into whichever frame is on top of
1809/// `CURRENT_DEPENDENCY_STACK`. Re-polling under a frame it has already
1810/// published into supersedes that frame's entry, so the common case is exactly
1811/// one publication and `rest` never allocates.
1812///
1813/// Retiring EVERY publication is load-bearing. Dropping a blocking handle
1814/// detaches rather than dequeues, so any entry left live keeps a job that
1815/// nobody awaits selectable for owner-lane lending -- and `select_for_owner`
1816/// picks the LOWEST live sequence, so a forgotten entry outranks dependencies
1817/// published after it. Lending then runs the detached job inline in the owner's
1818/// single lane, which deadlocks if that job waits on the dependency it
1819/// displaced. The job-id index this replaces cancelled every entry for the job
1820/// in the frame it was called on, so nothing was forgotten; a handle that
1821/// remembered only its newest publication would forget both a repeat poll in
1822/// one frame and a poll under a nested frame.
1823#[cfg(not(target_family = "wasm"))]
1824#[derive(Default)]
1825struct PublishedBlockingDependencies {
1826  first: Option<PublishedBlockingDependency>,
1827  rest: Vec<PublishedBlockingDependency>,
1828}
1829
1830#[cfg(not(target_family = "wasm"))]
1831impl PublishedBlockingDependency {
1832  /// Whether this publication is still worth remembering.
1833  ///
1834  /// A token stops standing when its frame is gone -- a dropped
1835  /// `TaskDependency` already cancelled every entry it held -- or when the
1836  /// frame no longer holds the entry as live, because its `begin_poll` drained
1837  /// `current`, `prune_stale_prefix` retired it, or a lending claim took it.
1838  ///
1839  /// Absence from this thread's dependency stack deliberately does NOT count.
1840  /// It would bound this list more tightly, but it is not sound: a `JoinHandle`
1841  /// is `Send + Unpin`, so a safe adapter can serialise one behind a mutex and
1842  /// poll it from several tasks, and then a frame parked on another thread can
1843  /// still be awaiting a handle that is being polled here. Retiring on local
1844  /// absence would take that frame's publication away while it still needs it
1845  /// for owner-lane lending.
1846  fn still_stands(&self) -> bool {
1847    self
1848      .state
1849      .upgrade()
1850      .is_some_and(|state| state.holds_live(self.sequence))
1851  }
1852}
1853
1854#[cfg(not(target_family = "wasm"))]
1855impl PublishedBlockingDependencies {
1856  /// Forget publications whose frame is gone.
1857  ///
1858  /// A dropped `TaskDependency` cancels every entry it holds, so an expired
1859  /// token has nothing left to retire. Discarding it here is what keeps this
1860  /// list at the handle's currently-live frame count rather than the total
1861  /// number of frames it has ever been polled under: without it, a pending
1862  /// handle carried through a sequence of completed tasks would accumulate one
1863  /// token per task and the lookup below would grow with it.
1864  /// Forget publications that no longer stand.
1865  ///
1866  /// A token is obsolete once its frame is gone -- a dropped `TaskDependency`
1867  /// cancels every entry it holds, so there is nothing left to retire -- or
1868  /// once that frame no longer holds the entry, because its `begin_poll`
1869  /// drained `current`. Dropping both kinds keeps this list at the handle's
1870  /// currently-live frame count rather than the number of frames it has ever
1871  /// been polled under.
1872  ///
1873  /// Testing the second condition costs a lock, so it is confined to the
1874  /// multi-frame path: a handle polled under a single frame -- the
1875  /// overwhelmingly common case -- reuses its one token and never reaches it.
1876  /// Without that condition a handle carried through many tasks whose
1877  /// `JoinHandle`s are still held would keep one token per task, since those
1878  /// handles keep the states alive, and both this walk and detach would grow
1879  /// with that count.
1880  fn prune(&mut self) {
1881    if self.rest.is_empty() {
1882      if self
1883        .first
1884        .as_ref()
1885        .is_some_and(|first| first.state.strong_count() == 0)
1886      {
1887        self.first = None;
1888      }
1889      return;
1890    }
1891    self.rest.retain(PublishedBlockingDependency::still_stands);
1892    if self
1893      .first
1894      .as_ref()
1895      .is_some_and(|first| !first.still_stands())
1896    {
1897      self.first = self.rest.pop();
1898    }
1899  }
1900
1901  fn publish(&mut self, state: Arc<TaskDependency>, dependency: BlockingDependency) {
1902    self.prune();
1903    let target = Arc::as_ptr(&state);
1904    if let Some(existing) = self
1905      .first
1906      .iter_mut()
1907      .chain(self.rest.iter_mut())
1908      .find(|published| std::ptr::eq(published.state.as_ptr(), target))
1909    {
1910      // The entry this handle already published into this frame still says the
1911      // frame depends on this job, so a repeat Pending poll must LEAVE IT
1912      // ALONE. Publishing again would append at the tail, and
1913      // `select_for_owner` picks the LOWEST live sequence, so re-polling a
1914      // handle would push its own dependency behind ones published after it.
1915      // In an H1 -> H2 -> H1 poll pass that reorders H1 behind H2, and if H2
1916      // waits on H1 the owner lane lends H2 inline while H1 stays queued
1917      // forever. Keeping the original sequence preserves the publication order
1918      // the job index produced, and skips a `DependencyClaim`/`DependencyLink`
1919      // pair per repeat poll.
1920      if state.holds_live(existing.sequence) {
1921        return;
1922      }
1923      // The entry was drained, pruned or claimed, so it no longer stands and
1924      // this frame needs a fresh one. Reusing the token keeps the list at one
1925      // entry per frame.
1926      existing.sequence = state.set_owned(dependency);
1927      return;
1928    }
1929    let sequence = state.set_owned(dependency);
1930    let published = PublishedBlockingDependency {
1931      state: Arc::downgrade(&state),
1932      sequence,
1933    };
1934    match &mut self.first {
1935      slot @ None => *slot = Some(published),
1936      Some(_) => self.rest.push(published),
1937    }
1938  }
1939
1940  /// Retire every publication, each against the state it was filed in.
1941  ///
1942  /// This is exactly inverse to publication, so it can never cancel an entry
1943  /// this handle did not create. It cannot strand a frame either: a frame is
1944  /// only stranded if it is parked awaiting something, and it cannot be parked
1945  /// awaiting a handle that is concurrently being dropped, because the handle
1946  /// is owned by the future that frame is driving. A frame that has already
1947  /// gone cancelled its entries in its own `Drop`, so an expired token is
1948  /// simply skipped.
1949  fn retire_all(self) {
1950    for published in self.first.into_iter().chain(self.rest) {
1951      if let Some(state) = published.state.upgrade() {
1952        state.retire(published.sequence);
1953      }
1954    }
1955  }
1956}
1957
1958#[cfg(not(target_family = "wasm"))]
1959fn record_blocking_dependency(
1960  published: &mut PublishedBlockingDependencies,
1961  dependency: BlockingDependency,
1962) {
1963  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1964  if let Some(current) = current {
1965    let dependency = BlockingDependency {
1966      owner: dependency
1967        .owner
1968        .or_else(|| BLOCKING_OWNER.with(std::cell::Cell::get)),
1969      ..dependency
1970    };
1971    published.publish(current, dependency);
1972  }
1973}
1974
1975#[cfg(not(target_family = "wasm"))]
1976fn propagate_blocking_dependency(publication: DependencyPublication) {
1977  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1978  if let Some(current) = current {
1979    let owner = BLOCKING_OWNER.with(std::cell::Cell::get);
1980    let publication = publication.with_owner_if_none(owner);
1981    current.set_borrowed(&publication);
1982  }
1983}
1984
1985#[cfg(not(target_family = "wasm"))]
1986fn clear_blocking_dependency(published: PublishedBlockingDependencies) {
1987  // Retire against the states that were published to, NOT against whatever
1988  // frame happens to be on top of the stack at drop time. The frame that
1989  // published is the frame whose dependency graph is now wrong.
1990  published.retire_all();
1991}
1992
1993#[cfg(not(target_family = "wasm"))]
1994fn clear_propagated_blocking_dependencies(publications: &[DependencyPublication]) {
1995  let current = CURRENT_DEPENDENCY_STACK.with(|stack| stack.borrow().last().cloned());
1996  if let Some(current) = current {
1997    current.clear_if_publications(publications);
1998  }
1999}
2000
2001enum JoinHandleInner<T> {
2002  Task {
2003    task: FallibleTask<Result<ContainedTaskOutput<T>, JoinError>>,
2004    awaiter: Option<PanicContainedWakerCache>,
2005    #[cfg(not(target_family = "wasm"))]
2006    dependency: Arc<TaskDependency>,
2007  },
2008  #[cfg(not(target_family = "wasm"))]
2009  Blocking {
2010    receiver: oneshot::Receiver<Result<ContainedTaskOutput<T>, JoinError>>,
2011    dependency: BlockingDependency,
2012    /// Every dependency this handle has published, so detaching retires each
2013    /// in O(1) instead of searching for them by job id.
2014    published: PublishedBlockingDependencies,
2015  },
2016  Ready(Option<Result<ContainedTaskOutput<T>, JoinError>>),
2017}
2018
2019pub struct JoinHandle<T>(JoinHandleInner<T>);
2020
2021impl<T> Unpin for JoinHandle<T> {}
2022
2023impl<T> JoinHandle<T> {
2024  fn detach_task(&mut self) {
2025    let inner = std::mem::replace(&mut self.0, JoinHandleInner::Ready(None));
2026    match inner {
2027      JoinHandleInner::Task {
2028        task,
2029        awaiter: _,
2030        #[cfg(not(target_family = "wasm"))]
2031        dependency,
2032      } => {
2033        #[cfg(not(target_family = "wasm"))]
2034        let current_dependencies = dependency.snapshot_all_live();
2035        #[cfg(not(target_family = "wasm"))]
2036        dependency.clear_waiter();
2037        task.detach();
2038        #[cfg(not(target_family = "wasm"))]
2039        // Dependency cleanup may wake an arbitrary caller-provided waker.
2040        // Retire the task first, then contain the notification boundary so
2041        // reentrant wake code cannot observe a half-detached handle.
2042        let _ = catch_unwind_contained(|| {
2043          clear_propagated_blocking_dependencies(&current_dependencies);
2044        });
2045      }
2046      #[cfg(not(target_family = "wasm"))]
2047      JoinHandleInner::Blocking {
2048        receiver,
2049        dependency: _,
2050        published,
2051      } => {
2052        // A completed result may already be buffered in the receiver. Its
2053        // destructor is user code and must receive the same containment as an
2054        // immediate CurrentThread result.
2055        let _ = catch_unwind_contained(|| drop(receiver));
2056        // Notify only after receiver retirement, and keep that arbitrary waker
2057        // behind an independent unwind boundary.
2058        let _ = catch_unwind_contained(|| clear_blocking_dependency(published));
2059      }
2060      JoinHandleInner::Ready(result) => {
2061        let _ = catch_unwind_contained(|| drop(result));
2062      }
2063    }
2064  }
2065
2066  pub fn detach(mut self) {
2067    self.detach_task();
2068  }
2069}
2070
2071impl<T> Drop for JoinHandle<T> {
2072  fn drop(&mut self) {
2073    // Tokio's JoinHandle detaches on drop. Preserve that compatibility:
2074    // fire-and-forget callers must not silently cancel work merely because
2075    // they ignore the returned handle.
2076    self.detach_task();
2077  }
2078}
2079
2080impl<T> Future for JoinHandle<T> {
2081  type Output = Result<T, JoinError>;
2082
2083  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2084    match &mut self.get_mut().0 {
2085      JoinHandleInner::Task {
2086        task,
2087        awaiter: awaiter_slot,
2088        #[cfg(not(target_family = "wasm"))]
2089        dependency,
2090      } => {
2091        let awaiter = match awaiter_slot {
2092          Some(awaiter) => {
2093            awaiter.update(cx.waker());
2094            awaiter
2095          }
2096          None => awaiter_slot.insert(PanicContainedWakerCache::new(cx.waker())),
2097        };
2098        let contained_waker = awaiter.contained();
2099        let mut contained_cx = Context::from_waker(contained_waker);
2100        let poll = Pin::new(task).poll(&mut contained_cx);
2101        #[cfg(not(target_family = "wasm"))]
2102        if poll.is_pending() {
2103          for current_dependency in dependency.register_waiter_and_get(contained_waker) {
2104            propagate_blocking_dependency(current_dependency);
2105          }
2106        } else {
2107          dependency.clear_waiter();
2108        }
2109        if poll.is_ready() {
2110          *awaiter_slot = None;
2111        }
2112        match poll {
2113          Poll::Ready(Some(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2114          Poll::Ready(None) => Poll::Ready(Err(JoinError {
2115            message: "async runtime stopped before the task completed".to_string(),
2116          })),
2117          Poll::Pending => Poll::Pending,
2118        }
2119      }
2120      #[cfg(not(target_family = "wasm"))]
2121      JoinHandleInner::Blocking {
2122        receiver,
2123        dependency,
2124        published,
2125      } => {
2126        let poll = Pin::new(receiver).poll(cx);
2127        if poll.is_pending() {
2128          record_blocking_dependency(published, *dependency);
2129        }
2130        match poll {
2131          Poll::Ready(Ok(result)) => Poll::Ready(result.map(ContainedTaskOutput::into_inner)),
2132          Poll::Ready(Err(_)) => Poll::Ready(Err(JoinError {
2133            message: "async runtime stopped before the blocking task completed".to_string(),
2134          })),
2135          Poll::Pending => Poll::Pending,
2136        }
2137      }
2138      JoinHandleInner::Ready(result) => Poll::Ready(
2139        result
2140          .take()
2141          .expect("JoinHandle polled after completion")
2142          .map(ContainedTaskOutput::into_inner),
2143      ),
2144    }
2145  }
2146}
2147
2148#[derive(Debug, Clone, Copy)]
2149pub struct RuntimeMetricsSnapshot {
2150  pub flavor: RuntimeFlavor,
2151  pub worker_threads: usize,
2152  pub max_blocking_tasks: usize,
2153  pub tasks_spawned: u64,
2154  pub tasks_completed: u64,
2155  pub tasks_panicked: u64,
2156  pub runnable_schedules: u64,
2157  pub runnable_polls: u64,
2158  pub queued_runnables: u64,
2159  pub max_queued_runnables: u64,
2160  pub active_runnables: u64,
2161  pub max_active_runnables: u64,
2162  pub blocking_tasks_started: u64,
2163  pub blocking_tasks_completed: u64,
2164  pub active_blocking_tasks: u64,
2165  pub max_active_blocking_tasks: u64,
2166}
2167
2168#[derive(Default)]
2169struct RuntimeMetrics {
2170  tasks_spawned: AtomicU64,
2171  tasks_completed: AtomicU64,
2172  tasks_panicked: AtomicU64,
2173  runnable_schedules: AtomicU64,
2174  runnable_polls: AtomicU64,
2175  queued_runnables: AtomicU64,
2176  max_queued_runnables: AtomicU64,
2177  active_runnables: AtomicU64,
2178  max_active_runnables: AtomicU64,
2179  // Bumped at ENQUEUE time (before the queue push), the blocking-side twin of
2180  // `runnable_schedules`. Internal-only (not exported in the public snapshot):
2181  // it exists so the deadlock detector's progress fingerprint can observe a
2182  // blocking submission the moment it is made -- `blocking_tasks_started`
2183  // only advances once a worker RUNS the job, which may be arbitrarily later.
2184  blocking_tasks_scheduled: AtomicU64,
2185  blocking_tasks_started: AtomicU64,
2186  blocking_tasks_completed: AtomicU64,
2187  active_blocking_tasks: AtomicU64,
2188  max_active_blocking_tasks: AtomicU64,
2189  // Seqlock generation around resets. The deadlock detector snapshots
2190  // resettable counters, so it must distinguish identical counter values from
2191  // different reset generations instead of mistaking that ABA for no progress.
2192  reset_generation: AtomicU64,
2193  reset_lock: Mutex<()>,
2194}
2195
2196#[cfg(test)]
2197thread_local! {
2198  static RUNTIME_METRICS_HIGH_WATER_TEST_HOOK:
2199    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
2200      const { std::cell::RefCell::new(None) };
2201}
2202
2203#[cfg(test)]
2204fn run_runtime_metrics_high_water_test_hook() {
2205  if let Some(hook) = RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
2206    hook();
2207  }
2208}
2209
2210impl RuntimeMetrics {
2211  fn runnable_scheduled(&self) {
2212    self.runnable_schedules.fetch_add(1, Ordering::Relaxed);
2213    let queued = self.queued_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2214    #[cfg(test)]
2215    run_runtime_metrics_high_water_test_hook();
2216    self
2217      .max_queued_runnables
2218      .fetch_max(queued, Ordering::Relaxed);
2219  }
2220
2221  fn runnable_started(&self) -> ActiveRunnableGuard<'_> {
2222    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2223    self.runnable_polls.fetch_add(1, Ordering::Relaxed);
2224    let active = self.active_runnables.fetch_add(1, Ordering::Relaxed) + 1;
2225    #[cfg(test)]
2226    run_runtime_metrics_high_water_test_hook();
2227    self
2228      .max_active_runnables
2229      .fetch_max(active, Ordering::Relaxed);
2230    ActiveRunnableGuard { metrics: self }
2231  }
2232
2233  #[cfg(any(not(target_family = "wasm"), test))]
2234  fn runnable_cancelled(&self) {
2235    self.queued_runnables.fetch_sub(1, Ordering::Relaxed);
2236  }
2237
2238  /// Enqueue-time twin of [`Self::runnable_scheduled`] for blocking work:
2239  /// called BEFORE the blocking-queue push, so the deadlock detector's
2240  /// fingerprint sees a submission even when no worker has started the job.
2241  fn blocking_scheduled(&self) {
2242    self
2243      .blocking_tasks_scheduled
2244      .fetch_add(1, Ordering::Relaxed);
2245  }
2246
2247  fn blocking_started(self: &Arc<Self>, count_active_lane: bool) -> BlockingTaskGuard {
2248    self.blocking_tasks_started.fetch_add(1, Ordering::Relaxed);
2249    if count_active_lane {
2250      let active = self.active_blocking_tasks.fetch_add(1, Ordering::Relaxed) + 1;
2251      #[cfg(test)]
2252      run_runtime_metrics_high_water_test_hook();
2253      self
2254        .max_active_blocking_tasks
2255        .fetch_max(active, Ordering::Relaxed);
2256    }
2257    BlockingTaskGuard {
2258      metrics: Arc::clone(self),
2259      count_active_lane,
2260    }
2261  }
2262
2263  fn snapshot(&self, options: &RuntimeOptions) -> RuntimeMetricsSnapshot {
2264    let queued_runnables = self.queued_runnables.load(Ordering::Relaxed);
2265    let active_runnables = self.active_runnables.load(Ordering::Relaxed);
2266    let active_blocking_tasks = self.active_blocking_tasks.load(Ordering::Relaxed);
2267    RuntimeMetricsSnapshot {
2268      flavor: options.flavor,
2269      worker_threads: options.worker_threads,
2270      max_blocking_tasks: options.max_blocking_tasks,
2271      tasks_spawned: self.tasks_spawned.load(Ordering::Relaxed),
2272      tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2273      tasks_panicked: self.tasks_panicked.load(Ordering::Relaxed),
2274      runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2275      runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2276      queued_runnables,
2277      max_queued_runnables: self
2278        .max_queued_runnables
2279        .load(Ordering::Relaxed)
2280        .max(queued_runnables),
2281      active_runnables,
2282      max_active_runnables: self
2283        .max_active_runnables
2284        .load(Ordering::Relaxed)
2285        .max(active_runnables),
2286      blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2287      blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2288      active_blocking_tasks,
2289      max_active_blocking_tasks: self
2290        .max_active_blocking_tasks
2291        .load(Ordering::Relaxed)
2292        .max(active_blocking_tasks),
2293    }
2294  }
2295
2296  /// Coarse liveness fingerprint for deadline-based deadlock detection (wake
2297  /// -path §8.5): any of these counters advancing between two reads means the
2298  /// runtime did SOMETHING (scheduled or polled a runnable, completed a task,
2299  /// scheduled, started or finished a blocking job), so a parked driver
2300  /// observing an advance re-arms its deadline instead of firing. Both ENQUEUE
2301  /// counters (`runnable_schedules`, `blocking_tasks_scheduled`) are included
2302  /// so a submission is progress the moment it is made -- not only once a
2303  /// worker gets to run it.
2304  fn progress_fingerprint(&self) -> ProgressFingerprint {
2305    loop {
2306      let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2307      if !reset_generation.is_multiple_of(2) {
2308        std::hint::spin_loop();
2309        continue;
2310      }
2311      let fingerprint = ProgressFingerprint {
2312        reset_generation,
2313        runnable_schedules: self.runnable_schedules.load(Ordering::Relaxed),
2314        runnable_polls: self.runnable_polls.load(Ordering::Relaxed),
2315        tasks_completed: self.tasks_completed.load(Ordering::Relaxed),
2316        blocking_tasks_scheduled: self.blocking_tasks_scheduled.load(Ordering::Relaxed),
2317        blocking_tasks_started: self.blocking_tasks_started.load(Ordering::Relaxed),
2318        blocking_tasks_completed: self.blocking_tasks_completed.load(Ordering::Relaxed),
2319      };
2320      if self.reset_generation.load(Ordering::SeqCst) == reset_generation {
2321        return fingerprint;
2322      }
2323    }
2324  }
2325
2326  fn reset(&self) {
2327    let reset_guard = self
2328      .reset_lock
2329      .lock()
2330      .unwrap_or_else(std::sync::PoisonError::into_inner);
2331    // Event counters are safe to reset independently. Live gauges are not:
2332    // queued/active guards may still hold a future decrement, and zeroing a
2333    // gauge underneath them wraps the unsigned counter on completion.
2334    //
2335    // High-water gauges remain lifetime values. Resetting them concurrently
2336    // with fetch_max would either lose a real peak or require synchronization
2337    // on every hot-path metric update.
2338    let reset_generation = self.reset_generation.load(Ordering::SeqCst);
2339    let Some(resetting_generation) = reset_generation.checked_add(1) else {
2340      drop(reset_guard);
2341      panic!("runtime metrics reset generation space exhausted");
2342    };
2343    let Some(settled_generation) = resetting_generation.checked_add(1) else {
2344      drop(reset_guard);
2345      panic!("runtime metrics reset generation space exhausted");
2346    };
2347    self
2348      .reset_generation
2349      .store(resetting_generation, Ordering::SeqCst);
2350    self.tasks_spawned.store(0, Ordering::Relaxed);
2351    self.tasks_completed.store(0, Ordering::Relaxed);
2352    self.tasks_panicked.store(0, Ordering::Relaxed);
2353    self.runnable_schedules.store(0, Ordering::Relaxed);
2354    self.runnable_polls.store(0, Ordering::Relaxed);
2355    self.blocking_tasks_scheduled.store(0, Ordering::Relaxed);
2356    self.blocking_tasks_started.store(0, Ordering::Relaxed);
2357    self.blocking_tasks_completed.store(0, Ordering::Relaxed);
2358    self
2359      .reset_generation
2360      .store(settled_generation, Ordering::SeqCst);
2361  }
2362}
2363
2364/// See [`RuntimeMetrics::progress_fingerprint`].
2365#[derive(Clone, Copy, PartialEq, Eq)]
2366struct ProgressFingerprint {
2367  reset_generation: u64,
2368  runnable_schedules: u64,
2369  runnable_polls: u64,
2370  tasks_completed: u64,
2371  blocking_tasks_scheduled: u64,
2372  blocking_tasks_started: u64,
2373  blocking_tasks_completed: u64,
2374}
2375
2376struct ActiveRunnableGuard<'a> {
2377  metrics: &'a RuntimeMetrics,
2378}
2379
2380impl Drop for ActiveRunnableGuard<'_> {
2381  fn drop(&mut self) {
2382    self
2383      .metrics
2384      .active_runnables
2385      .fetch_sub(1, Ordering::Relaxed);
2386  }
2387}
2388
2389struct BlockingTaskGuard {
2390  metrics: Arc<RuntimeMetrics>,
2391  count_active_lane: bool,
2392}
2393
2394impl Drop for BlockingTaskGuard {
2395  fn drop(&mut self) {
2396    if self.count_active_lane {
2397      self
2398        .metrics
2399        .active_blocking_tasks
2400        .fetch_sub(1, Ordering::Relaxed);
2401    }
2402    self
2403      .metrics
2404      .blocking_tasks_completed
2405      .fetch_add(1, Ordering::Relaxed);
2406  }
2407}
2408
2409static NEXT_RUNTIME_GENERATION: AtomicU64 = AtomicU64::new(0);
2410
2411fn next_runtime_generation() -> u64 {
2412  next_unique_id(
2413    &NEXT_RUNTIME_GENERATION,
2414    "async runtime generation id space exhausted",
2415  )
2416}
2417
2418thread_local! {
2419  static ACTIVE_RUNTIME_GENERATION: std::cell::Cell<Option<u64>> =
2420    const { std::cell::Cell::new(None) };
2421}
2422
2423fn active_runtime_generation() -> Option<u64> {
2424  ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get)
2425}
2426
2427struct RuntimeGenerationGuard {
2428  previous: Option<u64>,
2429}
2430
2431impl RuntimeGenerationGuard {
2432  fn enter(generation: u64) -> Self {
2433    Self::enter_optional(Some(generation))
2434  }
2435
2436  fn enter_optional(generation: Option<u64>) -> Self {
2437    Self {
2438      previous: ACTIVE_RUNTIME_GENERATION.with(|current| current.replace(generation)),
2439    }
2440  }
2441}
2442
2443impl Drop for RuntimeGenerationGuard {
2444  fn drop(&mut self) {
2445    ACTIVE_RUNTIME_GENERATION.with(|current| current.set(self.previous));
2446  }
2447}
2448
2449struct ContainedFuture<F> {
2450  future: ManuallyDrop<F>,
2451  generation: u64,
2452}
2453
2454impl<F> ContainedFuture<F> {
2455  fn new(future: F, generation: u64) -> Self {
2456    Self {
2457      future: ManuallyDrop::new(future),
2458      generation,
2459    }
2460  }
2461}
2462
2463impl<F: Future> Future for ContainedFuture<F> {
2464  type Output = F::Output;
2465
2466  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2467    // SAFETY: `future` is never moved after `ContainedFuture` is pinned.
2468    let this = unsafe { self.get_unchecked_mut() };
2469    let _generation = RuntimeGenerationGuard::enter(this.generation);
2470    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2471    // structurally pinned for the lifetime of `self`.
2472    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2473  }
2474}
2475
2476impl<F> Drop for ContainedFuture<F> {
2477  fn drop(&mut self) {
2478    let _generation = RuntimeGenerationGuard::enter(self.generation);
2479    let _ = catch_unwind_contained(|| {
2480      // SAFETY: this is the sole destruction path for `future`.
2481      unsafe { ManuallyDrop::drop(&mut self.future) };
2482    });
2483  }
2484}
2485
2486#[derive(Default)]
2487struct GenerationStop {
2488  stopping: AtomicBool,
2489  publication: Mutex<()>,
2490  parkers: Mutex<Vec<Weak<DriverParker>>>,
2491}
2492
2493impl GenerationStop {
2494  fn is_stopping(&self) -> bool {
2495    self.stopping.load(Ordering::Acquire)
2496  }
2497
2498  fn publication_guard(&self) -> GenerationStopPublicationGuard<'_> {
2499    #[cfg(all(test, not(target_family = "wasm")))]
2500    run_before_generation_stop_publication_lock_test_hook();
2501    self.runnable_claim_guard()
2502  }
2503
2504  fn runnable_claim_guard(&self) -> GenerationStopPublicationGuard<'_> {
2505    GenerationStopPublicationGuard {
2506      stop: self,
2507      _publication: self
2508        .publication
2509        .lock()
2510        .unwrap_or_else(std::sync::PoisonError::into_inner),
2511    }
2512  }
2513
2514  fn publish_stopping(&self) {
2515    self.publication_guard().publish_stopping();
2516  }
2517
2518  fn register(self: &Arc<Self>, parker: &Arc<DriverParker>) -> GenerationStopGuard {
2519    {
2520      let mut parkers = self
2521        .parkers
2522        .lock()
2523        .unwrap_or_else(std::sync::PoisonError::into_inner);
2524      parkers.retain(|candidate| candidate.strong_count() != 0);
2525      parkers.push(Arc::downgrade(parker));
2526    }
2527    if self.is_stopping() {
2528      parker.unpark();
2529    }
2530    GenerationStopGuard {
2531      stop: Arc::clone(self),
2532      parker: Arc::downgrade(parker),
2533    }
2534  }
2535
2536  /// Wake every active `block_on` frame without changing the generation state.
2537  /// The number of parkers is bounded by concurrent explicit drivers. Waking
2538  /// all avoids directing a queue wake only to a newer driver that is blocked
2539  /// inside its poll while an older driver is already asleep.
2540  fn wake_all(&self) -> usize {
2541    let mut count = 0;
2542    let mut registered = self
2543      .parkers
2544      .lock()
2545      .unwrap_or_else(std::sync::PoisonError::into_inner);
2546    registered.retain(|candidate| {
2547      let Some(parker) = candidate.upgrade() else {
2548        return false;
2549      };
2550      count += 1;
2551      // `DriverParker::unpark` invokes no user code and never enters the
2552      // generation registry, so waking under this lock is non-reentrant.
2553      parker.unpark();
2554      true
2555    });
2556    count
2557  }
2558
2559  fn begin_shutdown(&self) {
2560    self.publish_stopping();
2561    let parkers = {
2562      let mut registered = self
2563        .parkers
2564        .lock()
2565        .unwrap_or_else(std::sync::PoisonError::into_inner);
2566      let parkers = registered
2567        .iter()
2568        .filter_map(Weak::upgrade)
2569        .collect::<Vec<_>>();
2570      registered.clear();
2571      parkers
2572    };
2573    for parker in parkers {
2574      parker.unpark();
2575    }
2576  }
2577}
2578
2579struct GenerationStopPublicationGuard<'a> {
2580  stop: &'a GenerationStop,
2581  _publication: std::sync::MutexGuard<'a, ()>,
2582}
2583
2584impl GenerationStopPublicationGuard<'_> {
2585  fn is_stopping(&self) -> bool {
2586    self.stop.is_stopping()
2587  }
2588
2589  fn publish_stopping(&self) {
2590    self.stop.stopping.store(true, Ordering::Release);
2591  }
2592}
2593
2594struct GenerationStopGuard {
2595  stop: Arc<GenerationStop>,
2596  parker: Weak<DriverParker>,
2597}
2598
2599impl Drop for GenerationStopGuard {
2600  fn drop(&mut self) {
2601    let Some(parker) = self.parker.upgrade() else {
2602      return;
2603    };
2604    let mut registered = self
2605      .stop
2606      .parkers
2607      .lock()
2608      .unwrap_or_else(std::sync::PoisonError::into_inner);
2609    if let Some(index) = registered.iter().position(|candidate| {
2610      candidate
2611        .upgrade()
2612        .is_some_and(|value| Arc::ptr_eq(&value, &parker))
2613    }) {
2614      registered.swap_remove(index);
2615    }
2616  }
2617}
2618
2619struct GenerationWorkState {
2620  closed: bool,
2621  next_task_id: u64,
2622  active: usize,
2623  abort_handles: FxHashMap<u64, AbortHandle>,
2624}
2625
2626struct GenerationWork {
2627  id: u64,
2628  state: Mutex<GenerationWorkState>,
2629  idle: Condvar,
2630}
2631
2632impl GenerationWork {
2633  fn new() -> Arc<Self> {
2634    Arc::new(Self {
2635      id: next_runtime_generation(),
2636      state: Mutex::new(GenerationWorkState {
2637        closed: false,
2638        next_task_id: 0,
2639        active: 0,
2640        abort_handles: FxHashMap::default(),
2641      }),
2642      idle: Condvar::new(),
2643    })
2644  }
2645
2646  fn try_register_async(self: &Arc<Self>) -> Option<(AbortRegistration, GenerationWorkGuard)> {
2647    let (abort_handle, abort_registration) = AbortHandle::new_pair();
2648    let mut state = self
2649      .state
2650      .lock()
2651      .unwrap_or_else(std::sync::PoisonError::into_inner);
2652    if state.closed {
2653      return None;
2654    }
2655    let task_id = state.next_task_id;
2656    let Some(next_task_id) = state.next_task_id.checked_add(1) else {
2657      drop(state);
2658      panic!("async runtime generation task id space exhausted");
2659    };
2660    state.next_task_id = next_task_id;
2661    state.active += 1;
2662    state.abort_handles.insert(task_id, abort_handle);
2663    Some((
2664      abort_registration,
2665      GenerationWorkGuard {
2666        work: Arc::clone(self),
2667        task_id: Some(task_id),
2668      },
2669    ))
2670  }
2671
2672  fn try_register_work(self: &Arc<Self>) -> Option<GenerationWorkGuard> {
2673    let mut state = self
2674      .state
2675      .lock()
2676      .unwrap_or_else(std::sync::PoisonError::into_inner);
2677    if state.closed {
2678      return None;
2679    }
2680    state.active += 1;
2681    Some(GenerationWorkGuard {
2682      work: Arc::clone(self),
2683      task_id: None,
2684    })
2685  }
2686
2687  fn close_and_abort(&self) {
2688    let abort_handles = {
2689      let mut state = self
2690        .state
2691        .lock()
2692        .unwrap_or_else(std::sync::PoisonError::into_inner);
2693      state.closed = true;
2694      state.abort_handles.values().cloned().collect::<Vec<_>>()
2695    };
2696    for abort_handle in abort_handles {
2697      abort_handle.abort();
2698    }
2699  }
2700
2701  fn wait_until_idle(&self) {
2702    let mut state = self
2703      .state
2704      .lock()
2705      .unwrap_or_else(std::sync::PoisonError::into_inner);
2706    while state.active != 0 {
2707      state = self
2708        .idle
2709        .wait(state)
2710        .unwrap_or_else(std::sync::PoisonError::into_inner);
2711    }
2712  }
2713}
2714
2715struct GenerationWorkGuard {
2716  work: Arc<GenerationWork>,
2717  task_id: Option<u64>,
2718}
2719
2720impl Drop for GenerationWorkGuard {
2721  fn drop(&mut self) {
2722    let mut state = self
2723      .work
2724      .state
2725      .lock()
2726      .unwrap_or_else(std::sync::PoisonError::into_inner);
2727    if let Some(task_id) = self.task_id {
2728      state.abort_handles.remove(&task_id);
2729    }
2730    state.active = state
2731      .active
2732      .checked_sub(1)
2733      .expect("generation work count underflow");
2734    if state.active == 0 {
2735      self.work.idle.notify_all();
2736    }
2737  }
2738}
2739
2740/// Keeps accepted async work registered until the complete task generator has
2741/// been destroyed. In particular, an executor queue may close after
2742/// registration but before the initial runnable is published. `async-task`
2743/// then drops the generator unpolled, whose compiler-generated capture order
2744/// must not be allowed to retire the generation guard before user captures.
2745struct RegisteredTaskFuture<F> {
2746  future: ManuallyDrop<F>,
2747  registration: Option<GenerationWorkGuard>,
2748  generation: u64,
2749}
2750
2751impl<F> RegisteredTaskFuture<F> {
2752  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2753    Self {
2754      future: ManuallyDrop::new(future),
2755      registration: Some(registration),
2756      generation,
2757    }
2758  }
2759}
2760
2761impl<F: Future> Future for RegisteredTaskFuture<F> {
2762  type Output = F::Output;
2763
2764  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2765    // SAFETY: `future` is never moved after `RegisteredTaskFuture` is pinned.
2766    let this = unsafe { self.get_unchecked_mut() };
2767    let _generation = RuntimeGenerationGuard::enter(this.generation);
2768    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2769    // structurally pinned for the lifetime of `self`.
2770    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2771  }
2772}
2773
2774impl<F> Drop for RegisteredTaskFuture<F> {
2775  fn drop(&mut self) {
2776    let _generation = RuntimeGenerationGuard::enter(self.generation);
2777    let _ = catch_unwind_contained(|| {
2778      // SAFETY: this is the sole destruction path for `future`.
2779      unsafe { ManuallyDrop::drop(&mut self.future) };
2780    });
2781    // Shutdown/restart may observe this generation idle only after every user
2782    // capture and caught panic payload above has retired.
2783    drop(self.registration.take());
2784  }
2785}
2786
2787#[cfg(not(target_family = "wasm"))]
2788struct RegisteredBlockingFunction<F> {
2789  function: Option<F>,
2790  registration: Option<GenerationWorkGuard>,
2791  generation: u64,
2792}
2793
2794#[cfg(not(target_family = "wasm"))]
2795impl<F> RegisteredBlockingFunction<F> {
2796  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2797    Self {
2798      function: Some(function),
2799      registration: Some(registration),
2800      generation,
2801    }
2802  }
2803
2804  fn run<T>(mut self) -> Result<T, JoinError>
2805  where
2806    F: FnOnce() -> T,
2807  {
2808    let _generation = RuntimeGenerationGuard::enter(self.generation);
2809    let result = catch_unwind_join_error(
2810      self
2811        .function
2812        .take()
2813        .expect("blocking function must be available"),
2814    );
2815    drop(self.registration.take());
2816    result
2817  }
2818}
2819
2820#[cfg(not(target_family = "wasm"))]
2821impl<F> Drop for RegisteredBlockingFunction<F> {
2822  fn drop(&mut self) {
2823    let _generation = RuntimeGenerationGuard::enter(self.generation);
2824    if let Some(function) = self.function.take() {
2825      let _ = catch_unwind_contained(|| drop(function));
2826    }
2827    // A submission accepted by the controller may reach a queue that shutdown
2828    // already closed. Keep it registered until all captured user state above
2829    // has been destroyed on the submitting thread.
2830    drop(self.registration.take());
2831  }
2832}
2833
2834fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2835  let _active = metrics.runnable_started();
2836  let _ = catch_unwind_contained(|| runnable.run());
2837}
2838
2839#[cfg(not(target_family = "wasm"))]
2840enum RunnableClaim {
2841  Empty,
2842  Run(Runnable),
2843  Cancel(Runnable),
2844}
2845
2846static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2847static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2848
2849fn next_current_thread_dispatch_id() -> u64 {
2850  next_unique_id(
2851    &NEXT_CURRENT_THREAD_DISPATCH_ID,
2852    "CurrentThread dispatch id space exhausted",
2853  )
2854  .checked_add(1)
2855  .expect("CurrentThread dispatch id space exhausted")
2856}
2857
2858struct CurrentThreadBlockingAdmission {
2859  state: Mutex<CurrentThreadBlockingAdmissionState>,
2860}
2861
2862struct CurrentThreadBlockingAdmissionState {
2863  owner: Option<BlockingOwnerToken>,
2864  closed: bool,
2865  #[cfg(not(target_family = "wasm"))]
2866  queue: BlockingQueue,
2867}
2868
2869struct CurrentThreadExecutor {
2870  id: u64,
2871  generation: u64,
2872  stop: Arc<GenerationStop>,
2873  queue: Mutex<CurrentThreadQueue>,
2874  blocking_admission: CurrentThreadBlockingAdmission,
2875  draining: AtomicBool,
2876  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2877  scheduler_idle: Condvar,
2878  /// Exact nonzero internal capability for the currently published host turn.
2879  /// The host registry maps this to distinct registration-scoped delivery
2880  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
2881  dispatch_pending: AtomicU64,
2882  /// Exact capability of the one replacement dispatch allowed after a host
2883  /// scheduler rejects an accepted callback. A second matching cancellation
2884  /// fails the queued work instead of retrying forever.
2885  recovery_dispatch: AtomicU64,
2886  /// Terminal dispatch failure temporarily rejects scheduler publications
2887  /// while the affected runnable and blocking queues are cancelled.
2888  cancelling_failed_dispatch: AtomicBool,
2889  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2890  task_drivers: &'static CurrentThreadTaskDriverRegistry,
2891  metrics: Arc<RuntimeMetrics>,
2892  // Deadlock detection. `threadless`: no second thread can EVER deliver a
2893  // wake to a parked `block_on`, so a park decision with an
2894  // empty queue and no pending wake token is a PROVABLE deadlock (panic
2895  // immediately, no timing involved). `park_deadline`: on threaded builds,
2896  // the optional bound for a park with zero runtime progress -- off unless
2897  // configured, because a legitimately long park must never panic a
2898  // production build.
2899  threadless: bool,
2900  park_deadline: Option<Duration>,
2901  // HOST-driver timers currently armed on this runtime.
2902  // On the THREADED CT flavor a pending entry is a future wake token -- the
2903  // host event loop's timer callback -- so the deadline verdict treats a LIVE
2904  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
2905  // build it is NOT a wake token (the host relay shares the parked thread and
2906  // can never run); there the CERTAIN check only consults it to pick the
2907  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
2908  // futures minted for this executor (they insert at first poll and remove on
2909  // completion/drop).
2910  host_timers: Arc<HostTimerRegistry>,
2911}
2912
2913struct CurrentThreadQueue {
2914  closed: bool,
2915  runnables: VecDeque<Runnable>,
2916}
2917
2918#[derive(Default)]
2919struct CurrentThreadSchedulerState {
2920  active_host_turns: usize,
2921  active_dispatch_calls: usize,
2922  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2923}
2924
2925struct CurrentThreadDispatchPublication {
2926  dispatch: u64,
2927  republish_requested: bool,
2928}
2929
2930#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2931enum CurrentThreadDispatchRequest {
2932  Accepted,
2933  Coalesced,
2934  Unavailable,
2935}
2936
2937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2938struct CurrentThreadTaskDispatchFailure {
2939  dispatch: u64,
2940  epoch: u64,
2941}
2942
2943#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2944enum CurrentThreadHostDispatchResult {
2945  Accepted,
2946  Unavailable,
2947  Failed(CurrentThreadTaskDispatchFailure),
2948}
2949
2950enum CurrentThreadDispatchPublicationStep {
2951  Republish,
2952  Complete(Option<CurrentThreadHostDispatchResult>),
2953}
2954
2955struct CurrentThreadHostTurn {
2956  executor: Arc<CurrentThreadExecutor>,
2957  draining: bool,
2958  _generation: RuntimeGenerationGuard,
2959}
2960
2961impl CurrentThreadHostTurn {
2962  fn drive(&mut self) {
2963    let executor = Arc::clone(&self.executor);
2964    executor.drive_admitted_host_turn(self);
2965  }
2966
2967  fn release_draining(&mut self) {
2968    if !self.draining {
2969      return;
2970    }
2971    let _scheduler = self
2972      .executor
2973      .scheduler_idle_lock
2974      .lock()
2975      .unwrap_or_else(std::sync::PoisonError::into_inner);
2976    self.executor.draining.store(false, Ordering::Release);
2977    self.draining = false;
2978  }
2979}
2980
2981impl Drop for CurrentThreadHostTurn {
2982  fn drop(&mut self) {
2983    let mut scheduler = self
2984      .executor
2985      .scheduler_idle_lock
2986      .lock()
2987      .unwrap_or_else(std::sync::PoisonError::into_inner);
2988    if self.draining {
2989      self.executor.draining.store(false, Ordering::Release);
2990      self.draining = false;
2991    }
2992    scheduler.active_host_turns = scheduler
2993      .active_host_turns
2994      .checked_sub(1)
2995      .expect("CurrentThread active host-turn count underflow");
2996    self.executor.scheduler_idle.notify_all();
2997  }
2998}
2999
3000/// Retains one admitted CurrentThread host callback in its runtime generation.
3001///
3002/// The host must keep this opaque lease alive until its native callback has
3003/// acknowledged or failed the delivery and destroyed every callback-owned
3004/// payload. The `Rc` marker prevents moving a TLS generation guard to another
3005/// thread before it restores the callback thread's prior runtime identity.
3006#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3007pub struct CurrentThreadTaskCallbackLease {
3008  _host_turn: CurrentThreadHostTurn,
3009  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3010}
3011
3012impl CurrentThreadTaskCallbackLease {
3013  fn new(host_turn: CurrentThreadHostTurn) -> Self {
3014    Self {
3015      _host_turn: host_turn,
3016      _not_send: std::marker::PhantomData,
3017    }
3018  }
3019}
3020
3021struct CurrentThreadDispatchCall<'a> {
3022  executor: &'a CurrentThreadExecutor,
3023  publication: Option<u64>,
3024  _generation: RuntimeGenerationGuard,
3025}
3026
3027impl CurrentThreadDispatchCall<'_> {
3028  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3029    let dispatch = self
3030      .publication
3031      .expect("the CurrentThread dispatch call must own a publication");
3032    let index = scheduler
3033      .dispatch_publications
3034      .iter()
3035      .position(|publication| publication.dispatch == dispatch)
3036      .expect("the CurrentThread dispatch publication must remain registered");
3037    scheduler.dispatch_publications.swap_remove(index);
3038    self.publication = None;
3039  }
3040}
3041
3042impl Drop for CurrentThreadDispatchCall<'_> {
3043  fn drop(&mut self) {
3044    let mut scheduler = self
3045      .executor
3046      .scheduler_idle_lock
3047      .lock()
3048      .unwrap_or_else(std::sync::PoisonError::into_inner);
3049    if let Some(dispatch) = self.publication.take()
3050      && let Some(index) = scheduler
3051        .dispatch_publications
3052        .iter()
3053        .position(|publication| publication.dispatch == dispatch)
3054    {
3055      scheduler.dispatch_publications.swap_remove(index);
3056    }
3057    scheduler.active_dispatch_calls = scheduler
3058      .active_dispatch_calls
3059      .checked_sub(1)
3060      .expect("CurrentThread active dispatch-call count underflow");
3061    self.executor.scheduler_idle.notify_all();
3062  }
3063}
3064
3065struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3066
3067impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3068  fn drop(&mut self) {
3069    self
3070      .0
3071      .cancelling_failed_dispatch
3072      .store(false, Ordering::Release);
3073  }
3074}
3075
3076enum CurrentThreadDispatchFailureAction<'a> {
3077  None,
3078  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3079  Publish {
3080    dispatch: u64,
3081    call: CurrentThreadDispatchCall<'a>,
3082  },
3083}
3084
3085struct CurrentThreadDispatchFailureRecovery<'a> {
3086  action: CurrentThreadDispatchFailureAction<'a>,
3087  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3088}
3089
3090struct CurrentThreadRegistryCancellationGuard<'a> {
3091  registry: &'a CurrentThreadTaskDriverRegistry,
3092  failure: Option<CurrentThreadTaskDispatchFailure>,
3093}
3094
3095impl CurrentThreadRegistryCancellationGuard<'_> {
3096  fn new(
3097    registry: &CurrentThreadTaskDriverRegistry,
3098    failure: CurrentThreadTaskDispatchFailure,
3099  ) -> CurrentThreadRegistryCancellationGuard<'_> {
3100    CurrentThreadRegistryCancellationGuard {
3101      registry,
3102      failure: Some(failure),
3103    }
3104  }
3105}
3106
3107impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3108  fn drop(&mut self) {
3109    if let Some(failure) = self.failure.take() {
3110      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3111    }
3112  }
3113}
3114
3115struct CurrentThreadBlockingGuard<'a> {
3116  executor: &'a CurrentThreadExecutor,
3117  owner: BlockingOwnerToken,
3118  previous_owner: Option<BlockingOwnerToken>,
3119  owns_lane: bool,
3120}
3121
3122impl CurrentThreadBlockingGuard<'_> {
3123  fn counts_active_lane(&self) -> bool {
3124    self.owns_lane
3125  }
3126}
3127
3128impl Drop for CurrentThreadBlockingGuard<'_> {
3129  fn drop(&mut self) {
3130    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3131    if !self.owns_lane {
3132      return;
3133    }
3134    let mut state = self
3135      .executor
3136      .blocking_admission
3137      .state
3138      .lock()
3139      .unwrap_or_else(std::sync::PoisonError::into_inner);
3140    debug_assert_eq!(state.owner, Some(self.owner));
3141    state.owner = None;
3142    drop(state);
3143    self.executor.stop.wake_all();
3144  }
3145}
3146
3147impl CurrentThreadExecutor {
3148  /// Bound one host callback so a self-waking task cannot monopolize the
3149  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
3150  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3151  /// Force one blocking turn after this many consecutive runnable polls.
3152  #[cfg(not(target_family = "wasm"))]
3153  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3154
3155  /// Build-default construction, kept for the pre-existing unit tests
3156  /// (production goes through [`Self::with_detection`] in
3157  /// `RuntimeBackend::new`, which also resolves the env deadline).
3158  #[cfg(test)]
3159  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3160    Self::with_detection(metrics, THREADLESS_BUILD, None)
3161  }
3162
3163  /// Full constructor for the deadlock-detection knobs: `new` fixes
3164  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
3165  /// `RuntimeBackend::new` resolves the deadline from
3166  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
3167  /// native `threadless` stand-in and short deadlines through it.
3168  #[cfg(test)]
3169  fn with_detection(
3170    metrics: Arc<RuntimeMetrics>,
3171    threadless: bool,
3172    park_deadline: Option<Duration>,
3173  ) -> Self {
3174    Self::with_generation(
3175      metrics,
3176      threadless,
3177      park_deadline,
3178      u64::MAX,
3179      Arc::new(GenerationStop::default()),
3180    )
3181  }
3182
3183  fn with_generation(
3184    metrics: Arc<RuntimeMetrics>,
3185    threadless: bool,
3186    park_deadline: Option<Duration>,
3187    generation: u64,
3188    stop: Arc<GenerationStop>,
3189  ) -> Self {
3190    Self {
3191      id: next_unique_id(
3192        &NEXT_RUNTIME_EXECUTOR_ID,
3193        "async runtime executor id space exhausted",
3194      ),
3195      generation,
3196      stop,
3197      queue: Mutex::new(CurrentThreadQueue {
3198        closed: false,
3199        runnables: VecDeque::new(),
3200      }),
3201      blocking_admission: CurrentThreadBlockingAdmission {
3202        state: Mutex::new(CurrentThreadBlockingAdmissionState {
3203          owner: None,
3204          closed: false,
3205          #[cfg(not(target_family = "wasm"))]
3206          queue: BlockingQueue {
3207            closed: false,
3208            head: None,
3209            tail: None,
3210            jobs: FxHashMap::default(),
3211          },
3212        }),
3213      },
3214      draining: AtomicBool::new(false),
3215      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3216      scheduler_idle: Condvar::new(),
3217      dispatch_pending: AtomicU64::new(0),
3218      recovery_dispatch: AtomicU64::new(0),
3219      cancelling_failed_dispatch: AtomicBool::new(false),
3220      task_dispatch: dispatch_current_thread_tasks,
3221      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3222      metrics,
3223      threadless,
3224      park_deadline,
3225      host_timers: Arc::new(HostTimerRegistry::default()),
3226    }
3227  }
3228
3229  fn fresh_owner_token(&self) -> BlockingOwnerToken {
3230    BlockingOwnerToken {
3231      executor_id: self.id,
3232      frame: next_unique_id(
3233        &NEXT_BLOCKING_FRAME,
3234        "blocking owner frame id space exhausted",
3235      ),
3236    }
3237  }
3238
3239  fn enter_blocking_owner(
3240    &self,
3241    owner: BlockingOwnerToken,
3242    owns_lane: bool,
3243  ) -> CurrentThreadBlockingGuard<'_> {
3244    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3245    CurrentThreadBlockingGuard {
3246      executor: self,
3247      owner,
3248      previous_owner,
3249      owns_lane,
3250    }
3251  }
3252
3253  fn schedule_blocking<F, T>(
3254    self: &Arc<Self>,
3255    function: F,
3256    registration: GenerationWorkGuard,
3257  ) -> JoinHandle<T>
3258  where
3259    F: FnOnce() -> T + Send + 'static,
3260    T: Send + 'static,
3261  {
3262    // See the implementation notes in this crate's README.
3263    self.metrics.blocking_scheduled();
3264    let mut function = Some(function);
3265    let mut registration = Some(registration);
3266    let ambient_owner = BLOCKING_OWNER
3267      .with(std::cell::Cell::get)
3268      .filter(|owner| owner.executor_id == self.id);
3269    let mut run_owner = None;
3270    let mut owns_lane = false;
3271    let mut rejected = false;
3272
3273    #[cfg(not(target_family = "wasm"))]
3274    let (sender, receiver) = oneshot::channel();
3275    #[cfg(not(target_family = "wasm"))]
3276    let dependency = BlockingDependency {
3277      job: BlockingJobId(next_unique_id(
3278        &NEXT_BLOCKING_JOB_ID,
3279        "blocking job id space exhausted",
3280      )),
3281      owner: ambient_owner,
3282    };
3283    #[cfg(not(target_family = "wasm"))]
3284    let mut queued = false;
3285
3286    {
3287      let mut state = self
3288        .blocking_admission
3289        .state
3290        .lock()
3291        .unwrap_or_else(std::sync::PoisonError::into_inner);
3292      #[cfg(all(test, not(target_family = "wasm")))]
3293      run_current_thread_blocking_claim_test_hook();
3294      // Shutdown publishes generation stop before taking this mutex. Recheck
3295      // it while admission is serialized so work cannot claim or queue for the
3296      // CurrentThread lane after stop publication.
3297      if self.stop.is_stopping()
3298        || state.closed
3299        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3300      {
3301        rejected = true;
3302      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3303        run_owner = ambient_owner;
3304      } else {
3305        #[cfg(not(target_family = "wasm"))]
3306        let queue_empty = state.queue.is_empty();
3307        #[cfg(target_family = "wasm")]
3308        let queue_empty = true;
3309
3310        if state.owner.is_none() && queue_empty {
3311          let owner = self.fresh_owner_token();
3312          state.owner = Some(owner);
3313          run_owner = Some(owner);
3314          owns_lane = true;
3315        } else {
3316          #[cfg(not(target_family = "wasm"))]
3317          {
3318            let function = function
3319              .take()
3320              .expect("blocking function must be available");
3321            let registration = registration
3322              .take()
3323              .expect("blocking registration must be available");
3324            let generation = self.generation;
3325            let function = RegisteredBlockingFunction::new(function, registration, generation);
3326            state.queue.push(QueuedBlocking {
3327              id: dependency.job,
3328              run: Box::new(move || {
3329                let _generation = RuntimeGenerationGuard::enter(generation);
3330                let result = function
3331                  .run()
3332                  .map(|output| ContainedTaskOutput::new(output, generation));
3333                let _ = sender.send(result);
3334              }),
3335            });
3336            queued = true;
3337          }
3338          #[cfg(target_family = "wasm")]
3339          {
3340            // A threadless CurrentThread executor cannot reach unrelated
3341            // concurrent admission. Same-stack nesting carries `ambient_owner`
3342            // and takes the borrowing branch above.
3343            rejected = true;
3344          }
3345        }
3346      }
3347    }
3348
3349    #[cfg(not(target_family = "wasm"))]
3350    if queued {
3351      self.stop.wake_all();
3352      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3353        self.request_drain();
3354      }
3355      return JoinHandle(JoinHandleInner::Blocking {
3356        receiver,
3357        dependency,
3358        published: PublishedBlockingDependencies::default(),
3359      });
3360    }
3361
3362    if rejected {
3363      let _generation = RuntimeGenerationGuard::enter(self.generation);
3364      let _ = catch_unwind_contained(|| drop(function.take()));
3365      drop(registration.take());
3366      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3367        message: "the async runtime stopped before the blocking task could start".to_string(),
3368      }))));
3369    }
3370
3371    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3372    let blocking = self.enter_blocking_owner(owner, owns_lane);
3373    let _registration = registration
3374      .take()
3375      .expect("blocking registration must be available");
3376    let _generation = RuntimeGenerationGuard::enter(self.generation);
3377    let result = {
3378      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3379      catch_unwind_join_error(
3380        function
3381          .take()
3382          .expect("blocking function must be available"),
3383      )
3384    }
3385    .map(|output| ContainedTaskOutput::new(output, self.generation));
3386    drop(blocking);
3387    #[cfg(not(target_family = "wasm"))]
3388    if owns_lane && !self.draining.load(Ordering::Acquire) {
3389      // An admitted host turn already owns a bounded queue driver. Let its
3390      // outer loop service the released FIFO so blocking work cannot bypass
3391      // the host-turn budget and monopolize the JavaScript event loop.
3392      self.service_queued_blocking_work();
3393    }
3394    JoinHandle(JoinHandleInner::Ready(Some(result)))
3395  }
3396
3397  #[cfg(not(target_family = "wasm"))]
3398  fn take_queued_blocking(
3399    &self,
3400  ) -> Option<(
3401    Box<dyn FnOnce() + Send + 'static>,
3402    CurrentThreadBlockingGuard<'_>,
3403  )> {
3404    let (job, owner) = {
3405      let mut state = self
3406        .blocking_admission
3407        .state
3408        .lock()
3409        .unwrap_or_else(std::sync::PoisonError::into_inner);
3410      #[cfg(all(test, not(target_family = "wasm")))]
3411      run_current_thread_blocking_claim_test_hook();
3412      if self.stop.is_stopping()
3413        || state.closed
3414        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3415        || state.owner.is_some()
3416      {
3417        return None;
3418      }
3419      let job = state.queue.pop_front()?;
3420      let owner = self.fresh_owner_token();
3421      state.owner = Some(owner);
3422      (job, owner)
3423    };
3424    Some((job, self.enter_blocking_owner(owner, true)))
3425  }
3426
3427  #[cfg(not(target_family = "wasm"))]
3428  fn run_one_blocking(&self) -> bool {
3429    let Some((job, blocking)) = self.take_queued_blocking() else {
3430      return false;
3431    };
3432    let _generation = RuntimeGenerationGuard::enter(self.generation);
3433    {
3434      let _task = self.metrics.blocking_started(true);
3435      let _ = catch_unwind_contained(job);
3436    }
3437    drop(blocking);
3438    true
3439  }
3440
3441  #[cfg(not(target_family = "wasm"))]
3442  fn service_queued_blocking_work(&self) {
3443    while self.run_one_blocking() {}
3444  }
3445
3446  #[cfg(not(target_family = "wasm"))]
3447  fn has_serviceable_blocking_work(&self) -> bool {
3448    let state = self
3449      .blocking_admission
3450      .state
3451      .lock()
3452      .unwrap_or_else(std::sync::PoisonError::into_inner);
3453    !state.closed && state.owner.is_none() && !state.queue.is_empty()
3454  }
3455
3456  #[cfg(not(target_family = "wasm"))]
3457  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3458    let Some(ambient_owner) = BLOCKING_OWNER
3459      .with(std::cell::Cell::get)
3460      .filter(|owner| owner.executor_id == self.id)
3461    else {
3462      return false;
3463    };
3464    let Some(current) = dependency.select_for_owner(ambient_owner) else {
3465      return false;
3466    };
3467    let job = {
3468      let mut state = self
3469        .blocking_admission
3470        .state
3471        .lock()
3472        .unwrap_or_else(std::sync::PoisonError::into_inner);
3473      #[cfg(all(test, not(target_family = "wasm")))]
3474      run_current_thread_blocking_claim_test_hook();
3475      if self.stop.is_stopping()
3476        || state.closed
3477        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3478        || state.owner != Some(ambient_owner)
3479      {
3480        return false;
3481      }
3482      if dependency.try_claim(&current) {
3483        state.queue.remove(current.publication.dependency.job)
3484      } else {
3485        None
3486      }
3487    };
3488    let Some(job) = job else {
3489      return false;
3490    };
3491    let _generation = RuntimeGenerationGuard::enter(self.generation);
3492    let _task = self.metrics.blocking_started(false);
3493    let _ = catch_unwind_contained(job);
3494    true
3495  }
3496
3497  fn close_blocking_admission(&self) {
3498    #[cfg(not(target_family = "wasm"))]
3499    let queued = {
3500      let mut state = self
3501        .blocking_admission
3502        .state
3503        .lock()
3504        .unwrap_or_else(std::sync::PoisonError::into_inner);
3505      state.closed = true;
3506      state.queue.closed = true;
3507      state.queue.take_all()
3508    };
3509    #[cfg(target_family = "wasm")]
3510    {
3511      let mut state = self
3512        .blocking_admission
3513        .state
3514        .lock()
3515        .unwrap_or_else(std::sync::PoisonError::into_inner);
3516      state.closed = true;
3517    }
3518    #[cfg(not(target_family = "wasm"))]
3519    for job in queued {
3520      let _ = catch_unwind_contained(|| drop(job));
3521    }
3522  }
3523
3524  #[cfg(test)]
3525  fn with_task_dispatch(
3526    metrics: Arc<RuntimeMetrics>,
3527    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3528  ) -> Self {
3529    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
3530  }
3531
3532  #[cfg(test)]
3533  fn with_task_dispatch_registry(
3534    metrics: Arc<RuntimeMetrics>,
3535    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
3536    task_drivers: &'static CurrentThreadTaskDriverRegistry,
3537  ) -> Self {
3538    let mut executor = Self::new(metrics);
3539    executor.task_dispatch = task_dispatch;
3540    executor.task_drivers = task_drivers;
3541    executor
3542  }
3543
3544  fn schedule(self: &Arc<Self>, runnable: Runnable) {
3545    self.metrics.runnable_scheduled();
3546    let rejected = {
3547      let mut queue = self
3548        .queue
3549        .lock()
3550        .unwrap_or_else(std::sync::PoisonError::into_inner);
3551      if queue.closed || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3552        Some(runnable)
3553      } else {
3554        queue.runnables.push_back(runnable);
3555        None
3556      }
3557    };
3558    if let Some(runnable) = rejected {
3559      self
3560        .metrics
3561        .queued_runnables
3562        .fetch_sub(1, Ordering::Relaxed);
3563      let _generation = RuntimeGenerationGuard::enter(self.generation);
3564      let _ = catch_unwind_contained(|| drop(runnable));
3565      return;
3566    }
3567    // Queue work and only then wake an explicit block_on driver. This is still
3568    // enqueue-only with respect to the wake caller: polling happens on the
3569    // driver's thread, never inline from an arbitrary waker invocation.
3570    self.stop.wake_all();
3571    if !self.draining.load(Ordering::Acquire) {
3572      self.request_drain();
3573    }
3574  }
3575
3576  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
3577  ///
3578  /// A `Waker` may be invoked while its producer holds an internal mutex
3579  /// (`futures::Shared` does this). Polling inline from the scheduler callback
3580  /// can then re-enter that future and self-deadlock on the same mutex. Once a
3581  /// host dispatcher has accepted work, this executor stays host-driven for
3582  /// the rest of its generation; a temporarily missing env leaves work queued
3583  /// for the next registration or for shutdown cancellation.
3584  fn request_drain(self: &Arc<Self>) {
3585    let _ = self.request_drain_with(next_current_thread_dispatch_id);
3586  }
3587
3588  fn lock_scheduler_for_publication(
3589    &self,
3590  ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
3591    #[cfg(all(test, not(target_family = "wasm")))]
3592    match self.scheduler_idle_lock.try_lock() {
3593      Ok(scheduler) => scheduler,
3594      Err(std::sync::TryLockError::WouldBlock) => {
3595        run_current_thread_dispatch_publication_blocked_test_hook();
3596        self
3597          .scheduler_idle_lock
3598          .lock()
3599          .unwrap_or_else(std::sync::PoisonError::into_inner)
3600      }
3601      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
3602    }
3603    #[cfg(any(not(test), target_family = "wasm"))]
3604    self
3605      .scheduler_idle_lock
3606      .lock()
3607      .unwrap_or_else(std::sync::PoisonError::into_inner)
3608  }
3609
3610  fn request_drain_with(
3611    self: &Arc<Self>,
3612    next_dispatch: impl FnOnce() -> u64,
3613  ) -> CurrentThreadDispatchRequest {
3614    let mut scheduler = self.lock_scheduler_for_publication();
3615    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
3616      return CurrentThreadDispatchRequest::Unavailable;
3617    }
3618    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
3619      return CurrentThreadDispatchRequest::Coalesced;
3620    }
3621    let dispatch = next_dispatch();
3622    if self
3623      .dispatch_pending
3624      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
3625      .is_err()
3626    {
3627      return CurrentThreadDispatchRequest::Coalesced;
3628    }
3629    let dispatch_call = self
3630      .begin_host_dispatch_publication(&mut scheduler, dispatch)
3631      .expect("a fresh CurrentThread dispatch must own its publication");
3632    drop(scheduler);
3633    match self.publish_host_dispatch(dispatch, dispatch_call) {
3634      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
3635        CurrentThreadDispatchRequest::Accepted
3636      }
3637      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
3638    }
3639  }
3640
3641  fn claim_dispatch_call<'a>(
3642    &'a self,
3643    scheduler: &mut CurrentThreadSchedulerState,
3644  ) -> CurrentThreadDispatchCall<'a> {
3645    let generation = RuntimeGenerationGuard::enter(self.generation);
3646    let active_dispatch_calls = scheduler
3647      .active_dispatch_calls
3648      .checked_add(1)
3649      .expect("CurrentThread active dispatch-call count exhausted");
3650    scheduler.active_dispatch_calls = active_dispatch_calls;
3651    CurrentThreadDispatchCall {
3652      executor: self,
3653      publication: None,
3654      _generation: generation,
3655    }
3656  }
3657
3658  fn begin_host_dispatch_publication<'a>(
3659    &'a self,
3660    scheduler: &mut CurrentThreadSchedulerState,
3661    dispatch: u64,
3662  ) -> Option<CurrentThreadDispatchCall<'a>> {
3663    // See the implementation notes in this crate's README.
3664    if let Some(publication) = scheduler
3665      .dispatch_publications
3666      .iter_mut()
3667      .find(|publication| publication.dispatch == dispatch)
3668    {
3669      publication.republish_requested = true;
3670      return None;
3671    }
3672    let mut dispatch_call = self.claim_dispatch_call(scheduler);
3673    scheduler
3674      .dispatch_publications
3675      .push(CurrentThreadDispatchPublication {
3676        dispatch,
3677        republish_requested: false,
3678      });
3679    dispatch_call.publication = Some(dispatch);
3680    Some(dispatch_call)
3681  }
3682
3683  fn publish_host_dispatch(
3684    self: &Arc<Self>,
3685    dispatch: u64,
3686    mut dispatch_call: CurrentThreadDispatchCall<'_>,
3687  ) -> CurrentThreadHostDispatchResult {
3688    let mut panic_payload = RetainedPanicPayload::default();
3689    loop {
3690      let result = match catch_unwind(AssertUnwindSafe(|| {
3691        #[cfg(all(test, not(target_family = "wasm")))]
3692        run_current_thread_dispatch_call_test_hook();
3693        let result = (self.task_dispatch)(dispatch);
3694        #[cfg(all(test, not(target_family = "wasm")))]
3695        run_current_thread_dispatch_returned_test_hook();
3696        result
3697      })) {
3698        Ok(result) => Some(result),
3699        Err(payload) => {
3700          panic_payload.capture(payload);
3701          None
3702        }
3703      };
3704
3705      let step = catch_unwind(AssertUnwindSafe(|| {
3706        let (republish, cancelling, failure, result) = {
3707          let mut scheduler = self
3708            .scheduler_idle_lock
3709            .lock()
3710            .unwrap_or_else(std::sync::PoisonError::into_inner);
3711          let index = scheduler
3712            .dispatch_publications
3713            .iter()
3714            .position(|publication| publication.dispatch == dispatch)
3715            .expect("the CurrentThread dispatch publication must remain registered");
3716          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
3717          let republish = dispatch_remains_pending
3718            && !self.stop.is_stopping()
3719            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
3720            && scheduler.dispatch_publications[index].republish_requested;
3721          if republish {
3722            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3723              let _ = self.task_drivers.rearm_failed_publication(failure);
3724            }
3725            scheduler.dispatch_publications[index].republish_requested = false;
3726            (true, None, None, result)
3727          } else {
3728            dispatch_call.finish_publication(&mut scheduler);
3729            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
3730              #[cfg(all(test, not(target_family = "wasm")))]
3731              run_current_thread_failed_publication_retired_test_hook();
3732              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3733            } else {
3734              None
3735            };
3736            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
3737              && dispatch_remains_pending)
3738              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
3739              .flatten();
3740            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
3741              && !dispatch_remains_pending
3742            {
3743              // A synchronous callback may already have consumed, cancelled, or
3744              // superseded this exact capability.
3745              Some(CurrentThreadHostDispatchResult::Accepted)
3746            } else {
3747              result
3748            };
3749            (false, cancelling, failure, result)
3750          }
3751        };
3752
3753        if republish {
3754          return CurrentThreadDispatchPublicationStep::Republish;
3755        }
3756        if let Some(cancelling) = cancelling {
3757          self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
3758        }
3759        if let Some(failure) = failure {
3760          self.run_host_dispatch_failure(failure.action);
3761        }
3762        CurrentThreadDispatchPublicationStep::Complete(result)
3763      }));
3764
3765      match step {
3766        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
3767        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
3768          if panic_payload.is_some() {
3769            panic_payload.resume();
3770          }
3771          return result
3772            .expect("a CurrentThread host dispatch without a result must resume its panic");
3773        }
3774        Err(payload) => {
3775          panic_payload.capture(payload);
3776          panic_payload.resume();
3777        }
3778      }
3779    }
3780  }
3781
3782  fn claim_host_turn(
3783    self: &Arc<Self>,
3784    scheduler: &mut CurrentThreadSchedulerState,
3785  ) -> CurrentThreadHostTurn {
3786    scheduler.active_host_turns = scheduler
3787      .active_host_turns
3788      .checked_add(1)
3789      .expect("CurrentThread active host-turn count exhausted");
3790    self.draining.store(true, Ordering::Release);
3791    CurrentThreadHostTurn {
3792      executor: Arc::clone(self),
3793      draining: true,
3794      _generation: RuntimeGenerationGuard::enter(self.generation),
3795    }
3796  }
3797
3798  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
3799    let mut scheduler = self
3800      .scheduler_idle_lock
3801      .lock()
3802      .unwrap_or_else(std::sync::PoisonError::into_inner);
3803    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3804      return None;
3805    }
3806    if dispatch == 0
3807      || self
3808        .dispatch_pending
3809        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3810        .is_err()
3811    {
3812      return None;
3813    }
3814    #[cfg(all(test, not(target_family = "wasm")))]
3815    run_current_thread_dispatch_claim_test_hook();
3816    let _ =
3817      self
3818        .recovery_dispatch
3819        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
3820    Some(self.claim_host_turn(&mut scheduler))
3821  }
3822
3823  #[cfg(test)]
3824  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
3825    let mut scheduler = self
3826      .scheduler_idle_lock
3827      .lock()
3828      .unwrap_or_else(std::sync::PoisonError::into_inner);
3829    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
3830      return None;
3831    }
3832    Some(self.claim_host_turn(&mut scheduler))
3833  }
3834
3835  #[cfg(test)]
3836  fn drive_host_turn(self: &Arc<Self>) {
3837    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3838    let host_turn = if dispatch == 0 {
3839      self.try_admit_host_turn_without_dispatch()
3840    } else {
3841      self.try_admit_host_turn(dispatch)
3842    };
3843    if let Some(mut host_turn) = host_turn {
3844      host_turn.drive();
3845    }
3846  }
3847
3848  #[cfg(test)]
3849  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
3850    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
3851      host_turn.drive();
3852    }
3853  }
3854
3855  /// Drive a host turn whose scheduler role was claimed while the controller
3856  /// still held the lifecycle mutex. See
3857  /// the implementation notes in this crate's README.
3858  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
3859    #[cfg(not(target_family = "wasm"))]
3860    let mut runnable_streak = 0usize;
3861    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
3862      #[cfg(not(target_family = "wasm"))]
3863      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
3864        runnable_streak = 0;
3865        if self.run_one_blocking() {
3866          continue;
3867        }
3868      }
3869      if self.drain_one() {
3870        #[cfg(not(target_family = "wasm"))]
3871        {
3872          runnable_streak += 1;
3873        }
3874        continue;
3875      }
3876      #[cfg(not(target_family = "wasm"))]
3877      if self.run_one_blocking() {
3878        runnable_streak = 0;
3879        continue;
3880      }
3881      break;
3882    }
3883    // A bounded turn releases queue-drain exclusivity before publishing its
3884    // continuation, but remains an active host turn until that publication
3885    // returns. Shutdown waits for the latter lifecycle role, not merely the
3886    // former exclusivity bit.
3887    host_turn.release_draining();
3888    self.request_drain_if_work_remains();
3889  }
3890
3891  fn request_drain_if_queued(self: &Arc<Self>) {
3892    if !self.has_serviceable_work() {
3893      return;
3894    }
3895    let mut scheduler = self.lock_scheduler_for_publication();
3896    if self.stop.is_stopping()
3897      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3898      || self.draining.load(Ordering::Acquire)
3899    {
3900      return;
3901    }
3902    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
3903    if dispatch == 0 {
3904      drop(scheduler);
3905      self.request_drain_if_work_remains();
3906      return;
3907    }
3908
3909    // A newly registered host joins the existing all-host race through its own
3910    // delivery token. Reusing the executor capability preserves every other
3911    // host's accepted attempt and lets old registration tokens fail closed.
3912    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
3913      return;
3914    };
3915    drop(scheduler);
3916    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
3917  }
3918
3919  fn retire_unavailable_host_dispatch_locked(
3920    &self,
3921    dispatch: u64,
3922  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
3923    if self
3924      .dispatch_pending
3925      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3926      .is_err()
3927    {
3928      return None;
3929    }
3930    if self
3931      .recovery_dispatch
3932      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3933      .is_ok()
3934    {
3935      self.begin_failed_dispatch_cancellation()
3936    } else {
3937      None
3938    }
3939  }
3940
3941  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
3942    // See the implementation notes in this crate's README.
3943    let failure = {
3944      let mut scheduler = self
3945        .scheduler_idle_lock
3946        .lock()
3947        .unwrap_or_else(std::sync::PoisonError::into_inner);
3948      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
3949    };
3950    if let Some(failure) = failure {
3951      self.run_host_dispatch_failure(failure.action);
3952    }
3953  }
3954
3955  fn begin_host_dispatch_failure_locked<'a>(
3956    &'a self,
3957    scheduler: &mut CurrentThreadSchedulerState,
3958    failure: CurrentThreadTaskDispatchFailure,
3959  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
3960    if !self.task_drivers.is_current_failure(failure) {
3961      return None;
3962    }
3963    let registry_cancellation =
3964      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
3965    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
3966    Some(CurrentThreadDispatchFailureRecovery {
3967      action,
3968      _registry_cancellation: registry_cancellation,
3969    })
3970  }
3971
3972  fn begin_host_dispatch_failure_transition_locked<'a>(
3973    &'a self,
3974    scheduler: &mut CurrentThreadSchedulerState,
3975    dispatch: u64,
3976  ) -> CurrentThreadDispatchFailureAction<'a> {
3977    if dispatch == 0
3978      || self
3979        .dispatch_pending
3980        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3981        .is_err()
3982    {
3983      return CurrentThreadDispatchFailureAction::None;
3984    }
3985    #[cfg(all(test, not(target_family = "wasm")))]
3986    run_current_thread_dispatch_recovery_test_hook();
3987
3988    if self
3989      .recovery_dispatch
3990      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
3991      .is_ok()
3992    {
3993      // The one fresh-turn replacement failed too. Polling inline here would
3994      // re-enter the host callback, while another retry could spin forever.
3995      // Claim terminal cancellation before allowing another publication.
3996      return self.begin_failed_dispatch_cancellation().map_or(
3997        CurrentThreadDispatchFailureAction::None,
3998        CurrentThreadDispatchFailureAction::Cancel,
3999      );
4000    }
4001
4002    if self.stop.is_stopping()
4003      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4004      || self.draining.load(Ordering::Acquire)
4005      || !self.has_serviceable_work()
4006    {
4007      return CurrentThreadDispatchFailureAction::None;
4008    }
4009
4010    // Consume the failed capability and reserve its sole replacement while
4011    // publication is excluded. A newly registered host can only join this
4012    // tagged replacement, never publish an untagged dispatch in between.
4013    let replacement = next_current_thread_dispatch_id();
4014    self.recovery_dispatch.store(replacement, Ordering::Release);
4015    self.dispatch_pending.store(replacement, Ordering::Release);
4016    let dispatch_call = self
4017      .begin_host_dispatch_publication(scheduler, replacement)
4018      .expect("a reserved CurrentThread replacement must own its publication");
4019    CurrentThreadDispatchFailureAction::Publish {
4020      dispatch: replacement,
4021      call: dispatch_call,
4022    }
4023  }
4024
4025  #[cfg(test)]
4026  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4027    let failure = {
4028      let mut scheduler = self
4029        .scheduler_idle_lock
4030        .lock()
4031        .unwrap_or_else(std::sync::PoisonError::into_inner);
4032      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4033    };
4034    self.run_host_dispatch_failure(failure);
4035  }
4036
4037  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4038    match failure {
4039      CurrentThreadDispatchFailureAction::None => {}
4040      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4041        self.cancel_work_after_host_dispatch_failure_claimed(cancelling);
4042      }
4043      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4044        let _ = self.publish_host_dispatch(dispatch, call);
4045      }
4046    }
4047  }
4048
4049  fn has_serviceable_work(&self) -> bool {
4050    let has_queued_runnable = {
4051      let queue = self
4052        .queue
4053        .lock()
4054        .unwrap_or_else(std::sync::PoisonError::into_inner);
4055      !queue.closed && !queue.runnables.is_empty()
4056    };
4057    #[cfg(not(target_family = "wasm"))]
4058    let has_queued_blocking = self.has_serviceable_blocking_work();
4059    #[cfg(target_family = "wasm")]
4060    let has_queued_blocking = false;
4061    has_queued_runnable || has_queued_blocking
4062  }
4063
4064  fn request_drain_if_work_remains(self: &Arc<Self>) {
4065    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4066      self.request_drain();
4067    }
4068  }
4069
4070  fn begin_failed_dispatch_cancellation(
4071    &self,
4072  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4073    if self
4074      .cancelling_failed_dispatch
4075      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4076      .is_err()
4077    {
4078      return None;
4079    }
4080    self.dispatch_pending.store(0, Ordering::Release);
4081    self.recovery_dispatch.store(0, Ordering::Release);
4082    Some(CurrentThreadDispatchCancellationGuard(self))
4083  }
4084
4085  fn cancel_work_after_host_dispatch_failure_claimed(
4086    &self,
4087    _cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4088  ) {
4089    let queued = {
4090      let mut queue = self
4091        .queue
4092        .lock()
4093        .unwrap_or_else(std::sync::PoisonError::into_inner);
4094      std::mem::take(&mut queue.runnables)
4095    };
4096    #[cfg(not(target_family = "wasm"))]
4097    let queued_blocking = {
4098      let mut state = self
4099        .blocking_admission
4100        .state
4101        .lock()
4102        .unwrap_or_else(std::sync::PoisonError::into_inner);
4103      state.queue.take_all()
4104    };
4105
4106    let _generation = RuntimeGenerationGuard::enter(self.generation);
4107    for runnable in queued {
4108      self
4109        .metrics
4110        .queued_runnables
4111        .fetch_sub(1, Ordering::Relaxed);
4112      let _ = catch_unwind_contained(|| drop(runnable));
4113    }
4114    #[cfg(not(target_family = "wasm"))]
4115    for job in queued_blocking {
4116      let _ = catch_unwind_contained(|| drop(job));
4117    }
4118  }
4119
4120  fn take_runnable(&self) -> Option<Runnable> {
4121    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
4122    // generation shutdown publishes while holding `stop.publication`. Claiming
4123    // under both locks gives each publication one exact ordering edge with the
4124    // runnable queue. The guards are released before polling user code.
4125    let scheduler = self
4126      .scheduler_idle_lock
4127      .lock()
4128      .unwrap_or_else(std::sync::PoisonError::into_inner);
4129    #[cfg(all(test, not(target_family = "wasm")))]
4130    run_current_thread_runnable_claim_test_hook();
4131    let stop_publication = self.stop.publication_guard();
4132    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4133      return None;
4134    }
4135    let runnable = {
4136      let mut queue = self
4137        .queue
4138        .lock()
4139        .unwrap_or_else(std::sync::PoisonError::into_inner);
4140      if queue.closed {
4141        None
4142      } else {
4143        queue.runnables.pop_front()
4144      }
4145    };
4146    drop(stop_publication);
4147    drop(scheduler);
4148    runnable
4149  }
4150
4151  fn drain_one(&self) -> bool {
4152    let runnable = self.take_runnable();
4153    if let Some(runnable) = runnable {
4154      #[cfg(not(target_family = "wasm"))]
4155      let _non_owner = BlockingOwnerGuard::enter(None);
4156      run_runnable(&self.metrics, runnable);
4157      true
4158    } else {
4159      false
4160    }
4161  }
4162
4163  /// Drive `future` to completion on this thread, draining queued runnables
4164  /// between polls. This loop OWNS its park site: the future is polled with
4165  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
4166  /// inspect the wake token directly instead of handing it to
4167  /// `futures::executor::block_on`'s opaque parker:
4168  ///
4169  ///   * `Pending` + queue empty + wake token pending: a self-waking future
4170  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
4171  ///     park, NOT a deadlock; panic only at the park decision, never at
4172  ///     Pending-return.
4173  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
4174  ///     deadlock -- no other thread exists that could ever deliver the wake
4175  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
4176  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
4177  ///     rescue here: its `setTimeout` relay shares this very thread, so it
4178  ///     can never fire while the park holds it -- it only selects the
4179  ///     timer-backed variant of the same certain diagnostic.
4180  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
4181  ///     `park_deadline` is armed, the park is bounded with progress-based
4182  ///     reset (see [`park_with_deadline`]) and panics typed if a full
4183  ///     window passes with zero runtime progress.
4184  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4185    let parker = Arc::new(DriverParker::default());
4186    let _stop_registration = self.stop.register(&parker);
4187    #[cfg(not(target_family = "wasm"))]
4188    let dependency = BlockOnDependencyGuard::enter(self.generation);
4189    let waker = std::task::Waker::from(Arc::clone(&parker));
4190    let mut cx = Context::from_waker(&waker);
4191    #[cfg(not(target_family = "wasm"))]
4192    let mut runnable_streak = 0usize;
4193    loop {
4194      if self.stop.is_stopping() {
4195        return BlockOnOutcome::Stopped;
4196      }
4197      #[cfg(not(target_family = "wasm"))]
4198      dependency.clear();
4199      if future.as_mut().poll(&mut cx).is_ready() {
4200        return BlockOnOutcome::Completed;
4201      }
4202      // Shutdown must outrank queue work and self-wake permits. Otherwise an
4203      // always-self-waking future can continue before reaching the park-site
4204      // check forever while its generation work guard blocks quiescence.
4205      if self.stop.is_stopping() {
4206        return BlockOnOutcome::Stopped;
4207      }
4208      #[cfg(not(target_family = "wasm"))]
4209      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4210        runnable_streak = 0;
4211        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4212          continue;
4213        }
4214      }
4215      if self.drain_one() {
4216        #[cfg(not(target_family = "wasm"))]
4217        {
4218          runnable_streak += 1;
4219        }
4220        continue;
4221      }
4222      #[cfg(not(target_family = "wasm"))]
4223      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4224        runnable_streak = 0;
4225        continue;
4226      }
4227      #[cfg(not(target_family = "wasm"))]
4228      {
4229        runnable_streak = 0;
4230      }
4231      // PARK DECISION: the future is Pending and the queue is empty. A token
4232      // stored during the poll (or by a racing thread) means a wake is
4233      // already pending: consume it and re-poll instead of parking.
4234      if parker.consume_permit() {
4235        continue;
4236      }
4237      // Threadless timer invariant: on a THREADLESS build even a pending
4238      // HOST timer is NOT a wake-token source -- the host event loop's
4239      // `setTimeout` relay can only run on the very thread that is about to
4240      // park, so it can never fire while `block_on` holds it. EVERY threadless
4241      // park at this decision is therefore provably dead; a pending timer only
4242      // selects the more precise diagnostic. Falling through instead would hit
4243      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
4244      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
4245      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
4246      // below stays: it serves the THREADED CT flavor, where the host loop
4247      // genuinely runs concurrently.)
4248      if self.threadless {
4249        if self.host_timers.has_pending() {
4250          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4251        }
4252        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4253      }
4254      match self.park_deadline {
4255        // A wake landing between `consume_permit` above and the park is
4256        // stored as the parker's permit, so `park` returns immediately --
4257        // never lost.
4258        None => parker.park(),
4259        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4260          DeadlineParkOutcome::Woken => {}
4261          DeadlineParkOutcome::Expired(armed) => {
4262            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
4263            // a wake can land in the instructions after the window
4264            // closed, and the fingerprint read inside `park_with_deadline`
4265            // is stale by now. Re-check both before declaring death; a hit
4266            // means the park was healthy -- continue the loop (re-poll,
4267            // drain, fresh full window on the next park). A LIVE host-timer
4268            // wait also vetoes, re-checked LAST -- arming a host timer bumps
4269            // no progress
4270            // counter, so it is invisible to the fingerprint read: waiting
4271            // out a host timer with zero progress is legitimate, its wake is
4272            // scheduled on the host loop. `deadline` doubles as the grace
4273            // for host firing lag; a timer past due by more than one full
4274            // window without firing is the frozen-JS-loop signature this
4275            // detection exists for, so it stops vetoing.
4276            if parker.consume_permit()
4277              || self.metrics.progress_fingerprint() != armed
4278              || self.host_timers.has_live_wait(deadline)
4279            {
4280              continue;
4281            }
4282            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4283          }
4284        },
4285      }
4286    }
4287  }
4288
4289  fn begin_shutdown(&self) {
4290    let _generation = RuntimeGenerationGuard::enter(self.generation);
4291    self.stop.begin_shutdown();
4292    self.close_blocking_admission();
4293    let queued = {
4294      let mut queue = self
4295        .queue
4296        .lock()
4297        .unwrap_or_else(std::sync::PoisonError::into_inner);
4298      queue.closed = true;
4299      std::mem::take(&mut queue.runnables)
4300    };
4301    self.dispatch_pending.store(0, Ordering::Release);
4302    self.recovery_dispatch.store(0, Ordering::Release);
4303    self.task_drivers.retire_for_shutdown();
4304    for runnable in queued {
4305      self
4306        .metrics
4307        .queued_runnables
4308        .fetch_sub(1, Ordering::Relaxed);
4309      // Dropping the last runnable cancels its detached async-task and retires
4310      // the generation guard. Isolate user future destructors from shutdown.
4311      let _ = catch_unwind_contained(|| drop(runnable));
4312    }
4313    self.host_timers.shutdown();
4314  }
4315
4316  fn wait_until_scheduler_idle(&self) {
4317    let mut idle = self
4318      .scheduler_idle_lock
4319      .lock()
4320      .unwrap_or_else(std::sync::PoisonError::into_inner);
4321    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4322      idle = self
4323        .scheduler_idle
4324        .wait(idle)
4325        .unwrap_or_else(std::sync::PoisonError::into_inner);
4326    }
4327    debug_assert!(
4328      idle.dispatch_publications.is_empty(),
4329      "an idle CurrentThread scheduler must not retain dispatch publications"
4330    );
4331  }
4332}
4333
4334// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
4335// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
4336// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
4337// cooperatively instead of parking the worker. Carrying the executor id (not a
4338// bare bool) keeps this marker scoped exactly like
4339// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
4340// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
4341// none of B's accounting, so it parks like any foreign-thread caller rather
4342// than driving B's queues as an unaccounted within-cap driver. Under the single
4343// global executor used in production these ids always match, so this is a
4344// defensive scoping gate (consistent with the blocking-owner machinery), not a
4345// reachable production change.
4346#[cfg(not(target_family = "wasm"))]
4347thread_local! {
4348  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4349  /// Identifies the dedicated timer thread for lifecycle self-wait
4350  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
4351  /// timer thread must never become a cooperative Rayon scheduler driver.
4352  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4353}
4354
4355// Identifies scheduler-driver frames on a pool worker. Unlike
4356// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
4357// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
4358// must not place work in the per-thread LIFO slot because no drain frame may
4359// remain to consume it after the Rayon job returns.
4360#[cfg(not(target_family = "wasm"))]
4361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4362struct SchedulerDriverRole {
4363  executor_id: u64,
4364  can_run_blocking: bool,
4365}
4366
4367#[cfg(not(target_family = "wasm"))]
4368thread_local! {
4369  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
4370    const { std::cell::Cell::new(None) };
4371}
4372
4373// Per-worker LIFO slot (tokio-style): holds at most ONE
4374// runnable that was scheduled FROM this pool worker (typically a task the
4375// currently running runnable just woke). It is popped by this same thread's
4376// next drain/run_one iteration BEFORE the shared FIFO, so the hot
4377// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
4378// wake and no drainer spawn. Tagged with the executor id -- the same scoping
4379// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
4380// drain/run_one/flush of the SAME executor may pop it.
4381//
4382// STRANDING INVARIANT: slot work is invisible to every other
4383// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
4384// owning thread MUST either run its slot entry or flush it to the shared FIFO
4385// before it stops draining -- see the flush calls at `drain`'s returns, the
4386// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
4387// unwinds. A runnable left here while the thread returns to rayon would be a
4388// silently lost task and a `queued_runnables` leak.
4389#[cfg(not(target_family = "wasm"))]
4390thread_local! {
4391  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
4392    const { std::cell::Cell::new(None) };
4393}
4394
4395#[cfg(not(target_family = "wasm"))]
4396struct OnPoolWorkerGuard {
4397  previous_worker: Option<u64>,
4398  previous_driver: Option<SchedulerDriverRole>,
4399}
4400
4401#[cfg(not(target_family = "wasm"))]
4402impl OnPoolWorkerGuard {
4403  // Save the previous marker and install `id`, so nested/re-entrant drains
4404  // (possibly from different executors on the same thread) restore the exact
4405  // prior executor id on drop instead of unconditionally clearing it.
4406  fn enter(id: u64) -> Self {
4407    Self::enter_with_role(id, true)
4408  }
4409
4410  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
4411    Self {
4412      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
4413      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
4414        flag.replace(Some(SchedulerDriverRole {
4415          executor_id: id,
4416          can_run_blocking,
4417        }))
4418      }),
4419    }
4420  }
4421}
4422
4423#[cfg(not(target_family = "wasm"))]
4424impl Drop for OnPoolWorkerGuard {
4425  fn drop(&mut self) {
4426    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
4427    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
4428  }
4429}
4430
4431// Identifies one specific counted-blocking owner frame on one specific executor.
4432//
4433// `executor_id` is assigned once per executor (so a stale token left on a
4434// thread by a shut-down executor can never authorize work on a replacement);
4435// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
4436// half of dependency lending: the exact job half comes from the pending
4437// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
4438// runnable driver owns no frame, so it must respect normal blocking admission.
4439//
4440// CurrentThread uses the same identity to distinguish same-stack lane borrowing
4441// from cross-driver contention. MultiThread additionally reserves active owner
4442// frames while an otherwise-idle cooperative driver reuses their admitted lane.
4443#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
4444struct BlockingOwnerToken {
4445  executor_id: u64,
4446  frame: u64,
4447}
4448
4449#[cfg(not(target_family = "wasm"))]
4450enum OwnedBlockingOutcome {
4451  Ineligible,
4452  OwnerBusy(BlockingOwnerToken),
4453  ReservationReleased {
4454    owner: BlockingOwnerToken,
4455    ran: bool,
4456  },
4457}
4458
4459// Process-global owner-frame source for per-frame dependency-lending
4460// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
4461static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
4462#[cfg(not(target_family = "wasm"))]
4463static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
4464
4465// The owner frame (if any) currently running a counted blocking closure on THIS
4466// thread. The exact frame identity scopes any over-cap dependency lending.
4467thread_local! {
4468  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
4469    const { std::cell::Cell::new(None) };
4470}
4471
4472// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
4473// running a counted blocking closure, or with `None` while a cooperative driver
4474// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
4475// runnable an owner happens to drive via `run_one` is a logical non-owner and
4476// must not inherit the owner's over-cap privilege). Restored on drop, so the
4477// pattern is panic-safe even though `run_runnable` already catches panics.
4478#[cfg(not(target_family = "wasm"))]
4479struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
4480
4481#[cfg(not(target_family = "wasm"))]
4482impl BlockingOwnerGuard {
4483  fn enter(token: Option<BlockingOwnerToken>) -> Self {
4484    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
4485  }
4486}
4487
4488#[cfg(not(target_family = "wasm"))]
4489impl Drop for BlockingOwnerGuard {
4490  fn drop(&mut self) {
4491    BLOCKING_OWNER.with(|cell| cell.set(self.0));
4492  }
4493}
4494
4495#[cfg(not(target_family = "wasm"))]
4496#[derive(Default)]
4497struct ActiveBlockingOwners {
4498  // `None` is available; `Some(id)` is reserved by exactly that lending
4499  // transfer. The identity prevents a delayed stale drop from releasing a
4500  // newer reservation of the same owner frame.
4501  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
4502}
4503
4504#[cfg(not(target_family = "wasm"))]
4505impl ActiveBlockingOwners {
4506  fn register(&self, owner: BlockingOwnerToken) {
4507    let previous = self
4508      .owners
4509      .lock()
4510      .unwrap_or_else(std::sync::PoisonError::into_inner)
4511      .insert(owner, None);
4512    debug_assert!(previous.is_none());
4513  }
4514
4515  fn unregister(&self, owner: BlockingOwnerToken) {
4516    let removed = self
4517      .owners
4518      .lock()
4519      .unwrap_or_else(std::sync::PoisonError::into_inner)
4520      .remove(&owner);
4521    debug_assert!(removed.is_some());
4522  }
4523
4524  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
4525    let mut owners = self
4526      .owners
4527      .lock()
4528      .unwrap_or_else(std::sync::PoisonError::into_inner);
4529    let reservation = owners.get_mut(&owner)?;
4530    if reservation.is_some() {
4531      return None;
4532    }
4533    let id = next_unique_id(
4534      &NEXT_OWNER_RESERVATION,
4535      "blocking owner reservation id space exhausted",
4536    );
4537    *reservation = Some(id);
4538    Some(id)
4539  }
4540
4541  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
4542    let mut owners = self
4543      .owners
4544      .lock()
4545      .unwrap_or_else(std::sync::PoisonError::into_inner);
4546    if let Some(current) = owners.get_mut(&owner)
4547      && *current == Some(reservation)
4548    {
4549      *current = None;
4550    }
4551  }
4552
4553  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
4554    self
4555      .owners
4556      .lock()
4557      .unwrap_or_else(std::sync::PoisonError::into_inner)
4558      .get(&owner)
4559      .is_some_and(Option::is_none)
4560  }
4561}
4562
4563#[cfg(not(target_family = "wasm"))]
4564struct ActiveBlockingOwnerGuard<'a> {
4565  owners: &'a ActiveBlockingOwners,
4566  owner: BlockingOwnerToken,
4567}
4568
4569#[cfg(not(target_family = "wasm"))]
4570impl<'a> ActiveBlockingOwnerGuard<'a> {
4571  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
4572    owners.register(owner);
4573    Self { owners, owner }
4574  }
4575}
4576
4577#[cfg(not(target_family = "wasm"))]
4578impl Drop for ActiveBlockingOwnerGuard<'_> {
4579  fn drop(&mut self) {
4580    self.owners.unregister(self.owner);
4581  }
4582}
4583
4584#[cfg(not(target_family = "wasm"))]
4585struct OwnerLaneReservation<'a> {
4586  owners: &'a ActiveBlockingOwners,
4587  owner: BlockingOwnerToken,
4588  reservation: u64,
4589}
4590
4591#[cfg(not(target_family = "wasm"))]
4592impl<'a> OwnerLaneReservation<'a> {
4593  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
4594    let reservation = owners.reserve(owner)?;
4595    Some(Self {
4596      owners,
4597      owner,
4598      reservation,
4599    })
4600  }
4601}
4602
4603#[cfg(not(target_family = "wasm"))]
4604impl Drop for OwnerLaneReservation<'_> {
4605  fn drop(&mut self) {
4606    self.owners.release(self.owner, self.reservation);
4607  }
4608}
4609
4610// A blocking job queued for the pool. Its stable id is copied into the returned
4611// JoinHandle and propagated through async JoinHandle dependencies, allowing a
4612// saturated owner to run exactly the job its nested block_on awaits.
4613#[cfg(not(target_family = "wasm"))]
4614type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
4615
4616#[cfg(not(target_family = "wasm"))]
4617struct QueuedBlocking {
4618  id: BlockingJobId,
4619  run: BlockingClosure,
4620}
4621
4622#[cfg(not(target_family = "wasm"))]
4623struct BlockingQueueNode {
4624  previous: Option<BlockingJobId>,
4625  next: Option<BlockingJobId>,
4626  run: Box<dyn FnOnce() + Send + 'static>,
4627}
4628
4629#[cfg(not(target_family = "wasm"))]
4630struct BlockingQueue {
4631  closed: bool,
4632  head: Option<BlockingJobId>,
4633  tail: Option<BlockingJobId>,
4634  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
4635}
4636
4637#[cfg(not(target_family = "wasm"))]
4638impl BlockingQueue {
4639  fn push(&mut self, job: QueuedBlocking) {
4640    let previous = self.tail;
4641    let replaced = self.jobs.insert(
4642      job.id,
4643      BlockingQueueNode {
4644        previous,
4645        next: None,
4646        run: job.run,
4647      },
4648    );
4649    debug_assert!(replaced.is_none());
4650    if let Some(previous) = previous {
4651      self
4652        .jobs
4653        .get_mut(&previous)
4654        .expect("blocking queue tail must be indexed")
4655        .next = Some(job.id);
4656    } else {
4657      self.head = Some(job.id);
4658    }
4659    self.tail = Some(job.id);
4660  }
4661
4662  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4663    self.head.and_then(|id| self.remove(id))
4664  }
4665
4666  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
4667    let node = self.jobs.remove(&id)?;
4668    if let Some(previous) = node.previous {
4669      self
4670        .jobs
4671        .get_mut(&previous)
4672        .expect("blocking queue predecessor must be indexed")
4673        .next = node.next;
4674    } else {
4675      self.head = node.next;
4676    }
4677    if let Some(next) = node.next {
4678      self
4679        .jobs
4680        .get_mut(&next)
4681        .expect("blocking queue successor must be indexed")
4682        .previous = node.previous;
4683    } else {
4684      self.tail = node.previous;
4685    }
4686    Some(node.run)
4687  }
4688
4689  fn is_empty(&self) -> bool {
4690    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
4691    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
4692    self.jobs.is_empty()
4693  }
4694
4695  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
4696    self.head = None;
4697    self.tail = None;
4698    std::mem::take(&mut self.jobs)
4699      .into_values()
4700      .map(|node| node.run)
4701      .collect()
4702  }
4703}
4704
4705#[cfg(not(target_family = "wasm"))]
4706struct WorkerLifecycle {
4707  remaining: Mutex<usize>,
4708  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
4709}
4710
4711#[cfg(not(target_family = "wasm"))]
4712impl WorkerLifecycle {
4713  fn new(worker_threads: usize) -> Arc<Self> {
4714    Arc::new(Self {
4715      remaining: Mutex::new(worker_threads),
4716      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
4717    })
4718  }
4719
4720  fn retain(&self, handle: std::thread::JoinHandle<()>) {
4721    self
4722      .handles
4723      .lock()
4724      .unwrap_or_else(std::sync::PoisonError::into_inner)
4725      .as_mut()
4726      .expect("runtime worker handles already joined")
4727      .push(handle);
4728  }
4729
4730  fn worker_exited(&self) {
4731    let mut remaining = self
4732      .remaining
4733      .lock()
4734      .unwrap_or_else(std::sync::PoisonError::into_inner);
4735    *remaining = remaining
4736      .checked_sub(1)
4737      .expect("runtime worker exit count underflow");
4738  }
4739
4740  fn wait_for_all_workers(&self) {
4741    let handles = self
4742      .handles
4743      .lock()
4744      .unwrap_or_else(std::sync::PoisonError::into_inner)
4745      .take()
4746      .expect("runtime worker handles already joined");
4747    for handle in handles {
4748      if let Err(payload) = handle.join() {
4749        discard_panic_payload(payload);
4750      }
4751    }
4752  }
4753
4754  #[cfg(test)]
4755  fn remaining(&self) -> usize {
4756    *self
4757      .remaining
4758      .lock()
4759      .unwrap_or_else(std::sync::PoisonError::into_inner)
4760  }
4761}
4762
4763#[cfg(not(target_family = "wasm"))]
4764struct MultiThreadExecutor {
4765  generation: u64,
4766  stop: Arc<GenerationStop>,
4767  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
4768  // executor can never authorize an over-cap escape on a replacement.
4769  id: u64,
4770  pool: ThreadPool,
4771  // Lock-free MPMC injector replacing the former `Mutex<VecDeque<Runnable>>`.
4772  // Invariant: every push goes through `push_to_queue_and_wake` and every
4773  // emptiness check through `has_queued_runnable` -- their paired SeqCst fences
4774  // restore the removed mutex's synchronizes-with edge (full lost-wakeup proof
4775  // at `push_to_queue_and_wake`). A raw `injector.push`/`is_empty` at a new site
4776  // silently breaks the pairing.
4777  injector: Injector<Runnable>,
4778  blocking_queue: Mutex<BlockingQueue>,
4779  active_drainers: AtomicUsize,
4780  active_blocking: AtomicUsize,
4781  // Guards the idle-waiter count. `finish_draining` runs once per drain batch
4782  // -- i.e. per task whenever the drainer catches the producer -- and used to
4783  // call `notify_all` unconditionally. On Linux `Condvar::notify_all` issues a
4784  // `futex_wake_all` SYSCALL even with an empty waiter set, and the only waiter
4785  // site in this crate (`wait_until_scheduler_idle`) is reachable only from
4786  // shutdown/restart. Counting waiters under this same mutex lets the steady
4787  // state skip the syscall entirely.
4788  scheduler_idle_lock: Mutex<usize>,
4789  scheduler_idle: Condvar,
4790  worker_lifecycle: Arc<WorkerLifecycle>,
4791  // Registry of pool workers currently parked inside a re-entrant cooperative
4792  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
4793  // has a fixed number of OS threads, so a parked worker cannot rely on
4794  // `ensure_drainer` spawning a replacement (there is no free thread to run
4795  // it) -- it must wake and run the work itself. Future-wakes do NOT go
4796  // through this registry: each driver's own `DriverParker` is its awaited
4797  // future's waker (see the wake-domain note at `DriverParker`).
4798  parked_drivers: ParkedDrivers,
4799  active_blocking_owners: ActiveBlockingOwners,
4800  #[cfg(test)]
4801  owner_lending_attempts: AtomicUsize,
4802  // Linger-lifecycle observability for tests (see the idle-linger block in
4803  // `drain`): counts every linger-parker REGISTRATION into `parked_drivers`
4804  // and every idle-budget EXPIRY exit (the deadline-elapsed `break` paths,
4805  // counted AFTER the parker is deregistered, so `expiries` rising implies
4806  // the linger parker has permanently left the registry for that episode).
4807  // Tests that must observe a lingering drainer's full register->expire
4808  // lifecycle wait on these instead of sampling the non-monotonic registry
4809  // count, which is transiently at its settled value BEFORE the linger
4810  // parker registers.
4811  #[cfg(test)]
4812  drain_linger_registrations: AtomicUsize,
4813  #[cfg(test)]
4814  drain_linger_expiries: AtomicUsize,
4815  max_drainers: usize,
4816  max_blocking: usize,
4817  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
4818  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
4819  // design because it parks for entire builds and a deadline there would
4820  // panic healthy production runs. Off unless
4821  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
4822  // `PARK_DEADLINE_ENV` into it).
4823  park_deadline: Option<Duration>,
4824  // Idle-linger budget for pool drainers (`None` = disabled); resolved from
4825  // `RuntimeOptions::drain_linger` (`ZERO` = disabled, embedder-resolved from
4826  // `DRAIN_LINGER_ENV` at addon load) at construction and ceiling-clamped
4827  // there so even direct unvalidated test constructions stay bounded.
4828  drain_linger: Option<Duration>,
4829  // Shared with the timer heap because firing removes the timer before invoking
4830  // its waker. The admission remains active until that wake has published any
4831  // resulting runnable, so a final verdict cannot observe both sources empty.
4832  deadlock_state: Arc<MultiThreadDeadlockState>,
4833  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
4834  // Cooperative parks also fire due timers after timer-bounded waits.
4835  timers: TimerHeap,
4836  metrics: Arc<RuntimeMetrics>,
4837}
4838
4839#[cfg(not(target_family = "wasm"))]
4840impl MultiThreadExecutor {
4841  /// Max work units before a drainer yields. Cooperative drivers use the same
4842  /// value as the LIFO streak cap.
4843  const RUNNABLE_BUDGET: usize = 64;
4844  /// Force one blocking admission after this many consecutive runnable polls
4845  /// when blocking capacity and queued work are both available.
4846  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
4847
4848  #[cfg(test)]
4849  fn new(
4850    options: &RuntimeOptions,
4851    metrics: Arc<RuntimeMetrics>,
4852  ) -> Result<Self, RuntimeConfigError> {
4853    Self::new_for_generation(
4854      options,
4855      metrics,
4856      u64::MAX,
4857      Arc::new(GenerationStop::default()),
4858    )
4859  }
4860
4861  fn new_for_generation(
4862    options: &RuntimeOptions,
4863    metrics: Arc<RuntimeMetrics>,
4864    generation: u64,
4865    stop: Arc<GenerationStop>,
4866  ) -> Result<Self, RuntimeConfigError> {
4867    let id = next_unique_id(
4868      &NEXT_RUNTIME_EXECUTOR_ID,
4869      "async runtime executor id space exhausted",
4870    );
4871    let thread_name_prefix = options.thread_name_prefix.clone();
4872    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
4873      options.park_deadline.is_some(),
4874    ));
4875    let timers = TimerHeap::new(
4876      generation,
4877      id,
4878      format!("{thread_name_prefix}-timer"),
4879      Arc::clone(&deadlock_state),
4880    );
4881    // Production options are validated before construction, so this exact
4882    // physical count is also the configured/reported count. Direct executor
4883    // unit tests may still construct a one-thread topology intentionally.
4884    let pool_threads = options.worker_threads;
4885    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
4886    let exit_lifecycle = Arc::clone(&worker_lifecycle);
4887    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
4888    let pool = ThreadPoolBuilder::new()
4889      .num_threads(pool_threads)
4890      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
4891      .start_handler(move |_| {
4892        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
4893      })
4894      .exit_handler(move |_| {
4895        IN_SCHEDULER_DRIVER.with(|driver| {
4896          if driver.get().is_some_and(|role| role.executor_id == id) {
4897            driver.set(None);
4898          }
4899        });
4900        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
4901        // run after this Rayon hook and may re-enter lifecycle APIs while the
4902        // external shutdown caller is joining this worker; retaining the
4903        // executor identity makes that re-entry fail fast instead of waiting
4904        // in Stopping and deadlocking the join.
4905        exit_lifecycle.worker_exited();
4906      })
4907      .spawn_handler(move |thread| {
4908        let mut builder = std::thread::Builder::new();
4909        if let Some(name) = thread.name() {
4910          builder = builder.name(name.to_string());
4911        }
4912        if let Some(stack_size) = thread.stack_size() {
4913          builder = builder.stack_size(stack_size);
4914        }
4915        let handle = builder.spawn(move || thread.run())?;
4916        spawn_lifecycle.retain(handle);
4917        Ok(())
4918      })
4919      .build()
4920      .map_err(|error| {
4921        worker_lifecycle.wait_for_all_workers();
4922        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
4923      })?;
4924    Ok(Self {
4925      generation,
4926      stop,
4927      id,
4928      pool,
4929      injector: Injector::new(),
4930      blocking_queue: Mutex::new(BlockingQueue {
4931        closed: false,
4932        head: None,
4933        tail: None,
4934        jobs: FxHashMap::default(),
4935      }),
4936      active_drainers: AtomicUsize::new(0),
4937      active_blocking: AtomicUsize::new(0),
4938      scheduler_idle_lock: Mutex::new(0),
4939      scheduler_idle: Condvar::new(),
4940      worker_lifecycle,
4941      parked_drivers: ParkedDrivers::default(),
4942      active_blocking_owners: ActiveBlockingOwners::default(),
4943      #[cfg(test)]
4944      owner_lending_attempts: AtomicUsize::new(0),
4945      #[cfg(test)]
4946      drain_linger_registrations: AtomicUsize::new(0),
4947      #[cfg(test)]
4948      drain_linger_expiries: AtomicUsize::new(0),
4949      max_drainers: pool_threads,
4950      max_blocking: options.max_blocking_tasks,
4951      park_deadline: options.park_deadline,
4952      drain_linger: {
4953        // Defensive re-clamp: production options were already clamped by
4954        // `RuntimeOptions::validate`, but direct executor unit tests construct
4955        // from unvalidated options.
4956        let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
4957        (!budget.is_zero()).then_some(budget)
4958      },
4959      deadlock_state,
4960      timers,
4961      metrics,
4962    })
4963  }
4964
4965  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
4966    self.deadlock_state.begin_admission()
4967  }
4968
4969  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
4970    self.deadlock_state.try_begin_verdict()
4971  }
4972
4973  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
4974    // See the implementation notes in this crate's README.
4975    let _publication = self.deadlock_state.owner_handoff_publication_guard();
4976    self.parked_drivers.wake_one_owner_dependency(owner)
4977  }
4978
4979  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
4980    let owner = reservation.owner;
4981    // Keep release and its targeted handoff in one detector-visible critical
4982    // section. Unwind drops the mutex after the lane is already available, so
4983    // a later verdict rechecks that availability even if selection panicked.
4984    let _publication = self.deadlock_state.owner_handoff_publication_guard();
4985    drop(reservation);
4986    self.parked_drivers.wake_one_owner_dependency(owner)
4987  }
4988
4989  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4990    let mut admission = self.begin_deadlock_admission();
4991    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
4992    // still "queued" until popped (`runnable_started` balances it either way).
4993    self.metrics.runnable_scheduled();
4994    // LIFO fast path: a runnable scheduled from a pool
4995    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
4996    // shared FIFO -- this thread pops it before its next FIFO look, so no
4997    // queue mutex, no wake, no drainer spawn.
4998    //
4999    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
5000    // on the stack (drain/run_one loop iterations, cooperative polls). It is
5001    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
5002    // frame including `blocking()` calls, but the slot is only popped after
5003    // the closure returns -- so a closure that spawns and then synchronously
5004    // waits on the child would deadlock with the child slotted (no wake, no
5005    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
5006    // while straight-line blocking-closure code runs (drain/run_one blocking
5007    // branches, the over-cap escape) and `None` while runnables run (the
5008    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
5009    // slot and take the FIFO+wake path whenever an owner frame is ambient.
5010    // (A cooperative poll entered FROM a blocking closure also carries the
5011    // closure's token and loses the fast path -- a rare, conservative,
5012    // correctness-first trade.)
5013    if IN_SCHEDULER_DRIVER
5014      .with(std::cell::Cell::get)
5015      .is_some_and(|role| role.executor_id == self.id)
5016      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5017    {
5018      match LIFO_SLOT.with(std::cell::Cell::take) {
5019        None => {
5020          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5021          if let Some(admission) = admission.as_mut() {
5022            admission.mark_published();
5023          }
5024          return;
5025        }
5026        // Same-executor occupant: the NEWEST runnable takes the slot (it is
5027        // the hottest); the displaced occupant falls back to the shared FIFO
5028        // with a normal wake so other workers can pick it up.
5029        Some((id, displaced)) if id == self.id => {
5030          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5031          self.push_to_queue_and_wake(displaced);
5032          if let Some(admission) = admission.as_mut() {
5033            admission.mark_published();
5034          }
5035          return;
5036        }
5037        // Foreign-executor occupant (defensive: requires a thread acting as a
5038        // worker of two executors, e.g. under a test-installed marker; the
5039        // production process has one global executor). We must not flush
5040        // another executor's runnable onto OUR queue, so leave it in place --
5041        // its own executor's drain on this thread pops or flushes it -- and
5042        // route the new runnable to the FIFO.
5043        Some(foreign) => {
5044          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5045        }
5046      }
5047    }
5048    self.push_to_queue_and_wake(runnable);
5049    if let Some(admission) = admission.as_mut() {
5050      admission.mark_published();
5051    }
5052  }
5053
5054  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
5055  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
5056  /// already happened at their original schedule).
5057  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5058    self.injector.push(runnable);
5059    // The former queue mutex provided the synchronizes-with edge the
5060    // lost-wakeup proof depends on (see `wake_one`). With a lock-free injector
5061    // that edge is gone, so restore it explicitly: a SeqCst fence between the
5062    // push and BOTH wake mechanisms (`wake_one`'s SeqCst `count` load and
5063    // `ensure_drainer`'s `active_drainers` load), mirrored by the SeqCst fence
5064    // in `has_queued_runnable` before the waiter/drainer re-check. This is the
5065    // Dekker ordering rayon-core's injector+sleep protocol uses; without both
5066    // fences a worker can park next to stealable work.
5067    std::sync::atomic::fence(Ordering::SeqCst);
5068    self.wake_for_new_work();
5069  }
5070
5071  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
5072  /// parked cooperative driver; when none is parked, fall back to spawning a
5073  /// drainer. A single wake suffices because the woken driver re-checks
5074  /// every work source in its loop before re-parking, and a wake it absorbs
5075  /// without consuming is handed on at `cooperative_block_on` exit (miss
5076  /// compensation). MUST be called AFTER the work was pushed
5077  /// under its queue mutex -- see the lost-wakeup argument at
5078  /// [`ParkedDrivers::wake_one`].
5079  fn wake_for_new_work(self: &Arc<Self>) {
5080    if !self.parked_drivers.wake_one() {
5081      self.ensure_drainer();
5082    }
5083  }
5084
5085  fn wake_for_blocking_work(self: &Arc<Self>) {
5086    // Prefer a cooperative driver that can consume the blocking FIFO;
5087    // otherwise arm a normal Rayon drainer.
5088    if !self.parked_drivers.wake_one_blocking() {
5089      self.ensure_drainer();
5090    }
5091  }
5092
5093  #[cfg(test)]
5094  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5095  where
5096    F: FnOnce() -> T + Send + 'static,
5097    T: Send + 'static,
5098  {
5099    self.schedule_blocking_result(move || catch_unwind_join_error(function))
5100  }
5101
5102  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5103  where
5104    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5105    T: Send + 'static,
5106  {
5107    let mut admission = self.begin_deadlock_admission();
5108    let (sender, receiver) = oneshot::channel();
5109    let generation = self.generation;
5110    let dependency = BlockingDependency {
5111      job: BlockingJobId(next_unique_id(
5112        &NEXT_BLOCKING_JOB_ID,
5113        "blocking job id space exhausted",
5114      )),
5115      owner: BLOCKING_OWNER
5116        .with(std::cell::Cell::get)
5117        .filter(|owner| owner.executor_id == self.id),
5118    };
5119    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
5120    // detector's fingerprint must be able to observe
5121    // this submission even while the job merely sits queued -- symmetric with
5122    // `runnable_scheduled` on the runnable path.
5123    self.metrics.blocking_scheduled();
5124    let queued = QueuedBlocking {
5125      id: dependency.job,
5126      run: Box::new(move || {
5127        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5128        let _ = sender.send(result);
5129      }),
5130    };
5131    let rejected = {
5132      let mut queue = self
5133        .blocking_queue
5134        .lock()
5135        .unwrap_or_else(std::sync::PoisonError::into_inner);
5136      if queue.closed {
5137        Some(queued)
5138      } else {
5139        queue.push(queued);
5140        None
5141      }
5142    };
5143    if let Some(rejected) = rejected {
5144      // Shutdown may close the queue after the controller registered this
5145      // generation work but before it reaches the executor. Drop captured
5146      // user values outside the queue lock and contain hostile destructors,
5147      // matching the queued-job shutdown path.
5148      let _generation = RuntimeGenerationGuard::enter(self.generation);
5149      let _ = catch_unwind_contained(|| drop(rejected));
5150    } else {
5151      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
5152      self.wake_for_blocking_work();
5153      if let Some(admission) = admission.as_mut() {
5154        admission.mark_published();
5155      }
5156    }
5157    JoinHandle(JoinHandleInner::Blocking {
5158      receiver,
5159      dependency,
5160      published: PublishedBlockingDependencies::default(),
5161    })
5162  }
5163
5164  fn ensure_drainer(self: &Arc<Self>) {
5165    loop {
5166      let active = self.active_drainers.load(Ordering::Acquire);
5167      if active >= self.max_drainers {
5168        return;
5169      }
5170      if self
5171        .active_drainers
5172        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5173        .is_ok()
5174      {
5175        let executor = Arc::clone(self);
5176        self.pool.spawn_fifo(move || executor.drain());
5177        return;
5178      }
5179    }
5180  }
5181
5182  fn drain(self: Arc<Self>) {
5183    // LINGER GATE: captured BEFORE the guard installs the marker below. In
5184    // production `drain` only ever runs via `pool.spawn_fifo`, i.e. on a pool
5185    // worker whose start handler already set `ON_POOL_WORKER`, so lingering is
5186    // active there; a direct test call from a foreign thread keeps the legacy
5187    // return-when-empty shape.
5188    let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5189    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
5190    // drives the queue cooperatively instead of parking the worker.
5191    let _on_pool = OnPoolWorkerGuard::enter(self.id);
5192    // Unwind backstop: never leave a runnable stranded in
5193    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
5194    // below (so its re-check can see the flushed runnable); this guard only
5195    // covers unwinds, which should be impossible (runnables and blocking
5196    // wrappers are catch_unwind'd) but must not lose a task if they happen.
5197    let _slot_backstop = LifoSlotFlushGuard(&self);
5198
5199    let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5200    // Linger state (parker + generation-stop registration), created lazily on
5201    // this frame's first idle episode and reused for the rest of the frame.
5202    let mut linger: Option<DrainLinger> = None;
5203    // Absolute exit deadline of the CURRENT idle episode, armed on the first
5204    // empty observation after work ran. Spurious wakes (timer churn, a stolen
5205    // queue wake) re-park with the REMAINING time rather than a fresh budget,
5206    // so a lingering drainer occupies its pool slot for at most
5207    // `linger_budget` past the last work unit it executed.
5208    let mut idle_deadline: Option<Instant> = None;
5209    // Absolute residence deadline of the WHOLE frame, armed on the frame's
5210    // FIRST idle observation and -- unlike `idle_deadline` -- never reset by
5211    // executed work. Without it, periodic sub-budget work re-arms the episode
5212    // deadline forever and this frame never returns its rayon worker; see
5213    // [`DRAIN_LINGER_FRAME_FACTOR`].
5214    let mut frame_deadline: Option<Instant> = None;
5215
5216    'frame: loop {
5217      let mut runnable_streak = 0usize;
5218      for _ in 0..Self::RUNNABLE_BUDGET {
5219        if self.run_one_fair(&mut runnable_streak) {
5220          idle_deadline = None;
5221          continue;
5222        }
5223        // Queue observed empty. MANDATORY flush before the shared exit AND
5224        // before any park: slot work is invisible to other threads and to
5225        // `finish_draining`'s re-check, so no path may leave this loop (or
5226        // sleep) with an occupied slot. On this path the slot was observed
5227        // empty at the top of the iteration and no user code ran since, so
5228        // the flush is defensive -- kept unconditional so every exit upholds
5229        // the stranding invariant by construction.
5230        self.flush_lifo_slot();
5231        // IDLE LINGER: instead of exiting and paying a full
5232        // spawn_fifo/rayon-idle-protocol round trip on the NEXT wave, park on
5233        // our own parker, registered in `parked_drivers` so
5234        // `wake_for_new_work`/`wake_for_blocking_work` deliver ONE targeted
5235        // wake. `active_drainers` stays counted for the whole linger: this
5236        // frame is still a live drainer, so `ensure_drainer`'s cap and the
5237        // idle-wait predicate keep their meaning.
5238        //
5239        // LOST-WAKEUP ARGUMENT (identical protocol to a cooperative driver's
5240        // park): register FIRST (the registry `count` store), THEN re-check
5241        // the shared queues (`has_queued_work_for_role`'s SeqCst fence pairs
5242        // with `push_to_queue_and_wake`'s post-push fence), THEN park -- see
5243        // [`ParkedDrivers::wake_one`]. On the timeout path, `wake_one` may
5244        // have popped this parker (counting the wake as DELIVERED, so the
5245        // producer spawned no drainer) while its `unpark` is still in flight:
5246        // the pop precedes our `deregister` on the registry mutex, so the
5247        // producer's push (made BEFORE its wake) is visible to the
5248        // post-deregister permit/queue re-checks below, which then RESUME
5249        // draining instead of exiting. Only a truly empty re-check falls
5250        // through to `finish_draining`, whose own post-decrement re-check
5251        // remains the existing final net for pushes racing the exit.
5252        let Some(budget) = linger_budget else {
5253          break 'frame;
5254        };
5255        if self.stop.is_stopping() {
5256          break 'frame;
5257        }
5258        let now = Instant::now();
5259        // FRAME RESIDENCE BOUND: exiting here (or via the min-capped park
5260        // below) funnels through the UNCHANGED finish_draining path, whose
5261        // re-check re-arms a FIFO drain via ensure_drainer/spawn_fifo when
5262        // work remains -- that re-entry through rayon's queue is exactly the
5263        // interleaving point that lets pending raw rayon work run.
5264        let frame = *frame_deadline.get_or_insert_with(|| {
5265          now
5266            + budget
5267              .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5268              .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5269        });
5270        let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5271        if now >= deadline {
5272          // Deadline EXPIRY observed at the top: either a wake consumed the
5273          // rest of the episode's idle budget, or the frame residence bound
5274          // elapsed while work kept re-arming the episode deadline. Either
5275          // way this frame's lingering is over, and the linger parker of the
5276          // previous episode -- if one registered -- was already deregistered
5277          // after its park.
5278          #[cfg(test)]
5279          self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5280          break 'frame;
5281        }
5282        let state = match &linger {
5283          Some(state) => state,
5284          None => linger.insert(DrainLinger::new(&self)),
5285        };
5286        self
5287          .parked_drivers
5288          .register_with_role(&state.parker, true, None);
5289        #[cfg(test)]
5290        self
5291          .drain_linger_registrations
5292          .fetch_add(1, Ordering::SeqCst);
5293        if self.has_queued_work_for_role(true) {
5294          self.parked_drivers.deregister(&state.parker);
5295          continue 'frame;
5296        }
5297        // Stop re-check AFTER registering: `begin_shutdown` unparks every
5298        // generation-stop-registered parker (ours included, via
5299        // `DrainLinger`'s registration), so a stop publishing after this
5300        // check still cuts the park short instead of waiting out the budget.
5301        if self.stop.is_stopping() {
5302          self.parked_drivers.deregister(&state.parker);
5303          break 'frame;
5304        }
5305        let woken = state.parker.park_timeout(deadline - now);
5306        self.parked_drivers.deregister(&state.parker);
5307        if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5308          continue 'frame;
5309        }
5310        // Idle-budget EXPIRY: the park timed out with no wake, no permit and
5311        // no queued work. Counted AFTER the deregister above, so a test
5312        // observing `drain_linger_expiries` rise knows the linger parker has
5313        // permanently left the registry for this idle episode.
5314        #[cfg(test)]
5315        self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5316        break 'frame;
5317      }
5318      // Budget exhausted: yield this worker back to rayon exactly as before
5319      // the linger existed -- `finish_draining` re-arms a FIFO drain job
5320      // BEHIND any pending rayon work (par_iter splits, nested jobs), which
5321      // is the pool-fairness property the budget exists for.
5322      break 'frame;
5323    }
5324
5325    // MANDATORY flush before `finish_draining` so its
5326    // re-check sees a slot runnable on the shared FIFO and re-arms a drainer.
5327    self.flush_lifo_slot();
5328    self.finish_draining();
5329  }
5330
5331  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
5332  /// A foreign executor's entry is left untouched (see the scoping note at
5333  /// `LIFO_SLOT`).
5334  fn pop_lifo_slot(&self) -> Option<Runnable> {
5335    LIFO_SLOT.with(|slot| match slot.take() {
5336      Some((id, runnable)) if id == self.id => Some(runnable),
5337      other => {
5338        slot.set(other);
5339        None
5340      }
5341    })
5342  }
5343
5344  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
5345  /// with a wake so any worker can pick it up. MANDATORY on every path where
5346  /// this thread stops draining this executor: `drain`'s
5347  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
5348  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
5349  /// slot while its thread leaves executor code is a silently lost task.
5350  fn flush_lifo_slot(self: &Arc<Self>) {
5351    let mut admission = self.begin_deadlock_admission();
5352    if let Some(runnable) = self.pop_lifo_slot() {
5353      self.push_to_queue_and_wake(runnable);
5354      if let Some(admission) = admission.as_mut() {
5355        admission.mark_published();
5356      }
5357    }
5358  }
5359
5360  /// Mint a fresh owner token for a counted blocking closure entering on this
5361  /// executor. Each entry gets a unique `frame` so nested owners on the same
5362  /// thread can never run each other's queued jobs over the cap.
5363  fn fresh_owner_token(&self) -> BlockingOwnerToken {
5364    BlockingOwnerToken {
5365      executor_id: self.id,
5366      frame: next_unique_id(
5367        &NEXT_BLOCKING_FRAME,
5368        "blocking owner frame id space exhausted",
5369      ),
5370    }
5371  }
5372
5373  fn finish_draining(self: &Arc<Self>) {
5374    // LIFO-slot invariant: this re-check reads only the
5375    // SHARED queues -- per-worker slots are invisible to it, and that is
5376    // sound because slot work never needs a foreign observer. A slot entry is
5377    // always either popped and run by its owning thread (drain / run_one) or
5378    // flushed to the shared FIFO by that same thread BEFORE it stops draining
5379    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
5380    // covers unwinds; cooperative parks flush defensively). No thread leaves
5381    // executor code with its slot occupied, so the exit-then-respawn window
5382    // below cannot hide slot work from `ensure_drainer`.
5383    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
5384    let has_runnable = self.has_queued_runnable();
5385    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
5386      && !self
5387        .blocking_queue
5388        .lock()
5389        .unwrap_or_else(std::sync::PoisonError::into_inner)
5390        .is_empty();
5391    if has_runnable || has_blocking {
5392      self.ensure_drainer();
5393    }
5394    // The lock is still taken unconditionally: it is the release edge that
5395    // publishes the `active_drainers` decrement above to a waiter that is about
5396    // to evaluate the predicate. Only the wake is now conditional, and the
5397    // waiter count is read under the very mutex the waiter must hold to
5398    // register, so a waiter is never missed. See `wait_until_scheduler_idle`.
5399    let idle = self
5400      .scheduler_idle_lock
5401      .lock()
5402      .unwrap_or_else(std::sync::PoisonError::into_inner);
5403    if *idle > 0 {
5404      self.scheduler_idle.notify_all();
5405    }
5406  }
5407
5408  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
5409    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
5410      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
5411      // freeze a worker that the awaited future may itself need to make
5412      // progress. Drive the queue cooperatively instead (mirrors the
5413      // CurrentThread drive loop). The id check mirrors the
5414      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
5415      // owns none of this executor's accounting, so it must take the parking
5416      // path below rather than drive these queues as an unaccounted driver.
5417      self.cooperative_block_on(future)
5418    } else {
5419      // Non-pool (e.g. napi caller) thread, or a pool worker of another
5420      // executor: park without driving queues. The runtime owns this park so
5421      // generation shutdown can wake and cancel it.
5422      self.parking_block_on(future)
5423    }
5424  }
5425
5426  fn parking_block_on(
5427    self: &Arc<Self>,
5428    mut future: Pin<&mut dyn Future<Output = ()>>,
5429  ) -> BlockOnOutcome {
5430    let parker = Arc::new(DriverParker::default());
5431    let _stop_registration = self.stop.register(&parker);
5432    let waker = Waker::from(Arc::clone(&parker));
5433    let mut cx = Context::from_waker(&waker);
5434    loop {
5435      if self.stop.is_stopping() {
5436        return BlockOnOutcome::Stopped;
5437      }
5438      if future.as_mut().poll(&mut cx).is_ready() {
5439        return BlockOnOutcome::Completed;
5440      }
5441      if self.stop.is_stopping() {
5442        return BlockOnOutcome::Stopped;
5443      }
5444      parker.park();
5445    }
5446  }
5447
5448  fn run_runnable(
5449    self: &Arc<Self>,
5450    runnable: Runnable,
5451    admission: Option<MultiThreadDeadlockAdmissionGuard>,
5452  ) {
5453    // Ownership is lexical to a blocking closure. A runnable driven from that
5454    // closure must not borrow its over-cap privilege.
5455    let _non_owner = BlockingOwnerGuard::enter(None);
5456    let _active = self.metrics.runnable_started();
5457    #[cfg(test)]
5458    run_multi_thread_work_started_test_hook();
5459    // The queue/slot removal is invisible to the deadline verdict until start
5460    // accounting advances. Keep the detector gate open across exactly that gap,
5461    // but never across a user poll.
5462    drop(admission);
5463    let _ = catch_unwind_contained(|| runnable.run());
5464  }
5465
5466  fn cancel_runnable(
5467    &self,
5468    runnable: Runnable,
5469    admission: Option<MultiThreadDeadlockAdmissionGuard>,
5470  ) {
5471    self.metrics.runnable_cancelled();
5472    // Cancellation accounting closes the dequeue gap just as start accounting
5473    // does for a runnable that will be polled.
5474    drop(admission);
5475    let _generation = RuntimeGenerationGuard::enter(self.generation);
5476    let _ = catch_unwind_contained(|| drop(runnable));
5477  }
5478
5479  fn finish_runnable_claim(
5480    self: &Arc<Self>,
5481    claim: RunnableClaim,
5482    admission: Option<MultiThreadDeadlockAdmissionGuard>,
5483  ) -> bool {
5484    match claim {
5485      RunnableClaim::Empty => {
5486        drop(admission);
5487        false
5488      }
5489      RunnableClaim::Run(runnable) => {
5490        #[cfg(test)]
5491        run_multi_thread_work_claimed_test_hook();
5492        self.run_runnable(runnable, admission);
5493        true
5494      }
5495      RunnableClaim::Cancel(runnable) => {
5496        #[cfg(test)]
5497        run_multi_thread_work_claimed_test_hook();
5498        self.cancel_runnable(runnable, admission);
5499        true
5500      }
5501    }
5502  }
5503
5504  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
5505    match runnable {
5506      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
5507      Some(runnable) => RunnableClaim::Run(runnable),
5508      None => RunnableClaim::Empty,
5509    }
5510  }
5511
5512  /// Lock-free single-item steal from the shared injector, retrying the
5513  /// transient `Steal::Retry` contention state. Replaces the former
5514  /// `queue.lock().pop_front()`; `Injector` preserves FIFO order.
5515  fn steal_one(&self) -> Option<Runnable> {
5516    loop {
5517      match self.injector.steal() {
5518        Steal::Success(runnable) => return Some(runnable),
5519        Steal::Empty => return None,
5520        Steal::Retry => continue,
5521      }
5522    }
5523  }
5524
5525  /// Test-only raw FIFO enqueue: the former `queue.lock().push_back()` staging
5526  /// that tests use to seed queued work WITHOUT the accompanying wake (so no
5527  /// drainer races the manual `run_one`/`drain` calls). Keeps the post-push
5528  /// SeqCst fence from `push_to_queue_and_wake` so any wake/park interaction a
5529  /// test performs afterwards still sees the Dekker pairing documented on
5530  /// `injector`.
5531  #[cfg(test)]
5532  fn push_fifo_raw(&self, runnable: Runnable) {
5533    self.injector.push(runnable);
5534    std::sync::atomic::fence(Ordering::SeqCst);
5535  }
5536
5537  fn claim_fifo_runnable(&self) -> RunnableClaim {
5538    let stop_publication = self.stop.runnable_claim_guard();
5539    let runnable = self.steal_one();
5540    let stopping = stop_publication.is_stopping();
5541    drop(stop_publication);
5542    Self::runnable_claim(stopping, runnable)
5543  }
5544
5545  fn claim_runnable(&self) -> RunnableClaim {
5546    let stop_publication = self.stop.runnable_claim_guard();
5547    let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
5548    let stopping = stop_publication.is_stopping();
5549    drop(stop_publication);
5550    Self::runnable_claim(stopping, runnable)
5551  }
5552
5553  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
5554    let admission = self.begin_deadlock_admission();
5555    let claim = self.claim_fifo_runnable();
5556    self.finish_runnable_claim(claim, admission)
5557  }
5558
5559  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
5560  fn run_one_runnable(self: &Arc<Self>) -> bool {
5561    let admission = self.begin_deadlock_admission();
5562    // Same owner-clear as the FIFO-only path: a runnable from the slot must
5563    // not smuggle an owner frame's over-cap privilege.
5564    let claim = self.claim_runnable();
5565    self.finish_runnable_claim(claim, admission)
5566  }
5567
5568  fn run_one_blocking(self: &Arc<Self>) -> bool {
5569    if let Some((blocking, admission)) = self.take_blocking() {
5570      let _generation = RuntimeGenerationGuard::enter(self.generation);
5571      {
5572        let _task = self.metrics.blocking_started(true);
5573        #[cfg(test)]
5574        run_multi_thread_work_started_test_hook();
5575        // Publish start before the dequeue admission retires. The detector can
5576        // then observe either the queued job, the active admission, or progress.
5577        drop(admission);
5578        let owner = self.fresh_owner_token();
5579        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
5580        let _owner = BlockingOwnerGuard::enter(Some(owner));
5581        // The queued wrapper contains panics from the user function, but
5582        // delivering its result can still drop user-owned state when the
5583        // receiver is gone. Contain that destructor boundary as well so an
5584        // unwind cannot bypass blocking-slot and drainer retirement.
5585        let _ = catch_unwind_contained(blocking);
5586      }
5587      self.active_blocking.fetch_sub(1, Ordering::Release);
5588      return true;
5589    }
5590    false
5591  }
5592
5593  /// Run one fair scheduler unit. Runnable locality remains the normal
5594  /// priority, but a continuously hot runnable stream must yield one quantum
5595  /// to the blocking FIFO.
5596  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
5597    self.run_one_fair_with_exact(runnable_streak, None)
5598  }
5599
5600  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
5601  /// been satisfied, a live exact dependency must claim its owner's lane before
5602  /// unrelated FIFO blocking work can consume the last ordinary slot.
5603  fn run_one_fair_with_exact(
5604    self: &Arc<Self>,
5605    runnable_streak: &mut usize,
5606    exact_dependency: Option<&TaskDependency>,
5607  ) -> bool {
5608    let mut exact_attempted = false;
5609    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
5610      *runnable_streak = 0;
5611      if let Some(dependency) = exact_dependency {
5612        exact_attempted = true;
5613        if self.try_owned_blocking_over_cap(dependency) {
5614          return true;
5615        }
5616      }
5617      if self.run_one_blocking() {
5618        return true;
5619      }
5620    }
5621    if self.run_one_runnable() {
5622      *runnable_streak += 1;
5623      return true;
5624    }
5625    if !exact_attempted
5626      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
5627    {
5628      *runnable_streak = 0;
5629      return true;
5630    }
5631    if self.run_one_blocking() {
5632      *runnable_streak = 0;
5633      return true;
5634    }
5635    false
5636  }
5637
5638  #[cfg(test)]
5639  fn run_one(self: &Arc<Self>) -> bool {
5640    let mut runnable_streak = 0;
5641    self.run_one_fair(&mut runnable_streak)
5642  }
5643
5644  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
5645  /// park while the blocking cap is saturated. The dependency carries its
5646  /// exact owner frame through JoinHandle lineage. Reserving that frame
5647  /// transfers its counted lane to this otherwise-idle cooperative driver.
5648  ///
5649  /// Owner lending is lexical: the cooperative driver must carry the exact
5650  /// blocking frame whose counted lane it reuses. Rayon does not propagate
5651  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
5652  /// prove that an untagged worker belongs to the sole active owner.
5653  fn run_owned_blocking_over_cap(
5654    self: &Arc<Self>,
5655    dependency: &TaskDependency,
5656  ) -> OwnedBlockingOutcome {
5657    #[cfg(test)]
5658    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
5659
5660    let Some(ambient_owner) = BLOCKING_OWNER
5661      .with(std::cell::Cell::get)
5662      .filter(|owner| owner.executor_id == self.id)
5663    else {
5664      return OwnedBlockingOutcome::Ineligible;
5665    };
5666    let Some(current) = dependency.select_for_owner(ambient_owner) else {
5667      return OwnedBlockingOutcome::Ineligible;
5668    };
5669    let admission = self.begin_deadlock_admission();
5670    let owner = current
5671      .publication
5672      .dependency
5673      .owner
5674      .expect("selected dependency must carry its owner");
5675    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
5676    else {
5677      return OwnedBlockingOutcome::OwnerBusy(owner);
5678    };
5679    #[cfg(test)]
5680    run_before_owned_blocking_queue_claim_test_hook();
5681    let job = {
5682      let mut queue = self
5683        .blocking_queue
5684        .lock()
5685        .unwrap_or_else(std::sync::PoisonError::into_inner);
5686      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
5687      // admission states before consuming the dependency so a shutdown that
5688      // reached this claim first leaves the job queued for cancellation.
5689      if queue.closed || self.stop.is_stopping() {
5690        None
5691      // Claim while holding the queue lock. A concurrent clear/switch that
5692      // wins first makes this fail; once the claim succeeds, no stale snapshot
5693      // can authorize removal of the old job.
5694      } else if dependency.try_claim(&current) {
5695        queue.remove(current.publication.dependency.job)
5696      } else {
5697        None
5698      }
5699    };
5700    let ran = if let Some(job) = job {
5701      #[cfg(test)]
5702      run_multi_thread_work_claimed_test_hook();
5703      let _generation = RuntimeGenerationGuard::enter(self.generation);
5704      {
5705        let _task = self.metrics.blocking_started(false);
5706        #[cfg(test)]
5707        run_multi_thread_work_started_test_hook();
5708        // The exact job is no longer visible in the indexed FIFO. Publish its
5709        // start before allowing a deadline verdict to close the admission gate.
5710        drop(admission);
5711        let nested_owner = self.fresh_owner_token();
5712        let _active_owner =
5713          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
5714        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
5715        let _ = catch_unwind_contained(job);
5716      }
5717      true
5718    } else {
5719      drop(admission);
5720      false
5721    };
5722    // Every reservation release can make another exact dependency serviceable,
5723    // including failed-claim and already-removed-job exits.
5724    self.release_owner_lane_and_wake(reservation);
5725    OwnedBlockingOutcome::ReservationReleased { owner, ran }
5726  }
5727
5728  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
5729    matches!(
5730      self.run_owned_blocking_over_cap(dependency),
5731      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
5732    )
5733  }
5734
5735  fn service_owner_handoff(
5736    self: &Arc<Self>,
5737    parker: &DriverParker,
5738    dependency: &TaskDependency,
5739    can_run_blocking: bool,
5740  ) -> Option<bool> {
5741    let handoff_owner = parker.take_owner_handoff()?;
5742    if !can_run_blocking {
5743      self.wake_one_owner_dependency(handoff_owner);
5744      return Some(false);
5745    }
5746    match self.run_owned_blocking_over_cap(dependency) {
5747      OwnedBlockingOutcome::Ineligible => {
5748        self.wake_one_owner_dependency(handoff_owner);
5749        Some(false)
5750      }
5751      OwnedBlockingOutcome::OwnerBusy(owner) => {
5752        if owner != handoff_owner {
5753          self.wake_one_owner_dependency(handoff_owner);
5754        }
5755        Some(false)
5756      }
5757      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
5758        if owner != handoff_owner {
5759          self.wake_one_owner_dependency(handoff_owner);
5760        }
5761        Some(ran)
5762      }
5763    }
5764  }
5765
5766  fn forward_owner_handoff(&self, parker: &DriverParker) {
5767    if let Some(owner) = parker.take_owner_handoff() {
5768      self.wake_one_owner_dependency(owner);
5769    }
5770  }
5771
5772  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
5773    let Some(ambient_owner) = BLOCKING_OWNER
5774      .with(std::cell::Cell::get)
5775      .filter(|owner| owner.executor_id == self.id)
5776    else {
5777      return false;
5778    };
5779    self.active_blocking_owners.is_available(ambient_owner)
5780      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
5781  }
5782
5783  fn has_exact_owner_work_for_role(
5784    &self,
5785    parker: &DriverParker,
5786    dependency: &TaskDependency,
5787    can_run_blocking: bool,
5788  ) -> bool {
5789    parker.has_owner_handoff()
5790      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
5791  }
5792
5793  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
5794  /// of parking the worker (which would freeze one of the pool's fixed OS
5795  /// threads), it drives the awaited future while servicing queued work. When
5796  /// nothing is immediately runnable it parks on its own [`DriverParker`],
5797  /// which is woken directly by the awaited future's waker and through the
5798  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
5799  /// wake reaches this worker even when every worker is parked and
5800  /// `ensure_drainer` has no free thread to spawn a replacement.
5801  fn cooperative_block_on(
5802    self: &Arc<Self>,
5803    mut future: Pin<&mut dyn Future<Output = ()>>,
5804  ) -> BlockOnOutcome {
5805    // One parker per `block_on` frame. Nested re-entrant frames each own one:
5806    // only the innermost frame runs or parks at any moment, and an outer
5807    // frame's future-wake is stored as its parker's permit, consumed when
5808    // control returns to that frame's loop.
5809    let parker = Arc::new(DriverParker::default());
5810    let _stop_registration = self.stop.register(&parker);
5811    let dependency = BlockOnDependencyGuard::enter(self.generation);
5812    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
5813    let can_run_blocking =
5814      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
5815    let _exit = CooperativeDriverExitGuard {
5816      executor: self,
5817      parker: &parker,
5818      can_run_blocking,
5819    };
5820    // WAKE-DOMAIN SPLIT: the awaited future is polled with
5821    // THIS driver's own parker as its waker, so a future-wake targets exactly
5822    // the driver that awaits it -- delivered as a stored permit (skip next
5823    // park) when this driver is currently running, so it is never lost.
5824    let waker = std::task::Waker::from(Arc::clone(&parker));
5825    let mut cx = Context::from_waker(&waker);
5826    // Consecutive `run_one` successes since the last yield point: the
5827    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
5828    // it, a hot slot chain (each runnable schedules a successor into this
5829    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
5830    // loop inside `run_one` forever and the shared FIFO would never be
5831    // reached from this thread -- a deadlock on a 1-worker pool when the
5832    // awaited future depends on a FIFO task.
5833    let mut runnable_streak = 0usize;
5834    let mut work_streak = 0usize;
5835    let mut force_fifo = false;
5836    loop {
5837      if self.stop.is_stopping() {
5838        return BlockOnOutcome::Stopped;
5839      }
5840      dependency.clear();
5841      if future.as_mut().poll(&mut cx).is_ready() {
5842        return BlockOnOutcome::Completed;
5843      }
5844      if self.stop.is_stopping() {
5845        return BlockOnOutcome::Stopped;
5846      }
5847      if self
5848        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
5849        .is_some_and(|ran| ran)
5850      {
5851        continue;
5852      }
5853      if force_fifo {
5854        force_fifo = false;
5855        if self.run_one_fifo_runnable() {
5856          runnable_streak += 1;
5857          work_streak += 1;
5858          continue;
5859        }
5860      }
5861      let ran_work = if can_run_blocking {
5862        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
5863      } else if self.run_one_runnable() {
5864        runnable_streak += 1;
5865        true
5866      } else {
5867        false
5868      };
5869      if ran_work {
5870        work_streak += 1;
5871        if work_streak >= Self::RUNNABLE_BUDGET {
5872          // Budget yield: move a hot slot occupant to the shared FIFO (with
5873          // a wake), so the next `run_one` pops the FIFO front (the oldest
5874          // work) and other workers can steal the chain. The awaited future
5875          // is polled every iteration regardless, so it is never starved.
5876          self.flush_lifo_slot();
5877          work_streak = 0;
5878          force_fifo = true;
5879        }
5880        continue;
5881      }
5882      runnable_streak = 0;
5883      work_streak = 0;
5884      // MANDATORY pre-park flush. Defensive on every
5885      // currently reachable path: `run_one` above popped the slot and no user
5886      // code ran since -- but parking with a stranded slot runnable would
5887      // silently lose that task, so the invariant is upheld unconditionally.
5888      self.flush_lifo_slot();
5889      // Park protocol: register FIRST, then re-check, then park -- the
5890      // waiter-side mirror of the push-then-wake schedule side. See the
5891      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
5892      self.parked_drivers.register_with_role(
5893        &parker,
5894        can_run_blocking,
5895        Some(Arc::clone(&dependency.dependency)),
5896      );
5897      if self.has_queued_work_for_role(can_run_blocking)
5898        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
5899      {
5900        self.parked_drivers.deregister(&parker);
5901        continue;
5902      }
5903      if self.stop.is_stopping() {
5904        return BlockOnOutcome::Stopped;
5905      }
5906      // TIMER-BOUNDED PARK: while any heap
5907      // timer is pending, this park is a TIMER WAIT, not a deadlock
5908      // candidate -- the wall clock is a guaranteed wake source -- so it uses
5909      // its own bounded primitive instead of `park`/`park_with_deadline` and
5910      // fires whatever came due on the way out. An armed park deadline is
5911      // deliberately NOT evaluated during a timer wait: the fire schedules
5912      // the woken task (fingerprint progress), and the next timerless park
5913      // arms a fresh full window, so genuine-deadlock detection is delayed
5914      // past the timer, never lost. This is also load-bearing for liveness:
5915      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
5916      // job has no free thread to run on, so this driver must fire due
5917      // timers itself. (A timer registered between the deadline read and the
5918      // park re-arms this parker through `note_earlier_timer`'s
5919      // `wake_one` -- we are registered -- or wakes the timekeeper.)
5920      if let Some(next) = self.next_timer_deadline() {
5921        let now = Instant::now();
5922        if next > now {
5923          parker.park_timeout(next - now);
5924        }
5925        self.parked_drivers.deregister(&parker);
5926        self.fire_due_timers();
5927        continue;
5928      }
5929      match self.park_deadline {
5930        None => parker.park(),
5931        Some(deadline) => {
5932          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
5933          // candidate deadlock only when a
5934          // FULL deadline window passes with zero executor progress. The
5935          // foreign-thread whole-build park in `block_on`'s else-branch is
5936          // exempt by design.
5937          let deadlock_publications = self.deadlock_state.publications();
5938          match park_with_deadline(&parker, deadline, &self.metrics) {
5939            DeadlineParkOutcome::Woken => {}
5940            DeadlineParkOutcome::Expired(armed) => {
5941              #[cfg(test)]
5942              run_deadline_expiry_test_hook();
5943              // EXPIRY DECISION. While this parker was
5944              // registered, a racing `wake_one` may have popped it, stored
5945              // its permit and reported the wake as DELIVERED (so the
5946              // scheduler spawned no drainer) -- panicking now would swallow
5947              // a healthy wake and kill a live build. Deregister FIRST so no
5948              // FURTHER wake can be counted against this parker, then
5949              // re-check every way a wake could have landed around the
5950              // expiry:
5951              //   * permit: a `wake_one` (or direct future-wake) that landed
5952              //     while still registered MUST be acted on;
5953              //   * queued work: the work behind such a wake was pushed
5954              //     under the queue mutex BEFORE `wake_one` popped us, so
5955              //     even if its `unpark` is still in flight the queue
5956              //     re-check observes it;
5957              //   * pending timers: arming a timer bumps NO progress counter
5958              //     by design, so a registration racing this already-
5959              //     committed park (it read an empty heap before parking) is
5960              //     invisible to every other re-check -- yet its wake (the
5961              //     fire) is wall-clock-guaranteed, so this park is healthy.
5962              //     The loop then re-parks TIMER-BOUNDED (see above) and
5963              //     fires the timer itself if needed;
5964              //   * fingerprint: the verdict requires ZERO progress, and the
5965              //     read inside `park_with_deadline` is stale by the time we
5966              //     get here -- re-read as the LAST step. Both ENQUEUE
5967              //     counters are in the fingerprint (`runnable_schedules`
5968              //     and `blocking_tasks_scheduled`, both bumped before the
5969              //     push), so a submission of EITHER kind
5970              //     landing after the queue re-check above is still caught
5971              //     here.
5972              // Any hit => not a deadlock: continue the cooperative loop
5973              // (fresh future poll, `run_one` picks up the work, and the
5974              // next park arms a fresh FULL deadline window). The residue is
5975              // exactly a BARE direct future-wake -- no queue push, no
5976              // counter movement -- landing in the few instructions after
5977              // these re-checks: the inherent, unclosable residue of any
5978              // timing-based detector, one reason the deadline is opt-in.
5979              self.parked_drivers.deregister(&parker);
5980              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
5981                continue;
5982              }
5983              // Test-only seam between the permit/queued-work re-checks and
5984              // the fingerprint verdict.
5985              #[cfg(test)]
5986              run_deadline_verdict_test_hook();
5987              if self.metrics.progress_fingerprint() != armed {
5988                continue;
5989              }
5990              // Pending-timer re-check LAST, after even the fingerprint:
5991              // arming a timer bumps no counter, so it is the one healthy
5992              // wake source that can land after every earlier re-check and
5993              // still be invisible to the fingerprint read above. (A
5994              // registration racing the instructions after this line is the
5995              // same inherent residue class as the bare future-wake -- and
5996              // its timer is still served: `note_earlier_timer` claims the
5997              // timekeeper independently of this driver.)
5998              if self.next_timer_deadline().is_some() {
5999                continue;
6000              }
6001              // Test-only seam for a cross-thread scheduler admission after
6002              // every legacy final check and before the synchronized verdict.
6003              #[cfg(test)]
6004              run_deadline_final_verdict_test_hook();
6005              let Some(verdict) = self.try_begin_deadlock_verdict() else {
6006                continue;
6007              };
6008              #[cfg(test)]
6009              run_deadline_gated_verdict_test_hook();
6010              // Lock order is verdict gate -> stop publication -> exact-owner
6011              // publication. The controller takes lifecycle -> stop
6012              // publication and never acquires the verdict gate or exact-owner
6013              // publication while retaining either lock.
6014              let stop_publication = self.stop.publication_guard();
6015              #[cfg(test)]
6016              run_deadline_synchronized_stop_test_hook();
6017              if stop_publication.is_stopping() {
6018                return BlockOnOutcome::Stopped;
6019              }
6020              let owner_handoff_publication = self
6021                .deadlock_state
6022                .owner_handoff_publication_guard()
6023                .expect("a deadline verdict requires exact-owner publication");
6024              #[cfg(test)]
6025              run_deadline_exact_owner_verdict_test_hook(&parker);
6026              // The closed gate excludes later publications. Any admission
6027              // that began earlier either remains counted (so gate acquisition
6028              // failed above) or completed before this recheck and advanced
6029              // the publication epoch. The stop-publication guard also keeps
6030              // the lifecycle in Running if this verdict linearized first. The
6031              // exact-owner publication guard similarly orders reservation
6032              // release and targeted handoff with the final predicates below.
6033              if self.has_exact_owner_work_for_role(
6034                &parker,
6035                &dependency.dependency,
6036                can_run_blocking,
6037              ) || parker.consume_permit()
6038                || self.has_queued_work_for_role(can_run_blocking)
6039                || self.deadlock_state.publications() != deadlock_publications
6040                || self.metrics.progress_fingerprint() != armed
6041                || self.next_timer_deadline().is_some()
6042              {
6043                continue;
6044              }
6045              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6046              // Panic hooks run before unwind. Release every synchronization
6047              // guard first so a hook that submits work, releases an owner
6048              // lane, or starts shutdown cannot wait for the panic that invokes
6049              // it to unwind.
6050              drop(owner_handoff_publication);
6051              drop(stop_publication);
6052              drop(verdict);
6053              std::panic::panic_any(diagnostic);
6054            }
6055          }
6056        }
6057      }
6058      // `wake_one` removes the parkers it pops; a direct future-wake does
6059      // not. Deregister-if-present covers both.
6060      self.parked_drivers.deregister(&parker);
6061    }
6062  }
6063
6064  /// Is there work a cooperative driver could pick up right now? Used by the
6065  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
6066  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
6067  /// blocking job within the cap). The exact over-cap dependency is handled
6068  /// separately before registration.
6069  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6070    if self.has_queued_runnable() {
6071      return true;
6072    }
6073    can_run_blocking
6074      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6075      && !self
6076        .blocking_queue
6077        .lock()
6078        .unwrap_or_else(std::sync::PoisonError::into_inner)
6079        .is_empty()
6080  }
6081
6082  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6083    if self.has_queued_runnable() {
6084      self.wake_for_new_work();
6085    }
6086    // Shutdown-aborted runnables still need a driver so their generators can
6087    // retire, but queued blocking closures are cancelled by begin_shutdown and
6088    // must never be admitted by exit compensation after stop is observable.
6089    if self.stop.is_stopping() {
6090      return;
6091    }
6092    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6093      && !self
6094        .blocking_queue
6095        .lock()
6096        .unwrap_or_else(std::sync::PoisonError::into_inner)
6097        .is_empty()
6098    {
6099      // This driver may have absorbed the queue wake that made its awaited
6100      // future ready. Runnable and blocking residue are independent. If no
6101      // other blocking-capable driver is available, merely spawning a Rayon
6102      // job is insufficient: this worker may return into arbitrary
6103      // synchronous user code while every other Rayon lane is busy.
6104      // Consume one admitted job before exit, then hand any residue to another
6105      // blocking-capable driver.
6106      let consumed = can_run_blocking && self.run_one_blocking();
6107      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6108        && !self
6109          .blocking_queue
6110          .lock()
6111          .unwrap_or_else(std::sync::PoisonError::into_inner)
6112          .is_empty();
6113      if !self.stop.is_stopping() && (!consumed || has_residue) {
6114        self.wake_for_blocking_work();
6115      }
6116    }
6117  }
6118
6119  fn has_queued_runnable(&self) -> bool {
6120    // Waiter-side mirror of the `push_to_queue_and_wake` fence: a parking
6121    // driver (pre-park re-check) and the drainer-exit re-check in
6122    // `finish_draining` must not observe the injector empty when a racing push
6123    // is already visible to a wake. This SeqCst fence pairs with the post-push
6124    // fence so the Dekker ordering holds without the former queue mutex. Only
6125    // on park/quiesce decision paths, never the per-task hot path.
6126    std::sync::atomic::fence(Ordering::SeqCst);
6127    !self.injector.is_empty()
6128  }
6129
6130  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
6131  /// driver may run any job within `max_blocking`; only the over-cap escape is
6132  /// restricted to an exact dependency id.
6133  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6134    let admission = self.begin_deadlock_admission();
6135    loop {
6136      let active = self.active_blocking.load(Ordering::Acquire);
6137      if active >= self.max_blocking {
6138        return None;
6139      }
6140      let mut queue = self
6141        .blocking_queue
6142        .lock()
6143        .unwrap_or_else(std::sync::PoisonError::into_inner);
6144      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6145        return None;
6146      }
6147      if self
6148        .active_blocking
6149        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6150        .is_ok()
6151      {
6152        let blocking = queue
6153          .pop_front()
6154          .expect("a non-empty blocking queue must have a FIFO head");
6155        drop(queue);
6156        #[cfg(test)]
6157        run_multi_thread_work_claimed_test_hook();
6158        return Some((blocking, admission));
6159      }
6160    }
6161  }
6162}
6163
6164#[cfg(not(target_family = "wasm"))]
6165struct MultiThreadDeadlockState {
6166  enabled: bool,
6167  verdict_closed: AtomicBool,
6168  admissions: AtomicUsize,
6169  publications: AtomicU64,
6170  owner_handoff_publication: Mutex<()>,
6171}
6172
6173#[cfg(not(target_family = "wasm"))]
6174impl MultiThreadDeadlockState {
6175  fn new(enabled: bool) -> Self {
6176    Self {
6177      enabled,
6178      verdict_closed: AtomicBool::new(false),
6179      admissions: AtomicUsize::new(0),
6180      publications: AtomicU64::new(0),
6181      owner_handoff_publication: Mutex::new(()),
6182    }
6183  }
6184
6185  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6186    if !self.enabled {
6187      return None;
6188    }
6189    loop {
6190      while self.verdict_closed.load(Ordering::SeqCst) {
6191        #[cfg(test)]
6192        run_deadlock_closed_gate_admission_test_hook();
6193        std::hint::spin_loop();
6194      }
6195      self.admissions.fetch_add(1, Ordering::SeqCst);
6196      if !self.verdict_closed.load(Ordering::SeqCst) {
6197        return Some(MultiThreadDeadlockAdmissionGuard {
6198          state: Arc::clone(self),
6199          published: false,
6200        });
6201      }
6202      self.admissions.fetch_sub(1, Ordering::SeqCst);
6203    }
6204  }
6205
6206  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6207    debug_assert!(
6208      self.enabled,
6209      "a deadline verdict requires an enabled admission state"
6210    );
6211    if self
6212      .verdict_closed
6213      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6214      .is_err()
6215    {
6216      return None;
6217    }
6218    if self.admissions.load(Ordering::SeqCst) != 0 {
6219      self.verdict_closed.store(false, Ordering::SeqCst);
6220      return None;
6221    }
6222    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6223  }
6224
6225  fn publications(&self) -> u64 {
6226    self.publications.load(Ordering::SeqCst)
6227  }
6228
6229  fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6230    self.enabled.then(|| {
6231      self
6232        .owner_handoff_publication
6233        .lock()
6234        .unwrap_or_else(std::sync::PoisonError::into_inner)
6235    })
6236  }
6237}
6238
6239#[cfg(not(target_family = "wasm"))]
6240struct MultiThreadDeadlockAdmissionGuard {
6241  state: Arc<MultiThreadDeadlockState>,
6242  published: bool,
6243}
6244
6245#[cfg(not(target_family = "wasm"))]
6246impl MultiThreadDeadlockAdmissionGuard {
6247  fn mark_published(&mut self) {
6248    self.published = true;
6249  }
6250}
6251
6252#[cfg(not(target_family = "wasm"))]
6253impl Drop for MultiThreadDeadlockAdmissionGuard {
6254  fn drop(&mut self) {
6255    if self.published {
6256      self.state.publications.fetch_add(1, Ordering::SeqCst);
6257    }
6258    // Publication and every scheduler-state write above must precede the final
6259    // admission decrement observed by a closed verdict gate.
6260    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6261  }
6262}
6263
6264#[cfg(not(target_family = "wasm"))]
6265struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6266
6267#[cfg(not(target_family = "wasm"))]
6268impl Drop for MultiThreadDeadlockVerdictGuard {
6269  fn drop(&mut self) {
6270    self.0.verdict_closed.store(false, Ordering::SeqCst);
6271  }
6272}
6273
6274/// Cooperative-driver exit backstop. A user-future panic can unwind from any
6275/// poll, including after this driver absorbed a queue wake, received an exact
6276/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
6277/// independently so one cleanup failure cannot suppress the others or
6278/// double-panic the process.
6279#[cfg(not(target_family = "wasm"))]
6280struct CooperativeDriverExitGuard<'a> {
6281  executor: &'a Arc<MultiThreadExecutor>,
6282  parker: &'a Arc<DriverParker>,
6283  can_run_blocking: bool,
6284}
6285
6286#[cfg(not(target_family = "wasm"))]
6287impl Drop for CooperativeDriverExitGuard<'_> {
6288  fn drop(&mut self) {
6289    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6290    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6291    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6292    let _ = catch_unwind_contained(|| {
6293      self
6294        .executor
6295        .compensate_queued_work_wake(self.can_run_blocking);
6296    });
6297  }
6298}
6299
6300/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
6301/// unwinding drain can never strand a runnable in the
6302/// thread-local slot of a thread that is about to return to rayon. Normal
6303/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
6304/// no-op (the slot is already empty).
6305#[cfg(not(target_family = "wasm"))]
6306struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6307
6308#[cfg(not(target_family = "wasm"))]
6309impl Drop for LifoSlotFlushGuard<'_> {
6310  fn drop(&mut self) {
6311    self.0.flush_lifo_slot();
6312  }
6313}
6314
6315/// Per-drain-frame idle-linger state, created lazily on the frame's first
6316/// idle episode (see the linger block in `MultiThreadExecutor::drain`): the
6317/// parker queue wakes are delivered to while this drainer lingers, plus its
6318/// generation-stop registration so `begin_shutdown` cuts a linger short
6319/// instead of `wait_until_scheduler_idle` waiting out the idle budget. The
6320/// registration guard deregisters on drop when the drain frame exits.
6321#[cfg(not(target_family = "wasm"))]
6322struct DrainLinger {
6323  parker: Arc<DriverParker>,
6324  _stop_registration: GenerationStopGuard,
6325}
6326
6327#[cfg(not(target_family = "wasm"))]
6328impl DrainLinger {
6329  fn new(executor: &MultiThreadExecutor) -> Self {
6330    let parker = Arc::new(DriverParker::default());
6331    let stop_registration = executor.stop.register(&parker);
6332    Self {
6333      parker,
6334      _stop_registration: stop_registration,
6335    }
6336  }
6337}
6338
6339// ---------------------------------------------------------------------------
6340// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
6341//
6342// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
6343//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
6344//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
6345//     driver that awaits it. The permit bit makes a wake delivered while that
6346//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
6347//     next park instead of losing the wake.
6348//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
6349//     serve queue work, so these go through the `ParkedDrivers` registry and
6350//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
6351//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
6352//     exit (miss compensation).
6353// Foreign-thread (napi caller) parks in `block_on` use
6354// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
6355// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
6356// frame, no registry) so it too OWNS its park site for deadlock detection
6357// while `ParkedDrivers` remains MultiThread-only.
6358
6359/// No permit; the driver is running (or consumed its last permit).
6360const PARKER_EMPTY: usize = 0;
6361/// A wake permit is stored; the next `park` consumes it without sleeping.
6362const PARKER_NOTIFIED: usize = 1;
6363/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
6364const PARKER_SLEEPING: usize = 2;
6365
6366/// One driver's private parker: a saturating one-permit token plus a condvar
6367/// to sleep on when no permit is stored. Also the `Waker` for the future that
6368/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
6369#[derive(Default)]
6370struct DriverParker {
6371  state: AtomicUsize,
6372  lock: Mutex<()>,
6373  condvar: std::sync::Condvar,
6374  #[cfg(not(target_family = "wasm"))]
6375  owner_handoff_pending: AtomicBool,
6376  #[cfg(not(target_family = "wasm"))]
6377  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
6378}
6379
6380impl DriverParker {
6381  #[cfg(not(target_family = "wasm"))]
6382  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
6383    let mut pending = self
6384      .owner_handoff
6385      .lock()
6386      .unwrap_or_else(std::sync::PoisonError::into_inner);
6387    debug_assert!(pending.is_none());
6388    *pending = Some(owner);
6389    self.owner_handoff_pending.store(true, Ordering::Release);
6390  }
6391
6392  #[cfg(not(target_family = "wasm"))]
6393  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
6394    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
6395      return None;
6396    }
6397    self
6398      .owner_handoff
6399      .lock()
6400      .unwrap_or_else(std::sync::PoisonError::into_inner)
6401      .take()
6402  }
6403
6404  #[cfg(not(target_family = "wasm"))]
6405  fn has_owner_handoff(&self) -> bool {
6406    self.owner_handoff_pending.load(Ordering::Acquire)
6407  }
6408
6409  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
6410  /// permit is stored and the driver's next [`Self::park`] returns
6411  /// immediately -- a wake delivered while the driver is running is never
6412  /// lost. Multiple grants coalesce into one permit, which is safe because a
6413  /// woken driver re-checks every work source in its loop before it can park
6414  /// again.
6415  fn unpark(&self) {
6416    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
6417      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
6418      // releases the lock only inside `condvar.wait`; the empty critical
6419      // section here fences the notify so it cannot fire in the window
6420      // between the sleeper's state store and its wait.
6421      drop(
6422        self
6423          .lock
6424          .lock()
6425          .unwrap_or_else(std::sync::PoisonError::into_inner),
6426      );
6427      self.condvar.notify_one();
6428    }
6429  }
6430
6431  /// Consume a stored wake permit if one is present, without ever sleeping.
6432  /// This is the park DECISION's token check: a `true` return means a wake is
6433  /// already pending, so the caller must re-poll instead of parking (or
6434  /// panicking).
6435  fn consume_permit(&self) -> bool {
6436    self
6437      .state
6438      .compare_exchange(
6439        PARKER_NOTIFIED,
6440        PARKER_EMPTY,
6441        Ordering::SeqCst,
6442        Ordering::SeqCst,
6443      )
6444      .is_ok()
6445  }
6446
6447  /// Block until the permit is granted, consuming it. Returns immediately if
6448  /// a permit is already stored. Only the owning driver calls `park`, so at
6449  /// most one thread ever sleeps here.
6450  fn park(&self) {
6451    // Fast path: consume a stored permit without touching the lock.
6452    if self.consume_permit() {
6453      return;
6454    }
6455    let mut guard = self
6456      .lock
6457      .lock()
6458      .unwrap_or_else(std::sync::PoisonError::into_inner);
6459    match self.state.compare_exchange(
6460      PARKER_EMPTY,
6461      PARKER_SLEEPING,
6462      Ordering::SeqCst,
6463      Ordering::SeqCst,
6464    ) {
6465      Ok(_) => {}
6466      // The permit arrived between the fast path and here: consume it. (Only
6467      // this thread stores PARKER_SLEEPING, so the observed value can only be
6468      // PARKER_NOTIFIED.)
6469      Err(_) => {
6470        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6471        return;
6472      }
6473    }
6474    loop {
6475      guard = self
6476        .condvar
6477        .wait(guard)
6478        .unwrap_or_else(std::sync::PoisonError::into_inner);
6479      if self
6480        .state
6481        .compare_exchange(
6482          PARKER_NOTIFIED,
6483          PARKER_EMPTY,
6484          Ordering::SeqCst,
6485          Ordering::SeqCst,
6486        )
6487        .is_ok()
6488      {
6489        return;
6490      }
6491      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
6492    }
6493  }
6494
6495  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
6496  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
6497  /// A `false` return has retracted the SLEEPING claim and left the parker
6498  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
6499  /// detection.
6500  fn park_timeout(&self, timeout: Duration) -> bool {
6501    if self.consume_permit() {
6502      return true;
6503    }
6504    let mut guard = self
6505      .lock
6506      .lock()
6507      .unwrap_or_else(std::sync::PoisonError::into_inner);
6508    match self.state.compare_exchange(
6509      PARKER_EMPTY,
6510      PARKER_SLEEPING,
6511      Ordering::SeqCst,
6512      Ordering::SeqCst,
6513    ) {
6514      Ok(_) => {}
6515      // The permit arrived between the fast path and here: consume it. (Only
6516      // this thread stores PARKER_SLEEPING, so the observed value can only be
6517      // PARKER_NOTIFIED.)
6518      Err(_) => {
6519        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6520        return true;
6521      }
6522    }
6523    let Some(deadline) = Instant::now().checked_add(timeout) else {
6524      // An unrepresentable deadline is farther away than this platform can
6525      // observe. Treat it as an unbounded wait instead of panicking on a
6526      // user-provided deadlock-detection duration.
6527      loop {
6528        guard = self
6529          .condvar
6530          .wait(guard)
6531          .unwrap_or_else(std::sync::PoisonError::into_inner);
6532        if self
6533          .state
6534          .compare_exchange(
6535            PARKER_NOTIFIED,
6536            PARKER_EMPTY,
6537            Ordering::SeqCst,
6538            Ordering::SeqCst,
6539          )
6540          .is_ok()
6541        {
6542          return true;
6543        }
6544      }
6545    };
6546    loop {
6547      if self
6548        .state
6549        .compare_exchange(
6550          PARKER_NOTIFIED,
6551          PARKER_EMPTY,
6552          Ordering::SeqCst,
6553          Ordering::SeqCst,
6554        )
6555        .is_ok()
6556      {
6557        return true;
6558      }
6559      let now = Instant::now();
6560      if now >= deadline {
6561        // Timed out: retract the SLEEPING claim -- unless a permit landed in
6562        // the meantime (an `unpark` that already saw SLEEPING may be waiting
6563        // on `lock` to notify; consuming its permit here IS the wake it
6564        // intended to deliver, so report "woken", not "timed out").
6565        return match self.state.compare_exchange(
6566          PARKER_SLEEPING,
6567          PARKER_EMPTY,
6568          Ordering::SeqCst,
6569          Ordering::SeqCst,
6570        ) {
6571          Ok(_) => false,
6572          Err(_) => {
6573            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
6574            true
6575          }
6576        };
6577      }
6578      let (returned_guard, _timed_out) = self
6579        .condvar
6580        .wait_timeout(guard, deadline - now)
6581        .unwrap_or_else(std::sync::PoisonError::into_inner);
6582      guard = returned_guard;
6583      // Loop re-checks the permit first, then the deadline (spurious wakeups
6584      // simply wait out the remaining time).
6585    }
6586  }
6587}
6588
6589impl std::task::Wake for DriverParker {
6590  fn wake(self: Arc<Self>) {
6591    self.unpark();
6592  }
6593
6594  fn wake_by_ref(self: &Arc<Self>) {
6595    self.unpark();
6596  }
6597}
6598
6599/// Outcome of [`park_with_deadline`].
6600enum DeadlineParkOutcome {
6601  /// Woken by a permit (consumed); the park was healthy.
6602  Woken,
6603  /// A full deadline window elapsed with zero observed progress. Carries the
6604  /// fingerprint captured when that window was ARMED so the caller can make
6605  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
6606  /// verdict: a wake can land in the instructions after the window closes,
6607  /// and for MultiThread a `wake_one` can still pop the
6608  /// still-registered parker and count its wake as DELIVERED. Callers must
6609  /// deregister (MT), then re-check permit / queued work / fingerprint before
6610  /// panicking.
6611  Expired(ProgressFingerprint),
6612}
6613
6614/// Deadline-bounded park with PROGRESS-BASED reset.
6615/// Every time `deadline` elapses without a wake, the runtime's progress
6616/// fingerprint is compared against the value captured when the deadline was
6617/// ARMED: any advance re-arms the deadline instead of firing, because a
6618/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
6619/// proceeds -- must never be declared dead. `Expired` is a *candidate*
6620/// deadlock only; the panic decision lives with the caller (see
6621/// [`DeadlineParkOutcome`]).
6622fn park_with_deadline(
6623  parker: &DriverParker,
6624  deadline: Duration,
6625  metrics: &RuntimeMetrics,
6626) -> DeadlineParkOutcome {
6627  let mut armed = metrics.progress_fingerprint();
6628  loop {
6629    if parker.park_timeout(deadline) {
6630      return DeadlineParkOutcome::Woken;
6631    }
6632    let current = metrics.progress_fingerprint();
6633    if current != armed {
6634      armed = current;
6635      continue;
6636    }
6637    return DeadlineParkOutcome::Expired(armed);
6638  }
6639}
6640
6641// Test-only injection seam for the deadline-expiry race: fired on the driver
6642// thread immediately after
6643// `park_with_deadline` reports `Expired`, while the parker is STILL
6644// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
6645// exact window in which a racing `wake_one` can pop the parker, store its
6646// permit and report the wake as delivered. Lets a test interleave that race
6647// deterministically instead of wall-clock lottery.
6648#[cfg(all(test, not(target_family = "wasm")))]
6649thread_local! {
6650  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6651    const { std::cell::RefCell::new(None) };
6652}
6653
6654#[cfg(all(test, not(target_family = "wasm")))]
6655fn run_deadline_expiry_test_hook() {
6656  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6657    hook();
6658  }
6659}
6660
6661// Second test-only injection seam for the enqueue/verdict race: fired AFTER
6662// the expiry decision's permit and queued-work re-checks both came up
6663// empty (parker already deregistered) and BEFORE the final fingerprint
6664// verdict -- the window in which an enqueue is invisible to every earlier
6665// re-check and only its ENQUEUE counter in the fingerprint can prevent a
6666// false BlockOnDeadlock.
6667#[cfg(all(test, not(target_family = "wasm")))]
6668thread_local! {
6669  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6670    const { std::cell::RefCell::new(None) };
6671}
6672
6673#[cfg(all(test, not(target_family = "wasm")))]
6674fn run_deadline_verdict_test_hook() {
6675  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6676    hook();
6677  }
6678}
6679
6680// Final test-only injection seam: fired after every permit/queue/fingerprint/
6681// timer check from the legacy verdict and immediately before the synchronized
6682// admission gate closes. A separate OS thread can complete a publication here
6683// to prove the final verdict recheck has a real cross-thread ordering edge.
6684#[cfg(all(test, not(target_family = "wasm")))]
6685thread_local! {
6686  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6687    const { std::cell::RefCell::new(None) };
6688}
6689
6690#[cfg(all(test, not(target_family = "wasm")))]
6691fn run_deadline_final_verdict_test_hook() {
6692  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6693    hook();
6694  }
6695}
6696
6697// Fired after the synchronized verdict gate has closed and before the verdict
6698// takes the stop-publication mutex for its final rechecks. A test can pause
6699// here while shutdown publishes generation stop and its abort wake reaches the
6700// closed admission gate.
6701#[cfg(all(test, not(target_family = "wasm")))]
6702thread_local! {
6703  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6704    const { std::cell::RefCell::new(None) };
6705}
6706
6707#[cfg(all(test, not(target_family = "wasm")))]
6708fn run_deadline_gated_verdict_test_hook() {
6709  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6710    hook();
6711  }
6712}
6713
6714// Fired after the final verdict has acquired the stop-publication mutex. A
6715// shutdown test can retain that guard while another thread reaches the
6716// lifecycle -> stop-publication lock edge.
6717#[cfg(all(test, not(target_family = "wasm")))]
6718thread_local! {
6719  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6720    const { std::cell::RefCell::new(None) };
6721}
6722
6723#[cfg(all(test, not(target_family = "wasm")))]
6724fn run_deadline_synchronized_stop_test_hook() {
6725  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6726    hook();
6727  }
6728}
6729
6730// Fired after the final verdict owns exact-owner publication and before it
6731// checks handoff/lane predicates. Tests can remove unrelated saving predicates
6732// while retaining the synchronized owner state under examination.
6733#[cfg(all(test, not(target_family = "wasm")))]
6734type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
6735
6736#[cfg(all(test, not(target_family = "wasm")))]
6737thread_local! {
6738  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
6739    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
6740      const { std::cell::RefCell::new(None) };
6741}
6742
6743#[cfg(all(test, not(target_family = "wasm")))]
6744fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
6745  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6746    hook(parker);
6747  }
6748}
6749
6750// Fired immediately before a test thread takes the stop-publication mutex.
6751// Installed only on the shutdown thread, this proves the controller still
6752// holds the lifecycle in Running while waiting for a verdict-owned guard.
6753#[cfg(all(test, not(target_family = "wasm")))]
6754thread_local! {
6755  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
6756    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6757      const { std::cell::RefCell::new(None) };
6758}
6759
6760#[cfg(all(test, not(target_family = "wasm")))]
6761fn run_before_generation_stop_publication_lock_test_hook() {
6762  if let Some(hook) =
6763    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6764  {
6765    hook();
6766  }
6767}
6768
6769// Fired on an admission thread after it observes a closed verdict gate. The
6770// hook is thread-local so a shutdown test can prove that an abort wake reached
6771// this exact wait without affecting unrelated scheduler threads.
6772#[cfg(all(test, not(target_family = "wasm")))]
6773thread_local! {
6774  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
6775    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6776      const { std::cell::RefCell::new(None) };
6777}
6778
6779#[cfg(all(test, not(target_family = "wasm")))]
6780fn run_deadlock_closed_gate_admission_test_hook() {
6781  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
6782  {
6783    hook();
6784  }
6785}
6786
6787// Timer registration enters the shared verdict admission before this seam.
6788// Tests can pause there to prove the timer remains an announced publication
6789// until its heap entry becomes visible.
6790#[cfg(all(test, not(target_family = "wasm")))]
6791thread_local! {
6792  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6793    const { std::cell::RefCell::new(None) };
6794}
6795
6796#[cfg(all(test, not(target_family = "wasm")))]
6797fn run_timer_registration_admission_test_hook() {
6798  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6799    hook();
6800  }
6801}
6802
6803#[cfg(all(test, not(target_family = "wasm")))]
6804thread_local! {
6805  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
6806    const { std::cell::RefCell::new(None) };
6807}
6808
6809#[cfg(all(test, not(target_family = "wasm")))]
6810fn run_owner_handoff_selected_test_hook() {
6811  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
6812    hook();
6813  }
6814}
6815
6816/// Registry of parked (or committed-to-parking) cooperative drivers, used by
6817/// queue-wakes to wake exactly one of them.
6818#[cfg(not(target_family = "wasm"))]
6819#[derive(Default)]
6820struct ParkedDrivers {
6821  parked: Mutex<Vec<ParkedDriver>>,
6822  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
6823  // by `wake_one`'s no-waiter fast path.
6824  count: AtomicUsize,
6825}
6826
6827#[cfg(not(target_family = "wasm"))]
6828struct ParkedDriver {
6829  parker: Arc<DriverParker>,
6830  can_run_blocking: bool,
6831  dependency: Option<Arc<TaskDependency>>,
6832}
6833
6834#[cfg(not(target_family = "wasm"))]
6835impl ParkedDrivers {
6836  /// Register `parker` as parked-or-parking. MUST precede the caller's final
6837  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
6838  #[cfg(test)]
6839  fn register(&self, parker: &Arc<DriverParker>) {
6840    self.register_with_role(parker, true, None);
6841  }
6842
6843  fn register_with_role(
6844    &self,
6845    parker: &Arc<DriverParker>,
6846    can_run_blocking: bool,
6847    dependency: Option<Arc<TaskDependency>>,
6848  ) {
6849    let mut parked = self
6850      .parked
6851      .lock()
6852      .unwrap_or_else(std::sync::PoisonError::into_inner);
6853    parked.push(ParkedDriver {
6854      parker: Arc::clone(parker),
6855      can_run_blocking,
6856      dependency,
6857    });
6858    self.count.store(parked.len(), Ordering::SeqCst);
6859  }
6860
6861  /// Remove `parker` if still registered. `wake_one` removes the parkers it
6862  /// pops, while a direct future-wake does not, so drivers call this after
6863  /// every park (and after a re-check aborts one).
6864  fn deregister(&self, parker: &Arc<DriverParker>) {
6865    let removed = {
6866      let mut parked = self
6867        .parked
6868        .lock()
6869        .unwrap_or_else(std::sync::PoisonError::into_inner);
6870      let removed = parked
6871        .iter()
6872        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
6873        .map(|index| parked.swap_remove(index));
6874      self.count.store(parked.len(), Ordering::SeqCst);
6875      removed
6876    };
6877    drop(removed);
6878  }
6879
6880  /// Wake one parked driver -- most recently parked first, its stack and
6881  /// caches are the warmest -- returning whether anyone was woken.
6882  ///
6883  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
6884  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
6885  /// registers itself (the `count` store above, under the registry mutex),
6886  /// THEN re-checks the work queues under Q, and only then parks. Order the
6887  /// two Q critical sections:
6888  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
6889  ///     it, the re-check observes the work and the driver does not park --
6890  ///     skipping the wake (fast path below) is safe.
6891  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
6892  ///     push locked it. The driver's `count` store is sequenced before its
6893  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
6894  ///     lock of Q, and the push is sequenced before the fast-path load
6895  ///     below; the load therefore happens-after the `count` store and must
6896  ///     observe `count >= 1`: the slow path pops a registered parker and
6897  ///     wakes it.
6898  ///
6899  /// A wake delivered to a driver that raced out of its park (or aborted it)
6900  /// is stored as that driver's permit; the driver re-checks every work
6901  /// source before it can sleep on the next park, so the wake still lands.
6902  fn wake_one(&self) -> bool {
6903    if self.count.load(Ordering::SeqCst) == 0 {
6904      // No-waiter fast path: nobody is parked or committed to parking (any
6905      // in-flight parker that missed this check re-checks the queues after
6906      // registering, per the argument above).
6907      return false;
6908    }
6909    let driver = {
6910      let mut parked = self
6911        .parked
6912        .lock()
6913        .unwrap_or_else(std::sync::PoisonError::into_inner);
6914      let driver = parked.pop();
6915      self.count.store(parked.len(), Ordering::SeqCst);
6916      driver
6917    };
6918    match driver {
6919      Some(driver) => {
6920        driver.parker.unpark();
6921        true
6922      }
6923      None => false,
6924    }
6925  }
6926
6927  /// Wake the most recently parked cooperative driver that is eligible to
6928  /// consume blocking work.
6929  fn wake_one_blocking(&self) -> bool {
6930    if self.count.load(Ordering::SeqCst) == 0 {
6931      return false;
6932    }
6933    let driver = {
6934      let mut parked = self
6935        .parked
6936        .lock()
6937        .unwrap_or_else(std::sync::PoisonError::into_inner);
6938      let driver = parked
6939        .iter()
6940        .rposition(|driver| driver.can_run_blocking)
6941        .map(|index| parked.swap_remove(index));
6942      self.count.store(parked.len(), Ordering::SeqCst);
6943      driver
6944    };
6945    match driver {
6946      Some(driver) => {
6947        driver.parker.unpark();
6948        true
6949      }
6950      None => false,
6951    }
6952  }
6953
6954  /// Wake one parked driver whose live dependency belongs to `owner`.
6955  ///
6956  /// The dependency is published with the parker, so an unrelated newer
6957  /// driver cannot consume the bounded owner-lane handoff.
6958  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
6959    loop {
6960      if self.count.load(Ordering::SeqCst) == 0 {
6961        return false;
6962      }
6963      let candidates = {
6964        let parked = self
6965          .parked
6966          .lock()
6967          .unwrap_or_else(std::sync::PoisonError::into_inner);
6968        parked
6969          .iter()
6970          .rev()
6971          .filter_map(|driver| {
6972            if !driver.can_run_blocking {
6973              return None;
6974            }
6975            driver
6976              .dependency
6977              .as_ref()
6978              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
6979          })
6980          .collect::<Vec<_>>()
6981      };
6982      let selected = candidates.into_iter().find(|(_, dependency)| {
6983        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
6984      });
6985      let Some((selected_parker, selected_dependency)) = selected else {
6986        return false;
6987      };
6988      let driver = {
6989        let mut parked = self
6990          .parked
6991          .lock()
6992          .unwrap_or_else(std::sync::PoisonError::into_inner);
6993        let driver = parked
6994          .iter()
6995          .rposition(|driver| {
6996            Arc::ptr_eq(&driver.parker, &selected_parker)
6997              && driver
6998                .dependency
6999                .as_ref()
7000                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7001          })
7002          .map(|index| parked.swap_remove(index));
7003        // Publish the exact-owner token before releasing the registry lock. A
7004        // direct future wake can race this selection, but the driver must
7005        // deregister under this same lock before it can poll Ready and forward
7006        // an unconsumed handoff on exit.
7007        if let Some(driver) = &driver {
7008          driver.parker.grant_owner_handoff(owner);
7009        }
7010        self.count.store(parked.len(), Ordering::SeqCst);
7011        driver
7012      };
7013      if let Some(driver) = driver {
7014        #[cfg(test)]
7015        run_owner_handoff_selected_test_hook();
7016        driver.parker.unpark();
7017        return true;
7018      }
7019    }
7020  }
7021
7022  /// Wake every timer-bounded driver so cancellation of the earliest timer
7023  /// cannot leave pool workers parked until a stale deadline.
7024  fn wake_all(&self) {
7025    if self.count.load(Ordering::SeqCst) == 0 {
7026      return;
7027    }
7028    let drivers = {
7029      let mut parked = self
7030        .parked
7031        .lock()
7032        .unwrap_or_else(std::sync::PoisonError::into_inner);
7033      let drivers = parked.drain(..).collect::<Vec<_>>();
7034      self.count.store(0, Ordering::SeqCst);
7035      drivers
7036    };
7037    for driver in drivers {
7038      driver.parker.unpark();
7039    }
7040  }
7041}
7042
7043// ---------------------------------------------------------------------------
7044// Runtime timer facility: `sleep_until` without a tokio
7045// reactor. Two drivers, selected by runtime flavor:
7046//
7047//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
7048//     timekeeper thread. Timer waits never consume the configured CPU workers.
7049//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
7050//     deadline, so the parked driver can fire due timers without depending on
7051//     any particular inter-thread wake ordering.
7052//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
7053//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
7054//     driver at import; wasi-single uses the same path). A CT runtime without
7055//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
7056//
7057// DEADLOCK-DETECTION INTERACTIONS:
7058//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
7059//     clock is a guaranteed wake source. Timer waits therefore use their own
7060//     primitive (a plain `park_timeout` bounded by the earliest deadline),
7061//     NEVER `park_with_deadline`, so an armed park deadline
7062//     shorter than the next timer cannot fire on a legitimate timer wait; the
7063//     fire itself schedules the woken task, which IS fingerprint progress for
7064//     every other parked driver. The expiry VERDICT additionally re-checks
7065//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
7066//     counter by design, so a registration racing an already-committed
7067//     deadline park must veto the panic there.
7068//   * CT certain check (threadless): a pending HOST timer is NOT a wake
7069//     token -- the JS setTimeout relay can only run on the very thread that
7070//     is parking, so it can never fire while `block_on` holds it. The check
7071//     panics unconditionally on a threadless build; a pending timer merely
7072//     selects the timer-backed variant of the typed diagnostic. The CT
7073//     deadline verdict (threaded flavor, where the host loop genuinely runs
7074//     concurrently) counts only LIVE waits (deadline still in the future): a
7075//     pending timer whose deadline passed without firing is the
7076//     frozen-JS-event-loop signature that the deadline-based detection
7077//     exists to catch, so it must NOT suppress the panic.
7078
7079/// Host-turn dispatcher for the CurrentThread runnable queue.
7080///
7081/// The NAPI binding registers one weak native threadsafe-function driver per
7082/// importing environment. Its custom Node-API callback owns the fresh host turn
7083/// and keeps delivery capabilities out of JavaScript. Scheduling through a
7084/// fresh host turn is required for soundness: arbitrary futures may invoke
7085/// their waker while holding internal locks, so polling the runnable inline
7086/// from that wake can self-deadlock.
7087pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7088  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
7089  /// [`CurrentThreadTaskDelivery::capability`].
7090  ///
7091  /// The delivery capability is scoped to this exact host registration and
7092  /// attempt. The registry maps it back to the executor's one internal
7093  /// dispatch capability only while this attempt remains current.
7094  /// `false` means this driver can no longer dispatch and must be swept. After
7095  /// returning `true`, the driver must eventually call either
7096  /// [`acknowledge_current_thread_task_delivery`] or
7097  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
7098  /// acknowledgement is accepted only after
7099  /// [`drive_current_thread_tasks`] claimed this exact delivery.
7100  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7101
7102  fn is_live(&self) -> bool {
7103    true
7104  }
7105
7106  /// Called after this driver has been removed from the registry, with no
7107  /// registry lock held. Must be idempotent with explicit host cleanup.
7108  fn on_swept(&self) {}
7109}
7110
7111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7112pub struct CurrentThreadTaskDriverId(u64);
7113
7114type CurrentThreadTaskDriverSnapshot =
7115  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7116
7117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7118pub struct CurrentThreadTaskDelivery {
7119  driver: CurrentThreadTaskDriverId,
7120  capability: u64,
7121  dispatch: u64,
7122}
7123
7124impl CurrentThreadTaskDelivery {
7125  /// Opaque driver capability for this exact registration attempt.
7126  pub fn capability(self) -> u64 {
7127    self.capability
7128  }
7129}
7130
7131struct CurrentThreadTaskDeliveryState {
7132  delivery: CurrentThreadTaskDelivery,
7133  claimed: bool,
7134  accepted: bool,
7135}
7136
7137struct CurrentThreadTaskDriverEntry {
7138  driver: Arc<dyn CurrentThreadTaskDriver>,
7139  delivery: Option<CurrentThreadTaskDeliveryState>,
7140  pending: Option<u64>,
7141  previous: Option<CurrentThreadTaskDriverId>,
7142  next: Option<CurrentThreadTaskDriverId>,
7143}
7144
7145#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7146enum CurrentThreadDispatchPhase {
7147  #[default]
7148  Pending,
7149  Serviced,
7150  Cancelling,
7151}
7152
7153#[derive(Default)]
7154struct CurrentThreadDispatchState {
7155  phase: CurrentThreadDispatchPhase,
7156  epoch: u64,
7157  publications: usize,
7158  references: usize,
7159  accepted: bool,
7160  accepted_failure: bool,
7161}
7162
7163impl CurrentThreadDispatchState {
7164  fn is_serviced(&self) -> bool {
7165    self.phase == CurrentThreadDispatchPhase::Serviced
7166  }
7167
7168  fn is_cancelling(&self) -> bool {
7169    self.phase == CurrentThreadDispatchPhase::Cancelling
7170  }
7171
7172  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7173    CurrentThreadTaskDispatchFailure {
7174      dispatch,
7175      epoch: self.epoch,
7176    }
7177  }
7178}
7179
7180#[derive(Default)]
7181struct CurrentThreadTaskDriverRegistryState {
7182  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7183  oldest: Option<CurrentThreadTaskDriverId>,
7184  newest: Option<CurrentThreadTaskDriverId>,
7185  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7186  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7187}
7188
7189impl CurrentThreadTaskDriverRegistryState {
7190  fn insert_driver(
7191    &mut self,
7192    id: CurrentThreadTaskDriverId,
7193    driver: Arc<dyn CurrentThreadTaskDriver>,
7194  ) {
7195    let previous = self.newest;
7196    let replaced = self.entries.insert(
7197      id,
7198      CurrentThreadTaskDriverEntry {
7199        driver,
7200        delivery: None,
7201        pending: None,
7202        previous,
7203        next: None,
7204      },
7205    );
7206    assert!(
7207      replaced.is_none(),
7208      "CurrentThread task driver id must be unique"
7209    );
7210    if let Some(previous) = previous {
7211      self
7212        .entries
7213        .get_mut(&previous)
7214        .expect("the newest CurrentThread task driver must remain registered")
7215        .next = Some(id);
7216    } else {
7217      self.oldest = Some(id);
7218    }
7219    self.newest = Some(id);
7220  }
7221
7222  fn remove_driver(
7223    &mut self,
7224    id: CurrentThreadTaskDriverId,
7225  ) -> Option<CurrentThreadTaskDriverEntry> {
7226    let entry = self.entries.remove(&id)?;
7227    if let Some(previous) = entry.previous {
7228      self
7229        .entries
7230        .get_mut(&previous)
7231        .expect("the previous CurrentThread task driver must remain registered")
7232        .next = entry.next;
7233    } else {
7234      self.oldest = entry.next;
7235    }
7236    if let Some(next) = entry.next {
7237      self
7238        .entries
7239        .get_mut(&next)
7240        .expect("the next CurrentThread task driver must remain registered")
7241        .previous = entry.previous;
7242    } else {
7243      self.newest = entry.previous;
7244    }
7245    if let Some(delivery) = &entry.delivery {
7246      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7247      debug_assert_eq!(
7248        indexed,
7249        Some(id),
7250        "delivery capability index must match its driver"
7251      );
7252    }
7253    Some(entry)
7254  }
7255
7256  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7257    let mut snapshot = Vec::with_capacity(self.entries.len());
7258    let mut current = self.oldest;
7259    while let Some(id) = current {
7260      let entry = self
7261        .entries
7262        .get(&id)
7263        .expect("CurrentThread task driver order must reference a live entry");
7264      snapshot.push((id, Arc::clone(&entry.driver)));
7265      current = entry.next;
7266    }
7267    debug_assert_eq!(
7268      snapshot.len(),
7269      self.entries.len(),
7270      "CurrentThread task driver order must contain every entry exactly once"
7271    );
7272    snapshot
7273  }
7274
7275  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7276    if self.entries.len() != expected.len() {
7277      return false;
7278    }
7279    let mut current = self.oldest;
7280    for expected in expected {
7281      if current != Some(*expected) {
7282        return false;
7283      }
7284      current = self
7285        .entries
7286        .get(expected)
7287        .expect("CurrentThread task driver order must reference a live entry")
7288        .next;
7289    }
7290    current.is_none()
7291  }
7292}
7293
7294struct CurrentThreadTaskDriverSelection {
7295  live: CurrentThreadTaskDriverSnapshot,
7296  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7297}
7298
7299#[derive(Default)]
7300struct CurrentThreadTaskDeliveryCompletion {
7301  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7302  redispatch: bool,
7303}
7304
7305struct CurrentThreadTaskDispatchCompletion {
7306  result: CurrentThreadHostDispatchResult,
7307  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7308}
7309
7310struct CurrentThreadTaskDispatchPublicationGuard<'a> {
7311  registry: &'a CurrentThreadTaskDriverRegistry,
7312  dispatch: Option<u64>,
7313}
7314
7315impl CurrentThreadTaskDispatchPublicationGuard<'_> {
7316  fn finish(mut self) -> CurrentThreadHostDispatchResult {
7317    let dispatch = self
7318      .dispatch
7319      .expect("the CurrentThread task dispatch publication must remain armed");
7320    let result = self.registry.finish_publication(dispatch);
7321    self.dispatch = None;
7322    result
7323  }
7324}
7325
7326impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
7327  fn drop(&mut self) {
7328    if let Some(dispatch) = self.dispatch.take() {
7329      self.registry.abort_publication(dispatch);
7330    }
7331  }
7332}
7333
7334#[derive(Default)]
7335struct CurrentThreadTaskDriverRegistry {
7336  state: Mutex<CurrentThreadTaskDriverRegistryState>,
7337  next_id: AtomicU64,
7338  next_delivery: AtomicU64,
7339}
7340
7341impl CurrentThreadTaskDriverRegistry {
7342  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
7343    let id = next_unique_id(
7344      &self.next_id,
7345      "CurrentThread task driver id space exhausted",
7346    );
7347    let id = CurrentThreadTaskDriverId(id);
7348    self
7349      .state
7350      .lock()
7351      .unwrap_or_else(std::sync::PoisonError::into_inner)
7352      .insert_driver(id, driver);
7353    id
7354  }
7355
7356  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
7357    let (removed, failed_dispatches) = {
7358      let mut state = self
7359        .state
7360        .lock()
7361        .unwrap_or_else(std::sync::PoisonError::into_inner);
7362      let removed = state.remove_driver(id);
7363      let mut failed_dispatches = Vec::new();
7364      if let Some(entry) = &removed {
7365        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7366      }
7367      (removed, failed_dispatches)
7368    };
7369    let _ = catch_unwind_contained(|| drop(removed));
7370    CurrentThreadTaskDeliveryCompletion {
7371      failed_dispatches,
7372      redispatch: false,
7373    }
7374  }
7375
7376  fn live(&self) -> CurrentThreadTaskDriverSelection {
7377    let mut failed_dispatches = Vec::new();
7378    loop {
7379      let snapshot = CurrentThreadTaskDriverSnapshot::new({
7380        let state = self
7381          .state
7382          .lock()
7383          .unwrap_or_else(std::sync::PoisonError::into_inner);
7384        state.ordered_snapshot()
7385      });
7386      let mut dead_ids = FxHashSet::default();
7387      let mut live_ids = Vec::new();
7388      for (id, driver) in snapshot.as_slice() {
7389        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
7390          live_ids.push(*id);
7391        } else {
7392          dead_ids.insert(*id);
7393        }
7394      }
7395
7396      let (live, swept, retry) = {
7397        let mut state = self
7398          .state
7399          .lock()
7400          .unwrap_or_else(std::sync::PoisonError::into_inner);
7401        let mut swept = Vec::new();
7402        let mut current = state.oldest;
7403        while let Some(id) = current {
7404          let next = state
7405            .entries
7406            .get(&id)
7407            .expect("CurrentThread task driver order must reference a live entry")
7408            .next;
7409          if dead_ids.contains(&id)
7410            && let Some(entry) = state.remove_driver(id)
7411          {
7412            swept.push(entry);
7413          }
7414          current = next;
7415        }
7416        for entry in &swept {
7417          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
7418        }
7419        let retry = !state.ordered_ids_match(&live_ids);
7420        let live = if retry {
7421          Vec::new()
7422        } else {
7423          state.ordered_snapshot()
7424        };
7425        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
7426      };
7427
7428      // A reentrant probe can unregister multiple snapshot-only drivers.
7429      // See the implementation notes in this crate's README.
7430      drop(snapshot);
7431      for entry in swept {
7432        let _ = catch_unwind_contained(|| entry.driver.on_swept());
7433        let _ = catch_unwind_contained(|| drop(entry));
7434      }
7435      if !retry {
7436        return CurrentThreadTaskDriverSelection {
7437          live,
7438          failed_dispatches,
7439        };
7440      }
7441    }
7442  }
7443
7444  #[cfg(test)]
7445  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7446    let mut live = self.live().live;
7447    live.pop().map(|(id, driver)| (id, driver.into_inner()))
7448  }
7449
7450  fn next_delivery(
7451    &self,
7452    driver: CurrentThreadTaskDriverId,
7453    dispatch: u64,
7454  ) -> CurrentThreadTaskDelivery {
7455    let capability = next_unique_id(
7456      &self.next_delivery,
7457      "CurrentThread task delivery id space exhausted",
7458    )
7459    .checked_add(1)
7460    .expect("CurrentThread task delivery id space exhausted");
7461    CurrentThreadTaskDelivery {
7462      driver,
7463      capability,
7464      dispatch,
7465    }
7466  }
7467
7468  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7469    let dispatch_state = state
7470      .dispatches
7471      .get_mut(&dispatch)
7472      .expect("a referenced CurrentThread dispatch must remain registered");
7473    dispatch_state.references = dispatch_state
7474      .references
7475      .checked_add(1)
7476      .expect("CurrentThread dispatch reference count exhausted");
7477  }
7478
7479  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
7480    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7481      return;
7482    };
7483    dispatch_state.references = dispatch_state
7484      .references
7485      .checked_sub(1)
7486      .expect("CurrentThread dispatch reference count underflow");
7487  }
7488
7489  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
7490    let publication = {
7491      let mut state = self
7492        .state
7493        .lock()
7494        .unwrap_or_else(std::sync::PoisonError::into_inner);
7495      let dispatch_state = state.dispatches.entry(dispatch).or_default();
7496      if dispatch_state.is_serviced() {
7497        return CurrentThreadTaskDispatchCompletion {
7498          result: CurrentThreadHostDispatchResult::Accepted,
7499          failed_dispatches: Vec::new(),
7500        };
7501      }
7502      if dispatch_state.is_cancelling() {
7503        return CurrentThreadTaskDispatchCompletion {
7504          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
7505          failed_dispatches: Vec::new(),
7506        };
7507      }
7508      dispatch_state.publications = dispatch_state
7509        .publications
7510        .checked_add(1)
7511        .expect("CurrentThread dispatch publication count exhausted");
7512      CurrentThreadTaskDispatchPublicationGuard {
7513        registry: self,
7514        dispatch: Some(dispatch),
7515      }
7516    };
7517
7518    let CurrentThreadTaskDriverSelection {
7519      mut live,
7520      mut failed_dispatches,
7521    } = self.live();
7522    let planned_deliveries = {
7523      let mut state = self
7524        .state
7525        .lock()
7526        .unwrap_or_else(std::sync::PoisonError::into_inner);
7527      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
7528        return CurrentThreadTaskDispatchCompletion {
7529          result: CurrentThreadHostDispatchResult::Unavailable,
7530          failed_dispatches,
7531        };
7532      };
7533      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
7534        Vec::new()
7535      } else {
7536        // Reserve every capability and all output storage before mutating an
7537        // entry. If ID allocation unwinds, the publication guard can retire
7538        // the barrier without leaving an undispatched physical slot occupied.
7539        let mut reserved_deliveries = Vec::with_capacity(live.len());
7540        for (id, _) in live.as_slice().iter().rev() {
7541          let Some(entry) = state.entries.get(id) else {
7542            continue;
7543          };
7544          if entry.delivery.is_none() {
7545            reserved_deliveries.push(self.next_delivery(*id, dispatch));
7546          }
7547        }
7548        state.delivery_drivers.reserve(reserved_deliveries.len());
7549        let mut reserved_deliveries = reserved_deliveries.into_iter();
7550        let mut planned_deliveries = Vec::with_capacity(live.len());
7551        let mut retired = Vec::with_capacity(live.len());
7552        let mut accepted_reservation = false;
7553        for (id, _) in live.as_slice().iter().rev() {
7554          let Some(entry) = state.entries.get(id) else {
7555            continue;
7556          };
7557          if let Some(delivery) = &entry.delivery
7558            && delivery.delivery.dispatch == dispatch
7559          {
7560            accepted_reservation |= delivery.accepted;
7561            continue;
7562          }
7563          if entry.delivery.is_some() {
7564            let previous = state
7565              .entries
7566              .get_mut(id)
7567              .expect("the CurrentThread task driver must remain registered")
7568              .pending
7569              .replace(dispatch);
7570            if previous != Some(dispatch) {
7571              if let Some(previous) = previous {
7572                Self::remove_dispatch_reference(&mut state, previous);
7573              }
7574              Self::add_dispatch_reference(&mut state, dispatch);
7575            }
7576            retired.push(previous);
7577            accepted_reservation = true;
7578            continue;
7579          }
7580          let delivery = reserved_deliveries
7581            .next()
7582            .expect("every available CurrentThread host must have a reserved delivery");
7583          let pending = state
7584            .entries
7585            .get_mut(id)
7586            .expect("the CurrentThread task driver must remain registered")
7587            .pending
7588            .take();
7589          let accepted = pending == Some(dispatch);
7590          if !accepted && pending.is_some() {
7591            retired.push(pending);
7592          }
7593          match pending {
7594            Some(pending) if pending == dispatch => {}
7595            Some(pending) => {
7596              Self::remove_dispatch_reference(&mut state, pending);
7597              Self::add_dispatch_reference(&mut state, dispatch);
7598            }
7599            None => Self::add_dispatch_reference(&mut state, dispatch),
7600          }
7601          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
7602          assert!(
7603            indexed.is_none(),
7604            "CurrentThread delivery capability must be unique"
7605          );
7606          state
7607            .entries
7608            .get_mut(id)
7609            .expect("the CurrentThread task driver must remain registered")
7610            .delivery = Some(CurrentThreadTaskDeliveryState {
7611            delivery,
7612            claimed: false,
7613            accepted,
7614          });
7615          accepted_reservation |= accepted;
7616          planned_deliveries.push((*id, delivery));
7617        }
7618        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
7619        debug_assert!(
7620          all_reserved_deliveries_installed,
7621          "every reserved CurrentThread delivery must be installed"
7622        );
7623        if accepted_reservation {
7624          state
7625            .dispatches
7626            .get_mut(&dispatch)
7627            .expect("the publishing dispatch state must remain registered")
7628            .accepted = true;
7629        }
7630        for dispatch in retired.into_iter().flatten() {
7631          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7632        }
7633        planned_deliveries
7634      }
7635    };
7636
7637    // Pair newest-first delivery plans with contained driver owners after
7638    // releasing the registry lock. Normal, rejection, and unwind cleanup can
7639    // then destroy every selected driver independently without re-entering the
7640    // registry under its mutex.
7641    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
7642    let mut planned_deliveries = planned_deliveries.into_iter();
7643    let mut next_delivery = planned_deliveries.next();
7644    while let Some((id, driver)) = live.pop() {
7645      let Some((planned_id, delivery)) = next_delivery else {
7646        break;
7647      };
7648      if planned_id == id {
7649        deliveries.push((id, driver, delivery));
7650        next_delivery = planned_deliveries.next();
7651      } else {
7652        next_delivery = Some((planned_id, delivery));
7653      }
7654    }
7655    debug_assert!(
7656      next_delivery.is_none() && planned_deliveries.len() == 0,
7657      "every planned CurrentThread delivery must retain its selected driver"
7658    );
7659
7660    for (id, driver, delivery) in deliveries {
7661      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
7662        self.accept_delivery(id, delivery);
7663      } else {
7664        let (entry, failures) = self.reject_delivery(id, delivery);
7665        failed_dispatches.extend(failures);
7666        if let Some(entry) = entry {
7667          let _ = catch_unwind_contained(|| entry.driver.on_swept());
7668          let _ = catch_unwind_contained(|| drop(entry));
7669        }
7670      }
7671    }
7672
7673    let result = publication.finish();
7674    debug_assert!(
7675      !failed_dispatches
7676        .iter()
7677        .any(|failure| failure.dispatch == dispatch),
7678      "the active publication must report its own terminal outcome directly"
7679    );
7680    CurrentThreadTaskDispatchCompletion {
7681      result,
7682      failed_dispatches,
7683    }
7684  }
7685
7686  #[cfg(test)]
7687  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7688    self.dispatch_with_completion(dispatch).result
7689  }
7690
7691  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
7692    let mut state = self
7693      .state
7694      .lock()
7695      .unwrap_or_else(std::sync::PoisonError::into_inner);
7696    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
7697      entry
7698        .delivery
7699        .as_ref()
7700        .is_some_and(|current| current.delivery == delivery)
7701    }) {
7702      state
7703        .entries
7704        .get_mut(&id)
7705        .expect("the matching CurrentThread task driver must remain registered")
7706        .delivery
7707        .as_mut()
7708        .expect("the matching delivery must remain installed")
7709        .accepted = true;
7710      true
7711    } else {
7712      // A callback may complete or unregister synchronously before
7713      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
7714      // proves acceptance; if no host serviced it, losing the reference is an
7715      // accepted delivery failure retained behind this publication barrier.
7716      false
7717    };
7718    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
7719      dispatch_state.accepted = true;
7720      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
7721    }
7722  }
7723
7724  fn reject_delivery(
7725    &self,
7726    id: CurrentThreadTaskDriverId,
7727    delivery: CurrentThreadTaskDelivery,
7728  ) -> (
7729    Option<CurrentThreadTaskDriverEntry>,
7730    Vec<CurrentThreadTaskDispatchFailure>,
7731  ) {
7732    let mut state = self
7733      .state
7734      .lock()
7735      .unwrap_or_else(std::sync::PoisonError::into_inner);
7736    if !state.entries.get(&id).is_some_and(|entry| {
7737      entry
7738        .delivery
7739        .as_ref()
7740        .is_some_and(|current| current.delivery == delivery)
7741    }) {
7742      return (None, Vec::new());
7743    }
7744    let entry = state
7745      .remove_driver(id)
7746      .expect("the matching CurrentThread task driver must remain registered");
7747    let mut failures = Vec::new();
7748    Self::finish_entry_references(&mut state, &entry, &mut failures);
7749    (Some(entry), failures)
7750  }
7751
7752  fn finish_delivery(
7753    &self,
7754    delivery: CurrentThreadTaskDelivery,
7755    failed: bool,
7756  ) -> CurrentThreadTaskDeliveryCompletion {
7757    let mut state = self
7758      .state
7759      .lock()
7760      .unwrap_or_else(std::sync::PoisonError::into_inner);
7761    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
7762      entry
7763        .delivery
7764        .as_ref()
7765        .is_some_and(|current| current.delivery == delivery)
7766    }) {
7767      return CurrentThreadTaskDeliveryCompletion::default();
7768    }
7769    let current = state
7770      .entries
7771      .get_mut(&delivery.driver)
7772      .expect("the matching CurrentThread task driver must remain registered")
7773      .delivery
7774      .take()
7775      .expect("the matching delivery must remain installed");
7776    let indexed = state.delivery_drivers.remove(&delivery.capability);
7777    debug_assert_eq!(
7778      indexed,
7779      Some(delivery.driver),
7780      "delivery capability index must match its driver"
7781    );
7782    let redispatch = state
7783      .entries
7784      .get(&delivery.driver)
7785      .expect("the matching CurrentThread task driver must remain registered")
7786      .pending
7787      .is_some_and(|dispatch| {
7788        state
7789          .dispatches
7790          .get(&dispatch)
7791          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
7792      });
7793    let mut failed_dispatches = Vec::new();
7794    // Callback completion proves only that JavaScript ran. A success return is
7795    // authoritative only after `drive_current_thread_tasks` claimed this exact
7796    // host attempt; otherwise a no-op or malicious callback could retire the
7797    // registry reference while leaving the executor dispatch pending forever.
7798    Self::finish_dispatch_reference(
7799      &mut state,
7800      delivery.dispatch,
7801      true,
7802      failed || !current.claimed,
7803      &mut failed_dispatches,
7804    );
7805    CurrentThreadTaskDeliveryCompletion {
7806      failed_dispatches,
7807      redispatch,
7808    }
7809  }
7810
7811  fn claim_delivery(&self, capability: u64) -> Option<u64> {
7812    let mut state = self
7813      .state
7814      .lock()
7815      .unwrap_or_else(std::sync::PoisonError::into_inner);
7816    let driver = *state.delivery_drivers.get(&capability)?;
7817    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
7818    if delivery.capability != capability {
7819      return None;
7820    }
7821    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
7822    if dispatch_state.is_serviced()
7823      || dispatch_state.is_cancelling()
7824      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
7825    {
7826      return None;
7827    }
7828    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
7829    current.claimed = true;
7830    current.accepted = true;
7831    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
7832    Some(delivery.dispatch)
7833  }
7834
7835  fn mark_serviced(&self, dispatch: u64) {
7836    let mut state = self
7837      .state
7838      .lock()
7839      .unwrap_or_else(std::sync::PoisonError::into_inner);
7840    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7841      return;
7842    };
7843    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
7844    let mut removed_references = 0usize;
7845    for entry in state.entries.values_mut() {
7846      if entry.pending == Some(dispatch) {
7847        entry.pending = None;
7848        removed_references = removed_references
7849          .checked_add(1)
7850          .expect("CurrentThread dispatch reference count exhausted");
7851      }
7852    }
7853    let dispatch_state = state
7854      .dispatches
7855      .get_mut(&dispatch)
7856      .expect("the serviced CurrentThread dispatch must remain registered");
7857    dispatch_state.references = dispatch_state
7858      .references
7859      .checked_sub(removed_references)
7860      .expect("CurrentThread dispatch reference count underflow");
7861    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7862  }
7863
7864  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
7865    let delivery = {
7866      let state = self
7867        .state
7868        .lock()
7869        .unwrap_or_else(std::sync::PoisonError::into_inner);
7870      state
7871        .delivery_drivers
7872        .get(&capability)
7873        .and_then(|driver| state.entries.get(driver))
7874        .and_then(|entry| entry.delivery.as_ref())
7875        .map(|delivery| delivery.delivery)
7876        .filter(|delivery| delivery.capability == capability)
7877    };
7878    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
7879      self.finish_delivery(delivery, true)
7880    })
7881  }
7882
7883  fn finish_dispatch_reference(
7884    state: &mut CurrentThreadTaskDriverRegistryState,
7885    dispatch: u64,
7886    accepted: bool,
7887    failed: bool,
7888    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7889  ) {
7890    let (publications, references) = {
7891      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7892        return;
7893      };
7894      dispatch_state.references = dispatch_state
7895        .references
7896        .checked_sub(1)
7897        .expect("CurrentThread dispatch reference count underflow");
7898      dispatch_state.accepted |= accepted;
7899      dispatch_state.accepted_failure |= accepted && failed;
7900      (dispatch_state.publications, dispatch_state.references)
7901    };
7902    if references != 0 || publications != 0 {
7903      return;
7904    }
7905    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7906      return;
7907    };
7908    if dispatch_state.accepted_failure
7909      && !dispatch_state.is_serviced()
7910      && !dispatch_state.is_cancelling()
7911    {
7912      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7913      failures.push(dispatch_state.failure(dispatch));
7914    } else if !dispatch_state.is_cancelling() {
7915      state.dispatches.remove(&dispatch);
7916    }
7917  }
7918
7919  fn finish_entry_references(
7920    state: &mut CurrentThreadTaskDriverRegistryState,
7921    entry: &CurrentThreadTaskDriverEntry,
7922    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
7923  ) {
7924    if let Some(delivery) = &entry.delivery {
7925      Self::finish_dispatch_reference(
7926        state,
7927        delivery.delivery.dispatch,
7928        delivery.accepted,
7929        delivery.accepted,
7930        failures,
7931      );
7932    }
7933    if let Some(pending) = entry.pending {
7934      Self::finish_dispatch_reference(state, pending, true, true, failures);
7935    }
7936  }
7937
7938  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
7939    let mut state = self
7940      .state
7941      .lock()
7942      .unwrap_or_else(std::sync::PoisonError::into_inner);
7943    let publications = {
7944      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7945        return CurrentThreadHostDispatchResult::Unavailable;
7946      };
7947      dispatch_state.publications = dispatch_state
7948        .publications
7949        .checked_sub(1)
7950        .expect("CurrentThread dispatch publication count underflow");
7951      dispatch_state.publications
7952    };
7953    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
7954      return CurrentThreadHostDispatchResult::Accepted;
7955    }
7956    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7957      return CurrentThreadHostDispatchResult::Unavailable;
7958    };
7959    if dispatch_state.is_cancelling() {
7960      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7961    }
7962    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
7963      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
7964      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
7965    }
7966    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
7967    state.dispatches.remove(&dispatch);
7968    if accepted {
7969      CurrentThreadHostDispatchResult::Accepted
7970    } else {
7971      CurrentThreadHostDispatchResult::Unavailable
7972    }
7973  }
7974
7975  fn abort_publication(&self, dispatch: u64) {
7976    let mut state = self
7977      .state
7978      .lock()
7979      .unwrap_or_else(std::sync::PoisonError::into_inner);
7980    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
7981      return;
7982    };
7983    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
7984      return;
7985    };
7986    dispatch_state.publications = publications;
7987    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
7988  }
7989
7990  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
7991    let mut state = self
7992      .state
7993      .lock()
7994      .unwrap_or_else(std::sync::PoisonError::into_inner);
7995    let can_rearm = state
7996      .dispatches
7997      .get(&failure.dispatch)
7998      .is_some_and(|dispatch_state| {
7999        dispatch_state.is_cancelling()
8000          && dispatch_state.epoch == failure.epoch
8001          && dispatch_state.publications == 0
8002      })
8003      && !Self::has_dispatch_reference(&state, failure.dispatch);
8004    if !can_rearm {
8005      return false;
8006    }
8007    let dispatch_state = state
8008      .dispatches
8009      .get_mut(&failure.dispatch)
8010      .expect("the exact failed CurrentThread dispatch must remain registered");
8011    let epoch = dispatch_state
8012      .epoch
8013      .checked_add(1)
8014      .expect("CurrentThread dispatch failure epoch space exhausted");
8015    *dispatch_state = CurrentThreadDispatchState {
8016      epoch,
8017      ..CurrentThreadDispatchState::default()
8018    };
8019    true
8020  }
8021
8022  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8023    self
8024      .state
8025      .lock()
8026      .unwrap_or_else(std::sync::PoisonError::into_inner)
8027      .dispatches
8028      .get(&failure.dispatch)
8029      .is_some_and(|dispatch_state| {
8030        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8031      })
8032  }
8033
8034  fn retire_unreferenced_dispatch(
8035    state: &mut CurrentThreadTaskDriverRegistryState,
8036    dispatch: Option<u64>,
8037  ) {
8038    let Some(dispatch) = dispatch else {
8039      return;
8040    };
8041    if Self::has_dispatch_reference(state, dispatch) {
8042      return;
8043    }
8044    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8045      return;
8046    };
8047    if dispatch_state.publications == 0
8048      && !dispatch_state.is_cancelling()
8049      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8050    {
8051      state.dispatches.remove(&dispatch);
8052    }
8053  }
8054
8055  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8056    state
8057      .dispatches
8058      .get(&dispatch)
8059      .is_some_and(|dispatch_state| dispatch_state.references != 0)
8060  }
8061
8062  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8063    let mut state = self
8064      .state
8065      .lock()
8066      .unwrap_or_else(std::sync::PoisonError::into_inner);
8067    if state
8068      .dispatches
8069      .get(&failure.dispatch)
8070      .is_some_and(|dispatch_state| {
8071        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8072      })
8073    {
8074      state.dispatches.remove(&failure.dispatch);
8075    }
8076  }
8077
8078  fn retire_for_shutdown(&self) {
8079    let mut state = self
8080      .state
8081      .lock()
8082      .unwrap_or_else(std::sync::PoisonError::into_inner);
8083    state.dispatches.clear();
8084    for entry in state.entries.values_mut() {
8085      entry.pending = None;
8086    }
8087  }
8088}
8089
8090static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8091  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8092
8093pub fn register_current_thread_task_driver(
8094  driver: Arc<dyn CurrentThreadTaskDriver>,
8095) -> CurrentThreadTaskDriverId {
8096  CURRENT_THREAD_TASK_DRIVERS.register(driver)
8097}
8098
8099/// Request service for work that may have accumulated while no live host
8100/// dispatcher was registered. A new host receives the existing internal
8101/// dispatch capability through a fresh registration-scoped delivery token;
8102/// already accepting hosts keep their attempts.
8103pub fn request_current_thread_task_drain() {
8104  RUNTIME.request_current_thread_drain();
8105}
8106
8107pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8108  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8109}
8110
8111fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8112  let CurrentThreadTaskDispatchCompletion {
8113    result,
8114    failed_dispatches,
8115  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8116  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8117    failed_dispatches,
8118    redispatch: false,
8119  });
8120  result
8121}
8122
8123pub type TimerId = u64;
8124
8125/// Process-global id source for [`Sleep`] futures (both drivers).
8126static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8127
8128/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
8129/// helper thread on a threadless build, so it delegates
8130/// `sleep_until` to the host event loop through this trait. The Node binding
8131/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
8132/// at import -- one per importing napi env (main thread AND workers).
8133pub trait TimerDriver: Send + Sync + 'static {
8134  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
8135  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
8136  /// waker refresh, not a second timer.
8137  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8138  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
8139  /// raced ahead is acceptable (the woken task observes the sleep completed).
8140  fn cancel(&self, id: TimerId);
8141  /// Whether this driver can still deliver wakes. `false` once the driver's
8142  /// host is gone (the Node binding's driver dies with its owning napi env:
8143  /// worker exit aborts the weak threadsafe function). Selection skips and
8144  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
8145  fn is_live(&self) -> bool {
8146    true
8147  }
8148  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
8149  /// entries (its `is_live` turned false), with NO registry lock held.
8150  /// Implementations with pending-waker bookkeeping must wake everything
8151  /// armed on them here, so those sleeps re-poll onto the next live
8152  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
8153  /// host (before its env-cleanup hook or any call failure runs), and a
8154  /// silent `retain` would strand sleeps whose only wake source was the swept
8155  /// driver. Idempotent with the owner's other eviction paths. Default no-op
8156  /// for drivers without waker state.
8157  fn on_swept(&self) {}
8158}
8159
8160/// Handle to one registration in a [`TimerDriverRegistry`], returned by
8161/// [`TimerDriverRegistry::register`] and consumed by
8162/// [`TimerDriverRegistry::unregister`].
8163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8164pub struct TimerDriverId(u64);
8165
8166type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8167
8168struct TimerDriverEntry {
8169  driver: Arc<dyn TimerDriver>,
8170  previous: Option<TimerDriverId>,
8171  next: Option<TimerDriverId>,
8172}
8173
8174#[derive(Default)]
8175struct TimerDriverRegistryState {
8176  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8177  oldest: Option<TimerDriverId>,
8178  newest: Option<TimerDriverId>,
8179}
8180
8181impl TimerDriverRegistryState {
8182  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8183    let previous = self.newest;
8184    let replaced = self.entries.insert(
8185      id,
8186      TimerDriverEntry {
8187        driver,
8188        previous,
8189        next: None,
8190      },
8191    );
8192    assert!(replaced.is_none(), "timer driver id must be unique");
8193    if let Some(previous) = previous {
8194      self
8195        .entries
8196        .get_mut(&previous)
8197        .expect("the newest timer driver must remain registered")
8198        .next = Some(id);
8199    } else {
8200      self.oldest = Some(id);
8201    }
8202    self.newest = Some(id);
8203  }
8204
8205  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8206    let entry = self.entries.remove(&id)?;
8207    if let Some(previous) = entry.previous {
8208      self
8209        .entries
8210        .get_mut(&previous)
8211        .expect("the previous timer driver must remain registered")
8212        .next = entry.next;
8213    } else {
8214      self.oldest = entry.next;
8215    }
8216    if let Some(next) = entry.next {
8217      self
8218        .entries
8219        .get_mut(&next)
8220        .expect("the next timer driver must remain registered")
8221        .previous = entry.previous;
8222    } else {
8223      self.newest = entry.previous;
8224    }
8225    Some(entry)
8226  }
8227
8228  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8229    let mut snapshot = Vec::with_capacity(self.entries.len());
8230    let mut current = self.oldest;
8231    while let Some(id) = current {
8232      let entry = self
8233        .entries
8234        .get(&id)
8235        .expect("timer driver order must reference a live entry");
8236      snapshot.push((id, Arc::clone(&entry.driver)));
8237      current = entry.next;
8238    }
8239    debug_assert_eq!(
8240      snapshot.len(),
8241      self.entries.len(),
8242      "timer driver order must contain every entry exactly once"
8243    );
8244    snapshot
8245  }
8246
8247  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8248    if self.entries.len() != expected.len() {
8249      return false;
8250    }
8251    let mut current = self.oldest;
8252    for expected in expected {
8253      if current != Some(*expected) {
8254        return false;
8255      }
8256      current = self
8257        .entries
8258        .get(expected)
8259        .expect("timer driver order must reference a live entry")
8260        .next;
8261    }
8262    current.is_none()
8263  }
8264}
8265
8266/// Host timer drivers keyed by registration order. A driver is owned by a host
8267/// context that can die independently
8268/// of the process -- the Node binding registers one driver per importing napi
8269/// env, and a worker's env teardown kills its driver's threadsafe function. A
8270/// single first-wins slot is therefore unsound: a worker that imports the
8271/// binding first and then exits would permanently shadow the slot with a dead
8272/// driver, and every CT timer armed afterwards would busy-fail against it.
8273/// Selecting only the newest live driver is also
8274/// insufficient: a live worker can be CPU-blocked indefinitely while another
8275/// environment remains responsive. The registry therefore keeps all
8276/// registrants and returns a stable snapshot of every live driver at each
8277/// [`Sleep`] poll, sweeping dead entries out as it goes.
8278///
8279/// Instance-based (not a hidden global) so tests exercise
8280/// registration/eviction/selection against local registries; production uses
8281/// the process-global one behind
8282/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
8283#[derive(Default)]
8284pub struct TimerDriverRegistry {
8285  /// Keyed lookup with a linked registration order. Exact unregister and a
8286  /// swept binding host's idempotent unregister are expected O(1), while
8287  /// snapshots keep deterministic oldest-to-newest fallback order.
8288  state: Mutex<TimerDriverRegistryState>,
8289  /// Stable repoll signals for every currently polled host sleep. A newly
8290  /// registered driver snapshots these after publishing itself, then wakes
8291  /// them outside both registry locks so each sleep re-arms the same timer id
8292  /// on the expanded all-live driver set.
8293  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8294  next_id: AtomicU64,
8295}
8296
8297impl Drop for TimerDriverRegistry {
8298  fn drop(&mut self) {
8299    let state = self
8300      .state
8301      .get_mut()
8302      .unwrap_or_else(std::sync::PoisonError::into_inner);
8303    state.oldest = None;
8304    state.newest = None;
8305    let entries = std::mem::take(&mut state.entries);
8306    for (_, entry) in entries {
8307      let _ = catch_unwind_contained(|| drop(entry));
8308    }
8309  }
8310}
8311
8312impl TimerDriverRegistry {
8313  /// Add `driver` as the newest registrant and return its handle.
8314  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8315    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
8316    let id = TimerDriverId(id);
8317    self
8318      .state
8319      .lock()
8320      .unwrap_or_else(std::sync::PoisonError::into_inner)
8321      .insert_driver(id, driver);
8322    // Publish the driver before snapshotting pending sleeps. A sleep publishes
8323    // its repoll signal before selecting live drivers. Therefore either this
8324    // snapshot sees the sleep, or that sleep's subsequent selection sees this
8325    // driver; no registration/insertion interleaving can miss both sides.
8326    let pending = {
8327      let mut entries = self
8328        .pending
8329        .lock()
8330        .unwrap_or_else(std::sync::PoisonError::into_inner);
8331      let mut pending = Vec::with_capacity(entries.len());
8332      entries.retain(|_, repoll| {
8333        if let Some(repoll) = repoll.upgrade() {
8334          pending.push(repoll);
8335          true
8336        } else {
8337          false
8338        }
8339      });
8340      pending
8341    };
8342    for repoll in pending {
8343      repoll.wake();
8344    }
8345    id
8346  }
8347
8348  /// Drop the registration behind `id` (no-op when already swept/removed).
8349  pub fn unregister(&self, id: TimerDriverId) {
8350    let removed = self
8351      .state
8352      .lock()
8353      .unwrap_or_else(std::sync::PoisonError::into_inner)
8354      .remove_driver(id);
8355    // A driver's destructor may re-enter this registry. Keep it outside the
8356    // entries lock just like is_live/on_swept callbacks.
8357    let _ = catch_unwind_contained(|| drop(removed));
8358  }
8359
8360  /// Every LIVE driver in registration order, sweeping dead entries out as a
8361  /// side effect.
8362  ///
8363  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
8364  /// lock held. `is_live` is externally implemented and may re-enter this
8365  /// registry; `on_swept` does so in the binding by calling `unregister` and
8366  /// waking pending sleeps whose re-polls call `current()` again. Selection
8367  /// therefore probes a snapshot, removes dead entries under the lock, then
8368  /// runs hooks after release. A concurrent registration/unregistration can
8369  /// stale the snapshot, in which case selection retries.
8370  fn live(&self) -> HostTimerDriverSnapshot {
8371    loop {
8372      let snapshot = HostTimerDriverSnapshot::new({
8373        let state = self
8374          .state
8375          .lock()
8376          .unwrap_or_else(std::sync::PoisonError::into_inner);
8377        state.ordered_snapshot()
8378      });
8379      let mut dead_ids = FxHashSet::default();
8380      let mut live_ids = Vec::new();
8381      for (id, driver) in snapshot.as_slice() {
8382        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8383          live_ids.push(*id);
8384        } else {
8385          dead_ids.insert(*id);
8386        }
8387      }
8388
8389      let (live, swept, retry) = {
8390        let mut state = self
8391          .state
8392          .lock()
8393          .unwrap_or_else(std::sync::PoisonError::into_inner);
8394        let mut swept = Vec::new();
8395        let mut current = state.oldest;
8396        while let Some(id) = current {
8397          let next = state
8398            .entries
8399            .get(&id)
8400            .expect("timer driver order must reference a live entry")
8401            .next;
8402          if dead_ids.contains(&id)
8403            && let Some(entry) = state.remove_driver(id)
8404          {
8405            swept.push(entry);
8406          }
8407          current = next;
8408        }
8409        let retry = !state.ordered_ids_match(&live_ids);
8410        let live = if retry {
8411          Vec::new()
8412        } else {
8413          state.ordered_snapshot()
8414        };
8415        (HostTimerDriverSnapshot::new(live), swept, retry)
8416      };
8417
8418      // A reentrant probe can unregister multiple snapshot-only drivers.
8419      // See the implementation notes in this crate's README.
8420      drop(snapshot);
8421      for entry in swept {
8422        let _ = catch_unwind_contained(|| entry.driver.on_swept());
8423        let _ = catch_unwind_contained(|| drop(entry));
8424      }
8425      if !retry {
8426        return live;
8427      }
8428    }
8429  }
8430
8431  #[cfg(test)]
8432  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
8433    let mut live = self.live();
8434    live.pop().map(|(id, driver)| (id, driver.into_inner()))
8435  }
8436
8437  /// Whether a LIVE driver is currently registered (dead-only counts as no).
8438  pub fn has_live_driver(&self) -> bool {
8439    !self.live().is_empty()
8440  }
8441
8442  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
8443    let replaced = self
8444      .pending
8445      .lock()
8446      .unwrap_or_else(std::sync::PoisonError::into_inner)
8447      .insert(id, Arc::downgrade(repoll));
8448    assert!(
8449      replaced.is_none(),
8450      "host timer {id} registered its repoll signal twice"
8451    );
8452    // Shutdown can close and try to remove the signal immediately before this
8453    // insertion. The post-insert check is the other side of that race.
8454    if repoll.is_closed() {
8455      self.remove_pending(id);
8456    }
8457  }
8458
8459  fn remove_pending(&self, id: TimerId) {
8460    let removed = self
8461      .pending
8462      .lock()
8463      .unwrap_or_else(std::sync::PoisonError::into_inner)
8464      .remove(&id);
8465    let _ = catch_unwind_contained(|| drop(removed));
8466  }
8467
8468  #[cfg(test)]
8469  fn pending_len(&self) -> usize {
8470    self
8471      .pending
8472      .lock()
8473      .unwrap_or_else(std::sync::PoisonError::into_inner)
8474      .len()
8475  }
8476}
8477
8478/// The process-global registry behind [`sleep_until`] (see
8479/// [`TimerDriverRegistry`] for the lifetime story).
8480static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
8481  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
8482
8483/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
8484/// Every host context that can serve timers registers its own driver (the
8485/// Node binding: one per importing napi env); every LIVE registrant receives
8486/// each timer so a starved environment cannot own its only wake source.
8487/// Returns the handle for [`unregister_timer_driver`].
8488pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8489  TIMER_DRIVERS.register(driver)
8490}
8491
8492/// Remove a driver registered via [`register_timer_driver`] -- called when
8493/// its host context dies (the Node binding evicts on env teardown and on
8494/// callback failure). Idempotent.
8495pub fn unregister_timer_driver(id: TimerDriverId) {
8496  TIMER_DRIVERS.unregister(id);
8497}
8498
8499/// Whether a LIVE host timer driver is currently registered. Lets the
8500/// embedder report CurrentThread timer availability honestly (e.g. through a
8501/// capabilities export) instead of guessing: with no
8502/// live driver a CurrentThread `sleep_until` would panic, so the capability
8503/// must read `false` -- including when the only registrants are DEAD (their
8504/// owning envs torn down), not merely when none ever registered.
8505pub fn has_live_timer_driver() -> bool {
8506  TIMER_DRIVERS.has_live_driver()
8507}
8508
8509/// Coalescing wake signal used only to make an already-pending [`Sleep`]
8510/// re-select host drivers after a new registration. The task's arbitrary
8511/// waker is never cloned, invoked, or dropped while this mutex is held.
8512#[derive(Default)]
8513struct HostTimerRepoll {
8514  state: Mutex<HostTimerRepollState>,
8515}
8516
8517#[derive(Default)]
8518struct HostTimerRepollState {
8519  closed: bool,
8520  notified: bool,
8521  waker: Option<Waker>,
8522}
8523
8524impl HostTimerRepoll {
8525  fn register(&self, waker: Waker) {
8526    let (replaced, wake) = {
8527      let mut state = self
8528        .state
8529        .lock()
8530        .unwrap_or_else(std::sync::PoisonError::into_inner);
8531      if state.closed {
8532        (Some(waker), None)
8533      } else if state.notified {
8534        state.notified = false;
8535        (state.waker.take(), Some(waker))
8536      } else {
8537        (state.waker.replace(waker), None)
8538      }
8539    };
8540    let _ = catch_unwind_contained(|| drop(replaced));
8541    if let Some(waker) = wake {
8542      wake_waker_contained(waker);
8543    }
8544  }
8545
8546  fn wake(&self) {
8547    let waker = {
8548      let mut state = self
8549        .state
8550        .lock()
8551        .unwrap_or_else(std::sync::PoisonError::into_inner);
8552      if state.closed {
8553        return;
8554      }
8555      if let Some(waker) = state.waker.take() {
8556        Some(waker)
8557      } else {
8558        state.notified = true;
8559        None
8560      }
8561    };
8562    if let Some(waker) = waker {
8563      wake_waker_contained(waker);
8564    }
8565  }
8566
8567  fn close(&self) {
8568    let waker = {
8569      let mut state = self
8570        .state
8571        .lock()
8572        .unwrap_or_else(std::sync::PoisonError::into_inner);
8573      state.closed = true;
8574      state.notified = false;
8575      state.waker.take()
8576    };
8577    let _ = catch_unwind_contained(|| drop(waker));
8578  }
8579
8580  fn is_closed(&self) -> bool {
8581    self
8582      .state
8583      .lock()
8584      .unwrap_or_else(std::sync::PoisonError::into_inner)
8585      .closed
8586  }
8587}
8588
8589/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
8590/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
8591/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
8592/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
8593/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
8594/// detection -- see the interaction notes at the top of this section.
8595#[derive(Default)]
8596struct HostTimerRegistry {
8597  state: Mutex<HostTimerRegistryState>,
8598}
8599
8600#[derive(Default)]
8601struct HostTimerRegistryState {
8602  closed: bool,
8603  timers: FxHashMap<TimerId, HostTimerEntry>,
8604}
8605
8606struct HostTimerEntry {
8607  deadline: Instant,
8608  waker: Waker,
8609  fired: Arc<AtomicBool>,
8610  drivers: Weak<TimerDriverRegistry>,
8611  repoll: Arc<HostTimerRepoll>,
8612}
8613
8614impl HostTimerRegistry {
8615  fn register(
8616    &self,
8617    id: TimerId,
8618    deadline: Instant,
8619    waker: Waker,
8620    fired: &Arc<AtomicBool>,
8621    drivers: &Arc<TimerDriverRegistry>,
8622    repoll: &Arc<HostTimerRepoll>,
8623  ) -> bool {
8624    let replaced = {
8625      let mut state = self
8626        .state
8627        .lock()
8628        .unwrap_or_else(std::sync::PoisonError::into_inner);
8629      if state.closed || fired.load(Ordering::SeqCst) {
8630        fired.store(true, Ordering::SeqCst);
8631        Err(waker)
8632      } else {
8633        Ok(state.timers.insert(
8634          id,
8635          HostTimerEntry {
8636            deadline,
8637            waker,
8638            fired: Arc::clone(fired),
8639            drivers: Arc::downgrade(drivers),
8640            repoll: Arc::clone(repoll),
8641          },
8642        ))
8643      }
8644    };
8645    match replaced {
8646      Ok(Some(entry)) => {
8647        let _ = catch_unwind_contained(|| drop(entry));
8648        true
8649      }
8650      Ok(None) => true,
8651      Err(waker) => {
8652        let _ = catch_unwind_contained(|| drop(waker));
8653        false
8654      }
8655    }
8656  }
8657
8658  fn remove(&self, id: TimerId) {
8659    let removed = self
8660      .state
8661      .lock()
8662      .unwrap_or_else(std::sync::PoisonError::into_inner)
8663      .timers
8664      .remove(&id);
8665    if let Some(entry) = removed {
8666      let _ = catch_unwind_contained(|| drop(entry));
8667    }
8668  }
8669
8670  fn shutdown(&self) {
8671    let timers = {
8672      let mut state = self
8673        .state
8674        .lock()
8675        .unwrap_or_else(std::sync::PoisonError::into_inner);
8676      state.closed = true;
8677      state
8678        .timers
8679        .drain()
8680        .map(|(id, entry)| {
8681          entry.fired.store(true, Ordering::SeqCst);
8682          (id, entry)
8683        })
8684        .collect::<Vec<_>>()
8685    };
8686    for (id, entry) in timers {
8687      entry.repoll.close();
8688      if let Some(drivers) = entry.drivers.upgrade() {
8689        drivers.remove_pending(id);
8690      }
8691      wake_waker_contained(entry.waker);
8692    }
8693  }
8694
8695  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
8696  /// only to pick the diagnostic variant: on a threadless build the park is
8697  /// provably dead either way (the host relay shares the parked thread), but
8698  /// a pending timer means the timer-backed message applies.
8699  fn has_pending(&self) -> bool {
8700    !self
8701      .state
8702      .lock()
8703      .unwrap_or_else(std::sync::PoisonError::into_inner)
8704      .timers
8705      .is_empty()
8706  }
8707
8708  /// Any host timer whose deadline (plus `grace` for host firing lag) is
8709  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
8710  /// timer with zero progress is legitimate. A timer past due by more than
8711  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
8712  /// signature the deadline detection exists to catch, so it must not keep
8713  /// suppressing the panic. Callers pass the armed park deadline as `grace`
8714  /// (the detector's own granularity: firing lag within one window is
8715  /// indistinguishable from an expiry racing the fire).
8716  fn has_live_wait(&self, grace: Duration) -> bool {
8717    let now = Instant::now();
8718    self
8719      .state
8720      .lock()
8721      .unwrap_or_else(std::sync::PoisonError::into_inner)
8722      .timers
8723      .values()
8724      .any(|entry| {
8725        entry
8726          .deadline
8727          .checked_add(grace)
8728          .is_none_or(|deadline| deadline > now)
8729      })
8730  }
8731}
8732
8733/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
8734/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
8735/// arm) both retire the wake-token exactly once.
8736struct HostTimerPendingGuard {
8737  registry: Arc<HostTimerRegistry>,
8738  drivers: Arc<TimerDriverRegistry>,
8739  repoll: Arc<HostTimerRepoll>,
8740  id: TimerId,
8741}
8742
8743impl Drop for HostTimerPendingGuard {
8744  fn drop(&mut self) {
8745    self.repoll.close();
8746    self.registry.remove(self.id);
8747    self.drivers.remove_pending(self.id);
8748  }
8749}
8750
8751/// Runtime-owned timer heap for the MultiThread executor.
8752///
8753/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
8754/// Rayon and therefore cannot consume one of the configured CPU workers while
8755/// a long timer is armed.
8756#[cfg(not(target_family = "wasm"))]
8757struct TimerHeap {
8758  inner: Arc<Mutex<TimerHeapInner>>,
8759  parker: Arc<DriverParker>,
8760  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
8761  deadlock_state: Arc<MultiThreadDeadlockState>,
8762  #[cfg(test)]
8763  fail_next_start: AtomicBool,
8764  generation: u64,
8765  executor_id: u64,
8766  thread_name: String,
8767}
8768
8769#[cfg(not(target_family = "wasm"))]
8770#[derive(Default)]
8771struct TimerHeapInner {
8772  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
8773  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
8774  /// one later unbounded cleanup pass.
8775  queue: TimerDeadlineQueue,
8776  entries: FxHashMap<TimerId, HeapTimerEntry>,
8777  /// The parker of the dedicated timekeeper while it is waiting on a timer.
8778  /// `register_timer` unparks it when the earliest deadline moves up, so the
8779  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
8780  timekeeper_parker: Option<Arc<DriverParker>>,
8781  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
8782  /// later registrations must fire immediately (a `Sleep` polled after
8783  /// shutdown resolves early instead of parking a closing runtime forever).
8784  closed: bool,
8785}
8786
8787#[cfg(not(target_family = "wasm"))]
8788struct HeapTimerEntry {
8789  waker: Waker,
8790  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
8791  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
8792  /// even when the wall clock has not reached `deadline` (shutdown
8793  /// drain-fire).
8794  fired: Arc<AtomicBool>,
8795}
8796
8797#[cfg(not(target_family = "wasm"))]
8798#[derive(Default)]
8799struct TimerDeadlineQueue {
8800  nodes: Vec<(Instant, TimerId)>,
8801  positions: FxHashMap<TimerId, usize>,
8802}
8803
8804#[cfg(not(target_family = "wasm"))]
8805impl TimerDeadlineQueue {
8806  #[cfg(test)]
8807  fn len(&self) -> usize {
8808    self.nodes.len()
8809  }
8810
8811  #[cfg(test)]
8812  fn is_empty(&self) -> bool {
8813    self.nodes.is_empty()
8814  }
8815
8816  fn clear(&mut self) {
8817    self.nodes.clear();
8818    self.positions.clear();
8819  }
8820
8821  fn peek(&self) -> Option<(Instant, TimerId)> {
8822    self.nodes.first().copied()
8823  }
8824
8825  fn push(&mut self, deadline: Instant, id: TimerId) {
8826    let index = self.nodes.len();
8827    assert!(
8828      self.positions.insert(id, index).is_none(),
8829      "timer deadline queue received duplicate id {id}"
8830    );
8831    self.nodes.push((deadline, id));
8832    self.sift_up(index);
8833  }
8834
8835  fn pop(&mut self) -> Option<(Instant, TimerId)> {
8836    let first = self.nodes.first().copied()?;
8837    self.positions.remove(&first.1);
8838    let last = self
8839      .nodes
8840      .pop()
8841      .expect("the non-empty timer heap must have a final node");
8842    if !self.nodes.is_empty() {
8843      self.nodes[0] = last;
8844      self.positions.insert(last.1, 0);
8845      self.sift_down(0);
8846    }
8847    Some(first)
8848  }
8849
8850  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
8851    let index = self.positions.remove(&id)?;
8852    let removed = self.nodes[index];
8853    let last = self.nodes.pop().expect("an indexed timer node must exist");
8854    if index < self.nodes.len() {
8855      self.nodes[index] = last;
8856      self.positions.insert(last.1, index);
8857      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
8858        self.sift_up(index);
8859      } else {
8860        self.sift_down(index);
8861      }
8862    }
8863    Some(removed)
8864  }
8865
8866  fn sift_up(&mut self, mut index: usize) {
8867    while index > 0 {
8868      let parent = (index - 1) / 2;
8869      if self.nodes[parent] <= self.nodes[index] {
8870        break;
8871      }
8872      self.swap(parent, index);
8873      index = parent;
8874    }
8875  }
8876
8877  fn sift_down(&mut self, mut index: usize) {
8878    loop {
8879      let left = index * 2 + 1;
8880      if left >= self.nodes.len() {
8881        return;
8882      }
8883      let right = left + 1;
8884      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
8885        right
8886      } else {
8887        left
8888      };
8889      if self.nodes[index] <= self.nodes[child] {
8890        return;
8891      }
8892      self.swap(index, child);
8893      index = child;
8894    }
8895  }
8896
8897  fn swap(&mut self, left: usize, right: usize) {
8898    self.nodes.swap(left, right);
8899    self.positions.insert(self.nodes[left].1, left);
8900    self.positions.insert(self.nodes[right].1, right);
8901  }
8902}
8903
8904#[cfg(not(target_family = "wasm"))]
8905impl TimerHeapInner {
8906  fn next_deadline(&self) -> Option<Instant> {
8907    self.queue.peek().map(|(deadline, _)| deadline)
8908  }
8909}
8910
8911#[cfg(not(target_family = "wasm"))]
8912impl TimerHeap {
8913  fn new(
8914    generation: u64,
8915    executor_id: u64,
8916    thread_name: String,
8917    deadlock_state: Arc<MultiThreadDeadlockState>,
8918  ) -> Self {
8919    Self {
8920      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
8921      parker: Arc::new(DriverParker::default()),
8922      thread: Mutex::new(None),
8923      deadlock_state,
8924      #[cfg(test)]
8925      fail_next_start: AtomicBool::new(false),
8926      generation,
8927      executor_id,
8928      thread_name,
8929    }
8930  }
8931
8932  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
8933    if self
8934      .inner
8935      .lock()
8936      .unwrap_or_else(std::sync::PoisonError::into_inner)
8937      .closed
8938    {
8939      return Ok(());
8940    }
8941    let current_thread = std::thread::current().id();
8942    loop {
8943      let finished = {
8944        let mut thread = self
8945          .thread
8946          .lock()
8947          .unwrap_or_else(std::sync::PoisonError::into_inner);
8948        match thread.as_ref() {
8949          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
8950          Some(handle) if !handle.is_finished() => return Ok(()),
8951          Some(_) => thread.take(),
8952          None => {
8953            // Shutdown can close the heap after the optimistic check above.
8954            // Recheck before publishing another service thread.
8955            if self
8956              .inner
8957              .lock()
8958              .unwrap_or_else(std::sync::PoisonError::into_inner)
8959              .closed
8960            {
8961              return Ok(());
8962            }
8963            #[cfg(test)]
8964            if self.fail_next_start.swap(false, Ordering::SeqCst) {
8965              return Err(RuntimeConfigError(
8966                "injected runtime timer thread creation failure".to_string(),
8967              ));
8968            }
8969
8970            let inner = Arc::clone(&self.inner);
8971            let parker = Arc::clone(&self.parker);
8972            let deadlock_state = Arc::clone(&self.deadlock_state);
8973            let generation = self.generation;
8974            let executor_id = self.executor_id;
8975            let handle = std::thread::Builder::new()
8976              .name(self.thread_name.clone())
8977              .spawn(move || {
8978                // Keep the identity through OS-thread TLS teardown. Unlike
8979                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
8980                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
8981                let _generation = RuntimeGenerationGuard::enter(generation);
8982                timer_timekeeper_main(&inner, &parker, &deadlock_state);
8983              })
8984              .map_err(|error| {
8985                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
8986              })?;
8987            *thread = Some(handle);
8988            return Ok(());
8989          }
8990        }
8991      };
8992
8993      // A finished timer thread may still be running TLS destructors that
8994      // re-enter timer code. Never hold the handle mutex while waiting for
8995      // those destructors, and never attempt to join this thread from itself.
8996      if let Some(finished) = finished
8997        && finished.thread().id() != current_thread
8998        && let Err(payload) = finished.join()
8999      {
9000        discard_panic_payload(payload);
9001      }
9002      if self
9003        .inner
9004        .lock()
9005        .unwrap_or_else(std::sync::PoisonError::into_inner)
9006        .closed
9007      {
9008        return Ok(());
9009      }
9010    }
9011  }
9012
9013  fn close(&self) -> Vec<Waker> {
9014    let mut inner = self
9015      .inner
9016      .lock()
9017      .unwrap_or_else(std::sync::PoisonError::into_inner);
9018    inner.closed = true;
9019    inner.queue.clear();
9020    inner.timekeeper_parker = None;
9021    inner
9022      .entries
9023      .drain()
9024      .map(|(_, entry)| {
9025        entry.fired.store(true, Ordering::SeqCst);
9026        entry.waker
9027      })
9028      .collect()
9029  }
9030
9031  fn join(&self) {
9032    let handle = self
9033      .thread
9034      .lock()
9035      .unwrap_or_else(std::sync::PoisonError::into_inner)
9036      .take();
9037    if let Some(handle) = handle {
9038      if handle.thread().id() == std::thread::current().id() {
9039        // A custom RawWaker may own the final direct executor Arc and release
9040        // it from this thread. Closing the heap makes the loop exit after the
9041        // callback returns; joining self would deadlock that exit.
9042        drop(handle);
9043        return;
9044      }
9045      if let Err(payload) = handle.join() {
9046        discard_panic_payload(payload);
9047      }
9048    }
9049  }
9050}
9051
9052#[cfg(not(target_family = "wasm"))]
9053impl Drop for TimerHeap {
9054  fn drop(&mut self) {
9055    let mut admission = self.deadlock_state.begin_admission();
9056    let wakers = self.close();
9057    if !wakers.is_empty()
9058      && let Some(admission) = admission.as_mut()
9059    {
9060      admission.mark_published();
9061    }
9062    self.parker.unpark();
9063    for waker in wakers {
9064      wake_waker_contained(waker);
9065    }
9066    drop(admission);
9067    self.join();
9068  }
9069}
9070
9071#[cfg(not(target_family = "wasm"))]
9072fn timer_timekeeper_main(
9073  inner: &Mutex<TimerHeapInner>,
9074  parker: &Arc<DriverParker>,
9075  deadlock_state: &Arc<MultiThreadDeadlockState>,
9076) {
9077  loop {
9078    // Acquire before touching the heap. If a verdict has already closed the
9079    // gate, a due timer remains visible until the verdict's final timer recheck
9080    // causes that guard to reopen.
9081    let mut admission = deadlock_state.begin_admission();
9082    let (due, deadline) = {
9083      let mut inner = inner
9084        .lock()
9085        .unwrap_or_else(std::sync::PoisonError::into_inner);
9086      if inner.closed {
9087        inner.timekeeper_parker = None;
9088        return;
9089      }
9090      let now = Instant::now();
9091      let mut due = Vec::new();
9092      while let Some((deadline, id)) = inner.queue.peek() {
9093        if deadline > now {
9094          break;
9095        }
9096        inner.queue.pop();
9097        if let Some(entry) = inner.entries.remove(&id) {
9098          entry.fired.store(true, Ordering::SeqCst);
9099          due.push(entry.waker);
9100        }
9101      }
9102      let deadline = inner.next_deadline();
9103      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9104      (due, deadline)
9105    };
9106
9107    if !due.is_empty() {
9108      if let Some(admission) = admission.as_mut() {
9109        admission.mark_published();
9110      }
9111      for waker in due {
9112        wake_waker_contained(waker);
9113      }
9114      continue;
9115    }
9116    // Never retain an admission while sleeping: it protects only the
9117    // remove-then-wake publication window.
9118    drop(admission);
9119
9120    match deadline {
9121      Some(deadline) => {
9122        let now = Instant::now();
9123        if deadline > now {
9124          parker.park_timeout(deadline - now);
9125        }
9126      }
9127      None => parker.park(),
9128    }
9129  }
9130}
9131
9132#[cfg(not(target_family = "wasm"))]
9133impl MultiThreadExecutor {
9134  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
9135  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
9136  /// shutdown, and set here immediately when the heap is already closed (the
9137  /// caller re-checks it after registering, so a post-shutdown poll resolves
9138  /// instead of parking forever).
9139  fn register_timer(
9140    self: &Arc<Self>,
9141    id: TimerId,
9142    deadline: Instant,
9143    waker: Waker,
9144    fired: &Arc<AtomicBool>,
9145  ) {
9146    let mut admission = self.begin_deadlock_admission();
9147    #[cfg(test)]
9148    run_timer_registration_admission_test_hook();
9149    // Establish the service thread before publishing the entry. If thread
9150    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
9151    // heap state exists for Drop to miss.
9152    if let Err(error) = self.timers.ensure_started() {
9153      let _ = catch_unwind_contained(|| drop(waker));
9154      panic!("{error}");
9155    }
9156    let (became_earliest, published, discarded_waker) = {
9157      let mut inner = self
9158        .timers
9159        .inner
9160        .lock()
9161        .unwrap_or_else(std::sync::PoisonError::into_inner);
9162      if inner.closed || fired.load(Ordering::SeqCst) {
9163        fired.store(true, Ordering::SeqCst);
9164        (false, false, Some(waker))
9165      } else if let Some(entry) = inner.entries.get_mut(&id) {
9166        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
9167        (
9168          false,
9169          false,
9170          Some(std::mem::replace(&mut entry.waker, waker)),
9171        )
9172      } else {
9173        let previous = inner.next_deadline();
9174        inner.entries.insert(
9175          id,
9176          HeapTimerEntry {
9177            waker,
9178            fired: Arc::clone(fired),
9179          },
9180        );
9181        inner.queue.push(deadline, id);
9182        (
9183          previous.is_none_or(|previous| deadline < previous),
9184          true,
9185          None,
9186        )
9187      }
9188    };
9189    if published && let Some(admission) = admission.as_mut() {
9190      admission.mark_published();
9191    }
9192    if let Some(waker) = discarded_waker {
9193      // RawWaker destruction is user code and may re-enter timer cancellation.
9194      // Keep it outside the heap mutex and contain hostile destructors.
9195      let _ = catch_unwind_contained(|| drop(waker));
9196    }
9197    if became_earliest {
9198      // Re-arm the lazy service thread and any cooperative timer wait.
9199      self.note_earlier_timer();
9200    }
9201  }
9202
9203  /// Best-effort cancel: remove the entry and its indexed heap node.
9204  /// If cancellation changes the earliest deadline, wake every timer-bounded
9205  /// driver so no pool worker remains parked on the stale deadline.
9206  fn cancel_timer(&self, id: TimerId) {
9207    let (removed, earliest_changed) = {
9208      let mut inner = self
9209        .timers
9210        .inner
9211        .lock()
9212        .unwrap_or_else(std::sync::PoisonError::into_inner);
9213      let previous = inner.next_deadline();
9214      let removed = inner.entries.remove(&id);
9215      if removed.is_some() {
9216        let queued = inner.queue.remove(id);
9217        debug_assert!(
9218          queued.is_some(),
9219          "a live timer entry must have one indexed deadline node"
9220        );
9221      }
9222      let current = inner.next_deadline();
9223      (removed, previous != current)
9224    };
9225    if let Some(entry) = removed {
9226      // A RawWaker destructor may cancel another timer. Never run it while
9227      // holding the heap lock.
9228      let _ = catch_unwind_contained(|| drop(entry));
9229    }
9230    if earliest_changed {
9231      self.timers.parker.unpark();
9232      self.parked_drivers.wake_all();
9233    }
9234  }
9235
9236  /// Earliest pending deadline (None when the heap is empty or closed).
9237  fn next_timer_deadline(&self) -> Option<Instant> {
9238    self
9239      .timers
9240      .inner
9241      .lock()
9242      .unwrap_or_else(std::sync::PoisonError::into_inner)
9243      .next_deadline()
9244  }
9245
9246  /// Pop and wake everything due at/before now. Wakers are collected under
9247  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
9248  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
9249  /// the lock, before the wake, so the woken poll observes it.
9250  fn fire_due_timers(&self) {
9251    let mut admission = self.begin_deadlock_admission();
9252    let due: Vec<Waker> = {
9253      let mut inner = self
9254        .timers
9255        .inner
9256        .lock()
9257        .unwrap_or_else(std::sync::PoisonError::into_inner);
9258      let now = Instant::now();
9259      let mut due = Vec::new();
9260      while let Some((deadline, id)) = inner.queue.peek() {
9261        if deadline > now {
9262          break;
9263        }
9264        inner.queue.pop();
9265        if let Some(entry) = inner.entries.remove(&id) {
9266          entry.fired.store(true, Ordering::SeqCst);
9267          due.push(entry.waker);
9268        }
9269      }
9270      due
9271    };
9272    if !due.is_empty()
9273      && let Some(admission) = admission.as_mut()
9274    {
9275      admission.mark_published();
9276    }
9277    for waker in due {
9278      // Wakers may originate outside this executor. Isolate user RawWaker
9279      // implementations so a panic cannot unwind the dedicated timekeeper.
9280      wake_waker_contained(waker);
9281    }
9282  }
9283
9284  /// The earliest pending deadline moved up (or the heap went non-empty).
9285  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
9286  /// whose current park may still reflect the previous deadline.
9287  fn note_earlier_timer(self: &Arc<Self>) {
9288    self.timers.parker.unpark();
9289    self.parked_drivers.wake_one();
9290  }
9291
9292  /// Drain-fire every pending heap timer and poison later registrations.
9293  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
9294  /// sleeps resolve on their next poll; the woken tasks are
9295  /// scheduled through the normal path, so a coordinator awaiting a debounce
9296  /// completes its close sequence instead of parking forever.
9297  fn shutdown_timers(&self) {
9298    let mut admission = self.begin_deadlock_admission();
9299    let wakers = self.timers.close();
9300    if !wakers.is_empty()
9301      && let Some(admission) = admission.as_mut()
9302    {
9303      admission.mark_published();
9304    }
9305    for waker in wakers {
9306      // A user-provided RawWaker may panic. Shutdown is a lifecycle
9307      // transition, so one hostile wake must not leave the controller stuck
9308      // in `Stopping` and prevent every later restart.
9309      wake_waker_contained(waker);
9310    }
9311    self.timers.parker.unpark();
9312  }
9313
9314  fn begin_shutdown(&self) {
9315    let _generation = RuntimeGenerationGuard::enter(self.generation);
9316    // Publish stop before taking the blocking queue. The final deadlock verdict
9317    // holds stop publication while rechecking that queue, so queue -> stop
9318    // would invert its lock order. A concurrent taker either claims while stop
9319    // is still false or observes stop after this publication.
9320    self.stop.begin_shutdown();
9321    let queued = {
9322      let mut queue = self
9323        .blocking_queue
9324        .lock()
9325        .unwrap_or_else(std::sync::PoisonError::into_inner);
9326      // The blocking FIFO mutex closes queue admission. `take_blocking` either
9327      // claimed a job before stop publication or observes stop/closure.
9328      queue.closed = true;
9329      queue.take_all()
9330    };
9331    // Captured values belong to user code and may panic in Drop. Retire each
9332    // rejected closure independently so one destructor cannot strand the
9333    // runtime in `Stopping` or prevent the remaining queued jobs from being
9334    // cancelled.
9335    for queued in queued {
9336      let _ = catch_unwind_contained(|| drop(queued));
9337    }
9338    self.shutdown_timers();
9339  }
9340
9341  fn wait_until_scheduler_idle(&self) {
9342    self.timers.join();
9343    let mut idle = self
9344      .scheduler_idle_lock
9345      .lock()
9346      .unwrap_or_else(std::sync::PoisonError::into_inner);
9347    // Register BEFORE the first predicate evaluation and stay registered for
9348    // the whole wait. `finish_draining` reads this count under the same mutex,
9349    // so any drainer that retires after this increment is published either
9350    // sees `> 0` and wakes us, or had already decremented `active_drainers`
9351    // before we read the predicate -- in which case we never sleep.
9352    *idle += 1;
9353    while self.active_drainers.load(Ordering::Acquire) != 0 {
9354      idle = self
9355        .scheduler_idle
9356        .wait(idle)
9357        .unwrap_or_else(std::sync::PoisonError::into_inner);
9358    }
9359    *idle -= 1;
9360  }
9361}
9362
9363/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
9364/// (or early on runtime shutdown drain-fire). Dropping it cancels the
9365/// underlying registration -- the `tokio::select!` losing-arm semantics the
9366/// watch coordinator's debounce loop relies on.
9367pub struct Sleep {
9368  deadline: Instant,
9369  inner: SleepInner,
9370}
9371
9372enum SleepInner {
9373  #[cfg(not(target_family = "wasm"))]
9374  Heap(HeapSleep),
9375  Host(HostSleep),
9376}
9377
9378#[cfg(not(target_family = "wasm"))]
9379struct HeapSleep {
9380  id: TimerId,
9381  executor: Weak<MultiThreadExecutor>,
9382  fired: Arc<AtomicBool>,
9383  registered: bool,
9384}
9385
9386struct HostSleep {
9387  id: TimerId,
9388  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
9389  /// registrant. Arming redundant host timers prevents a live but starved
9390  /// environment from owning the sleep's only wake source.
9391  drivers: Arc<TimerDriverRegistry>,
9392  /// Drivers this sleep last armed on. The first responsive fire wakes the
9393  /// sleep; completion cancels every losing arm, including pre-delivery arms
9394  /// whose binding relay retains a cancellation tombstone.
9395  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9396  /// The executor's registry, held so the FIRST poll can arm the wake-token
9397  /// at the same moment `driver.register` arms the host timer.
9398  registry: Arc<HostTimerRegistry>,
9399  fired: Arc<AtomicBool>,
9400  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
9401  /// request a re-poll without retaining or invoking a task waker under the
9402  /// driver-registry locks.
9403  repoll: Arc<HostTimerRepoll>,
9404  /// Retires this sleep's wake-token in the executor's registry exactly once
9405  /// (created at first poll alongside the host registration, taken on
9406  /// completion, dropped with the Sleep on cancellation). `None` before the
9407  /// first poll: an unpolled Sleep holds no wake-token.
9408  pending: Option<HostTimerPendingGuard>,
9409}
9410
9411fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
9412  let _ = catch_unwind_contained(|| driver.cancel(id));
9413  let _ = catch_unwind_contained(|| drop(driver));
9414}
9415
9416fn cancel_host_timer_drivers(
9417  id: TimerId,
9418  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
9419) {
9420  for (_, driver) in armed.drain() {
9421    cancel_host_timer_driver(id, driver);
9422  }
9423}
9424
9425impl Future for Sleep {
9426  type Output = ();
9427
9428  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
9429    let deadline = self.deadline;
9430    let this = self.get_mut();
9431    match &mut this.inner {
9432      #[cfg(not(target_family = "wasm"))]
9433      SleepInner::Heap(heap) => {
9434        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9435          if heap.registered {
9436            // Wall-clock completion may beat the (busy) timekeeper's fire:
9437            // retire the registration so it cannot wake spuriously later.
9438            if let Some(executor) = heap.executor.upgrade() {
9439              executor.cancel_timer(heap.id);
9440            }
9441            heap.registered = false;
9442          }
9443          return Poll::Ready(());
9444        }
9445        let Some(executor) = heap.executor.upgrade() else {
9446          heap.fired.store(true, Ordering::SeqCst);
9447          heap.registered = false;
9448          return Poll::Ready(());
9449        };
9450        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
9451        heap.registered = true;
9452        // Registration on a closed (shut-down) heap stores nothing and sets
9453        // `fired` instead: re-check so this poll resolves rather than parking
9454        // a closing runtime forever.
9455        if heap.fired.load(Ordering::SeqCst) {
9456          heap.registered = false;
9457          return Poll::Ready(());
9458        }
9459        Poll::Pending
9460      }
9461      SleepInner::Host(host) => {
9462        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
9463          cancel_host_timer_drivers(host.id, &mut host.armed);
9464          host.pending.take();
9465          return Poll::Ready(());
9466        }
9467        // Clone both wakers before publishing either one. If the second clone
9468        // unwinds, destroy the first clone behind its own panic boundary before
9469        // resuming the original panic, so a hostile RawWaker destructor cannot
9470        // turn clone failure into a process-aborting double panic.
9471        let repoll_waker = cx.waker().clone();
9472        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
9473          Ok(waker) => waker,
9474          Err(payload) => {
9475            let _ = catch_unwind_contained(|| drop(repoll_waker));
9476            resume_unwind(payload);
9477          }
9478        };
9479        host.repoll.register(repoll_waker);
9480        if !host.registry.register(
9481          host.id,
9482          deadline,
9483          registry_waker,
9484          &host.fired,
9485          &host.drivers,
9486          &host.repoll,
9487        ) {
9488          cancel_host_timer_drivers(host.id, &mut host.armed);
9489          if host.pending.take().is_none() {
9490            host.repoll.close();
9491          }
9492          return Poll::Ready(());
9493        }
9494        if host.pending.is_none() {
9495          // FIRST poll: the registry entry appears at the same moment the
9496          // host timer is armed below -- and only then. An unpolled Sleep has
9497          // no host callback behind it, so counting it at creation would
9498          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
9499          // mislabel the threadless certain diagnostic as timer-backed. The
9500          // certain panic itself no longer consults pending timers for its
9501          // verdict.
9502          host.drivers.register_pending(host.id, &host.repoll);
9503          host.pending = Some(HostTimerPendingGuard {
9504            registry: Arc::clone(&host.registry),
9505            drivers: Arc::clone(&host.drivers),
9506            repoll: Arc::clone(&host.repoll),
9507            id: host.id,
9508          });
9509        }
9510        // Re-select every LIVE driver on every poll. A live but starved
9511        // environment may accept a TSFN call and then stop servicing its
9512        // event loop, so no single host may own the only timer arm.
9513        let current = host.drivers.live();
9514        assert!(
9515          !current.as_slice().is_empty(),
9516          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
9517           that registered timer drivers (via \
9518           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
9519           one per importing env through `registerTimerHost`) have all been torn down, so this \
9520           `sleep_until` can never be woken."
9521        );
9522        // Publish cancellation ownership for the complete live snapshot before
9523        // invoking any arbitrary callback. An earlier driver may unregister a
9524        // later one and then unwind; both the Sleep and the contained snapshot
9525        // must still own that later driver while the original panic propagates.
9526        // See the implementation notes in this crate's README.
9527        let missing = current
9528          .as_slice()
9529          .iter()
9530          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
9531          .count();
9532        host.armed.reserve(missing);
9533        for (current_id, current_driver) in current.as_slice() {
9534          host
9535            .armed
9536            .entry(*current_id)
9537            .or_insert_with(|| Arc::clone(current_driver));
9538        }
9539        for (current_id, _) in current.as_slice() {
9540          let armed_driver = host
9541            .armed
9542            .get(current_id)
9543            .expect("every live timer driver must have cancellation ownership before callbacks");
9544          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
9545        }
9546        let current_ids = current
9547          .as_slice()
9548          .iter()
9549          .map(|(current_id, _)| *current_id)
9550          .collect::<FxHashSet<_>>();
9551        let retired = host
9552          .armed
9553          .keys()
9554          .filter(|armed_id| !current_ids.contains(armed_id))
9555          .copied()
9556          .collect::<Vec<_>>();
9557        for retired in retired {
9558          let old_driver = host
9559            .armed
9560            .remove(&retired)
9561            .expect("the retired timer driver must remain armed");
9562          cancel_host_timer_driver(host.id, old_driver);
9563        }
9564        Poll::Pending
9565      }
9566    }
9567  }
9568}
9569
9570impl Drop for Sleep {
9571  fn drop(&mut self) {
9572    match &mut self.inner {
9573      #[cfg(not(target_family = "wasm"))]
9574      SleepInner::Heap(heap) => {
9575        if heap.registered
9576          && let Some(executor) = heap.executor.upgrade()
9577        {
9578          executor.cancel_timer(heap.id);
9579        }
9580      }
9581      SleepInner::Host(host) => {
9582        // Close directly as an unwind backstop even when first-poll setup
9583        // failed before `pending` could install its normal cleanup guard.
9584        host.repoll.close();
9585        cancel_host_timer_drivers(host.id, &mut host.armed);
9586        // `pending` (if still held) drops here and retires the wake-token.
9587      }
9588    }
9589  }
9590}
9591
9592/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
9593/// the executor-owned heap; CurrentThread requires a LIVE host driver
9594/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
9595/// missing driver must never become a silent never-firing debounce).
9596pub fn sleep_until(deadline: Instant) -> Sleep {
9597  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
9598}
9599
9600/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
9601/// build executors and driver registries locally and must not touch the
9602/// process-global ones).
9603fn make_sleep(
9604  backend: &RuntimeBackend,
9605  host_drivers: &Arc<TimerDriverRegistry>,
9606  deadline: Instant,
9607) -> Sleep {
9608  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
9609  match &backend.executor {
9610    #[cfg(not(target_family = "wasm"))]
9611    RuntimeExecutor::MultiThread(executor) => Sleep {
9612      deadline,
9613      inner: SleepInner::Heap(HeapSleep {
9614        id,
9615        executor: Arc::downgrade(executor),
9616        fired: Arc::new(AtomicBool::new(false)),
9617        registered: false,
9618      }),
9619    },
9620    RuntimeExecutor::CurrentThread(executor) => {
9621      // Fail loud at CREATION when no live driver exists (the
9622      // never-registered case gets its diagnostic at the sleep_until call
9623      // site, not on some later poll). The driver is NOT pinned here: each
9624      // poll re-selects every live registrant, so a driver dying while this
9625      // sleep is in flight is cancelled while the remaining hosts stay armed
9626      // -- and if every driver dies mid-flight, the poll path has its own loud
9627      // panic.
9628      assert!(
9629        host_drivers.has_live_driver(),
9630        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
9631         single-thread flavor delegates timers to the host event loop. Install one via \
9632         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
9633         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
9634      );
9635      // The registry entry is created at FIRST POLL (when `driver.register`
9636      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
9637      // -- nothing was handed to the host loop -- so counting it at creation
9638      // would wrongly veto the threaded deadline verdict and mislabel the
9639      // threadless certain diagnostic as timer-backed. First-poll accounting
9640      // has no false-panic side.
9641      Sleep {
9642        deadline,
9643        inner: SleepInner::Host(HostSleep {
9644          id,
9645          drivers: Arc::clone(host_drivers),
9646          armed: FxHashMap::default(),
9647          registry: Arc::clone(&executor.host_timers),
9648          fired: Arc::new(AtomicBool::new(false)),
9649          repoll: Arc::new(HostTimerRepoll::default()),
9650          pending: None,
9651        }),
9652      }
9653    }
9654  }
9655}
9656
9657#[derive(Clone)]
9658enum RuntimeExecutor {
9659  CurrentThread(Arc<CurrentThreadExecutor>),
9660  #[cfg(not(target_family = "wasm"))]
9661  MultiThread(Arc<MultiThreadExecutor>),
9662}
9663
9664enum WeakRuntimeExecutor {
9665  CurrentThread(Weak<CurrentThreadExecutor>),
9666  #[cfg(not(target_family = "wasm"))]
9667  MultiThread(Weak<MultiThreadExecutor>),
9668}
9669
9670impl WeakRuntimeExecutor {
9671  fn schedule(&self, runnable: Runnable) {
9672    match self {
9673      Self::CurrentThread(executor) => {
9674        if let Some(executor) = executor.upgrade() {
9675          executor.schedule(runnable);
9676        }
9677      }
9678      #[cfg(not(target_family = "wasm"))]
9679      Self::MultiThread(executor) => {
9680        if let Some(executor) = executor.upgrade() {
9681          executor.schedule(runnable);
9682        }
9683      }
9684    }
9685  }
9686}
9687
9688#[derive(Clone)]
9689struct RuntimeBackend {
9690  work: Arc<GenerationWork>,
9691  executor: RuntimeExecutor,
9692}
9693
9694impl RuntimeBackend {
9695  fn new(
9696    options: &RuntimeOptions,
9697    metrics: Arc<RuntimeMetrics>,
9698  ) -> Result<Self, RuntimeConfigError> {
9699    #[cfg(test)]
9700    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
9701      return Err(RuntimeConfigError(
9702        "injected async runtime backend creation failure".to_string(),
9703      ));
9704    }
9705
9706    let work = GenerationWork::new();
9707    let stop = Arc::new(GenerationStop::default());
9708    let executor = match options.flavor {
9709      RuntimeFlavor::CurrentThread => {
9710        RuntimeExecutor::CurrentThread(Arc::new(CurrentThreadExecutor::with_generation(
9711          metrics,
9712          THREADLESS_BUILD,
9713          options.park_deadline,
9714          work.id,
9715          Arc::clone(&stop),
9716        )))
9717      }
9718      RuntimeFlavor::MultiThread => {
9719        #[cfg(not(target_family = "wasm"))]
9720        {
9721          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
9722            options, metrics, work.id, stop,
9723          )?))
9724        }
9725        #[cfg(target_family = "wasm")]
9726        {
9727          let _ = metrics;
9728          return Err(RuntimeConfigError(
9729            "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
9730          ));
9731        }
9732      }
9733    };
9734    Ok(Self { work, executor })
9735  }
9736
9737  #[cfg(test)]
9738  fn from_executor(executor: RuntimeExecutor) -> Self {
9739    Self {
9740      work: GenerationWork::new(),
9741      executor,
9742    }
9743  }
9744
9745  fn generation(&self) -> u64 {
9746    self.work.id
9747  }
9748
9749  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
9750    match &self.executor {
9751      RuntimeExecutor::CurrentThread(executor) => {
9752        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
9753      }
9754      #[cfg(not(target_family = "wasm"))]
9755      RuntimeExecutor::MultiThread(executor) => {
9756        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
9757      }
9758    }
9759  }
9760
9761  fn schedule(&self, runnable: Runnable) {
9762    match &self.executor {
9763      RuntimeExecutor::CurrentThread(executor) => executor.schedule(runnable),
9764      #[cfg(not(target_family = "wasm"))]
9765      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
9766    }
9767  }
9768
9769  fn spawn_registered_blocking<F, T>(
9770    &self,
9771    function: F,
9772    registration: GenerationWorkGuard,
9773    metrics: &Arc<RuntimeMetrics>,
9774  ) -> JoinHandle<T>
9775  where
9776    F: FnOnce() -> T + Send + 'static,
9777    T: Send + 'static,
9778  {
9779    match &self.executor {
9780      RuntimeExecutor::CurrentThread(executor) => {
9781        let _ = metrics;
9782        executor.schedule_blocking(function, registration)
9783      }
9784      #[cfg(not(target_family = "wasm"))]
9785      RuntimeExecutor::MultiThread(executor) => {
9786        let generation = self.generation();
9787        let _ = metrics;
9788        let function = RegisteredBlockingFunction::new(function, registration, generation);
9789        executor.schedule_blocking_result(move || function.run())
9790      }
9791    }
9792  }
9793
9794  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
9795    match &self.executor {
9796      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
9797      #[cfg(not(target_family = "wasm"))]
9798      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
9799    }
9800  }
9801
9802  fn generation_stop(&self) -> Arc<GenerationStop> {
9803    match &self.executor {
9804      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
9805      #[cfg(not(target_family = "wasm"))]
9806      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
9807    }
9808  }
9809
9810  fn begin_shutdown(&self) {
9811    let _generation = RuntimeGenerationGuard::enter(self.generation());
9812    self.work.close_and_abort();
9813    match &self.executor {
9814      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
9815      #[cfg(not(target_family = "wasm"))]
9816      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
9817    }
9818  }
9819
9820  fn wait_until_idle(&self) {
9821    self.work.wait_until_idle();
9822    match &self.executor {
9823      RuntimeExecutor::CurrentThread(executor) => executor.wait_until_scheduler_idle(),
9824      #[cfg(not(target_family = "wasm"))]
9825      RuntimeExecutor::MultiThread(executor) => executor.wait_until_scheduler_idle(),
9826    }
9827  }
9828
9829  #[cfg(not(target_family = "wasm"))]
9830  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
9831    match &self.executor {
9832      RuntimeExecutor::CurrentThread(_) => None,
9833      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
9834    }
9835  }
9836
9837  fn stop_identity(&self) -> RuntimeStopIdentity {
9838    RuntimeStopIdentity {
9839      generation: self.generation(),
9840      #[cfg(not(target_family = "wasm"))]
9841      executor_id: match &self.executor {
9842        RuntimeExecutor::CurrentThread(_) => None,
9843        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
9844      },
9845    }
9846  }
9847
9848  fn is_current(&self) -> bool {
9849    self.stop_identity().is_current()
9850  }
9851}
9852
9853#[derive(Clone, Copy)]
9854struct RuntimeStopIdentity {
9855  generation: u64,
9856  #[cfg(not(target_family = "wasm"))]
9857  executor_id: Option<u64>,
9858}
9859
9860impl RuntimeStopIdentity {
9861  fn is_current(self) -> bool {
9862    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
9863      return true;
9864    }
9865    #[cfg(not(target_family = "wasm"))]
9866    if let Some(executor_id) = self.executor_id
9867      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
9868        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
9869    {
9870      return true;
9871    }
9872    false
9873  }
9874}
9875
9876enum RuntimeLifecycle {
9877  Initial,
9878  StoppedBeforeFirstUse,
9879  Running(RuntimeBackend),
9880  Stopping(RuntimeStopIdentity),
9881  StoppingWithoutBackend(ZeroBackendShutdownTarget),
9882  Stopped,
9883}
9884
9885#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9886enum ZeroBackendShutdownTarget {
9887  StoppedBeforeFirstUse,
9888  Stopped,
9889}
9890
9891impl ZeroBackendShutdownTarget {
9892  fn into_lifecycle(self) -> RuntimeLifecycle {
9893    match self {
9894      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
9895      Self::Stopped => RuntimeLifecycle::Stopped,
9896    }
9897  }
9898}
9899
9900struct RuntimeState {
9901  options: RuntimeOptions,
9902  lifecycle: RuntimeLifecycle,
9903  rejected_drops: usize,
9904}
9905
9906#[cfg(test)]
9907thread_local! {
9908  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
9909    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9910  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
9911    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9912  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
9913    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9914  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
9915    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9916  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
9917    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9918  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
9919    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
9920  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
9921    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
9922}
9923
9924#[cfg(test)]
9925fn run_before_runtime_submission_lock_test_hook() {
9926  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9927  {
9928    hook();
9929  }
9930}
9931
9932#[cfg(test)]
9933fn run_after_async_work_registration_test_hook() {
9934  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9935  {
9936    hook();
9937  }
9938}
9939
9940#[cfg(test)]
9941fn run_after_blocking_work_registration_test_hook() {
9942  if let Some(hook) =
9943    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9944  {
9945    hook();
9946  }
9947}
9948
9949#[cfg(test)]
9950fn run_after_current_thread_host_admission_test_hook() {
9951  if let Some(hook) =
9952    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9953  {
9954    hook();
9955  }
9956}
9957
9958#[cfg(test)]
9959fn run_before_initial_start_wait_test_hook() {
9960  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
9961    hook();
9962  }
9963}
9964
9965#[cfg(test)]
9966fn run_after_generation_stop_publication_test_hook() {
9967  if let Some(hook) =
9968    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
9969  {
9970    hook();
9971  }
9972}
9973
9974thread_local! {
9975  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
9976    const { std::cell::Cell::new(0) };
9977}
9978
9979struct RejectedSubmissionDropContext;
9980
9981impl RejectedSubmissionDropContext {
9982  fn enter() -> Self {
9983    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
9984    Self
9985  }
9986
9987  fn is_current() -> bool {
9988    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
9989  }
9990}
9991
9992impl Drop for RejectedSubmissionDropContext {
9993  fn drop(&mut self) {
9994    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
9995      depth.set(
9996        depth
9997          .get()
9998          .checked_sub(1)
9999          .expect("rejected submission drop depth underflow"),
10000      );
10001    });
10002  }
10003}
10004
10005struct RuntimeController {
10006  state: Mutex<RuntimeState>,
10007  lifecycle_changed: Condvar,
10008  metrics: Arc<RuntimeMetrics>,
10009  // Lock-free mirror of `state.lifecycle`'s backend, read by `fast_register`
10010  // without the `state` lock. Invariant: store `Some(b)` on every transition
10011  // INTO `Running(b)` and `None` on every transition OUT -- exactly 3 sites
10012  // today (lazy-create, start, shutdown). Any new Running-boundary write MUST
10013  // mirror here, or `fast_register` routes work onto a superseded backend.
10014  current_backend: ArcSwapOption<RuntimeBackend>,
10015}
10016
10017struct RejectedSubmissionGuard<'a> {
10018  controller: &'a RuntimeController,
10019}
10020
10021impl Drop for RejectedSubmissionGuard<'_> {
10022  fn drop(&mut self) {
10023    let mut state = self
10024      .controller
10025      .state
10026      .lock()
10027      .unwrap_or_else(std::sync::PoisonError::into_inner);
10028    state.rejected_drops = state
10029      .rejected_drops
10030      .checked_sub(1)
10031      .expect("rejected submission drop count underflow");
10032    self.controller.lifecycle_changed.notify_all();
10033  }
10034}
10035
10036impl RuntimeController {
10037  fn new() -> Self {
10038    let options = RuntimeOptions::default()
10039      .validate()
10040      .expect("default async runtime options must be valid");
10041    Self {
10042      state: Mutex::new(RuntimeState {
10043        options,
10044        lifecycle: RuntimeLifecycle::Initial,
10045        rejected_drops: 0,
10046      }),
10047      lifecycle_changed: Condvar::new(),
10048      metrics: Arc::new(RuntimeMetrics::default()),
10049      current_backend: ArcSwapOption::empty(),
10050    }
10051  }
10052
10053  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10054    let options = options.validate()?;
10055    let mut state = self
10056      .state
10057      .lock()
10058      .unwrap_or_else(std::sync::PoisonError::into_inner);
10059    Self::ensure_configuration_mutable(&state)?;
10060    state.options = options;
10061    Ok(())
10062  }
10063
10064  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10065    self.configure_partial_inner(patch, |_| {})
10066  }
10067
10068  fn configure_partial_inner(
10069    &self,
10070    patch: RuntimeOptionsPatch,
10071    after_merge: impl FnOnce(&RuntimeOptions),
10072  ) -> Result<(), RuntimeConfigError> {
10073    let mut state = self
10074      .state
10075      .lock()
10076      .unwrap_or_else(std::sync::PoisonError::into_inner);
10077    Self::ensure_configuration_mutable(&state)?;
10078
10079    let mut options = state.options.clone();
10080    patch.apply_to(&mut options);
10081    after_merge(&options);
10082    let options = options.validate()?;
10083    state.options = options;
10084    Ok(())
10085  }
10086
10087  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10088    if matches!(
10089      &state.lifecycle,
10090      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
10091    ) {
10092      return Ok(());
10093    }
10094    Err(RuntimeConfigError(
10095      "the async runtime configuration is frozen; configure it before the first async call"
10096        .to_string(),
10097    ))
10098  }
10099
10100  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10101    let Some(active_generation) = active_runtime_generation() else {
10102      return Ok(());
10103    };
10104    let lifecycle_generation = match &state.lifecycle {
10105      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10106      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10107      RuntimeLifecycle::Initial
10108      | RuntimeLifecycle::StoppedBeforeFirstUse
10109      | RuntimeLifecycle::StoppingWithoutBackend(_)
10110      | RuntimeLifecycle::Stopped => None,
10111    };
10112    if lifecycle_generation == Some(active_generation) {
10113      return Ok(());
10114    }
10115    Err(RuntimeConfigError(
10116      "cannot access or control the async runtime from work owned by a retired generation"
10117        .to_string(),
10118    ))
10119  }
10120
10121  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
10122    Self::ensure_active_generation_current(state)?;
10123    match &state.lifecycle {
10124      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
10125      RuntimeLifecycle::StoppedBeforeFirstUse
10126      | RuntimeLifecycle::Stopping(_)
10127      | RuntimeLifecycle::StoppingWithoutBackend(_)
10128      | RuntimeLifecycle::Stopped => {
10129        return Err(RuntimeConfigError(
10130          "the async runtime is stopped; call start before submitting work".to_string(),
10131        ));
10132      }
10133      RuntimeLifecycle::Initial => {}
10134    }
10135    if state.rejected_drops != 0 {
10136      return Err(RuntimeConfigError(
10137        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
10138      ));
10139    }
10140    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10141    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
10142    self.current_backend.store(Some(Arc::new(backend.clone())));
10143    Ok(backend)
10144  }
10145
10146  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
10147    let mut state = self
10148      .state
10149      .lock()
10150      .unwrap_or_else(std::sync::PoisonError::into_inner);
10151    self.backend_locked(&mut state)
10152  }
10153
10154  fn backend(&self) -> RuntimeBackend {
10155    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
10156  }
10157
10158  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
10159    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
10160      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10161      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10162      RuntimeLifecycle::Initial
10163      | RuntimeLifecycle::StoppedBeforeFirstUse
10164      | RuntimeLifecycle::StoppingWithoutBackend(_)
10165      | RuntimeLifecycle::Stopped => None,
10166    });
10167    state.rejected_drops += 1;
10168    generation
10169  }
10170
10171  /// Lock-free spawn fast path. Returns Some((backend, registration)) when the
10172  /// runtime is Running, the caller's generation is current, and registration
10173  /// succeeds -- all without taking the outer `state` lock. Returns None (fall
10174  /// to the locked slow path) for any miss: no current backend, retired
10175  /// generation, or a closed generation. Staleness is safe: see the safety
10176  /// invariant -- try_register_* re-checks `closed`, and wait_until_idle already
10177  /// gates on any registration that slips in during shutdown.
10178  #[inline]
10179  fn fast_register<R>(
10180    &self,
10181    register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
10182  ) -> Option<(Arc<RuntimeBackend>, R)> {
10183    let guard = self.current_backend.load();
10184    let backend = guard.as_ref()?; // None => Initial/Stopping/Stopped => slow path
10185    if let Some(active) = active_runtime_generation() {
10186      if active != backend.generation() {
10187        return None; // retired generation => slow path yields exact error
10188      }
10189    }
10190    let registration = register(&backend.work)?; // closed => None => slow path rejects
10191    // Hand back the already-loaded Arc (one refcount bump) rather than deep-
10192    // cloning `RuntimeBackend`'s two inner Arcs; both callers only borrow it.
10193    Some((Arc::clone(backend), registration))
10194  }
10195
10196  fn try_spawn_tracked<F, T>(
10197    &self,
10198    future: F,
10199  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10200  where
10201    F: Future<Output = T> + Send + 'static,
10202    T: Send + 'static,
10203  {
10204    #[cfg(test)]
10205    run_before_runtime_submission_lock_test_hook();
10206
10207    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
10208      #[cfg(test)]
10209      run_after_async_work_registration_test_hook();
10210      return Ok(spawn_registered(
10211        &backend,
10212        Arc::clone(&self.metrics),
10213        future,
10214        registration,
10215      ));
10216    }
10217
10218    let (backend, registration) = {
10219      let mut state = self
10220        .state
10221        .lock()
10222        .unwrap_or_else(std::sync::PoisonError::into_inner);
10223      let backend = match self.backend_locked(&mut state) {
10224        Ok(backend) => backend,
10225        Err(error) => {
10226          let generation = Self::begin_rejected_drop(&mut state);
10227          return Err((error, future, generation));
10228        }
10229      };
10230      let Some(registration) = backend.work.try_register_async() else {
10231        let generation = Self::begin_rejected_drop(&mut state);
10232        return Err((
10233          RuntimeConfigError(
10234            "the async runtime is stopped; call start before submitting work".to_string(),
10235          ),
10236          future,
10237          generation,
10238        ));
10239      };
10240      (backend, registration)
10241    };
10242    #[cfg(test)]
10243    run_after_async_work_registration_test_hook();
10244    Ok(spawn_registered(
10245      &backend,
10246      Arc::clone(&self.metrics),
10247      future,
10248      registration,
10249    ))
10250  }
10251
10252  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10253  where
10254    F: Future<Output = T> + Send + 'static,
10255    T: Send + 'static,
10256  {
10257    match self.try_spawn_tracked(future) {
10258      Ok(handle) => Ok(handle),
10259      Err((error, future, _generation)) => {
10260        drop(RejectedSubmissionGuard { controller: self });
10261        Err((error, future))
10262      }
10263    }
10264  }
10265
10266  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
10267  where
10268    F: Future<Output = T> + Send + 'static,
10269    T: Send + 'static,
10270  {
10271    match self.try_spawn_tracked(future) {
10272      Ok(handle) => handle,
10273      Err((error, future, generation)) => {
10274        let _rejection = RejectedSubmissionGuard { controller: self };
10275        let _drop_context = RejectedSubmissionDropContext::enter();
10276        let _generation = generation.map(RuntimeGenerationGuard::enter);
10277        let _ = catch_unwind_contained(|| drop(future));
10278        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10279          message: error.to_string(),
10280        }))))
10281      }
10282    }
10283  }
10284
10285  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
10286  where
10287    F: Future<Output = ()> + Send + 'static,
10288  {
10289    match self.try_spawn(future) {
10290      Ok(handle) => {
10291        handle.detach();
10292        Ok(())
10293      }
10294      Err((_, future)) => Err(future),
10295    }
10296  }
10297
10298  fn try_spawn_blocking_tracked<F, T>(
10299    &self,
10300    function: F,
10301  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10302  where
10303    F: FnOnce() -> T + Send + 'static,
10304    T: Send + 'static,
10305  {
10306    #[cfg(test)]
10307    run_before_runtime_submission_lock_test_hook();
10308
10309    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
10310      #[cfg(test)]
10311      run_after_blocking_work_registration_test_hook();
10312      return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
10313    }
10314
10315    let (backend, registration) = {
10316      let mut state = self
10317        .state
10318        .lock()
10319        .unwrap_or_else(std::sync::PoisonError::into_inner);
10320      let backend = match self.backend_locked(&mut state) {
10321        Ok(backend) => backend,
10322        Err(error) => {
10323          let generation = Self::begin_rejected_drop(&mut state);
10324          return Err((error, function, generation));
10325        }
10326      };
10327      let Some(registration) = backend.work.try_register_work() else {
10328        let generation = Self::begin_rejected_drop(&mut state);
10329        return Err((
10330          RuntimeConfigError(
10331            "the async runtime is stopped; call start before submitting work".to_string(),
10332          ),
10333          function,
10334          generation,
10335        ));
10336      };
10337      (backend, registration)
10338    };
10339    #[cfg(test)]
10340    run_after_blocking_work_registration_test_hook();
10341    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
10342  }
10343
10344  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10345  where
10346    F: FnOnce() -> T + Send + 'static,
10347    T: Send + 'static,
10348  {
10349    match self.try_spawn_blocking_tracked(function) {
10350      Ok(handle) => Ok(handle),
10351      Err((error, function, _generation)) => {
10352        drop(RejectedSubmissionGuard { controller: self });
10353        Err((error, function))
10354      }
10355    }
10356  }
10357
10358  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
10359  where
10360    F: FnOnce() -> T + Send + 'static,
10361    T: Send + 'static,
10362  {
10363    match self.try_spawn_blocking_tracked(function) {
10364      Ok(handle) => handle,
10365      Err((error, function, generation)) => {
10366        let _rejection = RejectedSubmissionGuard { controller: self };
10367        let _drop_context = RejectedSubmissionDropContext::enter();
10368        let _generation = generation.map(RuntimeGenerationGuard::enter);
10369        let _ = catch_unwind_contained(|| drop(function));
10370        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10371          message: error.to_string(),
10372        }))))
10373      }
10374    }
10375  }
10376
10377  #[cfg(test)]
10378  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10379    self
10380      .try_block_on(future)
10381      .unwrap_or_else(|error| panic!("{error}"))
10382  }
10383
10384  fn try_block_on_scope(
10385    &self,
10386  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
10387    let mut state = self
10388      .state
10389      .lock()
10390      .unwrap_or_else(std::sync::PoisonError::into_inner);
10391    let backend = match self.backend_locked(&mut state) {
10392      Ok(backend) => backend,
10393      Err(error) => {
10394        let generation = Self::begin_rejected_drop(&mut state);
10395        return Err((error, generation));
10396      }
10397    };
10398    let Some(registration) = backend.work.try_register_work() else {
10399      let generation = Self::begin_rejected_drop(&mut state);
10400      return Err((
10401        RuntimeConfigError(
10402          "the async runtime is stopped; call start before submitting work".to_string(),
10403        ),
10404        generation,
10405      ));
10406    };
10407    Ok((backend, registration))
10408  }
10409
10410  fn try_block_on(
10411    &self,
10412    future: Pin<&mut dyn Future<Output = ()>>,
10413  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
10414    let (backend, registration) = match self.try_block_on_scope() {
10415      Ok(scope) => scope,
10416      Err((error, _generation)) => {
10417        drop(RejectedSubmissionGuard { controller: self });
10418        return Err(error);
10419      }
10420    };
10421    let _registration = registration;
10422    let _generation = RuntimeGenerationGuard::enter(backend.generation());
10423    Ok(backend.block_on(future))
10424  }
10425
10426  fn start(&self) -> Result<(), RuntimeConfigError> {
10427    let mut state = self
10428      .state
10429      .lock()
10430      .unwrap_or_else(std::sync::PoisonError::into_inner);
10431    loop {
10432      Self::ensure_active_generation_current(&state)?;
10433      match &state.lifecycle {
10434        // napi starts the runtime while the addon is still being registered.
10435        // Keep the first backend lazy so configureAsyncRuntime remains usable
10436        // until the first async binding call.
10437        RuntimeLifecycle::Initial => {
10438          if state.rejected_drops == 0 {
10439            return Ok(());
10440          }
10441          if RejectedSubmissionDropContext::is_current() {
10442            return Err(RuntimeConfigError(
10443              "cannot start the async runtime while a rejected submission is being destroyed"
10444                .to_string(),
10445            ));
10446          }
10447          #[cfg(test)]
10448          run_before_initial_start_wait_test_hook();
10449          state = self
10450            .lifecycle_changed
10451            .wait(state)
10452            .unwrap_or_else(std::sync::PoisonError::into_inner);
10453        }
10454        RuntimeLifecycle::Running(_) => return Ok(()),
10455        RuntimeLifecycle::Stopping(identity) => {
10456          if identity.is_current() {
10457            return Err(RuntimeConfigError(
10458              "cannot restart the async runtime from work in the generation being stopped"
10459                .to_string(),
10460            ));
10461          }
10462          state = self
10463            .lifecycle_changed
10464            .wait(state)
10465            .unwrap_or_else(std::sync::PoisonError::into_inner);
10466        }
10467        RuntimeLifecycle::StoppingWithoutBackend(_) => {
10468          if RejectedSubmissionDropContext::is_current() {
10469            return Err(RuntimeConfigError(
10470              "cannot restart the async runtime while a rejected submission is being destroyed"
10471                .to_string(),
10472            ));
10473          }
10474          state = self
10475            .lifecycle_changed
10476            .wait(state)
10477            .unwrap_or_else(std::sync::PoisonError::into_inner);
10478        }
10479        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10480          if state.rejected_drops == 0 {
10481            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10482              state.lifecycle = RuntimeLifecycle::Initial;
10483              return Ok(());
10484            }
10485            break;
10486          }
10487          if RejectedSubmissionDropContext::is_current() {
10488            return Err(RuntimeConfigError(
10489              "cannot restart the async runtime while a rejected submission is being destroyed"
10490                .to_string(),
10491            ));
10492          }
10493          state = self
10494            .lifecycle_changed
10495            .wait(state)
10496            .unwrap_or_else(std::sync::PoisonError::into_inner);
10497        }
10498      }
10499    }
10500    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10501    self.current_backend.store(Some(Arc::new(backend.clone())));
10502    state.lifecycle = RuntimeLifecycle::Running(backend);
10503    Ok(())
10504  }
10505
10506  fn options(&self) -> RuntimeOptions {
10507    self
10508      .state
10509      .lock()
10510      .unwrap_or_else(std::sync::PoisonError::into_inner)
10511      .options
10512      .clone()
10513  }
10514
10515  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
10516    let state = self
10517      .state
10518      .lock()
10519      .unwrap_or_else(std::sync::PoisonError::into_inner);
10520    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10521      return None;
10522    };
10523    match &backend.executor {
10524      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
10525      #[cfg(not(target_family = "wasm"))]
10526      RuntimeExecutor::MultiThread(_) => None,
10527    }
10528  }
10529
10530  fn request_current_thread_drain(&self) {
10531    if let Some(executor) = self.running_current_thread_executor() {
10532      executor.request_drain_if_queued();
10533    }
10534  }
10535
10536  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
10537    let executor = {
10538      let state = self
10539        .state
10540        .lock()
10541        .unwrap_or_else(std::sync::PoisonError::into_inner);
10542      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10543        return;
10544      };
10545      match &backend.executor {
10546        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10547        #[cfg(not(target_family = "wasm"))]
10548        RuntimeExecutor::MultiThread(_) => return,
10549      }
10550    };
10551    executor.cancel_host_dispatch(failure);
10552  }
10553
10554  #[cfg(test)]
10555  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
10556    let executor = {
10557      let state = self
10558        .state
10559        .lock()
10560        .unwrap_or_else(std::sync::PoisonError::into_inner);
10561      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10562        return;
10563      };
10564      match &backend.executor {
10565        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
10566        #[cfg(not(target_family = "wasm"))]
10567        RuntimeExecutor::MultiThread(_) => return,
10568      }
10569    };
10570    executor.cancel_host_dispatch_for_test(dispatch);
10571  }
10572
10573  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
10574    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
10575    self.drive_current_thread_dispatch_with_lease(dispatch, true)
10576  }
10577
10578  fn drive_current_thread_dispatch_with_lease(
10579    &self,
10580    dispatch: u64,
10581    mark_serviced: bool,
10582  ) -> Option<CurrentThreadTaskCallbackLease> {
10583    let mut host_turn = {
10584      let state = self
10585        .state
10586        .lock()
10587        .unwrap_or_else(std::sync::PoisonError::into_inner);
10588      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
10589        return None;
10590      };
10591      let executor = match &backend.executor {
10592        RuntimeExecutor::CurrentThread(executor) => executor,
10593        #[cfg(not(target_family = "wasm"))]
10594        RuntimeExecutor::MultiThread(_) => return None,
10595      };
10596      let host_turn = executor.try_admit_host_turn(dispatch)?;
10597      if mark_serviced {
10598        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
10599      }
10600      host_turn
10601    };
10602    #[cfg(test)]
10603    run_after_current_thread_host_admission_test_hook();
10604    host_turn.drive();
10605    Some(CurrentThreadTaskCallbackLease::new(host_turn))
10606  }
10607
10608  #[cfg(test)]
10609  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
10610    self
10611      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
10612      .is_some()
10613  }
10614
10615  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
10616    let backend = {
10617      let mut state = self
10618        .state
10619        .lock()
10620        .unwrap_or_else(std::sync::PoisonError::into_inner);
10621      loop {
10622        Self::ensure_active_generation_current(&state)?;
10623        match &state.lifecycle {
10624          RuntimeLifecycle::Initial => {
10625            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
10626              return Err(RuntimeConfigError(
10627                "cannot shut down the async runtime while a rejected submission is being destroyed"
10628                  .to_string(),
10629              ));
10630            }
10631            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
10632            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10633            self.lifecycle_changed.notify_all();
10634            while state.rejected_drops != 0 {
10635              state = self
10636                .lifecycle_changed
10637                .wait(state)
10638                .unwrap_or_else(std::sync::PoisonError::into_inner);
10639            }
10640            debug_assert!(matches!(
10641              state.lifecycle,
10642              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10643            ));
10644            state.lifecycle = target.into_lifecycle();
10645            self.lifecycle_changed.notify_all();
10646            return Ok(());
10647          }
10648          RuntimeLifecycle::Running(backend) => {
10649            if backend.is_current() {
10650              return Err(RuntimeConfigError(
10651                "cannot shut down the async runtime from work running on that runtime".to_string(),
10652              ));
10653            }
10654            let identity = backend.stop_identity();
10655            let stop = backend.generation_stop();
10656            // Lock order is lifecycle -> stop publication. Publish before
10657            // mutating the lifecycle so a gated verdict that acquired the
10658            // publication mutex first wins while this state remains Running;
10659            // otherwise the verdict must observe the published stop.
10660            let stop_publication = stop.publication_guard();
10661            stop_publication.publish_stopping();
10662            let RuntimeLifecycle::Running(backend) =
10663              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
10664            else {
10665              unreachable!();
10666            };
10667            self.current_backend.store(None);
10668            // Release before aborting tasks or running any user destruction.
10669            // Neither path may retain the publication mutex across scheduler
10670            // admission, lifecycle reentry, or a panic boundary.
10671            drop(stop_publication);
10672            break backend;
10673          }
10674          RuntimeLifecycle::Stopping(identity) => {
10675            if identity.is_current() {
10676              return Err(RuntimeConfigError(
10677                "cannot wait for async runtime shutdown from work in the generation being stopped"
10678                  .to_string(),
10679              ));
10680            }
10681            state = self
10682              .lifecycle_changed
10683              .wait(state)
10684              .unwrap_or_else(std::sync::PoisonError::into_inner);
10685          }
10686          RuntimeLifecycle::StoppingWithoutBackend(_) => {
10687            if RejectedSubmissionDropContext::is_current() {
10688              return Err(RuntimeConfigError(
10689                "cannot shut down the async runtime while a rejected submission is being destroyed"
10690                  .to_string(),
10691              ));
10692            }
10693            state = self
10694              .lifecycle_changed
10695              .wait(state)
10696              .unwrap_or_else(std::sync::PoisonError::into_inner);
10697          }
10698          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
10699            if state.rejected_drops == 0 {
10700              return Ok(());
10701            }
10702            if RejectedSubmissionDropContext::is_current() {
10703              return Err(RuntimeConfigError(
10704                "cannot shut down the async runtime while a rejected submission is being destroyed"
10705                  .to_string(),
10706              ));
10707            }
10708            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
10709              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
10710            } else {
10711              ZeroBackendShutdownTarget::Stopped
10712            };
10713            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
10714            self.lifecycle_changed.notify_all();
10715            while state.rejected_drops != 0 {
10716              state = self
10717                .lifecycle_changed
10718                .wait(state)
10719                .unwrap_or_else(std::sync::PoisonError::into_inner);
10720            }
10721            debug_assert!(matches!(
10722              state.lifecycle,
10723              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
10724            ));
10725            state.lifecycle = target.into_lifecycle();
10726            self.lifecycle_changed.notify_all();
10727            return Ok(());
10728          }
10729        }
10730      }
10731    };
10732
10733    #[cfg(test)]
10734    run_after_generation_stop_publication_test_hook();
10735    backend.begin_shutdown();
10736    backend.wait_until_idle();
10737
10738    #[cfg(not(target_family = "wasm"))]
10739    let worker_lifecycle = backend.worker_lifecycle();
10740    drop(backend);
10741
10742    #[cfg(not(target_family = "wasm"))]
10743    if let Some(worker_lifecycle) = worker_lifecycle {
10744      worker_lifecycle.wait_for_all_workers();
10745    }
10746
10747    let mut state = self
10748      .state
10749      .lock()
10750      .unwrap_or_else(std::sync::PoisonError::into_inner);
10751    while state.rejected_drops != 0 {
10752      state = self
10753        .lifecycle_changed
10754        .wait(state)
10755        .unwrap_or_else(std::sync::PoisonError::into_inner);
10756    }
10757    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
10758    state.lifecycle = RuntimeLifecycle::Stopped;
10759    self.lifecycle_changed.notify_all();
10760    Ok(())
10761  }
10762}
10763
10764static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
10765
10766pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10767  RUNTIME.configure(options)
10768}
10769
10770pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10771  RUNTIME.configure_partial(patch)
10772}
10773
10774pub fn configured_options() -> RuntimeOptions {
10775  RUNTIME.options()
10776}
10777
10778pub fn is_multi_threaded() -> bool {
10779  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
10780}
10781
10782fn spawn_registered<F, T>(
10783  backend: &RuntimeBackend,
10784  metrics: Arc<RuntimeMetrics>,
10785  future: F,
10786  registration: (AbortRegistration, GenerationWorkGuard),
10787) -> JoinHandle<T>
10788where
10789  F: Future<Output = T> + Send + 'static,
10790  T: Send + 'static,
10791{
10792  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
10793  let generation = backend.generation();
10794  let (abort_registration, work_registration) = registration;
10795  #[cfg(not(target_family = "wasm"))]
10796  let dependency = Arc::new(TaskDependency::new_task(generation));
10797  #[cfg(not(target_family = "wasm"))]
10798  let task_dependency = Arc::clone(&dependency);
10799  let wrapped = async move {
10800    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
10801    let mut abortable = std::pin::pin!(abortable);
10802    let polled = futures::future::poll_fn(|cx| {
10803      #[cfg(not(target_family = "wasm"))]
10804      task_dependency.begin_poll();
10805      #[cfg(not(target_family = "wasm"))]
10806      let _dependency = TaskDependencyGuard::enter(&task_dependency);
10807      abortable.as_mut().poll(cx)
10808    });
10809    match AssertUnwindSafe(polled).catch_unwind().await {
10810      Ok(Ok(output)) => {
10811        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
10812        Ok(ContainedTaskOutput::new(output, generation))
10813      }
10814      Ok(Err(_)) => Err(JoinError {
10815        message: "async runtime stopped before the task completed".to_string(),
10816      }),
10817      Err(panic) => {
10818        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
10819        Err(panic_payload_to_join_error(panic))
10820      }
10821    }
10822  };
10823  let scheduler = backend.downgrade_executor();
10824  // `async-task` aborts the process if its future's destructor unwinds. Keep
10825  // the entire task generator behind our manually-dropped containment wrapper,
10826  // including the never-polled case where `future` is still a generator field.
10827  let (runnable, task) = async_task::spawn(
10828    RegisteredTaskFuture::new(wrapped, work_registration, generation),
10829    move |runnable| {
10830      scheduler.schedule(runnable);
10831    },
10832  );
10833  backend.schedule(runnable);
10834  JoinHandle(JoinHandleInner::Task {
10835    task: task.fallible(),
10836    awaiter: None,
10837    #[cfg(not(target_family = "wasm"))]
10838    dependency,
10839  })
10840}
10841
10842pub fn spawn<F, T>(future: F) -> JoinHandle<T>
10843where
10844  F: Future<Output = T> + Send + 'static,
10845  T: Send + 'static,
10846{
10847  RUNTIME.spawn(future)
10848}
10849
10850/// Submit a future without consuming it when the runtime is not accepting work.
10851///
10852/// Lifecycle owners can retain the returned future and retry it after
10853/// [`start`] completes a runtime restart.
10854pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10855where
10856  F: Future<Output = T> + Send + 'static,
10857  T: Send + 'static,
10858{
10859  RUNTIME.try_spawn(future)
10860}
10861
10862pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
10863where
10864  F: Future<Output = ()> + Send + 'static,
10865{
10866  RUNTIME.try_spawn_detached(future)
10867}
10868
10869pub fn spawn_detached<F>(future: F)
10870where
10871  F: Future<Output = ()> + Send + 'static,
10872{
10873  spawn(future).detach();
10874}
10875
10876pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
10877where
10878  F: FnOnce() -> T + Send + 'static,
10879  T: Send + 'static,
10880{
10881  RUNTIME.spawn_blocking(function)
10882}
10883
10884/// Submit blocking work without consuming it when the runtime is not accepting work.
10885///
10886/// Lifecycle owners can retain the returned closure and retry it after [`start`]
10887/// completes a runtime restart.
10888pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10889where
10890  F: FnOnce() -> T + Send + 'static,
10891  T: Send + 'static,
10892{
10893  RUNTIME.try_spawn_blocking(function)
10894}
10895
10896fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
10897  let (backend, registration) = match controller.try_block_on_scope() {
10898    Ok(scope) => scope,
10899    Err((error, generation)) => {
10900      let _rejection = RejectedSubmissionGuard { controller };
10901      let _drop_context = RejectedSubmissionDropContext::enter();
10902      let _generation = generation.map(RuntimeGenerationGuard::enter);
10903      let _ = catch_unwind_contained(|| drop(future));
10904      panic!("{error}");
10905    }
10906  };
10907  let generation = backend.generation();
10908  let _registration = registration;
10909  let _generation = RuntimeGenerationGuard::enter(generation);
10910  let mut output = None;
10911  {
10912    let future = ContainedFuture::new(future, generation);
10913    let mut erased = std::pin::pin!(async {
10914      output = Some(future.await);
10915    });
10916    let _ = backend.block_on(erased.as_mut());
10917    // `erased` is destroyed before this scope exits, while the controller's
10918    // generation work and identity guards are live. `ContainedFuture` keeps a
10919    // poll panic from becoming a process-aborting double panic if the owned
10920    // future's destructor also unwinds.
10921  }
10922  output.expect("async runtime returned before the future completed")
10923}
10924
10925pub fn block_on<F: Future>(future: F) -> F::Output {
10926  block_on_with_controller(&RUNTIME, future)
10927}
10928
10929pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
10930  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
10931}
10932
10933/// Drive a borrowed future without consuming it when admission or shutdown fails.
10934pub fn try_block_on_dyn(
10935  future: Pin<&mut dyn Future<Output = ()>>,
10936) -> Result<(), RuntimeConfigError> {
10937  match RUNTIME.try_block_on(future)? {
10938    BlockOnOutcome::Completed => Ok(()),
10939    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
10940      "the async runtime stopped before block_on completed its future".to_string(),
10941    )),
10942  }
10943}
10944
10945pub fn start() -> Result<(), RuntimeConfigError> {
10946  RUNTIME.start()
10947}
10948
10949pub fn shutdown() -> Result<(), RuntimeConfigError> {
10950  RUNTIME.shutdown()
10951}
10952
10953fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
10954  for failure in completion.failed_dispatches {
10955    RUNTIME.cancel_current_thread_dispatch(failure);
10956  }
10957  if completion.redispatch {
10958    RUNTIME.request_current_thread_drain();
10959  }
10960}
10961
10962/// Acknowledge that one exact host delivery callback returned the successful
10963/// claim result from [`drive_current_thread_tasks`].
10964///
10965/// This releases the host's single-flight slot and submits at most its latest
10966/// still-needed pending delivery. The registry verifies that this exact
10967/// delivery was claimed; a forged success for an unclaimed delivery is treated
10968/// as failure. A stale acknowledgement from an unregistered host or retired
10969/// attempt is a no-op.
10970pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10971  complete_current_thread_task_delivery(
10972    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
10973  );
10974}
10975
10976/// Report that one exact host delivery callback failed.
10977///
10978/// Recovery begins only after no other live host attempt can still service the
10979/// same executor dispatch. The registration and delivery identities prevent a
10980/// stale failure from affecting another host or a re-registered generation.
10981pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
10982  complete_current_thread_task_delivery(
10983    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
10984  );
10985}
10986
10987/// Cancel an accepted CurrentThread host delivery that failed before it could
10988/// schedule a fresh turn.
10989///
10990/// `delivery` is the opaque registration-scoped driver capability,
10991/// not the executor's shared dispatch capability. Repeated or stale failures
10992/// are no-ops.
10993pub fn cancel_current_thread_task_dispatch(delivery: u64) {
10994  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
10995}
10996
10997/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
10998///
10999/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
11000/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
11001/// Returns a callback lease only when this exact delivery was claimed, admitted,
11002/// and driven. It returns `None` for stale/unregistered deliveries, before
11003/// backend creation, and on MultiThread. The host must retain the lease through
11004/// delivery acknowledgement/failure and callback payload destruction.
11005pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11006  RUNTIME.drive_current_thread_tasks(delivery)
11007}
11008
11009pub fn reset_metrics() {
11010  RUNTIME.metrics.reset();
11011}
11012
11013pub fn metrics() -> RuntimeMetricsSnapshot {
11014  let options = RUNTIME.options();
11015  RUNTIME.metrics.snapshot(&options)
11016}
11017
11018#[cfg(test)]
11019mod tests {
11020  use super::*;
11021
11022  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11023  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11024    LazyLock::new(Mutex::default);
11025  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11026  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11027  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11028  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11029    LazyLock::new(Mutex::default);
11030  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11031  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11032    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11033  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11034    LazyLock::new(Mutex::default);
11035  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
11036  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
11037    CurrentThreadTaskDriverRegistry,
11038  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11039  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11040  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11041  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11042    LazyLock::new(Mutex::default);
11043  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11044    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11045  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11046    LazyLock::new(Mutex::default);
11047  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11048  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11049    LazyLock::new(Mutex::default);
11050  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11051    LazyLock::new(Mutex::default);
11052  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
11053    CurrentThreadTaskDriverRegistry,
11054  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11055  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11056    LazyLock::new(Mutex::default);
11057  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11058  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11059    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11060  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11061
11062  thread_local! {
11063    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
11064      const { std::cell::Cell::new(0) };
11065    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
11066      const { std::cell::Cell::new(0) };
11067  }
11068
11069  #[cfg(not(target_family = "wasm"))]
11070  struct StopPollProbe {
11071    polls: Arc<AtomicUsize>,
11072    first_poll: Option<std::sync::mpsc::Sender<()>>,
11073  }
11074
11075  #[cfg(not(target_family = "wasm"))]
11076  impl Future for StopPollProbe {
11077    type Output = ();
11078
11079    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11080      self.polls.fetch_add(1, Ordering::SeqCst);
11081      if let Some(first_poll) = self.first_poll.take() {
11082        first_poll.send(()).unwrap();
11083      }
11084      Poll::Pending
11085    }
11086  }
11087
11088  #[cfg(not(target_family = "wasm"))]
11089  struct PanickingJoinAwaiter {
11090    wakes: Arc<AtomicUsize>,
11091    drops: Arc<AtomicUsize>,
11092  }
11093
11094  #[cfg(not(target_family = "wasm"))]
11095  impl std::task::Wake for PanickingJoinAwaiter {
11096    fn wake(self: Arc<Self>) {
11097      self.wakes.fetch_add(1, Ordering::SeqCst);
11098      panic!("intentional JoinHandle awaiter wake panic");
11099    }
11100
11101    fn wake_by_ref(self: &Arc<Self>) {
11102      self.wakes.fetch_add(1, Ordering::SeqCst);
11103      panic!("intentional JoinHandle awaiter wake-by-ref panic");
11104    }
11105  }
11106
11107  #[cfg(not(target_family = "wasm"))]
11108  impl Drop for PanickingJoinAwaiter {
11109    fn drop(&mut self) {
11110      self.drops.fetch_add(1, Ordering::SeqCst);
11111      panic!("intentional JoinHandle awaiter destructor panic");
11112    }
11113  }
11114
11115  #[cfg(not(target_family = "wasm"))]
11116  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
11117    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
11118  }
11119
11120  #[cfg(not(target_family = "wasm"))]
11121  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11122  enum JoinAwaiterWakerEventKind {
11123    Clone,
11124    Wake,
11125    WakeByRef,
11126    Drop,
11127  }
11128
11129  #[cfg(not(target_family = "wasm"))]
11130  struct JoinAwaiterWakerState {
11131    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
11132  }
11133
11134  #[cfg(not(target_family = "wasm"))]
11135  impl JoinAwaiterWakerState {
11136    fn record(&self, kind: JoinAwaiterWakerEventKind) {
11137      self
11138        .events
11139        .lock()
11140        .unwrap_or_else(std::sync::PoisonError::into_inner)
11141        .push((kind, active_runtime_generation()));
11142    }
11143
11144    fn clear(&self) {
11145      self
11146        .events
11147        .lock()
11148        .unwrap_or_else(std::sync::PoisonError::into_inner)
11149        .clear();
11150    }
11151
11152    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
11153      self
11154        .events
11155        .lock()
11156        .unwrap_or_else(std::sync::PoisonError::into_inner)
11157        .clone()
11158    }
11159  }
11160
11161  #[cfg(not(target_family = "wasm"))]
11162  struct JoinAwaiterWakerToken {
11163    state: Arc<JoinAwaiterWakerState>,
11164  }
11165
11166  #[cfg(not(target_family = "wasm"))]
11167  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
11168    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11169    token.state.record(JoinAwaiterWakerEventKind::Clone);
11170    let clone = Box::new(JoinAwaiterWakerToken {
11171      state: Arc::clone(&token.state),
11172    });
11173    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
11174  }
11175
11176  #[cfg(not(target_family = "wasm"))]
11177  unsafe fn wake_join_awaiter_waker(data: *const ()) {
11178    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11179    token.state.record(JoinAwaiterWakerEventKind::Wake);
11180  }
11181
11182  #[cfg(not(target_family = "wasm"))]
11183  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
11184    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11185    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
11186  }
11187
11188  #[cfg(not(target_family = "wasm"))]
11189  unsafe fn drop_join_awaiter_waker(data: *const ()) {
11190    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11191    token.state.record(JoinAwaiterWakerEventKind::Drop);
11192  }
11193
11194  #[cfg(not(target_family = "wasm"))]
11195  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
11196    clone_join_awaiter_waker,
11197    wake_join_awaiter_waker,
11198    wake_join_awaiter_waker_by_ref,
11199    drop_join_awaiter_waker,
11200  );
11201
11202  #[cfg(not(target_family = "wasm"))]
11203  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
11204    let token = Box::new(JoinAwaiterWakerToken { state });
11205    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
11206    unsafe { Waker::from_raw(raw) }
11207  }
11208
11209  #[cfg(not(target_family = "wasm"))]
11210  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
11211    wait_until("the block_on driver to sleep", || {
11212      stop
11213        .parkers
11214        .lock()
11215        .unwrap_or_else(std::sync::PoisonError::into_inner)
11216        .iter()
11217        .filter_map(Weak::upgrade)
11218        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11219    });
11220  }
11221
11222  #[test]
11223  fn unique_ids_fail_before_wraparound() {
11224    let counter = AtomicU64::new(u64::MAX - 1);
11225    assert_eq!(
11226      next_unique_id(&counter, "test id space exhausted"),
11227      u64::MAX - 1
11228    );
11229
11230    let exhausted = catch_unwind(AssertUnwindSafe(|| {
11231      next_unique_id(&counter, "test id space exhausted");
11232    }));
11233    assert!(exhausted.is_err());
11234    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
11235  }
11236
11237  #[test]
11238  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
11239    let work = GenerationWork::new();
11240    {
11241      let mut state = work.state.lock().unwrap();
11242      state.next_task_id = u64::MAX;
11243    }
11244
11245    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
11246    assert!(exhausted.is_err());
11247
11248    let state = work.state.lock().unwrap();
11249    assert_eq!(state.next_task_id, u64::MAX);
11250    assert_eq!(state.active, 0);
11251    assert!(state.abort_handles.is_empty());
11252  }
11253
11254  #[test]
11255  fn caught_panic_payload_destructor_runs() {
11256    struct DropSignal(Arc<AtomicBool>);
11257
11258    impl Drop for DropSignal {
11259      fn drop(&mut self) {
11260        self.0.store(true, Ordering::SeqCst);
11261      }
11262    }
11263
11264    let dropped = Arc::new(AtomicBool::new(false));
11265    let payload = DropSignal(Arc::clone(&dropped));
11266
11267    assert!(
11268      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
11269      "the original panic must remain contained"
11270    );
11271    assert!(
11272      dropped.load(Ordering::SeqCst),
11273      "an ordinary caught panic payload must be destroyed"
11274    );
11275  }
11276
11277  #[test]
11278  fn hostile_caught_panic_payload_destructor_remains_contained() {
11279    struct PanicOnDrop(Arc<AtomicBool>);
11280
11281    impl Drop for PanicOnDrop {
11282      fn drop(&mut self) {
11283        self.0.store(true, Ordering::SeqCst);
11284        panic!("caught panic payload destructor escaped");
11285      }
11286    }
11287
11288    let dropped = Arc::new(AtomicBool::new(false));
11289    let payload = PanicOnDrop(Arc::clone(&dropped));
11290    let result = catch_unwind(AssertUnwindSafe(|| {
11291      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
11292    }));
11293
11294    assert!(
11295      result.is_ok(),
11296      "the payload destructor's nested panic must remain contained"
11297    );
11298    assert!(
11299      result.unwrap().is_none(),
11300      "the original panic must remain contained"
11301    );
11302    assert!(
11303      dropped.load(Ordering::SeqCst),
11304      "the hostile payload destructor must be attempted"
11305    );
11306  }
11307
11308  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11309    CurrentThreadHostDispatchResult::Accepted
11310  }
11311
11312  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11313    CurrentThreadHostDispatchResult::Unavailable
11314  }
11315
11316  fn count_current_thread_recovery_host_dispatch(
11317    _dispatch: u64,
11318  ) -> CurrentThreadHostDispatchResult {
11319    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
11320    CurrentThreadHostDispatchResult::Accepted
11321  }
11322
11323  fn reset_current_thread_recovery_host_dispatches() {
11324    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11325  }
11326
11327  fn current_thread_recovery_host_dispatches() -> usize {
11328    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
11329  }
11330
11331  fn accept_only_first_current_thread_host_dispatch(
11332    _dispatch: u64,
11333  ) -> CurrentThreadHostDispatchResult {
11334    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
11335      let previous = dispatches.get();
11336      dispatches.set(previous + 1);
11337      previous == 0
11338    }) {
11339      CurrentThreadHostDispatchResult::Accepted
11340    } else {
11341      CurrentThreadHostDispatchResult::Unavailable
11342    }
11343  }
11344
11345  fn reset_current_thread_one_shot_host_dispatches() {
11346    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
11347  }
11348
11349  fn current_thread_one_shot_host_dispatches() -> usize {
11350    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
11351  }
11352
11353  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11354    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11355    CurrentThreadHostDispatchResult::Accepted
11356  }
11357
11358  fn count_current_thread_blocking_budget_host_dispatch(
11359    _dispatch: u64,
11360  ) -> CurrentThreadHostDispatchResult {
11361    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11362    CurrentThreadHostDispatchResult::Accepted
11363  }
11364
11365  fn count_current_thread_admission_host_dispatch(
11366    _dispatch: u64,
11367  ) -> CurrentThreadHostDispatchResult {
11368    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11369    CurrentThreadHostDispatchResult::Accepted
11370  }
11371
11372  fn count_current_thread_interleaved_recovery_host_dispatch(
11373    _dispatch: u64,
11374  ) -> CurrentThreadHostDispatchResult {
11375    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11376    CurrentThreadHostDispatchResult::Accepted
11377  }
11378
11379  fn dispatch_late_registration_current_thread_tasks(
11380    dispatch: u64,
11381  ) -> CurrentThreadHostDispatchResult {
11382    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
11383  }
11384
11385  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11386    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
11387      .lock()
11388      .unwrap()
11389      .push(dispatch);
11390    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
11391  }
11392
11393  fn dispatch_authoritative_panic_current_thread_tasks(
11394    dispatch: u64,
11395  ) -> CurrentThreadHostDispatchResult {
11396    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
11397      .lock()
11398      .unwrap()
11399      .push(dispatch);
11400    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
11401      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
11402      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
11403      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
11404    }
11405  }
11406
11407  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
11408    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
11409  }
11410
11411  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
11412    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
11413      .lock()
11414      .unwrap()
11415      .push(dispatch);
11416    assert!(
11417      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11418      "injected CurrentThread host dispatch panic"
11419    );
11420    CurrentThreadHostDispatchResult::Accepted
11421  }
11422
11423  fn panic_once_current_thread_armed_republish_host_dispatch(
11424    dispatch: u64,
11425  ) -> CurrentThreadHostDispatchResult {
11426    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
11427      .lock()
11428      .unwrap()
11429      .push(dispatch);
11430    assert!(
11431      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
11432      "injected armed CurrentThread host dispatch panic"
11433    );
11434    CurrentThreadHostDispatchResult::Accepted
11435  }
11436
11437  fn record_current_thread_republish_rearm_dispatch(
11438    dispatch: u64,
11439  ) -> CurrentThreadHostDispatchResult {
11440    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
11441      .lock()
11442      .unwrap()
11443      .push(dispatch);
11444    CurrentThreadHostDispatchResult::Accepted
11445  }
11446
11447  fn reject_initial_current_thread_publication_once(
11448    dispatch: u64,
11449  ) -> CurrentThreadHostDispatchResult {
11450    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
11451      .lock()
11452      .unwrap();
11453    dispatches.push(dispatch);
11454    if dispatches.len() == 1 {
11455      CurrentThreadHostDispatchResult::Unavailable
11456    } else {
11457      CurrentThreadHostDispatchResult::Accepted
11458    }
11459  }
11460
11461  fn reject_current_thread_replacement_publication_once(
11462    dispatch: u64,
11463  ) -> CurrentThreadHostDispatchResult {
11464    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
11465      .lock()
11466      .unwrap();
11467    dispatches.push(dispatch);
11468    if dispatches.len() == 2 {
11469      CurrentThreadHostDispatchResult::Unavailable
11470    } else {
11471      CurrentThreadHostDispatchResult::Accepted
11472    }
11473  }
11474
11475  fn fail_current_thread_replacement_publication_once(
11476    dispatch: u64,
11477  ) -> CurrentThreadHostDispatchResult {
11478    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
11479      .lock()
11480      .unwrap()
11481      .push(dispatch);
11482    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
11483  }
11484
11485  fn count_current_thread_shutdown_republish_host_dispatch(
11486    _dispatch: u64,
11487  ) -> CurrentThreadHostDispatchResult {
11488    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11489    CurrentThreadHostDispatchResult::Accepted
11490  }
11491
11492  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
11493    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
11494    CurrentThreadHostDispatchResult::Accepted
11495  }
11496
11497  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
11498    let pending = executor.dispatch_pending.load(Ordering::Acquire);
11499    if pending != 0 {
11500      return pending;
11501    }
11502    let dispatch = next_current_thread_dispatch_id();
11503    match executor.dispatch_pending.compare_exchange(
11504      0,
11505      dispatch,
11506      Ordering::AcqRel,
11507      Ordering::Acquire,
11508    ) {
11509      Ok(_) => dispatch,
11510      Err(pending) => pending,
11511    }
11512  }
11513
11514  fn current_thread_dispatch_failure(
11515    dispatch: u64,
11516    epoch: u64,
11517  ) -> CurrentThreadTaskDispatchFailure {
11518    CurrentThreadTaskDispatchFailure { dispatch, epoch }
11519  }
11520
11521  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
11522    let executor = controller
11523      .running_current_thread_executor()
11524      .expect("the test controller must have a running CurrentThread executor");
11525    current_thread_dispatch(&executor)
11526  }
11527
11528  #[test]
11529  fn current_thread_executor_drives_spawned_tasks() {
11530    let metrics = Arc::new(RuntimeMetrics::default());
11531    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
11532    let scheduler = Arc::clone(&executor);
11533    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
11534      scheduler.schedule(runnable);
11535    });
11536
11537    executor.schedule(runnable);
11538
11539    let mut output = None;
11540    {
11541      let mut future = std::pin::pin!(async {
11542        output = Some(task.await);
11543      });
11544      executor.block_on(future.as_mut());
11545    }
11546    assert_eq!(output, Some(42));
11547    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
11548    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
11549  }
11550
11551  #[cfg(not(target_family = "wasm"))]
11552  #[test]
11553  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
11554    use std::sync::mpsc;
11555
11556    let executor = Arc::new(CurrentThreadExecutor::with_detection(
11557      Arc::new(RuntimeMetrics::default()),
11558      false,
11559      None,
11560    ));
11561    let polls = Arc::new(AtomicUsize::new(0));
11562    let (first_poll_tx, first_poll_rx) = mpsc::channel();
11563    let (outcome_tx, outcome_rx) = mpsc::channel();
11564    let driver_executor = Arc::clone(&executor);
11565    let driver_polls = Arc::clone(&polls);
11566    let driver = std::thread::spawn(move || {
11567      let mut future = std::pin::pin!(StopPollProbe {
11568        polls: driver_polls,
11569        first_poll: Some(first_poll_tx)
11570      });
11571      outcome_tx
11572        .send(driver_executor.block_on(future.as_mut()))
11573        .unwrap();
11574    });
11575
11576    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11577    wait_for_generation_parker_to_sleep(&executor.stop);
11578    executor.stop.begin_shutdown();
11579
11580    assert_eq!(
11581      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11582      BlockOnOutcome::Stopped
11583    );
11584    driver.join().unwrap();
11585    assert_eq!(
11586      polls.load(Ordering::SeqCst),
11587      1,
11588      "a shutdown wake must be checked before the next CurrentThread user poll"
11589    );
11590  }
11591
11592  #[cfg(not(target_family = "wasm"))]
11593  #[test]
11594  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
11595    use std::sync::mpsc;
11596
11597    let metrics = Arc::new(RuntimeMetrics::default());
11598    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11599      Arc::clone(&metrics),
11600      accept_current_thread_host_dispatch,
11601    ));
11602    let polls = Arc::new(AtomicUsize::new(0));
11603    let task_polls = Arc::clone(&polls);
11604    let (runnable, task) = async_task::spawn(
11605      async move {
11606        task_polls.fetch_add(1, Ordering::SeqCst);
11607      },
11608      |_| {},
11609    );
11610    executor.schedule(runnable);
11611    let dispatch = current_thread_dispatch(&executor);
11612
11613    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
11614    let (release_claim_tx, release_claim_rx) = mpsc::channel();
11615    let driver_executor = Arc::clone(&executor);
11616    let driver = std::thread::spawn(move || {
11617      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
11618        *slot.borrow_mut() = Some(Box::new(move || {
11619          claim_locked_tx.send(()).unwrap();
11620          release_claim_rx.recv().unwrap();
11621        }));
11622      });
11623      driver_executor.drive_host_turn_for_dispatch(dispatch);
11624    });
11625    claim_locked_rx
11626      .recv_timeout(Duration::from_secs(2))
11627      .expect("the admitted host turn must pause before its runnable claim");
11628
11629    executor.stop.publish_stopping();
11630    release_claim_tx.send(()).unwrap();
11631    driver.join().unwrap();
11632
11633    assert_eq!(
11634      polls.load(Ordering::SeqCst),
11635      0,
11636      "stop publication must outrank a later runnable claim by an already-admitted host turn"
11637    );
11638    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11639
11640    executor.begin_shutdown();
11641    assert!(
11642      futures::executor::block_on(task.fallible()).is_none(),
11643      "shutdown must cancel the runnable rejected by the stopped claim"
11644    );
11645    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11646  }
11647
11648  #[cfg(not(target_family = "wasm"))]
11649  #[test]
11650  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
11651    use std::sync::mpsc;
11652
11653    let metrics = Arc::new(RuntimeMetrics::default());
11654    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11655      Arc::clone(&metrics),
11656      accept_current_thread_host_dispatch,
11657    ));
11658    let polls = Arc::new(AtomicUsize::new(0));
11659    let task_polls = Arc::clone(&polls);
11660    let (runnable, task) = async_task::spawn(
11661      async move {
11662        task_polls.fetch_add(1, Ordering::SeqCst);
11663      },
11664      |_| {},
11665    );
11666    executor.schedule(runnable);
11667
11668    let queue = executor
11669      .queue
11670      .lock()
11671      .unwrap_or_else(std::sync::PoisonError::into_inner);
11672    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
11673    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
11674    let cancelling_executor = Arc::clone(&executor);
11675    let cancellation = std::thread::spawn(move || {
11676      let scheduler = cancelling_executor
11677        .scheduler_idle_lock
11678        .lock()
11679        .unwrap_or_else(std::sync::PoisonError::into_inner);
11680      let cancelling = cancelling_executor
11681        .begin_failed_dispatch_cancellation()
11682        .expect("the test must publish terminal dispatch cancellation");
11683      drop(scheduler);
11684      cancellation_published_tx.send(()).unwrap();
11685      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
11686      cancellation_done_tx.send(()).unwrap();
11687    });
11688    cancellation_published_rx
11689      .recv_timeout(Duration::from_secs(2))
11690      .expect("terminal dispatch cancellation must publish before queue draining");
11691
11692    let claimant_executor = Arc::clone(&executor);
11693    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
11694    assert!(
11695      !claimant.join().unwrap(),
11696      "a runnable claim after terminal cancellation publication must be rejected"
11697    );
11698    assert_eq!(polls.load(Ordering::SeqCst), 0);
11699    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
11700    assert!(
11701      matches!(
11702        cancellation_done_rx.try_recv(),
11703        Err(mpsc::TryRecvError::Empty)
11704      ),
11705      "terminal cancellation must still be waiting to drain the queued runnable"
11706    );
11707
11708    drop(queue);
11709    cancellation_done_rx
11710      .recv_timeout(Duration::from_secs(2))
11711      .expect("terminal cancellation must drain the rejected runnable");
11712    cancellation.join().unwrap();
11713    assert!(
11714      futures::executor::block_on(task.fallible()).is_none(),
11715      "terminal cancellation must cancel the runnable it prevented from starting"
11716    );
11717    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11718  }
11719
11720  #[cfg(not(target_family = "wasm"))]
11721  #[test]
11722  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
11723    use std::sync::mpsc;
11724
11725    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
11726    let polls = Arc::new(AtomicUsize::new(0));
11727    let (first_poll_tx, first_poll_rx) = mpsc::channel();
11728    let (outcome_tx, outcome_rx) = mpsc::channel();
11729    let driver_executor = Arc::clone(&executor);
11730    let driver_polls = Arc::clone(&polls);
11731    let driver = std::thread::spawn(move || {
11732      let mut future = std::pin::pin!(StopPollProbe {
11733        polls: driver_polls,
11734        first_poll: Some(first_poll_tx)
11735      });
11736      outcome_tx
11737        .send(driver_executor.block_on(future.as_mut()))
11738        .unwrap();
11739    });
11740
11741    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11742    wait_for_generation_parker_to_sleep(&executor.stop);
11743    executor.stop.begin_shutdown();
11744
11745    assert_eq!(
11746      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11747      BlockOnOutcome::Stopped
11748    );
11749    driver.join().unwrap();
11750    assert_eq!(
11751      polls.load(Ordering::SeqCst),
11752      1,
11753      "a shutdown wake must be checked before the next parking block_on user poll"
11754    );
11755  }
11756
11757  #[cfg(not(target_family = "wasm"))]
11758  #[test]
11759  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
11760    use std::sync::mpsc;
11761
11762    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
11763    let polls = Arc::new(AtomicUsize::new(0));
11764    let (first_poll_tx, first_poll_rx) = mpsc::channel();
11765    let (outcome_tx, outcome_rx) = mpsc::channel();
11766    let driver_executor = Arc::clone(&executor);
11767    let driver_polls = Arc::clone(&polls);
11768    let driver = std::thread::spawn(move || {
11769      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
11770      let mut future = std::pin::pin!(StopPollProbe {
11771        polls: driver_polls,
11772        first_poll: Some(first_poll_tx)
11773      });
11774      outcome_tx
11775        .send(driver_executor.block_on(future.as_mut()))
11776        .unwrap();
11777    });
11778
11779    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11780    wait_for_generation_parker_to_sleep(&executor.stop);
11781    executor.stop.begin_shutdown();
11782
11783    assert_eq!(
11784      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11785      BlockOnOutcome::Stopped
11786    );
11787    driver.join().unwrap();
11788    assert_eq!(
11789      polls.load(Ordering::SeqCst),
11790      1,
11791      "a shutdown wake must be checked before the next cooperative user poll"
11792    );
11793  }
11794
11795  #[test]
11796  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
11797    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11798      Arc::new(RuntimeMetrics::default()),
11799      accept_current_thread_host_dispatch,
11800    ));
11801    let pending = u64::MAX - 1;
11802    executor.dispatch_pending.store(pending, Ordering::Release);
11803    let mut allocations = 0;
11804
11805    executor.request_drain_with(|| {
11806      allocations += 1;
11807      u64::MAX
11808    });
11809
11810    assert_eq!(allocations, 0);
11811    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
11812  }
11813
11814  #[cfg(not(target_family = "wasm"))]
11815  #[test]
11816  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
11817    use std::sync::mpsc;
11818
11819    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11820      Arc::new(RuntimeMetrics::default()),
11821      accept_current_thread_host_dispatch,
11822    ));
11823    let dispatch = current_thread_dispatch(&executor);
11824    let scheduler = executor
11825      .scheduler_idle_lock
11826      .lock()
11827      .unwrap_or_else(std::sync::PoisonError::into_inner);
11828    let (claiming_tx, claiming_rx) = mpsc::channel();
11829    let (claimed_tx, claimed_rx) = mpsc::channel();
11830    let claiming_executor = Arc::clone(&executor);
11831    let claimant = std::thread::spawn(move || {
11832      claiming_tx.send(()).unwrap();
11833      claimed_tx
11834        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
11835        .unwrap();
11836    });
11837    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
11838
11839    executor.stop.begin_shutdown();
11840    drop(scheduler);
11841
11842    assert!(
11843      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
11844      "an old-generation dispatch must not claim a host turn after stop becomes visible"
11845    );
11846    claimant.join().unwrap();
11847    assert_eq!(
11848      executor.dispatch_pending.load(Ordering::Acquire),
11849      dispatch,
11850      "rejecting the stopped claim must not consume its stale capability"
11851    );
11852  }
11853
11854  #[cfg(not(target_family = "wasm"))]
11855  #[test]
11856  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
11857    use std::sync::mpsc;
11858
11859    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
11860    let metrics = Arc::new(RuntimeMetrics::default());
11861    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11862      Arc::clone(&metrics),
11863      count_current_thread_stopped_host_dispatch,
11864    ));
11865    let scheduler = executor
11866      .scheduler_idle_lock
11867      .lock()
11868      .unwrap_or_else(std::sync::PoisonError::into_inner);
11869    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
11870    let scheduling_executor = Arc::clone(&executor);
11871    let scheduling = std::thread::spawn(move || {
11872      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
11873        *slot.borrow_mut() = Some(Box::new(move || {
11874          publication_blocked_tx.send(()).unwrap();
11875        }));
11876      });
11877      let scheduler = Arc::clone(&scheduling_executor);
11878      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
11879        scheduler.schedule(runnable);
11880      });
11881      task.detach();
11882      scheduling_executor.schedule(runnable);
11883    });
11884
11885    publication_blocked_rx
11886      .recv_timeout(Duration::from_secs(2))
11887      .expect("dispatch publication must block behind the scheduler-idle mutex");
11888    executor.begin_shutdown();
11889    drop(scheduler);
11890    scheduling.join().unwrap();
11891    executor.wait_until_scheduler_idle();
11892
11893    assert_eq!(
11894      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
11895      0,
11896      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
11897    );
11898    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
11899    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11900  }
11901
11902  #[test]
11903  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
11904    use std::{sync::mpsc, time::Duration};
11905
11906    let metrics = Arc::new(RuntimeMetrics::default());
11907    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
11908      Arc::clone(&metrics),
11909      accept_current_thread_host_dispatch,
11910    ));
11911    let scheduler = Arc::clone(&executor);
11912    let completed = Arc::new(AtomicBool::new(false));
11913    let completed_task = Arc::clone(&completed);
11914    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
11915    let shared = async move {
11916      wake_rx.await.expect("wake sender dropped");
11917    }
11918    .boxed()
11919    .shared();
11920    let (runnable, task) = async_task::spawn(
11921      async move {
11922        shared.await;
11923        completed_task.store(true, Ordering::SeqCst);
11924      },
11925      move |runnable| scheduler.schedule(runnable),
11926    );
11927
11928    executor.schedule(runnable);
11929    assert!(
11930      !completed.load(Ordering::SeqCst),
11931      "accepted host dispatch must defer the first poll"
11932    );
11933    executor.drive_host_turn();
11934
11935    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
11936    std::thread::spawn(move || {
11937      wake_tx.send(()).expect("shared wake receiver dropped");
11938      send_returned_tx.send(()).unwrap();
11939    });
11940    send_returned_rx
11941      .recv_timeout(Duration::from_secs(1))
11942      .expect("Shared wake re-entered its own waker mutex through inline polling");
11943    assert!(
11944      !completed.load(Ordering::SeqCst),
11945      "the wake must remain queued until a fresh host turn"
11946    );
11947
11948    executor.drive_host_turn();
11949    futures::executor::block_on(task);
11950    assert!(completed.load(Ordering::SeqCst));
11951    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
11952  }
11953
11954  #[test]
11955  fn current_thread_hostless_wake_is_enqueue_only() {
11956    use std::{sync::mpsc, time::Duration};
11957
11958    struct LockOnSecondPoll {
11959      gate: Arc<Mutex<()>>,
11960      first: bool,
11961      waker: Option<mpsc::Sender<Waker>>,
11962      completed: Arc<AtomicBool>,
11963    }
11964
11965    impl Future for LockOnSecondPoll {
11966      type Output = ();
11967
11968      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
11969        if self.first {
11970          self.first = false;
11971          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
11972          return Poll::Pending;
11973        }
11974        drop(self.gate.lock().unwrap());
11975        self.completed.store(true, Ordering::SeqCst);
11976        Poll::Ready(())
11977      }
11978    }
11979
11980    let metrics = Arc::new(RuntimeMetrics::default());
11981    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
11982    let gate = Arc::new(Mutex::new(()));
11983    let completed = Arc::new(AtomicBool::new(false));
11984    let (waker_tx, waker_rx) = mpsc::channel();
11985    let scheduler = Arc::clone(&executor);
11986    let (runnable, task) = async_task::spawn(
11987      LockOnSecondPoll {
11988        gate: Arc::clone(&gate),
11989        first: true,
11990        waker: Some(waker_tx),
11991        completed: Arc::clone(&completed),
11992      },
11993      move |runnable| scheduler.schedule(runnable),
11994    );
11995    executor.schedule(runnable);
11996    executor.drive_host_turn();
11997    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
11998
11999    let (returned_tx, returned_rx) = mpsc::channel();
12000    let wake_gate = Arc::clone(&gate);
12001    let waking = std::thread::spawn(move || {
12002      let _held = wake_gate.lock().unwrap();
12003      waker.wake();
12004      returned_tx.send(()).unwrap();
12005    });
12006    returned_rx
12007      .recv_timeout(Duration::from_secs(1))
12008      .expect("hostless wake must enqueue without polling under the caller's lock");
12009    waking.join().unwrap();
12010    assert!(!completed.load(Ordering::SeqCst));
12011
12012    executor.drive_host_turn();
12013    futures::executor::block_on(task);
12014    assert!(completed.load(Ordering::SeqCst));
12015  }
12016
12017  #[cfg(not(target_family = "wasm"))]
12018  #[test]
12019  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
12020    use std::sync::mpsc;
12021
12022    let controller = Arc::new(current_thread_controller(
12023      "current-thread-hostless-late-work",
12024    ));
12025    let backend = controller.backend();
12026    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
12027      panic!("configured CurrentThread backend must create a CurrentThread executor");
12028    };
12029    let executor = Arc::clone(executor);
12030    drop(backend);
12031
12032    let (value_tx, value_rx) = oneshot::channel();
12033    let (done_tx, done_rx) = mpsc::channel();
12034    let block_on_controller = Arc::clone(&controller);
12035    let runner = std::thread::spawn(move || {
12036      let mut output = None;
12037      let outcome = {
12038        let mut future = std::pin::pin!(async {
12039          output = Some(value_rx.await.expect("scheduled sender must deliver"));
12040        });
12041        block_on_controller.block_on(future.as_mut())
12042      };
12043      done_tx.send((outcome, output)).unwrap();
12044    });
12045
12046    wait_until(
12047      "the hostless CurrentThread block_on driver to sleep",
12048      || {
12049        executor
12050          .stop
12051          .parkers
12052          .lock()
12053          .unwrap_or_else(std::sync::PoisonError::into_inner)
12054          .iter()
12055          .filter_map(Weak::upgrade)
12056          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12057      },
12058    );
12059
12060    controller
12061      .try_spawn_detached(async move {
12062        value_tx
12063          .send(37usize)
12064          .expect("block_on receiver must remain alive");
12065      })
12066      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
12067
12068    let (outcome, output) = done_rx
12069      .recv_timeout(Duration::from_secs(2))
12070      .expect("queue publication must wake the sleeping explicit driver");
12071    assert_eq!(outcome, BlockOnOutcome::Completed);
12072    assert_eq!(output, Some(37));
12073    runner.join().unwrap();
12074    controller.shutdown().unwrap();
12075  }
12076
12077  #[cfg(not(target_family = "wasm"))]
12078  #[test]
12079  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
12080    use std::sync::mpsc;
12081
12082    struct BlockingPoll {
12083      entered: Option<mpsc::Sender<()>>,
12084      release: mpsc::Receiver<()>,
12085    }
12086
12087    impl Future for BlockingPoll {
12088      type Output = ();
12089
12090      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12091        self.entered.take().unwrap().send(()).unwrap();
12092        self.release.recv().unwrap();
12093        Poll::Ready(())
12094      }
12095    }
12096
12097    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12098      Arc::new(RuntimeMetrics::default()),
12099      reject_current_thread_host_dispatch,
12100    ));
12101    let (value_tx, value_rx) = oneshot::channel();
12102    let (older_done_tx, older_done_rx) = mpsc::channel();
12103    let older_executor = Arc::clone(&executor);
12104    let older = std::thread::spawn(move || {
12105      let mut value = None;
12106      {
12107        let mut future = std::pin::pin!(async {
12108          value = Some(value_rx.await.unwrap());
12109        });
12110        assert_eq!(
12111          older_executor.block_on(future.as_mut()),
12112          BlockOnOutcome::Completed
12113        );
12114      }
12115      older_done_tx.send(value.unwrap()).unwrap();
12116    });
12117    wait_until("the older CurrentThread driver to sleep", || {
12118      executor
12119        .stop
12120        .parkers
12121        .lock()
12122        .unwrap_or_else(std::sync::PoisonError::into_inner)
12123        .iter()
12124        .filter_map(Weak::upgrade)
12125        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12126    });
12127
12128    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
12129    let (release_newer_tx, release_newer_rx) = mpsc::channel();
12130    let newer_executor = Arc::clone(&executor);
12131    let newer = std::thread::spawn(move || {
12132      let mut future = std::pin::pin!(BlockingPoll {
12133        entered: Some(newer_entered_tx),
12134        release: release_newer_rx
12135      });
12136      assert_eq!(
12137        newer_executor.block_on(future.as_mut()),
12138        BlockOnOutcome::Completed
12139      );
12140    });
12141    newer_entered_rx
12142      .recv_timeout(Duration::from_secs(1))
12143      .expect("the newer driver must be blocked inside its poll");
12144
12145    let scheduler = Arc::clone(&executor);
12146    let (runnable, task) = async_task::spawn(
12147      async move {
12148        value_tx.send(41usize).unwrap();
12149      },
12150      move |runnable| scheduler.schedule(runnable),
12151    );
12152    task.detach();
12153    executor.schedule(runnable);
12154
12155    assert_eq!(
12156      older_done_rx
12157        .recv_timeout(Duration::from_secs(2))
12158        .expect("queue publication must also wake the older sleeping driver"),
12159      41
12160    );
12161    release_newer_tx.send(()).unwrap();
12162    older.join().unwrap();
12163    newer.join().unwrap();
12164  }
12165
12166  #[test]
12167  fn current_thread_shutdown_cancels_host_queued_task() {
12168    use std::sync::mpsc;
12169
12170    struct DropSignal(mpsc::Sender<()>);
12171
12172    impl Drop for DropSignal {
12173      fn drop(&mut self) {
12174        self.0.send(()).unwrap();
12175      }
12176    }
12177
12178    let metrics = Arc::new(RuntimeMetrics::default());
12179    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12180      Arc::clone(&metrics),
12181      accept_current_thread_host_dispatch,
12182    ));
12183    let scheduler = Arc::clone(&executor);
12184    let (dropped_tx, dropped_rx) = mpsc::channel();
12185    let signal = DropSignal(dropped_tx);
12186    let (runnable, task) = async_task::spawn(
12187      async move {
12188        let _signal = signal;
12189        futures::future::pending::<()>().await;
12190      },
12191      move |runnable| scheduler.schedule(runnable),
12192    );
12193
12194    executor.schedule(runnable);
12195    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12196    executor.begin_shutdown();
12197
12198    dropped_rx
12199      .recv_timeout(Duration::from_secs(1))
12200      .expect("shutdown must drop a task queued behind a host turn");
12201    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12202    assert!(
12203      futures::executor::block_on(task.fallible()).is_none(),
12204      "the queued task handle must resolve as cancelled"
12205    );
12206  }
12207
12208  #[test]
12209  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
12210    reset_current_thread_recovery_host_dispatches();
12211    let metrics = Arc::new(RuntimeMetrics::default());
12212    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12213      Arc::clone(&metrics),
12214      count_current_thread_recovery_host_dispatch,
12215    ));
12216    let scheduler = Arc::clone(&executor);
12217    let completed = Arc::new(AtomicBool::new(false));
12218    let completed_task = Arc::clone(&completed);
12219    let (runnable, task) = async_task::spawn(
12220      async move {
12221        completed_task.store(true, Ordering::SeqCst);
12222      },
12223      move |runnable| scheduler.schedule(runnable),
12224    );
12225
12226    executor.schedule(runnable);
12227    assert_eq!(current_thread_recovery_host_dispatches(), 1);
12228    let lost_dispatch = current_thread_dispatch(&executor);
12229
12230    // Model another host registering while the first host still owns an
12231    // accepted delivery. The executor capability stays stable; the host
12232    // registry gives the new registration a distinct delivery token.
12233    executor.request_drain_if_queued();
12234    assert_eq!(
12235      current_thread_recovery_host_dispatches(),
12236      2,
12237      "the newly registered host must receive the pending executor dispatch"
12238    );
12239    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
12240    executor.drive_host_turn_for_dispatch(lost_dispatch);
12241    futures::executor::block_on(task);
12242    assert!(completed.load(Ordering::SeqCst));
12243    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12244
12245    let second_completed = Arc::new(AtomicBool::new(false));
12246    let second_completed_task = Arc::clone(&second_completed);
12247    let second_scheduler = Arc::clone(&executor);
12248    let (second_runnable, second_task) = async_task::spawn(
12249      async move {
12250        second_completed_task.store(true, Ordering::SeqCst);
12251      },
12252      move |runnable| second_scheduler.schedule(runnable),
12253    );
12254    executor.schedule(second_runnable);
12255    assert_eq!(
12256      current_thread_recovery_host_dispatches(),
12257      3,
12258      "work queued after the stale callback must receive a new dispatch"
12259    );
12260    let third_dispatch = current_thread_dispatch(&executor);
12261
12262    // The already-consumed capability remains stale relative to dispatch 2 and
12263    // cannot service its work.
12264    executor.drive_host_turn_for_dispatch(lost_dispatch);
12265    assert!(!second_completed.load(Ordering::SeqCst));
12266    executor.drive_host_turn_for_dispatch(third_dispatch);
12267    futures::executor::block_on(second_task);
12268    assert!(second_completed.load(Ordering::SeqCst));
12269
12270    let third_scheduler = Arc::clone(&executor);
12271    let (third_runnable, third_task) =
12272      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
12273    executor.schedule(third_runnable);
12274    assert_eq!(
12275      current_thread_recovery_host_dispatches(),
12276      4,
12277      "a stale callback must not leave the dispatch latch stuck"
12278    );
12279    executor.drive_host_turn();
12280    futures::executor::block_on(third_task);
12281  }
12282
12283  #[test]
12284  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
12285    let metrics = Arc::new(RuntimeMetrics::default());
12286    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12287      Arc::clone(&metrics),
12288      accept_current_thread_host_dispatch,
12289    ));
12290    let scheduler = Arc::clone(&executor);
12291    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12292      scheduler.schedule(runnable)
12293    });
12294
12295    executor.schedule(runnable);
12296    let failed_dispatch = current_thread_dispatch(&executor);
12297    executor.cancel_host_dispatch_for_test(failed_dispatch);
12298    let recovery_dispatch = current_thread_dispatch(&executor);
12299    assert_eq!(
12300      executor.recovery_dispatch.load(Ordering::Acquire),
12301      recovery_dispatch
12302    );
12303
12304    executor.request_drain_if_queued();
12305    let new_host_dispatch = current_thread_dispatch(&executor);
12306    assert_eq!(new_host_dispatch, recovery_dispatch);
12307    assert_eq!(
12308      executor.recovery_dispatch.load(Ordering::Acquire),
12309      recovery_dispatch
12310    );
12311    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12312
12313    executor.drive_host_turn_for_dispatch(new_host_dispatch);
12314    assert_eq!(futures::executor::block_on(task), 17);
12315  }
12316
12317  #[cfg(not(target_family = "wasm"))]
12318  #[test]
12319  fn current_thread_host_admission_serializes_dispatch_publication() {
12320    use std::sync::mpsc;
12321
12322    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12323    let metrics = Arc::new(RuntimeMetrics::default());
12324    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12325      Arc::clone(&metrics),
12326      count_current_thread_admission_host_dispatch,
12327    ));
12328    let first_scheduler = Arc::clone(&executor);
12329    let (first_runnable, first_task) =
12330      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
12331    first_task.detach();
12332    executor.schedule(first_runnable);
12333    let dispatch = current_thread_dispatch(&executor);
12334    assert_eq!(
12335      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12336      1
12337    );
12338
12339    let (claimed_tx, claimed_rx) = mpsc::channel();
12340    let (release_tx, release_rx) = mpsc::channel();
12341    let driver_executor = Arc::clone(&executor);
12342    let driver = std::thread::spawn(move || {
12343      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
12344        *slot.borrow_mut() = Some(Box::new(move || {
12345          claimed_tx.send(()).unwrap();
12346          release_rx.recv().unwrap();
12347        }));
12348      });
12349      driver_executor.drive_host_turn_for_dispatch(dispatch);
12350    });
12351    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12352
12353    let second_ran = Arc::new(AtomicBool::new(false));
12354    let second_ran_task = Arc::clone(&second_ran);
12355    let scheduling_executor = Arc::clone(&executor);
12356    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
12357    let (scheduled_tx, scheduled_rx) = mpsc::channel();
12358    let scheduling = std::thread::spawn(move || {
12359      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
12360        *slot.borrow_mut() = Some(Box::new(move || {
12361          publication_blocked_tx.send(()).unwrap();
12362        }));
12363      });
12364      let scheduler = Arc::clone(&scheduling_executor);
12365      let (runnable, task) = async_task::spawn(
12366        async move {
12367          second_ran_task.store(true, Ordering::SeqCst);
12368        },
12369        move |runnable| scheduler.schedule(runnable),
12370      );
12371      task.detach();
12372      scheduling_executor.schedule(runnable);
12373      scheduled_tx.send(()).unwrap();
12374    });
12375
12376    publication_blocked_rx
12377      .recv_timeout(Duration::from_secs(2))
12378      .unwrap();
12379    assert!(
12380      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
12381      "dispatch publication must wait until host admission publishes its draining role"
12382    );
12383    assert_eq!(
12384      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
12385      1,
12386      "the admission gap must not publish a duplicate host dispatch"
12387    );
12388
12389    release_tx.send(()).unwrap();
12390    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12391    scheduling.join().unwrap();
12392    driver.join().unwrap();
12393    if !second_ran.load(Ordering::SeqCst) {
12394      executor.drive_host_turn();
12395    }
12396    assert!(second_ran.load(Ordering::SeqCst));
12397    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12398  }
12399
12400  #[test]
12401  fn current_thread_host_turn_yields_after_runnable_budget() {
12402    struct SelfWakingFuture {
12403      remaining: usize,
12404    }
12405
12406    impl Future for SelfWakingFuture {
12407      type Output = ();
12408
12409      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12410        if self.remaining == 0 {
12411          return Poll::Ready(());
12412        }
12413        self.remaining -= 1;
12414        cx.waker().wake_by_ref();
12415        Poll::Pending
12416      }
12417    }
12418
12419    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12420    let metrics = Arc::new(RuntimeMetrics::default());
12421    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12422      Arc::clone(&metrics),
12423      count_current_thread_budget_host_dispatch,
12424    ));
12425    let scheduler = Arc::clone(&executor);
12426    let (runnable, task) = async_task::spawn(
12427      SelfWakingFuture {
12428        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12429      },
12430      move |runnable| scheduler.schedule(runnable),
12431    );
12432
12433    executor.schedule(runnable);
12434    executor.drive_host_turn();
12435
12436    assert_eq!(
12437      metrics.runnable_polls.load(Ordering::Relaxed),
12438      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
12439    );
12440    assert_eq!(
12441      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12442      2,
12443      "remaining hot work must continue through a fresh host turn"
12444    );
12445
12446    executor.drive_host_turn();
12447    futures::executor::block_on(task);
12448    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12449  }
12450
12451  #[cfg(not(target_family = "wasm"))]
12452  #[test]
12453  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
12454    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
12455
12456    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12457    let metrics = Arc::new(RuntimeMetrics::default());
12458    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12459      Arc::clone(&metrics),
12460      count_current_thread_blocking_budget_host_dispatch,
12461    ));
12462    let work = GenerationWork::new();
12463    let observed = Arc::new(Mutex::new(Vec::new()));
12464
12465    let task_executor = Arc::clone(&executor);
12466    let wake_executor = Arc::clone(&executor);
12467    let producer_executor = Arc::clone(&executor);
12468    let producer_work = Arc::clone(&work);
12469    let producer_observed = Arc::clone(&observed);
12470    let outer_registration = work
12471      .try_register_work()
12472      .expect("the outer blocking job must be accepted");
12473    let (runnable, task) = async_task::spawn(
12474      async move {
12475        let queued = task_executor
12476          .schedule_blocking(
12477            move || {
12478              std::thread::spawn(move || {
12479                (0..QUEUED_BLOCKING_JOBS)
12480                  .map(|index| {
12481                    let registration = producer_work
12482                      .try_register_work()
12483                      .expect("queued blocking work must be accepted");
12484                    let observed = Arc::clone(&producer_observed);
12485                    producer_executor.schedule_blocking(
12486                      move || {
12487                        observed
12488                          .lock()
12489                          .unwrap_or_else(std::sync::PoisonError::into_inner)
12490                          .push(index);
12491                      },
12492                      registration,
12493                    )
12494                  })
12495                  .collect::<Vec<_>>()
12496              })
12497              .join()
12498              .expect("the blocking producer must finish")
12499            },
12500            outer_registration,
12501          )
12502          .await
12503          .expect("the inline blocking job must finish");
12504        for handle in queued {
12505          handle.await.expect("queued blocking work must finish");
12506        }
12507      },
12508      move |runnable| wake_executor.schedule(runnable),
12509    );
12510
12511    executor.schedule(runnable);
12512    executor.drive_host_turn();
12513
12514    let first_turn = observed
12515      .lock()
12516      .unwrap_or_else(std::sync::PoisonError::into_inner)
12517      .clone();
12518    assert!(
12519      !first_turn.is_empty(),
12520      "the first host turn must service queued blocking work"
12521    );
12522    assert!(
12523      first_turn.len() < QUEUED_BLOCKING_JOBS,
12524      "inline lane release must not drain the entire blocking FIFO in one host turn"
12525    );
12526    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
12527    assert_eq!(
12528      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
12529      2,
12530      "remaining blocking work must continue through a fresh host turn"
12531    );
12532
12533    let mut continuation_turns = 0;
12534    while observed
12535      .lock()
12536      .unwrap_or_else(std::sync::PoisonError::into_inner)
12537      .len()
12538      < QUEUED_BLOCKING_JOBS
12539    {
12540      continuation_turns += 1;
12541      assert!(
12542        continuation_turns <= QUEUED_BLOCKING_JOBS,
12543        "queued blocking work failed to make bounded host-turn progress"
12544      );
12545      executor.drive_host_turn();
12546    }
12547    executor.drive_host_turn();
12548    futures::executor::block_on(task);
12549
12550    assert_eq!(
12551      *observed
12552        .lock()
12553        .unwrap_or_else(std::sync::PoisonError::into_inner),
12554      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
12555      "blocking jobs must preserve FIFO order across host turns"
12556    );
12557    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12558  }
12559
12560  #[cfg(not(target_family = "wasm"))]
12561  #[test]
12562  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
12563    use std::sync::mpsc;
12564
12565    struct SelfWakingFuture {
12566      remaining: usize,
12567    }
12568
12569    impl Future for SelfWakingFuture {
12570      type Output = ();
12571
12572      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
12573        if self.remaining == 0 {
12574          return Poll::Ready(());
12575        }
12576        self.remaining -= 1;
12577        cx.waker().wake_by_ref();
12578        Poll::Pending
12579      }
12580    }
12581
12582    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
12583    let old_generation = controller.backend().generation();
12584    controller
12585      .try_spawn_detached(SelfWakingFuture {
12586        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
12587      })
12588      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
12589    let dispatch = controller_current_thread_dispatch(&controller);
12590
12591    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12592    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12593    let driver_controller = Arc::clone(&controller);
12594    let driver = std::thread::spawn(move || {
12595      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12596        *slot.borrow_mut() = Some(Box::new(move || {
12597          dispatch_entered_tx.send(()).unwrap();
12598          release_dispatch_rx.recv().unwrap();
12599        }));
12600      });
12601      driver_controller.drive_current_thread_dispatch(dispatch, false);
12602    });
12603    dispatch_entered_rx
12604      .recv_timeout(Duration::from_secs(2))
12605      .expect("the bounded host turn must begin publishing its continuation");
12606
12607    let (shutdown_tx, shutdown_rx) = mpsc::channel();
12608    let shutdown_controller = Arc::clone(&controller);
12609    let shutdown = std::thread::spawn(move || {
12610      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12611    });
12612    wait_until("CurrentThread shutdown to leave Running", || {
12613      let state = controller
12614        .state
12615        .lock()
12616        .unwrap_or_else(std::sync::PoisonError::into_inner);
12617      !matches!(
12618        &state.lifecycle,
12619        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12620      )
12621    });
12622
12623    let (restart_tx, restart_rx) = mpsc::channel();
12624    let restart_controller = Arc::clone(&controller);
12625    let restart = std::thread::spawn(move || {
12626      let result = restart_controller
12627        .start()
12628        .map(|()| restart_controller.backend().generation());
12629      restart_tx.send(result).unwrap();
12630    });
12631
12632    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12633    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12634    let shutdown_overlapped = early_shutdown.is_some();
12635    let restart_overlapped = early_restart.is_some();
12636    release_dispatch_tx.send(()).unwrap();
12637    driver.join().unwrap();
12638
12639    let shutdown_result = early_shutdown.unwrap_or_else(|| {
12640      shutdown_rx
12641        .recv_timeout(Duration::from_secs(2))
12642        .expect("shutdown must finish after continuation publication retires")
12643    });
12644    shutdown_result.unwrap();
12645    let new_generation = early_restart.unwrap_or_else(|| {
12646      restart_rx
12647        .recv_timeout(Duration::from_secs(2))
12648        .expect("restart must follow continuation publication and shutdown")
12649    });
12650    let new_generation = new_generation.unwrap();
12651    shutdown.join().unwrap();
12652    restart.join().unwrap();
12653
12654    assert!(
12655      !shutdown_overlapped,
12656      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
12657    );
12658    assert!(
12659      !restart_overlapped,
12660      "restart must not overlap an old-generation continuation dispatch"
12661    );
12662    assert_ne!(new_generation, old_generation);
12663    controller.shutdown().unwrap();
12664  }
12665
12666  #[cfg(not(target_family = "wasm"))]
12667  #[test]
12668  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
12669    use std::sync::mpsc;
12670
12671    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
12672    let old_generation = controller.backend().generation();
12673    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
12674    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
12675    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
12676    let submit_controller = Arc::clone(&controller);
12677    let reentrant_controller = Arc::clone(&controller);
12678    let submitter = std::thread::spawn(move || {
12679      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12680        *slot.borrow_mut() = Some(Box::new(move || {
12681          reentrant_shutdown_tx
12682            .send(reentrant_controller.shutdown())
12683            .unwrap();
12684          dispatch_entered_tx.send(()).unwrap();
12685          release_dispatch_rx.recv().unwrap();
12686        }));
12687      });
12688      submit_controller
12689        .try_spawn_detached(std::future::pending::<()>())
12690        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
12691    });
12692
12693    let reentrant_shutdown = reentrant_shutdown_rx
12694      .recv_timeout(Duration::from_secs(2))
12695      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
12696    dispatch_entered_rx
12697      .recv_timeout(Duration::from_secs(2))
12698      .expect("the initial host dispatch publication must reach the injected pause");
12699
12700    let (shutdown_tx, shutdown_rx) = mpsc::channel();
12701    let shutdown_controller = Arc::clone(&controller);
12702    let shutdown = std::thread::spawn(move || {
12703      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
12704    });
12705    wait_until("CurrentThread shutdown to leave Running", || {
12706      let state = controller
12707        .state
12708        .lock()
12709        .unwrap_or_else(std::sync::PoisonError::into_inner);
12710      !matches!(
12711        &state.lifecycle,
12712        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
12713      )
12714    });
12715
12716    let (restart_tx, restart_rx) = mpsc::channel();
12717    let restart_controller = Arc::clone(&controller);
12718    let restart = std::thread::spawn(move || {
12719      let result = restart_controller
12720        .start()
12721        .map(|()| restart_controller.backend().generation());
12722      restart_tx.send(result).unwrap();
12723    });
12724
12725    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
12726    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
12727    let shutdown_overlapped = early_shutdown.is_some();
12728    let restart_overlapped = early_restart.is_some();
12729    release_dispatch_tx.send(()).unwrap();
12730    submitter.join().unwrap();
12731
12732    let shutdown_result = early_shutdown.unwrap_or_else(|| {
12733      shutdown_rx
12734        .recv_timeout(Duration::from_secs(2))
12735        .expect("shutdown must finish after initial dispatch publication retires")
12736    });
12737    shutdown_result.unwrap();
12738    let new_generation = early_restart.unwrap_or_else(|| {
12739      restart_rx
12740        .recv_timeout(Duration::from_secs(2))
12741        .expect("restart must follow initial dispatch publication and shutdown")
12742    });
12743    let new_generation = new_generation.unwrap();
12744    shutdown.join().unwrap();
12745    restart.join().unwrap();
12746
12747    let reentrant_error = reentrant_shutdown
12748      .expect_err("dispatch publication must be generation-scoped scheduler work");
12749    assert!(
12750      reentrant_error
12751        .to_string()
12752        .contains("work running on that runtime")
12753    );
12754    assert!(
12755      !shutdown_overlapped,
12756      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
12757    );
12758    assert!(
12759      !restart_overlapped,
12760      "restart must not overlap an old-generation host dispatch call"
12761    );
12762    assert_ne!(new_generation, old_generation);
12763    controller.shutdown().unwrap();
12764  }
12765
12766  #[test]
12767  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
12768    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12769      .lock()
12770      .unwrap()
12771      .clear();
12772    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12773    let metrics = Arc::new(RuntimeMetrics::default());
12774    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12775      Arc::clone(&metrics),
12776      panic_once_current_thread_host_dispatch,
12777    ));
12778    let scheduler = Arc::clone(&executor);
12779    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
12780      scheduler.schedule(runnable)
12781    });
12782
12783    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
12784    assert!(
12785      publication.is_err(),
12786      "the injected host dispatch panic must unwind"
12787    );
12788    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12789    {
12790      let scheduler = executor
12791        .scheduler_idle_lock
12792        .lock()
12793        .unwrap_or_else(std::sync::PoisonError::into_inner);
12794      assert_eq!(scheduler.active_dispatch_calls, 0);
12795      assert!(scheduler.dispatch_publications.is_empty());
12796    }
12797
12798    executor.request_drain_if_queued();
12799
12800    assert_ne!(dispatch, 0);
12801    assert_eq!(
12802      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12803        .lock()
12804        .unwrap(),
12805      vec![dispatch, dispatch],
12806      "a later host registration must retry the same pending capability"
12807    );
12808    {
12809      let scheduler = executor
12810        .scheduler_idle_lock
12811        .lock()
12812        .unwrap_or_else(std::sync::PoisonError::into_inner);
12813      assert_eq!(scheduler.active_dispatch_calls, 0);
12814      assert!(scheduler.dispatch_publications.is_empty());
12815    }
12816    executor.drive_host_turn_for_dispatch(dispatch);
12817    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
12818    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12819  }
12820
12821  #[cfg(not(target_family = "wasm"))]
12822  #[test]
12823  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
12824    use std::sync::mpsc;
12825
12826    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12827      .lock()
12828      .unwrap()
12829      .clear();
12830    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
12831    let metrics = Arc::new(RuntimeMetrics::default());
12832    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12833      Arc::clone(&metrics),
12834      panic_once_current_thread_armed_republish_host_dispatch,
12835    ));
12836    let scheduler = Arc::clone(&executor);
12837    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
12838      scheduler.schedule(runnable)
12839    });
12840
12841    let (called_tx, called_rx) = mpsc::channel();
12842    let (release_tx, release_rx) = mpsc::channel();
12843    let publishing_executor = Arc::clone(&executor);
12844    let publication = std::thread::spawn(move || {
12845      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12846        *slot.borrow_mut() = Some(Box::new(move || {
12847          called_tx.send(()).unwrap();
12848          release_rx.recv().unwrap();
12849        }));
12850      });
12851      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12852    });
12853    called_rx
12854      .recv_timeout(Duration::from_secs(2))
12855      .expect("the first publication must pause before calling the host dispatcher");
12856
12857    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12858    executor.request_drain_if_queued();
12859    let publication_state = {
12860      let scheduler = executor
12861        .scheduler_idle_lock
12862        .lock()
12863        .unwrap_or_else(std::sync::PoisonError::into_inner);
12864      (
12865        scheduler.active_dispatch_calls,
12866        scheduler.dispatch_publications.len(),
12867        scheduler.dispatch_publications[0].dispatch,
12868        scheduler.dispatch_publications[0].republish_requested,
12869      )
12870    };
12871    release_tx.send(()).unwrap();
12872
12873    let publication = publication.join().unwrap();
12874    assert!(
12875      publication.is_err(),
12876      "the original host-dispatch panic must resume"
12877    );
12878    assert_ne!(dispatch, 0);
12879    assert_eq!(publication_state, (1, 1, dispatch, true));
12880    assert_eq!(
12881      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12882        .lock()
12883        .unwrap(),
12884      vec![dispatch, dispatch],
12885      "the armed request must retry automatically before the original panic resumes"
12886    );
12887    {
12888      let scheduler = executor
12889        .scheduler_idle_lock
12890        .lock()
12891        .unwrap_or_else(std::sync::PoisonError::into_inner);
12892      assert_eq!(scheduler.active_dispatch_calls, 0);
12893      assert!(scheduler.dispatch_publications.is_empty());
12894    }
12895    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
12896    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12897
12898    executor.drive_host_turn_for_dispatch(dispatch);
12899    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
12900    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12901  }
12902
12903  #[cfg(not(target_family = "wasm"))]
12904  #[test]
12905  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
12906    use std::sync::mpsc;
12907
12908    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12909      .lock()
12910      .unwrap()
12911      .clear();
12912    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
12913    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12914    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
12915    {
12916      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12917        .state
12918        .lock()
12919        .unwrap_or_else(std::sync::PoisonError::into_inner);
12920      assert!(state.entries.is_empty());
12921      assert!(state.dispatches.is_empty());
12922    }
12923    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12924      .register(Arc::new(FailDuringAuthoritativePanicPublication));
12925    let metrics = Arc::new(RuntimeMetrics::default());
12926    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
12927      Arc::clone(&metrics),
12928      dispatch_authoritative_panic_current_thread_tasks,
12929      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
12930    ));
12931    let scheduler = Arc::clone(&executor);
12932    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
12933      scheduler.schedule(runnable)
12934    });
12935
12936    let (called_tx, called_rx) = mpsc::channel();
12937    let (release_tx, release_rx) = mpsc::channel();
12938    let publishing_executor = Arc::clone(&executor);
12939    let publication = std::thread::spawn(move || {
12940      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
12941        *slot.borrow_mut() = Some(Box::new(move || {
12942          called_tx.send(()).unwrap();
12943          release_rx.recv().unwrap();
12944        }));
12945      });
12946      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
12947    });
12948    called_rx
12949      .recv_timeout(Duration::from_secs(2))
12950      .expect("the first publication must pause before raising its panic");
12951
12952    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
12953    executor.request_drain_if_queued();
12954    release_tx.send(()).unwrap();
12955
12956    let payload = publication
12957      .join()
12958      .unwrap()
12959      .expect_err("the first host-dispatch panic must resume");
12960    assert!(
12961      payload.is::<FirstHostileCurrentThreadPanic>(),
12962      "the first panic must remain authoritative over nested recovery"
12963    );
12964    assert_eq!(
12965      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12966      1,
12967      "the later hostile payload must be discarded under containment"
12968    );
12969    assert_eq!(
12970      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12971      0,
12972      "the authoritative payload must remain owned by the resumed unwind"
12973    );
12974    discard_panic_payload(payload);
12975    assert_eq!(
12976      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
12977      1
12978    );
12979
12980    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12981      .lock()
12982      .unwrap()
12983      .clone();
12984    assert_eq!(dispatches.len(), 3);
12985    let replacement_dispatch = dispatches[2];
12986    assert_eq!(dispatches[0], failed_dispatch);
12987    assert_eq!(dispatches[1], failed_dispatch);
12988    assert_ne!(replacement_dispatch, failed_dispatch);
12989    assert_eq!(
12990      executor.dispatch_pending.load(Ordering::Acquire),
12991      replacement_dispatch
12992    );
12993    assert_eq!(
12994      executor.recovery_dispatch.load(Ordering::Acquire),
12995      replacement_dispatch
12996    );
12997    {
12998      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
12999        .state
13000        .lock()
13001        .unwrap_or_else(std::sync::PoisonError::into_inner);
13002      assert!(
13003        !state.dispatches.contains_key(&failed_dispatch),
13004        "nested recovery unwind must retire the exact failed epoch"
13005      );
13006      assert!(!state.dispatches.contains_key(&replacement_dispatch));
13007    }
13008
13009    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
13010    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13011    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13012      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13013    executor.request_drain_if_queued();
13014
13015    assert_eq!(
13016      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13017        .lock()
13018        .unwrap(),
13019      vec![
13020        failed_dispatch,
13021        failed_dispatch,
13022        replacement_dispatch,
13023        replacement_dispatch
13024      ],
13025      "retry must retain the replacement reserved before the nested panic"
13026    );
13027    let delivery = healthy.deliveries()[0];
13028    assert_eq!(
13029      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
13030      Some(replacement_dispatch)
13031    );
13032    let mut host_turn = executor
13033      .try_admit_host_turn(replacement_dispatch)
13034      .expect("the retried replacement delivery must be admitted");
13035    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13036    host_turn.drive();
13037    assert!(
13038      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13039        .finish_delivery(delivery, false)
13040        .failed_dispatches
13041        .is_empty()
13042    );
13043    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
13044    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13045
13046    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
13047    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13048      .state
13049      .lock()
13050      .unwrap_or_else(std::sync::PoisonError::into_inner);
13051    assert!(state.entries.is_empty());
13052    assert!(state.dispatches.is_empty());
13053  }
13054
13055  #[cfg(not(target_family = "wasm"))]
13056  #[test]
13057  fn current_thread_shutdown_suppresses_queued_republish() {
13058    use std::sync::mpsc;
13059
13060    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13061    let metrics = Arc::new(RuntimeMetrics::default());
13062    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13063      Arc::clone(&metrics),
13064      count_current_thread_shutdown_republish_host_dispatch,
13065    ));
13066    let scheduler = Arc::clone(&executor);
13067    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13068      scheduler.schedule(runnable)
13069    });
13070
13071    let (returned_tx, returned_rx) = mpsc::channel();
13072    let (release_tx, release_rx) = mpsc::channel();
13073    let publishing_executor = Arc::clone(&executor);
13074    let publication = std::thread::spawn(move || {
13075      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13076        *slot.borrow_mut() = Some(Box::new(move || {
13077          returned_tx.send(()).unwrap();
13078          release_rx.recv().unwrap();
13079        }));
13080      });
13081      publishing_executor.schedule(runnable);
13082    });
13083    returned_rx
13084      .recv_timeout(Duration::from_secs(2))
13085      .expect("the initial publication must reach its completion barrier");
13086
13087    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13088    executor.request_drain_if_queued();
13089    let publication_state = {
13090      let scheduler = executor
13091        .scheduler_idle_lock
13092        .lock()
13093        .unwrap_or_else(std::sync::PoisonError::into_inner);
13094      (
13095        scheduler.active_dispatch_calls,
13096        scheduler.dispatch_publications.len(),
13097        scheduler.dispatch_publications[0].dispatch,
13098        scheduler.dispatch_publications[0].republish_requested,
13099      )
13100    };
13101    executor.begin_shutdown();
13102    release_tx.send(()).unwrap();
13103    publication.join().unwrap();
13104    executor.wait_until_scheduler_idle();
13105
13106    assert_ne!(dispatch, 0);
13107    assert_eq!(publication_state, (1, 1, dispatch, true));
13108    assert_eq!(
13109      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
13110      1
13111    );
13112    {
13113      let scheduler = executor
13114        .scheduler_idle_lock
13115        .lock()
13116        .unwrap_or_else(std::sync::PoisonError::into_inner);
13117      assert_eq!(scheduler.active_dispatch_calls, 0);
13118      assert!(scheduler.dispatch_publications.is_empty());
13119    }
13120    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13121    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13122    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13123    assert_eq!(futures::executor::block_on(task.fallible()), None);
13124  }
13125
13126  #[cfg(not(target_family = "wasm"))]
13127  #[test]
13128  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
13129    use std::sync::mpsc;
13130
13131    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13132      .lock()
13133      .unwrap()
13134      .clear();
13135    let metrics = Arc::new(RuntimeMetrics::default());
13136    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13137      Arc::clone(&metrics),
13138      record_current_thread_republish_rearm_dispatch,
13139    ));
13140    let scheduler = Arc::clone(&executor);
13141    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
13142      scheduler.schedule(runnable)
13143    });
13144
13145    let (first_returned_tx, first_returned_rx) = mpsc::channel();
13146    let (release_first_tx, release_first_rx) = mpsc::channel();
13147    let (second_returned_tx, second_returned_rx) = mpsc::channel();
13148    let (release_second_tx, release_second_rx) = mpsc::channel();
13149    let publishing_executor = Arc::clone(&executor);
13150    let publication = std::thread::spawn(move || {
13151      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13152        *slot.borrow_mut() = Some(Box::new(move || {
13153          first_returned_tx.send(()).unwrap();
13154          release_first_rx.recv().unwrap();
13155          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13156            *slot.borrow_mut() = Some(Box::new(move || {
13157              second_returned_tx.send(()).unwrap();
13158              release_second_rx.recv().unwrap();
13159            }));
13160          });
13161        }));
13162      });
13163      publishing_executor.schedule(runnable);
13164    });
13165    first_returned_rx
13166      .recv_timeout(Duration::from_secs(2))
13167      .expect("the first publication must reach its completion barrier");
13168
13169    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13170    executor.request_drain_if_queued();
13171    executor.request_drain_if_queued();
13172    let first_publication_state = {
13173      let scheduler = executor
13174        .scheduler_idle_lock
13175        .lock()
13176        .unwrap_or_else(std::sync::PoisonError::into_inner);
13177      (
13178        scheduler.active_dispatch_calls,
13179        scheduler.dispatch_publications.len(),
13180        scheduler.dispatch_publications[0].dispatch,
13181        scheduler.dispatch_publications[0].republish_requested,
13182      )
13183    };
13184    release_first_tx.send(()).unwrap();
13185    second_returned_rx
13186      .recv_timeout(Duration::from_secs(2))
13187      .expect("the second publication must reach its completion barrier");
13188
13189    executor.request_drain_if_queued();
13190    executor.request_drain_if_queued();
13191    let second_publication_state = {
13192      let scheduler = executor
13193        .scheduler_idle_lock
13194        .lock()
13195        .unwrap_or_else(std::sync::PoisonError::into_inner);
13196      (
13197        scheduler.active_dispatch_calls,
13198        scheduler.dispatch_publications.len(),
13199        scheduler.dispatch_publications[0].dispatch,
13200        scheduler.dispatch_publications[0].republish_requested,
13201      )
13202    };
13203    release_second_tx.send(()).unwrap();
13204    publication.join().unwrap();
13205
13206    assert_ne!(dispatch, 0);
13207    assert_eq!(first_publication_state, (1, 1, dispatch, true));
13208    assert_eq!(second_publication_state, (1, 1, dispatch, true));
13209    assert_eq!(
13210      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
13211      vec![dispatch, dispatch, dispatch],
13212      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
13213    );
13214    {
13215      let scheduler = executor
13216        .scheduler_idle_lock
13217        .lock()
13218        .unwrap_or_else(std::sync::PoisonError::into_inner);
13219      assert_eq!(scheduler.active_dispatch_calls, 0);
13220      assert!(scheduler.dispatch_publications.is_empty());
13221    }
13222    executor.drive_host_turn_for_dispatch(dispatch);
13223    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
13224    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13225  }
13226
13227  #[cfg(not(target_family = "wasm"))]
13228  #[test]
13229  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
13230    use std::sync::mpsc;
13231
13232    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13233      .lock()
13234      .unwrap()
13235      .clear();
13236    let metrics = Arc::new(RuntimeMetrics::default());
13237    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13238      Arc::clone(&metrics),
13239      reject_initial_current_thread_publication_once,
13240    ));
13241    let scheduler = Arc::clone(&executor);
13242    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
13243      scheduler.schedule(runnable)
13244    });
13245
13246    let (returned_tx, returned_rx) = mpsc::channel();
13247    let (release_tx, release_rx) = mpsc::channel();
13248    let publishing_executor = Arc::clone(&executor);
13249    let publication = std::thread::spawn(move || {
13250      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13251        *slot.borrow_mut() = Some(Box::new(move || {
13252          returned_tx.send(()).unwrap();
13253          release_rx.recv().unwrap();
13254        }));
13255      });
13256      publishing_executor.schedule(runnable);
13257    });
13258    returned_rx
13259      .recv_timeout(Duration::from_secs(2))
13260      .expect("the unavailable initial publication must reach its completion barrier");
13261
13262    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13263    executor.request_drain_if_queued();
13264    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13265      .lock()
13266      .unwrap()
13267      .len();
13268
13269    release_tx.send(()).unwrap();
13270    publication.join().unwrap();
13271
13272    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13273      .lock()
13274      .unwrap()
13275      .clone();
13276    assert_ne!(dispatch, 0);
13277    assert_eq!(
13278      attempts_while_unavailable_is_paused, 1,
13279      "a racing host request must join the in-flight publication instead of retiring it independently"
13280    );
13281    assert_eq!(
13282      dispatches,
13283      vec![dispatch, dispatch],
13284      "the publication owner must retry the same internal capability after the new host joins"
13285    );
13286    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
13287    executor.drive_host_turn_for_dispatch(dispatch);
13288    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
13289    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13290  }
13291
13292  #[cfg(not(target_family = "wasm"))]
13293  #[test]
13294  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
13295    use std::sync::mpsc;
13296
13297    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13298      .lock()
13299      .unwrap()
13300      .clear();
13301    let metrics = Arc::new(RuntimeMetrics::default());
13302    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13303      Arc::clone(&metrics),
13304      reject_current_thread_replacement_publication_once,
13305    ));
13306    let scheduler = Arc::clone(&executor);
13307    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
13308      scheduler.schedule(runnable)
13309    });
13310    executor.schedule(runnable);
13311    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13312    assert_ne!(failed_dispatch, 0);
13313
13314    let (returned_tx, returned_rx) = mpsc::channel();
13315    let (release_tx, release_rx) = mpsc::channel();
13316    let cancelling_executor = Arc::clone(&executor);
13317    let cancellation = std::thread::spawn(move || {
13318      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13319        *slot.borrow_mut() = Some(Box::new(move || {
13320          returned_tx.send(()).unwrap();
13321          release_rx.recv().unwrap();
13322        }));
13323      });
13324      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
13325    });
13326    returned_rx
13327      .recv_timeout(Duration::from_secs(2))
13328      .expect("the unavailable replacement publication must reach its completion barrier");
13329
13330    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13331    executor.request_drain_if_queued();
13332    let attempts_while_unavailable_is_paused =
13333      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13334        .lock()
13335        .unwrap()
13336        .len();
13337
13338    release_tx.send(()).unwrap();
13339    cancellation.join().unwrap();
13340
13341    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
13342      .lock()
13343      .unwrap()
13344      .clone();
13345    assert_ne!(replacement_dispatch, 0);
13346    assert_ne!(replacement_dispatch, failed_dispatch);
13347    assert_eq!(
13348      attempts_while_unavailable_is_paused, 2,
13349      "a racing host request must join the in-flight replacement publication"
13350    );
13351    assert_eq!(
13352      dispatches,
13353      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13354      "republication must reuse the sole replacement capability instead of allocating another"
13355    );
13356    assert_eq!(
13357      executor.dispatch_pending.load(Ordering::Acquire),
13358      replacement_dispatch
13359    );
13360    assert_eq!(
13361      executor.recovery_dispatch.load(Ordering::Acquire),
13362      replacement_dispatch
13363    );
13364    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13365
13366    executor.drive_host_turn_for_dispatch(replacement_dispatch);
13367    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
13368    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13369    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13370  }
13371
13372  #[cfg(not(target_family = "wasm"))]
13373  #[test]
13374  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
13375    use std::sync::mpsc;
13376
13377    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13378      .lock()
13379      .unwrap()
13380      .clear();
13381    {
13382      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13383        .state
13384        .lock()
13385        .unwrap_or_else(std::sync::PoisonError::into_inner);
13386      assert!(state.entries.is_empty());
13387      assert!(state.dispatches.is_empty());
13388    }
13389    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13390    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13391      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13392    let metrics = Arc::new(RuntimeMetrics::default());
13393    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13394      Arc::clone(&metrics),
13395      fail_current_thread_replacement_publication_once,
13396      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
13397    ));
13398    let scheduler = Arc::clone(&executor);
13399    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
13400      scheduler.schedule(runnable)
13401    });
13402    executor.schedule(runnable);
13403    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13404    assert_ne!(failed_dispatch, 0);
13405    let initial_delivery = initial.deliveries()[0];
13406    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13407      .finish_delivery(initial_delivery, true);
13408    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
13409      panic!("the initial accepted delivery must report one exact failure");
13410    };
13411    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
13412    let failed_dispatch_failure = *failed_dispatch_failure;
13413    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
13414    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13415      .register(Arc::new(FailDuringReplacementRepublishPublication));
13416
13417    let (returned_tx, returned_rx) = mpsc::channel();
13418    let (release_tx, release_rx) = mpsc::channel();
13419    let cancelling_executor = Arc::clone(&executor);
13420    let cancellation = std::thread::spawn(move || {
13421      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13422        *slot.borrow_mut() = Some(Box::new(move || {
13423          returned_tx.send(()).unwrap();
13424          release_rx.recv().unwrap();
13425        }));
13426      });
13427      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
13428    });
13429    returned_rx
13430      .recv_timeout(Duration::from_secs(2))
13431      .expect("the failed replacement publication must reach its completion barrier");
13432
13433    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13434    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13435    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13436      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13437    executor.request_drain_if_queued();
13438    let publication_state = {
13439      let scheduler = executor
13440        .scheduler_idle_lock
13441        .lock()
13442        .unwrap_or_else(std::sync::PoisonError::into_inner);
13443      (
13444        scheduler.active_dispatch_calls,
13445        scheduler.dispatch_publications.len(),
13446        scheduler.dispatch_publications[0].dispatch,
13447        scheduler.dispatch_publications[0].republish_requested,
13448      )
13449    };
13450    release_tx.send(()).unwrap();
13451    cancellation.join().unwrap();
13452
13453    assert_ne!(replacement_dispatch, 0);
13454    assert_ne!(replacement_dispatch, failed_dispatch);
13455    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
13456    assert_eq!(
13457      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
13458        .lock()
13459        .unwrap(),
13460      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13461      "the newly armed host request must receive the exact replacement before cancellation"
13462    );
13463    assert_eq!(
13464      executor.dispatch_pending.load(Ordering::Acquire),
13465      replacement_dispatch
13466    );
13467    assert_eq!(
13468      executor.recovery_dispatch.load(Ordering::Acquire),
13469      replacement_dispatch
13470    );
13471    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13472
13473    let delivery = healthy.deliveries()[0];
13474    assert_eq!(delivery.dispatch, replacement_dispatch);
13475    assert_eq!(
13476      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13477        .claim_delivery(delivery.capability()),
13478      Some(replacement_dispatch)
13479    );
13480    let mut host_turn = executor
13481      .try_admit_host_turn(replacement_dispatch)
13482      .expect("the rearmed replacement delivery must be admitted");
13483    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13484    host_turn.drive();
13485    assert!(
13486      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13487        .finish_delivery(delivery, false)
13488        .failed_dispatches
13489        .is_empty()
13490    );
13491    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
13492    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13493    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13494
13495    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
13496    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
13497    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
13498      .state
13499      .lock()
13500      .unwrap_or_else(std::sync::PoisonError::into_inner);
13501    assert!(state.entries.is_empty());
13502    assert!(state.dispatches.is_empty());
13503  }
13504
13505  #[cfg(not(target_family = "wasm"))]
13506  #[test]
13507  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
13508    use std::sync::mpsc;
13509
13510    {
13511      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13512        .state
13513        .lock()
13514        .unwrap_or_else(std::sync::PoisonError::into_inner);
13515      assert!(state.entries.is_empty());
13516      assert!(state.dispatches.is_empty());
13517    }
13518    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
13519    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13520      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
13521    let metrics = Arc::new(RuntimeMetrics::default());
13522    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13523      Arc::clone(&metrics),
13524      dispatch_stale_failure_current_thread_tasks,
13525      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
13526    ));
13527    let scheduler = Arc::clone(&executor);
13528    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
13529      scheduler.schedule(runnable)
13530    });
13531    executor.schedule(runnable);
13532
13533    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13534    let initial_delivery = initial.deliveries()[0];
13535    let delayed_completion =
13536      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
13537    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
13538      panic!("the initial accepted delivery must report one exact failure");
13539    };
13540    assert_eq!(
13541      *delayed_failure,
13542      current_thread_dispatch_failure(dispatch, 0)
13543    );
13544    let delayed_failure = *delayed_failure;
13545
13546    let (returned_tx, returned_rx) = mpsc::channel();
13547    let (release_tx, release_rx) = mpsc::channel();
13548    let publishing_executor = Arc::clone(&executor);
13549    let publication = std::thread::spawn(move || {
13550      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13551        *slot.borrow_mut() = Some(Box::new(move || {
13552          returned_tx.send(()).unwrap();
13553          release_rx.recv().unwrap();
13554        }));
13555      });
13556      publishing_executor.request_drain_if_queued();
13557    });
13558    returned_rx
13559      .recv_timeout(Duration::from_secs(2))
13560      .expect("the old failed epoch must pause before executor recovery");
13561
13562    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13563    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13564      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13565    executor.request_drain_if_queued();
13566    release_tx.send(()).unwrap();
13567    publication.join().unwrap();
13568
13569    let healthy_delivery = healthy.deliveries()[0];
13570    let delayed_initial_delivery = initial.deliveries()[1];
13571    {
13572      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13573        .state
13574        .lock()
13575        .unwrap_or_else(std::sync::PoisonError::into_inner);
13576      let dispatch_state = state
13577        .dispatches
13578        .get(&dispatch)
13579        .expect("the rearmed dispatch must remain registered");
13580      assert_eq!(dispatch_state.epoch, 1);
13581      assert!(!dispatch_state.is_cancelling());
13582    }
13583
13584    executor.cancel_host_dispatch(delayed_failure);
13585
13586    assert_eq!(
13587      executor.dispatch_pending.load(Ordering::Acquire),
13588      dispatch,
13589      "the delayed old-epoch failure must not consume the rearmed executor capability"
13590    );
13591    assert_eq!(
13592      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13593        .state
13594        .lock()
13595        .unwrap_or_else(std::sync::PoisonError::into_inner)
13596        .dispatches
13597        .get(&dispatch)
13598        .map(|state| (state.epoch, state.phase)),
13599      Some((1, CurrentThreadDispatchPhase::Pending)),
13600      "the delayed old-epoch cancellation must not remove the accepted rearm"
13601    );
13602
13603    assert_eq!(
13604      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
13605      Some(dispatch)
13606    );
13607    let mut host_turn = executor
13608      .try_admit_host_turn(dispatch)
13609      .expect("the current-epoch delivery must be admitted");
13610    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
13611    host_turn.drive();
13612    assert!(
13613      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13614        .finish_delivery(healthy_delivery, false)
13615        .failed_dispatches
13616        .is_empty()
13617    );
13618    assert!(
13619      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13620        .finish_delivery(delayed_initial_delivery, false)
13621        .failed_dispatches
13622        .is_empty()
13623    );
13624    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
13625    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13626
13627    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
13628    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
13629    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
13630      .state
13631      .lock()
13632      .unwrap_or_else(std::sync::PoisonError::into_inner);
13633    assert!(state.entries.is_empty());
13634    assert!(state.dispatches.is_empty());
13635  }
13636
13637  #[cfg(not(target_family = "wasm"))]
13638  #[test]
13639  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
13640    use std::sync::mpsc;
13641
13642    {
13643      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13644        .state
13645        .lock()
13646        .unwrap_or_else(std::sync::PoisonError::into_inner);
13647      assert!(state.entries.is_empty());
13648      assert!(state.dispatches.is_empty());
13649    }
13650    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13651      .register(Arc::new(FailDuringLateRegistrationPublication));
13652
13653    let metrics = Arc::new(RuntimeMetrics::default());
13654    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13655      Arc::clone(&metrics),
13656      dispatch_late_registration_current_thread_tasks,
13657      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
13658    ));
13659    let scheduler = Arc::clone(&executor);
13660    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13661      scheduler.schedule(runnable)
13662    });
13663    metrics.runnable_scheduled();
13664    executor
13665      .queue
13666      .lock()
13667      .unwrap_or_else(std::sync::PoisonError::into_inner)
13668      .runnables
13669      .push_back(runnable);
13670    let replacement_dispatch = next_current_thread_dispatch_id();
13671    executor
13672      .recovery_dispatch
13673      .store(replacement_dispatch, Ordering::Release);
13674    executor
13675      .dispatch_pending
13676      .store(replacement_dispatch, Ordering::Release);
13677
13678    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
13679    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
13680    let publishing_executor = Arc::clone(&executor);
13681    let publication = std::thread::spawn(move || {
13682      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13683        *slot.borrow_mut() = Some(Box::new(move || {
13684          owner_retired_tx.send(()).unwrap();
13685          request_blocked_rx
13686            .recv_timeout(Duration::from_secs(2))
13687            .expect("the binding-like drain request must wait for the failure transition");
13688        }));
13689      });
13690      publishing_executor.request_drain_if_queued();
13691    });
13692    owner_retired_rx
13693      .recv_timeout(Duration::from_secs(2))
13694      .expect("the failed replacement publication must retire its owner");
13695
13696    // Match the binding sequence: publish the host registration first, then
13697    // request service for work that may have accumulated before registration.
13698    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13699    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13700      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13701    let requesting_executor = Arc::clone(&executor);
13702    let request = std::thread::spawn(move || {
13703      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13704        *slot.borrow_mut() = Some(Box::new(move || {
13705          request_blocked_tx.send(()).unwrap();
13706        }));
13707      });
13708      requesting_executor.request_drain_if_queued();
13709    });
13710
13711    publication.join().unwrap();
13712    request.join().unwrap();
13713
13714    assert!(
13715      healthy.deliveries().is_empty(),
13716      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
13717    );
13718    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13719    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13720    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13721    assert_eq!(futures::executor::block_on(task.fallible()), None);
13722    {
13723      let scheduler = executor
13724        .scheduler_idle_lock
13725        .lock()
13726        .unwrap_or_else(std::sync::PoisonError::into_inner);
13727      assert_eq!(scheduler.active_dispatch_calls, 0);
13728      assert!(scheduler.dispatch_publications.is_empty());
13729    }
13730
13731    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
13732
13733    let scheduler = Arc::clone(&executor);
13734    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
13735      scheduler.schedule(runnable)
13736    });
13737    executor.schedule(later_runnable);
13738    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13739    let later_delivery = healthy.deliveries()[0];
13740    assert_eq!(
13741      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
13742      Some(later_dispatch)
13743    );
13744    let mut host_turn = executor
13745      .try_admit_host_turn(later_dispatch)
13746      .expect("the later exact delivery must be admitted");
13747    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
13748    host_turn.drive();
13749    assert!(
13750      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13751        .finish_delivery(later_delivery, false)
13752        .failed_dispatches
13753        .is_empty()
13754    );
13755    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
13756    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13757
13758    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
13759    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
13760      .state
13761      .lock()
13762      .unwrap_or_else(std::sync::PoisonError::into_inner);
13763    assert!(state.entries.is_empty());
13764    assert!(state.dispatches.is_empty());
13765  }
13766
13767  #[cfg(not(target_family = "wasm"))]
13768  #[test]
13769  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
13770  {
13771    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13772      .lock()
13773      .unwrap()
13774      .clear();
13775    {
13776      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13777        .state
13778        .lock()
13779        .unwrap_or_else(std::sync::PoisonError::into_inner);
13780      assert!(state.entries.is_empty());
13781      assert!(state.dispatches.is_empty());
13782    }
13783    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13784      .next_delivery
13785      .store(0, Ordering::Relaxed);
13786    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13787      .register(Arc::new(FailDuringNestedUnwindPublication));
13788
13789    let metrics = Arc::new(RuntimeMetrics::default());
13790    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13791      Arc::clone(&metrics),
13792      dispatch_nested_unwind_current_thread_tasks,
13793      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
13794    ));
13795    let scheduler = Arc::clone(&executor);
13796    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
13797      scheduler.schedule(runnable)
13798    });
13799
13800    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
13801      *slot.borrow_mut() = Some(Box::new(|| {
13802        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13803          .next_delivery
13804          .store(u64::MAX, Ordering::Relaxed);
13805      }));
13806    });
13807    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13808    assert!(
13809      publication.is_err(),
13810      "replacement delivery-id exhaustion must unwind through the outer failed publication"
13811    );
13812
13813    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
13814      .lock()
13815      .unwrap()
13816      .clone();
13817    assert_eq!(dispatches.len(), 2);
13818    let failed_dispatch = dispatches[0];
13819    let replacement_dispatch = dispatches[1];
13820    assert_ne!(failed_dispatch, replacement_dispatch);
13821    assert_eq!(
13822      executor.dispatch_pending.load(Ordering::Acquire),
13823      replacement_dispatch
13824    );
13825    assert_eq!(
13826      executor.recovery_dispatch.load(Ordering::Acquire),
13827      replacement_dispatch
13828    );
13829    {
13830      let scheduler = executor
13831        .scheduler_idle_lock
13832        .lock()
13833        .unwrap_or_else(std::sync::PoisonError::into_inner);
13834      assert_eq!(scheduler.active_dispatch_calls, 0);
13835      assert!(scheduler.dispatch_publications.is_empty());
13836    }
13837    {
13838      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13839        .state
13840        .lock()
13841        .unwrap_or_else(std::sync::PoisonError::into_inner);
13842      assert!(
13843        !state.dispatches.contains_key(&failed_dispatch),
13844        "outer unwind cleanup must retire the exact old cancelling dispatch"
13845      );
13846      assert!(
13847        !state.dispatches.contains_key(&replacement_dispatch),
13848        "the replacement publication guard must release its aborted registry state"
13849      );
13850    }
13851
13852    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13853      .next_delivery
13854      .store(1, Ordering::Relaxed);
13855    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
13856    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13857    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13858      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13859
13860    executor.request_drain_if_queued();
13861
13862    assert_eq!(
13863      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
13864      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
13865      "retry must reuse the replacement reserved before the nested unwind"
13866    );
13867    let delivery = healthy.deliveries()[0];
13868    assert_eq!(delivery.dispatch, replacement_dispatch);
13869    assert_eq!(
13870      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
13871      Some(replacement_dispatch)
13872    );
13873    let mut host_turn = executor
13874      .try_admit_host_turn(replacement_dispatch)
13875      .expect("the retried replacement delivery must be admitted");
13876    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13877    host_turn.drive();
13878    assert!(
13879      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13880        .finish_delivery(delivery, false)
13881        .failed_dispatches
13882        .is_empty()
13883    );
13884    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
13885    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13886
13887    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
13888    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
13889      .state
13890      .lock()
13891      .unwrap_or_else(std::sync::PoisonError::into_inner);
13892    assert!(state.entries.is_empty());
13893    assert!(state.dispatches.is_empty());
13894  }
13895
13896  #[test]
13897  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
13898    reset_current_thread_recovery_host_dispatches();
13899    let metrics = Arc::new(RuntimeMetrics::default());
13900    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13901      Arc::clone(&metrics),
13902      count_current_thread_recovery_host_dispatch,
13903    ));
13904
13905    let scheduler = Arc::clone(&executor);
13906    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
13907      scheduler.schedule(runnable)
13908    });
13909    executor.schedule(runnable);
13910    assert_eq!(current_thread_recovery_host_dispatches(), 1);
13911    let failed_dispatch = current_thread_dispatch(&executor);
13912
13913    // Model the host scheduler throwing after Rust accepted the callback but
13914    // before JavaScript could queue a fresh turn.
13915    executor.cancel_host_dispatch_for_test(failed_dispatch);
13916    assert_eq!(
13917      current_thread_recovery_host_dispatches(),
13918      2,
13919      "cancellation must request one replacement without another submission"
13920    );
13921    let replacement_dispatch = current_thread_dispatch(&executor);
13922    assert_ne!(replacement_dispatch, failed_dispatch);
13923    assert_eq!(
13924      executor.recovery_dispatch.load(Ordering::Acquire),
13925      replacement_dispatch
13926    );
13927
13928    executor.drive_host_turn_for_dispatch(replacement_dispatch);
13929    assert_eq!(futures::executor::block_on(task), 1);
13930    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13931    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13932  }
13933
13934  #[test]
13935  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
13936    reset_current_thread_recovery_host_dispatches();
13937    let metrics = Arc::new(RuntimeMetrics::default());
13938    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13939      Arc::clone(&metrics),
13940      count_current_thread_recovery_host_dispatch,
13941    ));
13942    let backend =
13943      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
13944
13945    let registration = backend
13946      .work
13947      .try_register_async()
13948      .expect("the backend must accept work");
13949    let task = spawn_registered(
13950      &backend,
13951      Arc::clone(&metrics),
13952      std::future::pending::<()>(),
13953      registration,
13954    );
13955    let failed_dispatch = current_thread_dispatch(&executor);
13956    executor.cancel_host_dispatch_for_test(failed_dispatch);
13957    let replacement_dispatch = current_thread_dispatch(&executor);
13958
13959    executor.cancel_host_dispatch_for_test(replacement_dispatch);
13960
13961    assert_eq!(
13962      current_thread_recovery_host_dispatches(),
13963      2,
13964      "a second scheduler failure must not create an unbounded retry loop"
13965    );
13966    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13967    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13968    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13969    assert_eq!(
13970      futures::executor::block_on(task).unwrap_err().to_string(),
13971      "async runtime stopped before the task completed",
13972      "terminal host failure must settle the original operation as cancelled"
13973    );
13974    assert_eq!(backend.work.state.lock().unwrap().active, 0);
13975
13976    let later_registration = backend
13977      .work
13978      .try_register_async()
13979      .expect("terminal cancellation must reopen acceptance");
13980    let later_task = spawn_registered(
13981      &backend,
13982      Arc::clone(&metrics),
13983      async { 2usize },
13984      later_registration,
13985    );
13986    assert_eq!(
13987      current_thread_recovery_host_dispatches(),
13988      3,
13989      "terminal cancellation must not permanently close the executor"
13990    );
13991    executor.drive_host_turn();
13992    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
13993  }
13994
13995  #[cfg(not(target_family = "wasm"))]
13996  #[test]
13997  fn current_thread_recovery_reservation_blocks_new_host_publication() {
13998    use std::sync::mpsc;
13999
14000    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
14001    let metrics = Arc::new(RuntimeMetrics::default());
14002    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14003      Arc::clone(&metrics),
14004      count_current_thread_interleaved_recovery_host_dispatch,
14005    ));
14006    let backend =
14007      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14008
14009    let registration = backend
14010      .work
14011      .try_register_async()
14012      .expect("the backend must accept work");
14013    let task = spawn_registered(
14014      &backend,
14015      Arc::clone(&metrics),
14016      std::future::pending::<()>(),
14017      registration,
14018    );
14019    let failed_dispatch = current_thread_dispatch(&executor);
14020    assert_eq!(
14021      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14022      1
14023    );
14024
14025    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
14026    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
14027    let cancelling_executor = Arc::clone(&executor);
14028    let cancellation = std::thread::spawn(move || {
14029      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
14030        *slot.borrow_mut() = Some(Box::new(move || {
14031          recovery_cleared_tx.send(()).unwrap();
14032          release_recovery_rx.recv().unwrap();
14033        }));
14034      });
14035      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14036    });
14037    recovery_cleared_rx
14038      .recv_timeout(Duration::from_secs(2))
14039      .expect("failed dispatch cancellation must pause after consuming the old capability");
14040
14041    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
14042    let (publication_done_tx, publication_done_rx) = mpsc::channel();
14043    let publishing_executor = Arc::clone(&executor);
14044    let publication = std::thread::spawn(move || {
14045      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14046        *slot.borrow_mut() = Some(Box::new(move || {
14047          publication_blocked_tx.send(()).unwrap();
14048        }));
14049      });
14050      publishing_executor.request_drain_if_queued();
14051      publication_done_tx.send(()).unwrap();
14052    });
14053
14054    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
14055      release_recovery_tx.send(()).unwrap();
14056      cancellation.join().unwrap();
14057      publication.join().unwrap();
14058      panic!("a newly registered host must wait for recovery reservation: {error}");
14059    }
14060    assert!(
14061      matches!(
14062        publication_done_rx.try_recv(),
14063        Err(mpsc::TryRecvError::Empty)
14064      ),
14065      "the new host must not publish in the consumed-to-recovery gap"
14066    );
14067    release_recovery_tx.send(()).unwrap();
14068
14069    cancellation.join().unwrap();
14070    publication_done_rx
14071      .recv_timeout(Duration::from_secs(2))
14072      .unwrap();
14073    publication.join().unwrap();
14074
14075    let replacement_dispatch = current_thread_dispatch(&executor);
14076    assert_ne!(replacement_dispatch, failed_dispatch);
14077    assert_eq!(
14078      executor.recovery_dispatch.load(Ordering::Acquire),
14079      replacement_dispatch
14080    );
14081    assert_eq!(
14082      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14083      3,
14084      "the original publication and two host attempts for its one replacement must run"
14085    );
14086
14087    executor.cancel_host_dispatch_for_test(replacement_dispatch);
14088
14089    assert_eq!(
14090      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14091      3,
14092      "failure of the tagged replacement must cancel work without a second replacement"
14093    );
14094    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14095    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14096    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14097    assert_eq!(
14098      futures::executor::block_on(task).unwrap_err().to_string(),
14099      "async runtime stopped before the task completed"
14100    );
14101
14102    let later_registration = backend
14103      .work
14104      .try_register_async()
14105      .expect("terminal cancellation must reopen acceptance");
14106    let later_task = spawn_registered(
14107      &backend,
14108      Arc::clone(&metrics),
14109      async { 2usize },
14110      later_registration,
14111    );
14112    assert_eq!(
14113      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
14114      4
14115    );
14116    executor.drive_host_turn();
14117    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
14118  }
14119
14120  #[cfg(not(target_family = "wasm"))]
14121  #[test]
14122  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
14123    use std::sync::mpsc;
14124
14125    struct BlockingDrop {
14126      entered: mpsc::Sender<()>,
14127      release: mpsc::Receiver<()>,
14128    }
14129
14130    impl Drop for BlockingDrop {
14131      fn drop(&mut self) {
14132        self.entered.send(()).unwrap();
14133        self.release.recv().unwrap();
14134      }
14135    }
14136
14137    reset_current_thread_recovery_host_dispatches();
14138    let metrics = Arc::new(RuntimeMetrics::default());
14139    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14140      Arc::clone(&metrics),
14141      count_current_thread_recovery_host_dispatch,
14142    ));
14143    let backend =
14144      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14145
14146    let holder_registration = backend
14147      .work
14148      .try_register_work()
14149      .expect("the backend must accept the blocking holder");
14150    let (holder_started_tx, holder_started_rx) = mpsc::channel();
14151    let (release_holder_tx, release_holder_rx) = mpsc::channel();
14152    let holder_executor = Arc::clone(&executor);
14153    let holder = std::thread::spawn(move || {
14154      let handle = holder_executor.schedule_blocking(
14155        move || {
14156          holder_started_tx.send(()).unwrap();
14157          release_holder_rx.recv().unwrap();
14158        },
14159        holder_registration,
14160      );
14161      futures::executor::block_on(handle).unwrap();
14162    });
14163    holder_started_rx
14164      .recv_timeout(Duration::from_secs(2))
14165      .expect("the first blocking call must hold the CurrentThread lane");
14166
14167    let cancelled_registration = backend
14168      .work
14169      .try_register_work()
14170      .expect("the backend must accept queued blocking work");
14171    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
14172    let (release_drop_tx, release_drop_rx) = mpsc::channel();
14173    let blocking_drop = BlockingDrop {
14174      entered: drop_entered_tx,
14175      release: release_drop_rx,
14176    };
14177    let cancelled = executor.schedule_blocking(
14178      move || {
14179        let _blocking_drop = blocking_drop;
14180      },
14181      cancelled_registration,
14182    );
14183
14184    let async_registration = backend
14185      .work
14186      .try_register_async()
14187      .expect("the backend must accept queued async work");
14188    let pending = spawn_registered(
14189      &backend,
14190      Arc::clone(&metrics),
14191      std::future::pending::<()>(),
14192      async_registration,
14193    );
14194    let failed_dispatch = current_thread_dispatch(&executor);
14195    executor.cancel_host_dispatch_for_test(failed_dispatch);
14196    let replacement_dispatch = current_thread_dispatch(&executor);
14197
14198    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
14199    let cancelling_executor = Arc::clone(&executor);
14200    let cancellation = std::thread::spawn(move || {
14201      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
14202      cancel_done_tx.send(()).unwrap();
14203    });
14204    drop_entered_rx
14205      .recv_timeout(Duration::from_secs(2))
14206      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
14207    let active_during_drop = backend.work.state.lock().unwrap().active;
14208    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
14209
14210    release_drop_tx.send(()).unwrap();
14211    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
14212    cancellation.join().unwrap();
14213
14214    assert_eq!(
14215      active_during_drop, 2,
14216      "the blocking capture's work registration must remain active until its destructor returns"
14217    );
14218    assert!(
14219      cancellation_blocked,
14220      "terminal cancellation must wait for capture destruction"
14221    );
14222    assert!(futures::executor::block_on(cancelled).is_err());
14223    assert!(futures::executor::block_on(pending).is_err());
14224    assert_eq!(backend.work.state.lock().unwrap().active, 1);
14225
14226    release_holder_tx.send(()).unwrap();
14227    holder.join().unwrap();
14228    assert_eq!(backend.work.state.lock().unwrap().active, 0);
14229  }
14230
14231  #[test]
14232  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
14233    reset_current_thread_one_shot_host_dispatches();
14234    let metrics = Arc::new(RuntimeMetrics::default());
14235    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14236      Arc::clone(&metrics),
14237      accept_only_first_current_thread_host_dispatch,
14238    ));
14239
14240    let scheduler = Arc::clone(&executor);
14241    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14242      scheduler.schedule(runnable)
14243    });
14244    executor.schedule(runnable);
14245    let failed_dispatch = current_thread_dispatch(&executor);
14246
14247    executor.cancel_host_dispatch_for_test(failed_dispatch);
14248
14249    assert_eq!(
14250      current_thread_one_shot_host_dispatches(),
14251      2,
14252      "the original and its one bounded replacement must be attempted"
14253    );
14254    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14255    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14256    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14257    assert!(
14258      futures::executor::block_on(task.fallible()).is_none(),
14259      "an unavailable replacement host must settle the original task as cancelled"
14260    );
14261  }
14262
14263  enum TestCurrentThreadTaskDriverBehavior {
14264    Healthy,
14265    PanicInLivenessAndSweep,
14266    PanicInDispatchAndSweep,
14267  }
14268
14269  struct TestCurrentThreadTaskDriver {
14270    behavior: TestCurrentThreadTaskDriverBehavior,
14271    dispatched: AtomicBool,
14272  }
14273
14274  #[derive(Default)]
14275  struct ManualCurrentThreadTaskDriver {
14276    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
14277  }
14278
14279  struct FailDuringAuthoritativePanicPublication;
14280  struct FailDuringLateRegistrationPublication;
14281  struct FailDuringNestedUnwindPublication;
14282  struct FailDuringReplacementRepublishPublication;
14283  struct FirstHostileCurrentThreadPanic;
14284  struct LaterHostileCurrentThreadPanic;
14285
14286  impl Drop for FirstHostileCurrentThreadPanic {
14287    fn drop(&mut self) {
14288      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14289      panic!("the first retained CurrentThread panic payload was dropped");
14290    }
14291  }
14292
14293  impl Drop for LaterHostileCurrentThreadPanic {
14294    fn drop(&mut self) {
14295      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
14296      panic!("a later CurrentThread panic payload escaped containment");
14297    }
14298  }
14299
14300  impl ManualCurrentThreadTaskDriver {
14301    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
14302      self.deliveries.lock().unwrap().clone()
14303    }
14304  }
14305
14306  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
14307    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14308      self.deliveries.lock().unwrap().push(delivery);
14309      true
14310    }
14311  }
14312
14313  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
14314    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14315      let completion =
14316        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
14317      assert!(
14318        completion.failed_dispatches.is_empty(),
14319        "the failed delivery must remain provisional until its publication retires"
14320      );
14321      true
14322    }
14323  }
14324
14325  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
14326    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14327      let completion =
14328        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
14329      assert!(
14330        completion.failed_dispatches.is_empty(),
14331        "the failed delivery must remain provisional until its publication retires"
14332      );
14333      true
14334    }
14335  }
14336
14337  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
14338    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14339      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
14340      assert!(
14341        completion.failed_dispatches.is_empty(),
14342        "the failed delivery must remain provisional until its publication retires"
14343      );
14344      true
14345    }
14346  }
14347
14348  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
14349    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14350      let completion =
14351        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
14352      assert!(
14353        completion.failed_dispatches.is_empty(),
14354        "the failed delivery must remain provisional until its publication retires"
14355      );
14356      true
14357    }
14358  }
14359
14360  struct ReentrantCurrentThreadTaskDriver {
14361    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14362    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
14363  }
14364
14365  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
14366    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14367      true
14368    }
14369
14370    fn is_live(&self) -> bool {
14371      let registration = *self.registration.lock().unwrap();
14372      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
14373        registry.unregister(id);
14374      }
14375      false
14376    }
14377  }
14378
14379  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
14380    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14381    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14382  }
14383
14384  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
14385    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14386      true
14387    }
14388
14389    fn is_live(&self) -> bool {
14390      if let Some(registry) = self.registry.upgrade() {
14391        for registration in self.registrations.lock().unwrap().iter().copied() {
14392          let completion = registry.unregister(registration);
14393          assert!(completion.failed_dispatches.is_empty());
14394          assert!(!completion.redispatch);
14395        }
14396      }
14397      false
14398    }
14399  }
14400
14401  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14402    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14403    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
14404    drops: Arc<AtomicUsize>,
14405  }
14406
14407  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14408    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14409      if let Some(registry) = self.registry.upgrade() {
14410        for registration in self.registrations.lock().unwrap().iter().copied() {
14411          let completion = registry.unregister(registration);
14412          assert!(completion.failed_dispatches.is_empty());
14413          assert!(!completion.redispatch);
14414        }
14415      }
14416      true
14417    }
14418  }
14419
14420  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
14421    fn drop(&mut self) {
14422      self.drops.fetch_add(1, Ordering::SeqCst);
14423      panic!("intentional reentrant CurrentThread task-driver destructor panic");
14424    }
14425  }
14426
14427  struct PanickingDropCurrentThreadTaskDriver {
14428    drops: Arc<AtomicUsize>,
14429  }
14430
14431  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
14432    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14433      true
14434    }
14435  }
14436
14437  impl Drop for PanickingDropCurrentThreadTaskDriver {
14438    fn drop(&mut self) {
14439      self.drops.fetch_add(1, Ordering::SeqCst);
14440      panic!("intentional CurrentThread task-driver destructor panic");
14441    }
14442  }
14443
14444  struct ReentrantCurrentThreadTaskDriverDrop {
14445    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14446    fallback: CurrentThreadTaskDriverId,
14447  }
14448
14449  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
14450    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14451      true
14452    }
14453  }
14454
14455  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
14456    fn drop(&mut self) {
14457      if let Some(registry) = self.registry.upgrade() {
14458        registry.unregister(self.fallback);
14459      }
14460    }
14461  }
14462
14463  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
14464    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14465      assert!(
14466        !matches!(
14467          self.behavior,
14468          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
14469        ),
14470        "intentional task-driver dispatch panic"
14471      );
14472      self.dispatched.store(true, Ordering::SeqCst);
14473      true
14474    }
14475
14476    fn is_live(&self) -> bool {
14477      assert!(
14478        !matches!(
14479          self.behavior,
14480          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
14481        ),
14482        "intentional task-driver liveness panic"
14483      );
14484      true
14485    }
14486
14487    fn on_swept(&self) {
14488      assert!(
14489        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
14490        "intentional task-driver sweep panic"
14491      );
14492    }
14493  }
14494
14495  #[test]
14496  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
14497    let registry = CurrentThreadTaskDriverRegistry::default();
14498    let fallback = Arc::new(TestCurrentThreadTaskDriver {
14499      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
14500      dispatched: AtomicBool::new(false),
14501    });
14502    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
14503    registry.register(Arc::new(TestCurrentThreadTaskDriver {
14504      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
14505      dispatched: AtomicBool::new(false),
14506    }));
14507    registry.register(Arc::new(TestCurrentThreadTaskDriver {
14508      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
14509      dispatched: AtomicBool::new(false),
14510    }));
14511
14512    assert_eq!(
14513      registry.dispatch(37),
14514      CurrentThreadHostDispatchResult::Accepted,
14515      "a panicking newest host must fall back to a live host"
14516    );
14517    assert!(fallback.dispatched.load(Ordering::SeqCst));
14518  }
14519
14520  #[test]
14521  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
14522    let registry = CurrentThreadTaskDriverRegistry::default();
14523    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
14524    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
14525    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
14526    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
14527    registry
14528      .next_delivery
14529      .store(u64::MAX - 1, Ordering::Relaxed);
14530
14531    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
14532    assert!(
14533      publication.is_err(),
14534      "delivery-id exhaustion on the second host must unwind the publication"
14535    );
14536    {
14537      let state = registry
14538        .state
14539        .lock()
14540        .unwrap_or_else(std::sync::PoisonError::into_inner);
14541      assert!(
14542        !state.dispatches.contains_key(&38),
14543        "unwind cleanup must release the exact registry publication barrier"
14544      );
14545      assert!(
14546        state
14547          .entries
14548          .values()
14549          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
14550        "failed transactional reservation must leave every physical host slot clean"
14551      );
14552    }
14553    assert!(first.deliveries().is_empty());
14554    assert!(second.deliveries().is_empty());
14555
14556    registry.next_delivery.store(0, Ordering::Relaxed);
14557    assert_eq!(
14558      registry.dispatch(38),
14559      CurrentThreadHostDispatchResult::Accepted
14560    );
14561    let first_delivery = first.deliveries()[0];
14562    let second_delivery = second.deliveries()[0];
14563    assert_eq!(
14564      registry.claim_delivery(first_delivery.capability()),
14565      Some(38)
14566    );
14567    assert_eq!(
14568      registry.claim_delivery(second_delivery.capability()),
14569      Some(38)
14570    );
14571    registry.mark_serviced(38);
14572    assert!(
14573      registry
14574        .finish_delivery(first_delivery, false)
14575        .failed_dispatches
14576        .is_empty()
14577    );
14578    assert!(
14579      registry
14580        .finish_delivery(second_delivery, false)
14581        .failed_dispatches
14582        .is_empty()
14583    );
14584
14585    assert_eq!(
14586      registry.dispatch(39),
14587      CurrentThreadHostDispatchResult::Accepted
14588    );
14589    let first_later = first.deliveries()[1];
14590    let second_later = second.deliveries()[1];
14591    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
14592    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
14593    registry.mark_serviced(39);
14594    assert!(
14595      registry
14596        .finish_delivery(first_later, false)
14597        .failed_dispatches
14598        .is_empty()
14599    );
14600    assert!(
14601      registry
14602        .finish_delivery(second_later, false)
14603        .failed_dispatches
14604        .is_empty()
14605    );
14606    assert!(
14607      registry
14608        .state
14609        .lock()
14610        .unwrap_or_else(std::sync::PoisonError::into_inner)
14611        .dispatches
14612        .is_empty()
14613    );
14614    let state = registry
14615      .state
14616      .lock()
14617      .unwrap_or_else(std::sync::PoisonError::into_inner);
14618    assert!(
14619      state
14620        .entries
14621        .values()
14622        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
14623    );
14624  }
14625
14626  #[test]
14627  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
14628    let registry = CurrentThreadTaskDriverRegistry::default();
14629    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
14630    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
14631    let dispatch = 40;
14632
14633    assert_eq!(
14634      registry.dispatch(dispatch),
14635      CurrentThreadHostDispatchResult::Accepted
14636    );
14637    let failed_delivery = failed.deliveries()[0];
14638    let failure = current_thread_dispatch_failure(dispatch, 0);
14639    assert_eq!(
14640      registry
14641        .finish_delivery(failed_delivery, true)
14642        .failed_dispatches,
14643      vec![failure]
14644    );
14645    registry.unregister(failed_id);
14646
14647    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
14648    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
14649    assert!(
14650      registry.rearm_failed_publication(failure),
14651      "a closed unreferenced dispatch must reopen for its armed publication owner"
14652    );
14653    assert_eq!(
14654      registry.dispatch(dispatch),
14655      CurrentThreadHostDispatchResult::Accepted
14656    );
14657    let replacement_delivery = replacement.deliveries()[0];
14658    assert_eq!(replacement_delivery.dispatch, dispatch);
14659    assert_eq!(
14660      registry.claim_delivery(replacement_delivery.capability()),
14661      Some(dispatch)
14662    );
14663    registry.mark_serviced(dispatch);
14664    assert!(
14665      registry
14666        .finish_delivery(replacement_delivery, false)
14667        .failed_dispatches
14668        .is_empty()
14669    );
14670    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14671  }
14672
14673  #[test]
14674  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
14675    struct RacingTaskDriver {
14676      executor: Arc<CurrentThreadExecutor>,
14677      drives: bool,
14678      dispatches: Mutex<Vec<u64>>,
14679    }
14680
14681    impl CurrentThreadTaskDriver for RacingTaskDriver {
14682      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14683        self.dispatches.lock().unwrap().push(delivery.dispatch);
14684        if self.drives {
14685          self
14686            .executor
14687            .drive_host_turn_for_dispatch(delivery.dispatch);
14688        }
14689        true
14690      }
14691    }
14692
14693    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14694      Arc::new(RuntimeMetrics::default()),
14695      accept_current_thread_host_dispatch,
14696    ));
14697    let polls = Arc::new(AtomicUsize::new(0));
14698    let task_polls = Arc::clone(&polls);
14699    let scheduler = Arc::clone(&executor);
14700    let (runnable, task) = async_task::spawn(
14701      async move {
14702        task_polls.fetch_add(1, Ordering::SeqCst);
14703      },
14704      move |runnable| scheduler.schedule(runnable),
14705    );
14706    executor.schedule(runnable);
14707    let dispatch = current_thread_dispatch(&executor);
14708
14709    let registry = CurrentThreadTaskDriverRegistry::default();
14710    let responsive = Arc::new(RacingTaskDriver {
14711      executor: Arc::clone(&executor),
14712      drives: true,
14713      dispatches: Mutex::new(Vec::new()),
14714    });
14715    let starved = Arc::new(RacingTaskDriver {
14716      executor: Arc::clone(&executor),
14717      drives: false,
14718      dispatches: Mutex::new(Vec::new()),
14719    });
14720    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
14721    // The newest host is intentionally live but unresponsive.
14722    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
14723
14724    assert_eq!(
14725      registry.dispatch(dispatch),
14726      CurrentThreadHostDispatchResult::Accepted
14727    );
14728    assert_eq!(
14729      responsive.dispatches.lock().unwrap().as_slice(),
14730      &[dispatch]
14731    );
14732    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
14733    assert_eq!(
14734      polls.load(Ordering::SeqCst),
14735      1,
14736      "the responsive older host must win even when the newest host is starved"
14737    );
14738
14739    // The delayed callback from the starved host carries the same consumed
14740    // capability and cannot poll the task a second time.
14741    executor.drive_host_turn_for_dispatch(dispatch);
14742    assert_eq!(polls.load(Ordering::SeqCst), 1);
14743    futures::executor::block_on(task);
14744  }
14745
14746  #[test]
14747  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
14748    const HOSTS: usize = 1_024;
14749
14750    let registry = CurrentThreadTaskDriverRegistry::default();
14751    let mut ids = Vec::with_capacity(HOSTS);
14752    let mut drivers = Vec::with_capacity(HOSTS);
14753    for _ in 0..HOSTS {
14754      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
14755      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
14756      drivers.push(driver);
14757    }
14758
14759    let dispatch = u64::MAX - 300;
14760    assert_eq!(
14761      registry.dispatch(dispatch),
14762      CurrentThreadHostDispatchResult::Accepted
14763    );
14764    let deliveries = drivers
14765      .iter()
14766      .map(|driver| driver.deliveries()[0])
14767      .collect::<Vec<_>>();
14768    {
14769      let state = registry
14770        .state
14771        .lock()
14772        .unwrap_or_else(std::sync::PoisonError::into_inner);
14773      assert_eq!(state.delivery_drivers.len(), HOSTS);
14774      assert_eq!(
14775        state
14776          .dispatches
14777          .get(&dispatch)
14778          .map(|state| state.references),
14779        Some(HOSTS)
14780      );
14781      assert_eq!(
14782        state
14783          .ordered_snapshot()
14784          .iter()
14785          .map(|(id, _)| *id)
14786          .collect::<Vec<_>>(),
14787        ids,
14788        "keyed driver lookup must retain registration order"
14789      );
14790    }
14791
14792    for delivery in &deliveries {
14793      assert_eq!(
14794        registry.claim_delivery(delivery.capability()),
14795        Some(dispatch)
14796      );
14797    }
14798    registry.mark_serviced(dispatch);
14799    for delivery in deliveries {
14800      assert!(
14801        registry
14802          .finish_delivery(delivery, false)
14803          .failed_dispatches
14804          .is_empty()
14805      );
14806    }
14807    {
14808      let state = registry
14809        .state
14810        .lock()
14811        .unwrap_or_else(std::sync::PoisonError::into_inner);
14812      assert!(state.delivery_drivers.is_empty());
14813      assert!(state.dispatches.is_empty());
14814    }
14815
14816    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
14817    for id in &removed {
14818      let completion = registry.unregister(*id);
14819      assert!(completion.failed_dispatches.is_empty());
14820      assert!(!completion.redispatch);
14821    }
14822    let expected = ids
14823      .iter()
14824      .copied()
14825      .filter(|id| !removed.contains(id))
14826      .collect::<Vec<_>>();
14827    let live = registry.live().live;
14828    assert_eq!(
14829      live
14830        .as_slice()
14831        .iter()
14832        .map(|(id, _)| *id)
14833        .collect::<Vec<_>>(),
14834      expected,
14835      "O(1) removals must preserve the relative order of surviving host registrations"
14836    );
14837    drop(live);
14838
14839    for id in expected {
14840      registry.unregister(id);
14841    }
14842    let state = registry
14843      .state
14844      .lock()
14845      .unwrap_or_else(std::sync::PoisonError::into_inner);
14846    assert!(state.entries.is_empty());
14847    assert!(state.oldest.is_none());
14848    assert!(state.newest.is_none());
14849  }
14850
14851  #[test]
14852  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
14853    let registry = CurrentThreadTaskDriverRegistry::default();
14854    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
14855    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
14856    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14857    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
14858    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
14859    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14860
14861    let dispatch = 41;
14862    assert_eq!(
14863      registry.dispatch(dispatch),
14864      CurrentThreadHostDispatchResult::Accepted
14865    );
14866    let failing_delivery = failing.deliveries()[0];
14867    let delayed_delivery = delayed.deliveries()[0];
14868    let healthy_delivery = healthy.deliveries()[0];
14869    assert_eq!(failing_delivery.dispatch, dispatch);
14870    assert_eq!(delayed_delivery.dispatch, dispatch);
14871    assert_eq!(healthy_delivery.dispatch, dispatch);
14872
14873    assert!(
14874      registry
14875        .finish_delivery(failing_delivery, true)
14876        .failed_dispatches
14877        .is_empty(),
14878      "one host failure must not recover while other accepted attempts remain"
14879    );
14880    assert_eq!(
14881      registry.claim_delivery(healthy_delivery.capability()),
14882      Some(dispatch)
14883    );
14884    registry.mark_serviced(dispatch);
14885    assert!(
14886      registry
14887        .finish_delivery(healthy_delivery, false)
14888        .failed_dispatches
14889        .is_empty()
14890    );
14891    assert_eq!(
14892      registry.claim_delivery(delayed_delivery.capability()),
14893      None,
14894      "a delayed losing delivery must become stale after another host services the dispatch"
14895    );
14896    assert!(
14897      registry
14898        .finish_delivery(delayed_delivery, true)
14899        .failed_dispatches
14900        .is_empty(),
14901      "a stale losing failure must not recover or cancel a serviced dispatch"
14902    );
14903    assert!(
14904      registry.state.lock().unwrap().dispatches.is_empty(),
14905      "all per-dispatch acknowledgement state must retire"
14906    );
14907  }
14908
14909  #[test]
14910  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
14911    let registry = CurrentThreadTaskDriverRegistry::default();
14912    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
14913    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
14914
14915    let dispatch = 56;
14916    assert_eq!(
14917      registry.dispatch(dispatch),
14918      CurrentThreadHostDispatchResult::Accepted
14919    );
14920    let delivery = no_op.deliveries()[0];
14921    let completion = registry.finish_delivery(delivery, false);
14922    let failure = current_thread_dispatch_failure(dispatch, 0);
14923    assert_eq!(
14924      completion.failed_dispatches,
14925      vec![failure],
14926      "a callback return without an exact capability claim must enter recovery"
14927    );
14928    assert!(
14929      registry
14930        .state
14931        .lock()
14932        .unwrap()
14933        .dispatches
14934        .get(&dispatch)
14935        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14936    );
14937    registry.finish_cancellation(failure);
14938    assert!(registry.state.lock().unwrap().dispatches.is_empty());
14939  }
14940
14941  #[test]
14942  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
14943    struct FailDuringPublication {
14944      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
14945    }
14946
14947    impl CurrentThreadTaskDriver for FailDuringPublication {
14948      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
14949        let completion = self
14950          .registry
14951          .upgrade()
14952          .expect("the registry must outlive publication")
14953          .finish_delivery(delivery, true);
14954        assert!(
14955          completion.failed_dispatches.is_empty(),
14956          "failure must remain provisional until the broadcast closes"
14957        );
14958        true
14959      }
14960    }
14961
14962    struct RejectProvisional;
14963
14964    impl CurrentThreadTaskDriver for RejectProvisional {
14965      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
14966        false
14967      }
14968    }
14969
14970    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
14971    registry.register(Arc::new(RejectProvisional));
14972    // Newest-first publication calls the accepted-then-failing host while the
14973    // rejecting host still has an unresolved provisional delivery.
14974    registry.register(Arc::new(FailDuringPublication {
14975      registry: Arc::downgrade(&registry),
14976    }));
14977
14978    let failure = current_thread_dispatch_failure(57, 0);
14979    assert_eq!(
14980      registry.dispatch(57),
14981      CurrentThreadHostDispatchResult::Failed(failure),
14982      "the final provisional rejection must expose the earlier accepted failure"
14983    );
14984    assert!(
14985      registry
14986        .state
14987        .lock()
14988        .unwrap()
14989        .dispatches
14990        .get(&57)
14991        .is_some_and(CurrentThreadDispatchState::is_cancelling)
14992    );
14993    assert_eq!(
14994      registry.dispatch(57),
14995      CurrentThreadHostDispatchResult::Failed(failure),
14996      "a racing replacement publication must preserve terminal failure until cancellation runs"
14997    );
14998    registry.finish_cancellation(failure);
14999  }
15000
15001  #[test]
15002  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
15003    struct RejectProvisional;
15004
15005    impl CurrentThreadTaskDriver for RejectProvisional {
15006      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
15007        false
15008      }
15009    }
15010
15011    let registry = CurrentThreadTaskDriverRegistry::default();
15012    registry.register(Arc::new(RejectProvisional));
15013    registry.register(Arc::new(RejectProvisional));
15014
15015    assert_eq!(
15016      registry.dispatch(58),
15017      CurrentThreadHostDispatchResult::Unavailable
15018    );
15019    assert!(
15020      registry.state.lock().unwrap().dispatches.is_empty(),
15021      "a broadcast with no accepted attempt must not enter failure recovery"
15022    );
15023  }
15024
15025  #[test]
15026  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
15027    let registry = CurrentThreadTaskDriverRegistry::default();
15028    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
15029    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
15030
15031    assert_eq!(
15032      registry.dispatch(1),
15033      CurrentThreadHostDispatchResult::Accepted
15034    );
15035    let first = blocked.deliveries()[0];
15036    for dispatch in 2..=256 {
15037      assert_eq!(
15038        registry.dispatch(dispatch),
15039        CurrentThreadHostDispatchResult::Accepted
15040      );
15041      assert_eq!(
15042        blocked.deliveries().len(),
15043        1,
15044        "a blocked host must never receive a second queued or in-flight callback"
15045      );
15046    }
15047    {
15048      let state = registry.state.lock().unwrap();
15049      let entry = state
15050        .entries
15051        .get(&blocked_id)
15052        .expect("the blocked host must remain registered");
15053      assert_eq!(entry.pending, Some(256));
15054      assert_eq!(
15055        state.dispatches.len(),
15056        2,
15057        "only the in-flight and latest pending dispatches may retain registry state"
15058      );
15059    }
15060
15061    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
15062    registry.mark_serviced(1);
15063    let completion = registry.finish_delivery(first, false);
15064    assert!(completion.failed_dispatches.is_empty());
15065    assert!(completion.redispatch);
15066    assert_eq!(
15067      registry.dispatch(256),
15068      CurrentThreadHostDispatchResult::Accepted
15069    );
15070    let deliveries = blocked.deliveries();
15071    assert_eq!(deliveries.len(), 2);
15072    assert_eq!(
15073      deliveries[1].dispatch, 256,
15074      "completion must schedule exactly the latest coalesced capability"
15075    );
15076    assert_eq!(registry.claim_delivery(first.capability()), None);
15077    assert_eq!(
15078      registry.claim_delivery(deliveries[1].capability()),
15079      Some(256)
15080    );
15081    registry.mark_serviced(256);
15082    assert!(
15083      registry
15084        .finish_delivery(deliveries[1], false)
15085        .failed_dispatches
15086        .is_empty()
15087    );
15088    assert!(registry.state.lock().unwrap().dispatches.is_empty());
15089  }
15090
15091  #[test]
15092  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
15093    struct RejectAfterFirst {
15094      calls: AtomicUsize,
15095      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
15096    }
15097
15098    impl CurrentThreadTaskDriver for RejectAfterFirst {
15099      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15100        self.deliveries.lock().unwrap().push(delivery);
15101        self.calls.fetch_add(1, Ordering::SeqCst) == 0
15102      }
15103    }
15104
15105    let registry = CurrentThreadTaskDriverRegistry::default();
15106    let host = Arc::new(RejectAfterFirst {
15107      calls: AtomicUsize::new(0),
15108      deliveries: Mutex::new(Vec::new()),
15109    });
15110    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15111    assert_eq!(
15112      registry.dispatch(11),
15113      CurrentThreadHostDispatchResult::Accepted
15114    );
15115    let first = host.deliveries.lock().unwrap()[0];
15116    assert_eq!(
15117      registry.dispatch(12),
15118      CurrentThreadHostDispatchResult::Accepted
15119    );
15120
15121    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
15122    registry.mark_serviced(11);
15123    let completion = registry.finish_delivery(first, false);
15124    assert!(completion.redispatch);
15125    let failure = current_thread_dispatch_failure(12, 0);
15126    assert_eq!(
15127      registry.dispatch(12),
15128      CurrentThreadHostDispatchResult::Failed(failure),
15129      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
15130    );
15131    assert!(
15132      registry
15133        .state
15134        .lock()
15135        .unwrap()
15136        .dispatches
15137        .get(&12)
15138        .is_some_and(CurrentThreadDispatchState::is_cancelling)
15139    );
15140    registry.finish_cancellation(failure);
15141    assert!(registry.state.lock().unwrap().dispatches.is_empty());
15142  }
15143
15144  #[test]
15145  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
15146    let registry = CurrentThreadTaskDriverRegistry::default();
15147    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
15148    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
15149    assert_eq!(
15150      registry.dispatch(73),
15151      CurrentThreadHostDispatchResult::Accepted
15152    );
15153    let old_delivery = old.deliveries()[0];
15154
15155    let completion = registry.unregister(old_id);
15156    let failure = current_thread_dispatch_failure(73, 0);
15157    assert_eq!(
15158      completion.failed_dispatches,
15159      vec![failure],
15160      "unregistering the final accepting host must retire its exact dispatch"
15161    );
15162    registry.finish_cancellation(failure);
15163    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
15164    let replacement_id =
15165      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
15166    assert_ne!(replacement_id, old_id);
15167    assert_eq!(
15168      registry.dispatch(74),
15169      CurrentThreadHostDispatchResult::Accepted
15170    );
15171    let replacement_delivery = replacement.deliveries()[0];
15172    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
15173
15174    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15175    assert!(
15176      registry
15177        .finish_delivery(old_delivery, true)
15178        .failed_dispatches
15179        .is_empty()
15180    );
15181    assert!(
15182      registry
15183        .fail_delivery(old_delivery.capability())
15184        .failed_dispatches
15185        .is_empty()
15186    );
15187    assert_eq!(
15188      registry.claim_delivery(replacement_delivery.capability()),
15189      Some(74)
15190    );
15191    registry.mark_serviced(74);
15192    assert!(
15193      registry
15194        .finish_delivery(replacement_delivery, false)
15195        .failed_dispatches
15196        .is_empty()
15197    );
15198    assert!(registry.state.lock().unwrap().dispatches.is_empty());
15199  }
15200
15201  #[test]
15202  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
15203    struct QueuePendingThenReject {
15204      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
15205    }
15206
15207    impl CurrentThreadTaskDriver for QueuePendingThenReject {
15208      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
15209        assert_eq!(delivery.dispatch, 81);
15210        let completion = self
15211          .registry
15212          .upgrade()
15213          .expect("the registry must outlive publication")
15214          .dispatch_with_completion(82);
15215        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
15216        assert!(completion.failed_dispatches.is_empty());
15217        false
15218      }
15219    }
15220
15221    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15222    registry.register(Arc::new(QueuePendingThenReject {
15223      registry: Arc::downgrade(&registry),
15224    }));
15225
15226    let completion = registry.dispatch_with_completion(81);
15227    assert_eq!(
15228      completion.result,
15229      CurrentThreadHostDispatchResult::Unavailable
15230    );
15231    let failure = current_thread_dispatch_failure(82, 0);
15232    assert_eq!(
15233      completion.failed_dispatches,
15234      vec![failure],
15235      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
15236    );
15237    assert!(
15238      registry
15239        .state
15240        .lock()
15241        .unwrap()
15242        .dispatches
15243        .get(&82)
15244        .is_some_and(CurrentThreadDispatchState::is_cancelling)
15245    );
15246    registry.finish_cancellation(failure);
15247    assert!(registry.state.lock().unwrap().dispatches.is_empty());
15248  }
15249
15250  #[test]
15251  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
15252    let registry = CurrentThreadTaskDriverRegistry::default();
15253    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
15254    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
15255    assert_eq!(
15256      registry.dispatch(91),
15257      CurrentThreadHostDispatchResult::Accepted
15258    );
15259    let old_delivery = host.deliveries()[0];
15260    assert_eq!(
15261      registry.dispatch(92),
15262      CurrentThreadHostDispatchResult::Accepted
15263    );
15264
15265    registry.retire_for_shutdown();
15266    {
15267      let state = registry.state.lock().unwrap();
15268      assert!(state.dispatches.is_empty());
15269      let entry = state
15270        .entries
15271        .get(&host_id)
15272        .expect("the host must remain registered");
15273      assert_eq!(entry.pending, None);
15274      assert!(
15275        entry.delivery.is_some(),
15276        "the physical callback remains single-flight until it actually retires"
15277      );
15278    }
15279    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
15280
15281    assert_eq!(
15282      registry.dispatch(93),
15283      CurrentThreadHostDispatchResult::Accepted
15284    );
15285    assert_eq!(host.deliveries().len(), 1);
15286    let completion = registry.finish_delivery(old_delivery, true);
15287    assert!(
15288      completion.failed_dispatches.is_empty(),
15289      "a retired-generation failure must not cancel restarted work"
15290    );
15291    assert!(completion.redispatch);
15292    assert_eq!(
15293      registry.dispatch(93),
15294      CurrentThreadHostDispatchResult::Accepted
15295    );
15296    let restarted_delivery = host.deliveries()[1];
15297    assert_eq!(restarted_delivery.dispatch, 93);
15298    assert_eq!(
15299      registry.claim_delivery(restarted_delivery.capability()),
15300      Some(93)
15301    );
15302    registry.mark_serviced(93);
15303    assert!(
15304      registry
15305        .finish_delivery(restarted_delivery, false)
15306        .failed_dispatches
15307        .is_empty()
15308    );
15309
15310    registry.unregister(host_id);
15311    let state = registry.state.lock().unwrap();
15312    assert!(state.entries.is_empty());
15313    assert!(state.dispatches.is_empty());
15314  }
15315
15316  #[test]
15317  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
15318    use std::{sync::mpsc, time::Duration};
15319
15320    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15321    let fallback = Arc::new(TestCurrentThreadTaskDriver {
15322      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15323      dispatched: AtomicBool::new(false),
15324    });
15325    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15326    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
15327      registry: Arc::downgrade(&registry),
15328      registration: Mutex::new(None),
15329    });
15330    let reentrant_id =
15331      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15332    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
15333
15334    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
15335    let selection_registry = Arc::clone(&registry);
15336    let selection_thread = std::thread::spawn(move || {
15337      selection_tx
15338        .send(selection_registry.current().map(|(id, _)| id))
15339        .unwrap();
15340    });
15341    assert_eq!(
15342      selection_rx
15343        .recv_timeout(Duration::from_secs(10))
15344        .expect("is_live re-entry deadlocked"),
15345      Some(fallback_id)
15346    );
15347    selection_thread.join().unwrap();
15348
15349    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
15350      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
15351        registry: Arc::downgrade(&registry),
15352        fallback: fallback_id,
15353      });
15354    let drop_id = registry.register(Arc::clone(&drop_driver));
15355    drop(drop_driver);
15356
15357    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
15358    let drop_registry = Arc::clone(&registry);
15359    let drop_thread = std::thread::spawn(move || {
15360      drop_registry.unregister(drop_id);
15361      drop_tx.send(()).unwrap();
15362    });
15363    drop_rx
15364      .recv_timeout(Duration::from_secs(10))
15365      .expect("driver destructor re-entry deadlocked");
15366    drop_thread.join().unwrap();
15367    assert!(
15368      registry.current().is_none(),
15369      "the destructor must unregister the fallback"
15370    );
15371  }
15372
15373  #[test]
15374  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
15375    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
15376
15377    if std::env::var_os(CHILD_ENV).is_some() {
15378      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15379      let fallback = Arc::new(TestCurrentThreadTaskDriver {
15380        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
15381        dispatched: AtomicBool::new(false),
15382      });
15383      let fallback_id =
15384        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
15385      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
15386        registry: Arc::downgrade(&registry),
15387        registrations: Mutex::new(Vec::new()),
15388      });
15389      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15390
15391      let first_drops = Arc::new(AtomicUsize::new(0));
15392      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15393        drops: Arc::clone(&first_drops),
15394      });
15395      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15396      drop(first);
15397
15398      let second_drops = Arc::new(AtomicUsize::new(0));
15399      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15400        drops: Arc::clone(&second_drops),
15401      });
15402      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15403      drop(second);
15404      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
15405
15406      assert_eq!(
15407        registry.current().map(|(id, _)| id),
15408        Some(fallback_id),
15409        "selection must retry after reentrant removals and preserve the healthy fallback"
15410      );
15411      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15412      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15413      let state = registry
15414        .state
15415        .lock()
15416        .unwrap_or_else(std::sync::PoisonError::into_inner);
15417      assert_eq!(state.entries.len(), 1);
15418      assert!(state.entries.contains_key(&fallback_id));
15419      assert!(state.dispatches.is_empty());
15420      return;
15421    }
15422
15423    let output = std::process::Command::new(std::env::current_exe().unwrap())
15424      .arg("--exact")
15425      .arg(
15426        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
15427      )
15428      .arg("--nocapture")
15429      .env(CHILD_ENV, "1")
15430      .output()
15431      .expect("the hostile task-driver snapshot subprocess test must start");
15432    assert!(
15433      output.status.success(),
15434      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15435      output.status.code(),
15436      String::from_utf8_lossy(&output.stdout),
15437      String::from_utf8_lossy(&output.stderr)
15438    );
15439  }
15440
15441  #[test]
15442  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
15443    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
15444
15445    if std::env::var_os(CHILD_ENV).is_some() {
15446      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
15447
15448      let first_drops = Arc::new(AtomicUsize::new(0));
15449      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
15450        drops: Arc::clone(&first_drops),
15451      });
15452      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
15453      drop(first);
15454
15455      let second_drops = Arc::new(AtomicUsize::new(0));
15456      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
15457        drops: Arc::clone(&second_drops),
15458      });
15459      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
15460      drop(second);
15461
15462      let reentrant_drops = Arc::new(AtomicUsize::new(0));
15463      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
15464        registry: Arc::downgrade(&registry),
15465        registrations: Mutex::new(Vec::new()),
15466        drops: Arc::clone(&reentrant_drops),
15467      });
15468      let reentrant_id =
15469        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
15470      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
15471      drop(reentrant);
15472
15473      let dispatch = 59;
15474      let failure = current_thread_dispatch_failure(dispatch, 0);
15475      assert_eq!(
15476        registry.dispatch(dispatch),
15477        CurrentThreadHostDispatchResult::Failed(failure),
15478        "reentrant removal of every provisional host must report accepted delivery failure"
15479      );
15480      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
15481      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
15482      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
15483      registry.finish_cancellation(failure);
15484      let state = registry
15485        .state
15486        .lock()
15487        .unwrap_or_else(std::sync::PoisonError::into_inner);
15488      assert!(state.entries.is_empty());
15489      assert!(state.dispatches.is_empty());
15490      return;
15491    }
15492
15493    let output = std::process::Command::new(std::env::current_exe().unwrap())
15494      .arg("--exact")
15495      .arg(
15496        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
15497      )
15498      .arg("--nocapture")
15499      .env(CHILD_ENV, "1")
15500      .output()
15501      .expect("the hostile task-driver delivery subprocess test must start");
15502    assert!(
15503      output.status.success(),
15504      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
15505      output.status.code(),
15506      String::from_utf8_lossy(&output.stdout),
15507      String::from_utf8_lossy(&output.stderr)
15508    );
15509  }
15510
15511  #[test]
15512  fn dropping_join_handle_detaches_task_like_tokio() {
15513    let completed = Arc::new(AtomicBool::new(false));
15514    let completed_task = Arc::clone(&completed);
15515    let (runnable, task) = async_task::spawn(
15516      async move {
15517        completed_task.store(true, Ordering::SeqCst);
15518        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
15519      },
15520      |_| {},
15521    );
15522    let handle = JoinHandle(JoinHandleInner::Task {
15523      task: task.fallible(),
15524      awaiter: None,
15525      dependency: Arc::new(TaskDependency::default()),
15526    });
15527
15528    drop(handle);
15529    runnable.run();
15530    assert!(completed.load(Ordering::SeqCst));
15531  }
15532
15533  #[cfg(not(target_family = "wasm"))]
15534  #[test]
15535  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
15536    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
15537
15538    if std::env::var_os(CHILD_ENV).is_some() {
15539      let wakes = Arc::new(AtomicUsize::new(0));
15540      let drops = Arc::new(AtomicUsize::new(0));
15541      let (runnable, task) = async_task::spawn(
15542        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
15543        |_| {},
15544      );
15545      let mut handle = JoinHandle(JoinHandleInner::Task {
15546        task: task.fallible(),
15547        awaiter: None,
15548        dependency: Arc::new(TaskDependency::default()),
15549      });
15550      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15551      {
15552        let mut cx = Context::from_waker(&waker);
15553        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15554      }
15555      drop(waker);
15556
15557      runnable.run();
15558
15559      let waker = futures::task::noop_waker();
15560      let mut cx = Context::from_waker(&waker);
15561      assert!(matches!(
15562        Pin::new(&mut handle).poll(&mut cx),
15563        Poll::Ready(Ok(()))
15564      ));
15565      assert_eq!(
15566        drops.load(Ordering::SeqCst),
15567        1,
15568        "a completed handle must not retain the caller's awaiter"
15569      );
15570      drop(handle);
15571      assert!(wakes.load(Ordering::SeqCst) >= 1);
15572      assert_eq!(drops.load(Ordering::SeqCst), 1);
15573      return;
15574    }
15575
15576    let output = std::process::Command::new(std::env::current_exe().unwrap())
15577      .arg("--exact")
15578      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
15579      .arg("--nocapture")
15580      .env(CHILD_ENV, "1")
15581      .output()
15582      .expect("the JoinHandle completion awaiter subprocess must start");
15583    assert!(
15584      output.status.success(),
15585      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15586      output.status.code(),
15587      String::from_utf8_lossy(&output.stdout),
15588      String::from_utf8_lossy(&output.stderr)
15589    );
15590  }
15591
15592  #[cfg(not(target_family = "wasm"))]
15593  #[test]
15594  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
15595    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
15596
15597    if std::env::var_os(CHILD_ENV).is_some() {
15598      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
15599      let wakes = Arc::new(AtomicUsize::new(0));
15600      let drops = Arc::new(AtomicUsize::new(0));
15601      let mut handle = controller.spawn(std::future::pending::<()>());
15602      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
15603      {
15604        let mut cx = Context::from_waker(&waker);
15605        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15606      }
15607      drop(waker);
15608
15609      controller
15610        .shutdown()
15611        .expect("shutdown must survive the hostile awaiter");
15612      drop(handle);
15613      assert!(wakes.load(Ordering::SeqCst) >= 1);
15614      assert_eq!(drops.load(Ordering::SeqCst), 1);
15615      return;
15616    }
15617
15618    let output = std::process::Command::new(std::env::current_exe().unwrap())
15619      .arg("--exact")
15620      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
15621      .arg("--nocapture")
15622      .env(CHILD_ENV, "1")
15623      .output()
15624      .expect("the JoinHandle shutdown awaiter subprocess must start");
15625    assert!(
15626      output.status.success(),
15627      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
15628      output.status.code(),
15629      String::from_utf8_lossy(&output.stdout),
15630      String::from_utf8_lossy(&output.stderr)
15631    );
15632  }
15633
15634  #[cfg(not(target_family = "wasm"))]
15635  #[test]
15636  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
15637    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
15638    let mut handle = controller.spawn(std::future::pending::<()>());
15639    let old_generation = controller.backend().generation();
15640    let state = Arc::new(JoinAwaiterWakerState {
15641      events: Mutex::new(Vec::new()),
15642    });
15643    let waker = {
15644      let _generation = RuntimeGenerationGuard::enter(old_generation);
15645      join_awaiter_waker(Arc::clone(&state))
15646    };
15647    {
15648      let _generation = RuntimeGenerationGuard::enter(old_generation);
15649      let mut cx = Context::from_waker(&waker);
15650      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15651    }
15652    {
15653      let _generation = RuntimeGenerationGuard::enter(old_generation);
15654      drop(waker);
15655    }
15656
15657    controller
15658      .shutdown()
15659      .expect("the first generation must stop");
15660    controller.start().expect("the runtime must restart");
15661    let new_generation = controller.backend().generation();
15662    assert_ne!(new_generation, old_generation);
15663    state.clear();
15664
15665    let replacement = futures::task::noop_waker();
15666    {
15667      let _generation = RuntimeGenerationGuard::enter(new_generation);
15668      let mut cx = Context::from_waker(&replacement);
15669      assert!(matches!(
15670        Pin::new(&mut handle).poll(&mut cx),
15671        Poll::Ready(Err(_))
15672      ));
15673    }
15674
15675    let events = state.snapshot();
15676    assert!(
15677      !events.is_empty(),
15678      "completion must retire the registered awaiter waker"
15679    );
15680    assert!(
15681      events.iter().all(|(kind, generation)| {
15682        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15683      }),
15684      "old-generation awaiter retirement escaped its generation: {events:?}"
15685    );
15686    controller
15687      .shutdown()
15688      .expect("the replacement generation must stop");
15689  }
15690
15691  #[cfg(not(target_family = "wasm"))]
15692  #[test]
15693  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
15694    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
15695    let mut handle = controller.spawn(std::future::pending::<()>());
15696    let old_generation = controller.backend().generation();
15697    let state = Arc::new(JoinAwaiterWakerState {
15698      events: Mutex::new(Vec::new()),
15699    });
15700    let waker = {
15701      let _generation = RuntimeGenerationGuard::enter(old_generation);
15702      join_awaiter_waker(Arc::clone(&state))
15703    };
15704    {
15705      let _generation = RuntimeGenerationGuard::enter(old_generation);
15706      let mut cx = Context::from_waker(&waker);
15707      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
15708    }
15709    {
15710      let _generation = RuntimeGenerationGuard::enter(old_generation);
15711      drop(waker);
15712    }
15713
15714    controller
15715      .shutdown()
15716      .expect("the first generation must stop");
15717    controller.start().expect("the runtime must restart");
15718    let new_generation = controller.backend().generation();
15719    assert_ne!(new_generation, old_generation);
15720    state.clear();
15721
15722    {
15723      let _generation = RuntimeGenerationGuard::enter(new_generation);
15724      drop(handle);
15725    }
15726
15727    let events = state.snapshot();
15728    assert!(
15729      !events.is_empty(),
15730      "handle drop must retire the registered awaiter waker"
15731    );
15732    assert!(
15733      events.iter().all(|(kind, generation)| {
15734        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
15735      }),
15736      "old-generation awaiter destruction escaped its generation: {events:?}"
15737    );
15738    controller
15739      .shutdown()
15740      .expect("the replacement generation must stop");
15741  }
15742
15743  #[cfg(not(target_family = "wasm"))]
15744  #[test]
15745  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
15746    use std::{sync::mpsc, time::Duration};
15747
15748    let options = RuntimeOptions {
15749      flavor: RuntimeFlavor::MultiThread,
15750      worker_threads: 2,
15751      max_blocking_tasks: 1,
15752      thread_name_prefix: "worker-classification".to_string(),
15753      park_deadline: None,
15754      drain_linger: DEFAULT_DRAIN_LINGER,
15755    };
15756    let executor =
15757      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
15758    let id = executor.id;
15759    let (tx, rx) = mpsc::sync_channel(1);
15760
15761    executor.pool.spawn(move || {
15762      tx.send((
15763        ON_POOL_WORKER.with(std::cell::Cell::get),
15764        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
15765      ))
15766      .unwrap();
15767    });
15768
15769    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
15770    assert_eq!(
15771      worker,
15772      Some(id),
15773      "nested Rayon work must use cooperative block_on"
15774    );
15775    assert_eq!(
15776      driver, None,
15777      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
15778    );
15779  }
15780
15781  #[cfg(not(target_family = "wasm"))]
15782  #[test]
15783  fn multi_thread_block_on_does_not_park_pool_worker() {
15784    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
15785    // future depending on pool-scheduled blocking work must not park its worker.
15786    // If every worker parks this way, no worker remains to pick up the blocking
15787    // jobs they await.
15788    use std::sync::{Barrier, mpsc};
15789    use std::time::Duration;
15790
15791    let (done_tx, done_rx) = mpsc::channel();
15792    let runner = std::thread::spawn(move || {
15793      let metrics = Arc::new(RuntimeMetrics::default());
15794      let options = RuntimeOptions {
15795        flavor: RuntimeFlavor::MultiThread,
15796        worker_threads: 2,
15797        max_blocking_tasks: 2,
15798        thread_name_prefix: "rd1-test".to_string(),
15799        park_deadline: None,
15800        drain_linger: DEFAULT_DRAIN_LINGER,
15801      };
15802      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15803
15804      let task_count = 2usize;
15805      // Guarantees every task is being polled on its own pool worker *before*
15806      // any of them parks in `block_on`, so the deadlock is deterministic.
15807      let barrier = Arc::new(Barrier::new(task_count));
15808
15809      let mut tasks = Vec::new();
15810      for _ in 0..task_count {
15811        let exec = Arc::clone(&executor);
15812        let barrier = Arc::clone(&barrier);
15813        let body = async move {
15814          barrier.wait();
15815          let inner_exec = Arc::clone(&exec);
15816          let mut inner = std::pin::pin!(async move {
15817            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
15818            assert_eq!(value, 7);
15819          });
15820          // Synchronous `block_on` from inside a pool-worker task: the vector.
15821          exec.block_on(inner.as_mut());
15822        };
15823        let scheduler = Arc::clone(&executor);
15824        let (runnable, task) =
15825          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
15826        executor.schedule(runnable);
15827        tasks.push(task);
15828      }
15829
15830      for task in tasks {
15831        futures::executor::block_on(task);
15832      }
15833      let _ = done_tx.send(());
15834    });
15835
15836    match done_rx.recv_timeout(Duration::from_secs(10)) {
15837      Ok(()) => runner.join().unwrap(),
15838      Err(error) => panic!(
15839        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
15840      ),
15841    }
15842  }
15843
15844  #[cfg(not(target_family = "wasm"))]
15845  #[test]
15846  fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
15847    // Idle-linger residence bound (adversarial rayon-starvation shape).
15848    //
15849    // W=2 pool. Worker B runs a blocking closure that spawns a raw rayon job
15850    // R onto the SAME pool (it lands in worker B's rayon deque) and then
15851    // cooperatively block_on's R's completion -- so worker B never returns to
15852    // rayon's scheduler. Worker A's drain frame runs the queues empty and
15853    // lingers on its own parker. A producer then publishes one trivial
15854    // runnable every ~200us (< the 500us idle budget), stopping only after R
15855    // ran. Every wake pops the MOST recently registered parker -- the
15856    // lingering drainer, which re-registers on top after each task -- and
15857    // every executed task clears the idle deadline, so without a bound on the
15858    // frame's TOTAL lingering residence worker A never exits its drain frame,
15859    // never returns its thread to rayon, and R (which only an idle rayon
15860    // worker can steal) starves indefinitely: producer never stops, worker B
15861    // never unparks. The frame residence bound must force the drainer out
15862    // through `finish_draining` (exit-and-respawn through rayon's queue is
15863    // exactly the interleaving point that lets R run), well inside the
15864    // watchdog below.
15865    use std::sync::mpsc;
15866
15867    for round in 0..3 {
15868      let metrics = Arc::new(RuntimeMetrics::default());
15869      let options = RuntimeOptions {
15870        flavor: RuntimeFlavor::MultiThread,
15871        worker_threads: 2,
15872        max_blocking_tasks: 1,
15873        thread_name_prefix: format!("linger-starve-{round}"),
15874        park_deadline: None,
15875        drain_linger: DEFAULT_DRAIN_LINGER,
15876      };
15877      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
15878
15879      let r_done = Arc::new(AtomicBool::new(false));
15880      let give_up = Arc::new(AtomicBool::new(false));
15881      let (done_tx, done_rx) = mpsc::channel::<Duration>();
15882
15883      // T1 pins worker A until the cooperative driver (worker B, inside the
15884      // blocking closure's re-entrant block_on) has parked. Worker A's drain
15885      // frame then observes empty queues and starts lingering AFTER the
15886      // cooperative driver registered, making the lingering drainer the
15887      // most-recently-registered (i.e. wake-preferred) parker.
15888      let t1_executor = Arc::clone(&executor);
15889      let t1_started = Arc::new(AtomicBool::new(false));
15890      let t1_started_setter = Arc::clone(&t1_started);
15891      let t1_body = async move {
15892        t1_started_setter.store(true, Ordering::SeqCst);
15893        let deadline = Instant::now() + Duration::from_secs(10);
15894        while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
15895          if Instant::now() >= deadline {
15896            // Give the worker back; the watchdog below reports the failure.
15897            return;
15898          }
15899          std::hint::spin_loop();
15900        }
15901      };
15902      let t1_scheduler = Arc::clone(&executor);
15903      let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
15904      executor.schedule(t1_runnable);
15905      wait_until("the worker-pinning task to start", || {
15906        t1_started.load(Ordering::SeqCst)
15907      });
15908
15909      // The blocking closure on worker B: spawn R, then block_on its result.
15910      let blocking_executor = Arc::clone(&executor);
15911      let r_done_setter = Arc::clone(&r_done);
15912      let started = Instant::now();
15913      let handle = executor.schedule_blocking(move || {
15914        let (r_tx, r_rx) = oneshot::channel::<()>();
15915        blocking_executor.pool.spawn(move || {
15916          r_done_setter.store(true, Ordering::SeqCst);
15917          let _ = r_tx.send(());
15918        });
15919        let mut wait_for_r = std::pin::pin!(async move {
15920          let _ = r_rx.await;
15921        });
15922        blocking_executor.block_on(wait_for_r.as_mut());
15923        let _ = done_tx.send(started.elapsed());
15924      });
15925
15926      // Wait until both the cooperative driver and the lingering drainer are
15927      // registered; if the linger expired first (R already ran), that round
15928      // completed without ever reaching the adversarial interleaving.
15929      let arm_deadline = Instant::now() + Duration::from_secs(15);
15930      while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
15931        && !r_done.load(Ordering::SeqCst)
15932      {
15933        assert!(
15934          Instant::now() < arm_deadline,
15935          "timed out arming the linger-starvation interleaving"
15936        );
15937        std::thread::yield_now();
15938      }
15939
15940      // Producer: one trivial runnable every ~200us (spin-paced, so cadence
15941      // stays far below the 500us idle budget), until R completed.
15942      let producer_executor = Arc::clone(&executor);
15943      let producer_r_done = Arc::clone(&r_done);
15944      let producer_give_up = Arc::clone(&give_up);
15945      let producer = std::thread::spawn(move || {
15946        let cadence = Duration::from_micros(200);
15947        let mut next_tick = Instant::now();
15948        while !producer_r_done.load(Ordering::SeqCst)
15949          && !producer_give_up.load(Ordering::SeqCst)
15950        {
15951          let scheduler = Arc::clone(&producer_executor);
15952          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
15953          producer_executor.schedule(runnable);
15954          task.detach();
15955          next_tick += cadence;
15956          while Instant::now() < next_tick {
15957            std::hint::spin_loop();
15958          }
15959        }
15960      });
15961
15962      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
15963      give_up.store(true, Ordering::SeqCst);
15964      producer.join().unwrap();
15965      match outcome {
15966        Ok(elapsed) => {
15967          futures::executor::block_on(handle).expect("blocking closure join");
15968          drop(t1_task);
15969          eprintln!("round {round}: completed in {elapsed:?}");
15970        }
15971        Err(error) => panic!(
15972          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
15973           sub-budget periodic work must not keep a drain frame resident forever"
15974        ),
15975      }
15976    }
15977  }
15978
15979  #[cfg(not(target_family = "wasm"))]
15980  #[test]
15981  #[ignore = "microbenchmark: run explicitly in release mode"]
15982  fn parked_drivers_registry_cycle_microbench() {
15983    // Isolates the registry traffic of one steady-state linger cycle at
15984    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
15985    // drainer deregisters (the entry is already gone, so the position scan
15986    // walks the WHOLE vector and fails), then re-registers (push). Also
15987    // measures the timeout-path deregister (entry present) and a
15988    // wake_one_owner_dependency sweep over dependency-less lingerers.
15989    for &n in &[8usize, 64, 256] {
15990      let registry = ParkedDrivers::default();
15991      let parkers: Vec<Arc<DriverParker>> =
15992        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
15993      for parker in &parkers {
15994        registry.register_with_role(parker, true, None);
15995      }
15996      let hot = &parkers[n - 1];
15997
15998      let iters = 2_000_000u32;
15999      let start = Instant::now();
16000      for _ in 0..iters {
16001        assert!(registry.wake_one());
16002        registry.deregister(hot);
16003        assert!(hot.consume_permit());
16004        registry.register_with_role(hot, true, None);
16005      }
16006      let wake_cycle = start.elapsed() / iters;
16007
16008      let start = Instant::now();
16009      for _ in 0..iters {
16010        registry.deregister(hot);
16011        registry.register_with_role(hot, true, None);
16012      }
16013      let timeout_cycle = start.elapsed() / iters;
16014
16015      let owner = BlockingOwnerToken {
16016        executor_id: u64::MAX,
16017        frame: u64::MAX,
16018      };
16019      let start = Instant::now();
16020      for _ in 0..iters {
16021        assert!(!registry.wake_one_owner_dependency(owner));
16022      }
16023      let owner_sweep = start.elapsed() / iters;
16024
16025      eprintln!(
16026        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
16027         timeout-deregister+re-register {timeout_cycle:?}, \
16028         owner-dependency sweep {owner_sweep:?}"
16029      );
16030    }
16031  }
16032
16033  #[cfg(not(target_family = "wasm"))]
16034  #[test]
16035  fn multi_thread_block_on_from_caller_thread_still_parks() {
16036    // The non-pool (napi caller) path must keep using the plain parking
16037    // `block_on`; here we just assert it drives a ready future to completion.
16038    let metrics = Arc::new(RuntimeMetrics::default());
16039    let options = RuntimeOptions {
16040      flavor: RuntimeFlavor::MultiThread,
16041      worker_threads: 3,
16042      max_blocking_tasks: 2,
16043      thread_name_prefix: "rd1-caller".to_string(),
16044      park_deadline: None,
16045      drain_linger: DEFAULT_DRAIN_LINGER,
16046    };
16047    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16048
16049    let mut output = None;
16050    let mut future = std::pin::pin!(async {
16051      output = Some(41usize + 1);
16052    });
16053    executor.block_on(future.as_mut());
16054    assert_eq!(output, Some(42));
16055  }
16056
16057  #[cfg(not(target_family = "wasm"))]
16058  #[test]
16059  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
16060    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
16061    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
16062    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
16063    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
16064    // This pins ONLY the cross-thread wake path: the pool task completing must wake
16065    // the parked caller's waker so `block_on` returns the pool-computed value.
16066    //
16067    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
16068    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
16069    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
16070    // parking waker is registered on the task), signals "caller parked" to a releaser
16071    // thread. The releaser then -- and only then -- releases the gate, so the pool
16072    // task completes strictly AFTER the caller registered its waker. This forecloses
16073    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
16074    // caller's first poll, because its gate is opened only in response to that first
16075    // `Pending` poll. The completion is thus forced to travel the real cross-thread
16076    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
16077    // if that wake is broken the caller parks forever and the bounded recv_timeout
16078    // below fails loudly instead of hanging the suite.
16079    //
16080    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
16081    // caller-is-pool-worker case, which has dedicated reentrancy tests.
16082    use std::sync::mpsc;
16083    use std::time::Duration;
16084
16085    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
16086    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
16087    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
16088    // is already registered before the gate can open.
16089    struct CallerProbe {
16090      task: Task<usize>,
16091      parked_tx: Option<mpsc::Sender<()>>,
16092    }
16093    impl Future for CallerProbe {
16094      type Output = usize;
16095      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
16096        // `Task` is `Unpin`; poll it through a fresh `Pin`.
16097        let poll = Pin::new(&mut self.task).poll(cx);
16098        if poll.is_pending()
16099          && let Some(tx) = self.parked_tx.take()
16100        {
16101          // Waker is now registered on `task`; release the gate is safe.
16102          let _ = tx.send(());
16103        }
16104        poll
16105      }
16106    }
16107
16108    let (done_tx, done_rx) = mpsc::channel();
16109    let runner = std::thread::spawn(move || {
16110      let metrics = Arc::new(RuntimeMetrics::default());
16111      let options = RuntimeOptions {
16112        flavor: RuntimeFlavor::MultiThread,
16113        worker_threads: 2,
16114        max_blocking_tasks: 2,
16115        thread_name_prefix: "rd10-caller".to_string(),
16116        park_deadline: None,
16117        drain_linger: DEFAULT_DRAIN_LINGER,
16118      };
16119      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16120
16121      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
16122      // worker registers the gate waker and returns `Pending`. When the releaser sends
16123      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
16124      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
16125      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
16126      let scheduler = Arc::clone(&executor);
16127      let (runnable, task) = async_task::spawn(
16128        async move { gate_rx.await.expect("gate sender dropped") },
16129        move |r| {
16130          scheduler.schedule(r);
16131        },
16132      );
16133      executor.schedule(runnable);
16134
16135      // Releaser thread: opens the gate ONLY after the caller has parked (registered
16136      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
16137      // completes, so completion must travel the cross-thread wake path.
16138      let (parked_tx, parked_rx) = mpsc::channel::<()>();
16139      let releaser = std::thread::spawn(move || {
16140        if parked_rx.recv().is_ok() {
16141          let _ = gate_tx.send(42usize);
16142        }
16143      });
16144
16145      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
16146      // `futures::executor::block_on` rather than driving the queue cooperatively.
16147      let mut output = None;
16148      {
16149        let mut future = std::pin::pin!(async {
16150          output = Some(
16151            CallerProbe {
16152              task,
16153              parked_tx: Some(parked_tx),
16154            }
16155            .await,
16156          );
16157        });
16158        executor.block_on(future.as_mut());
16159      }
16160
16161      releaser.join().unwrap();
16162      assert_eq!(
16163        output,
16164        Some(42usize),
16165        "block_on must return the pool-computed value"
16166      );
16167      let _ = done_tx.send(());
16168    });
16169
16170    match done_rx.recv_timeout(Duration::from_secs(10)) {
16171      Ok(()) => runner.join().unwrap(),
16172      Err(error) => panic!(
16173        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
16174      ),
16175    }
16176  }
16177
16178  #[cfg(not(target_family = "wasm"))]
16179  #[test]
16180  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
16181    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
16182    // before the work that will wake it is scheduled. Once every pool worker
16183    // is parked this way, `ensure_drainer` cannot spawn a replacement because
16184    // the parked workers are still counted as active drainers. A later
16185    // `schedule()` must wake a parked driver directly so the runnable can run.
16186    use std::sync::{Barrier, mpsc};
16187    use std::time::Duration;
16188
16189    let (done_tx, done_rx) = mpsc::channel();
16190    let runner = std::thread::spawn(move || {
16191      let metrics = Arc::new(RuntimeMetrics::default());
16192      let options = RuntimeOptions {
16193        flavor: RuntimeFlavor::MultiThread,
16194        worker_threads: 2,
16195        max_blocking_tasks: 2,
16196        thread_name_prefix: "rd1-park".to_string(),
16197        park_deadline: None,
16198        drain_linger: DEFAULT_DRAIN_LINGER,
16199      };
16200      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16201
16202      let task_count = 2usize;
16203      let barrier = Arc::new(Barrier::new(task_count));
16204
16205      let mut senders = Vec::new();
16206      let mut tasks = Vec::new();
16207      for _ in 0..task_count {
16208        let (tx, rx) = oneshot::channel::<usize>();
16209        senders.push(tx);
16210        let exec = Arc::clone(&executor);
16211        let barrier = Arc::clone(&barrier);
16212        let body = async move {
16213          // Make sure both pool workers are occupied before either parks.
16214          barrier.wait();
16215          // Child task whose wake routes through `schedule()`: the oneshot
16216          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
16217          let child_exec = Arc::clone(&exec);
16218          let (child_runnable, child_task) =
16219            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
16220              child_exec.schedule(r)
16221            });
16222          exec.schedule(child_runnable);
16223          let mut inner = std::pin::pin!(async move {
16224            assert_eq!(child_task.await, 9usize);
16225          });
16226          exec.block_on(inner.as_mut());
16227        };
16228        let scheduler = Arc::clone(&executor);
16229        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
16230        executor.schedule(runnable);
16231        tasks.push(task);
16232      }
16233
16234      // Give both workers time to poll their child Pending and park in block_on.
16235      std::thread::sleep(Duration::from_millis(200));
16236      for tx in senders {
16237        let _ = tx.send(9usize);
16238      }
16239
16240      for task in tasks {
16241        futures::executor::block_on(task);
16242      }
16243      let _ = done_tx.send(());
16244    });
16245
16246    match done_rx.recv_timeout(Duration::from_secs(10)) {
16247      Ok(()) => runner.join().unwrap(),
16248      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
16249    }
16250  }
16251
16252  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
16253  /// Used to observe executor-internal states (e.g. "both drivers are
16254  /// registered as parked") without sleeps. The bound is a HANG detector,
16255  /// not a latency assertion: starved CI runners can stall fresh threads for
16256  /// hundreds of milliseconds, so it is deliberately generous.
16257  #[cfg(not(target_family = "wasm"))]
16258  fn wait_until(what: &str, condition: impl Fn() -> bool) {
16259    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
16260    while !condition() {
16261      assert!(
16262        std::time::Instant::now() < deadline,
16263        "timed out waiting for: {what}"
16264      );
16265      std::thread::yield_now();
16266    }
16267  }
16268
16269  /// Bounded join for harness threads that already signalled completion of
16270  /// their assertions: everything left on the thread is teardown (executor
16271  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
16272  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
16273  /// into a silent libtest-worker hang for the full CI step timeout (observed
16274  /// on Windows in run 29195160953: several park-deadline-family tests never
16275  /// completed while every in-test assertion had already passed). Fail loudly
16276  /// and NAME the awaited phase instead.
16277  #[cfg(not(target_family = "wasm"))]
16278  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
16279    let deadline = std::time::Instant::now() + timeout;
16280    while !handle.is_finished() {
16281      assert!(
16282        std::time::Instant::now() < deadline,
16283        "thread wedged past its {timeout:?} bound while: {what}"
16284      );
16285      std::thread::sleep(Duration::from_millis(10));
16286    }
16287    handle.join().unwrap();
16288  }
16289
16290  #[cfg(not(target_family = "wasm"))]
16291  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
16292    executor: &Arc<MultiThreadExecutor>,
16293    start_counter: &AtomicU64,
16294    claim: impl FnOnce() -> T + Send + 'static,
16295  ) -> T {
16296    use std::sync::mpsc;
16297
16298    let (claimed_tx, claimed_rx) = mpsc::channel();
16299    let (release_claim_tx, release_claim_rx) = mpsc::channel();
16300    let (started_tx, started_rx) = mpsc::channel();
16301    let (release_start_tx, release_start_rx) = mpsc::channel();
16302    let starts_before = start_counter.load(Ordering::SeqCst);
16303    let worker = std::thread::spawn(move || {
16304      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
16305        *slot.borrow_mut() = Some(Box::new(move || {
16306          claimed_tx.send(()).unwrap();
16307          release_claim_rx.recv().unwrap();
16308        }));
16309      });
16310      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
16311        *slot.borrow_mut() = Some(Box::new(move || {
16312          started_tx.send(()).unwrap();
16313          release_start_rx.recv().unwrap();
16314        }));
16315      });
16316      claim()
16317    });
16318    claimed_rx
16319      .recv_timeout(Duration::from_secs(2))
16320      .expect("the scheduler claim must pause after removing work");
16321
16322    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16323    let verdict = executor.try_begin_deadlock_verdict();
16324    let claim_verdict_rejected = verdict.is_none();
16325    drop(verdict);
16326    release_claim_tx.send(()).unwrap();
16327
16328    started_rx
16329      .recv_timeout(Duration::from_secs(2))
16330      .expect("the scheduler claim must pause after publishing its start");
16331    let admission_active_after_start =
16332      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16333    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
16334    let verdict = executor.try_begin_deadlock_verdict();
16335    let start_verdict_rejected = verdict.is_none();
16336    drop(verdict);
16337    release_start_tx.send(()).unwrap();
16338    let output = worker.join().unwrap();
16339
16340    assert!(
16341      admission_active,
16342      "removing scheduler work must retain a detector admission until start accounting"
16343    );
16344    assert!(
16345      claim_verdict_rejected,
16346      "a deadline verdict must not close while removed work has not published its start"
16347    );
16348    assert!(
16349      start_published,
16350      "work start accounting must publish before admission retires"
16351    );
16352    assert!(
16353      admission_active_after_start,
16354      "the detector admission must remain active until after start accounting"
16355    );
16356    assert!(
16357      start_verdict_rejected,
16358      "a deadline verdict must not close between start accounting and admission retirement"
16359    );
16360    output
16361  }
16362
16363  #[cfg(not(target_family = "wasm"))]
16364  #[test]
16365  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
16366    let executor =
16367      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
16368
16369    let fifo_ran = Arc::new(AtomicBool::new(false));
16370    let fifo_task_ran = Arc::clone(&fifo_ran);
16371    let (fifo, fifo_task) = async_task::spawn(
16372      async move {
16373        fifo_task_ran.store(true, Ordering::SeqCst);
16374      },
16375      |_| {},
16376    );
16377    executor.metrics.runnable_scheduled();
16378    executor.push_fifo_raw(fifo);
16379    let fifo_executor = Arc::clone(&executor);
16380    assert!(run_claim_with_deadlock_gate_assertion(
16381      &executor,
16382      &executor.metrics.runnable_polls,
16383      move || {
16384        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16385        fifo_executor.run_one_fifo_runnable()
16386      }
16387    ));
16388    futures::executor::block_on(fifo_task);
16389    assert!(fifo_ran.load(Ordering::SeqCst));
16390
16391    let fifo_order = Arc::new(Mutex::new(Vec::new()));
16392    let mut fifo_tasks = Vec::new();
16393    for name in ["first", "second"] {
16394      let task_order = Arc::clone(&fifo_order);
16395      let (runnable, task) = async_task::spawn(
16396        async move {
16397          task_order.lock().unwrap().push(name);
16398        },
16399        |_| {},
16400      );
16401      executor.metrics.runnable_scheduled();
16402      executor.push_fifo_raw(runnable);
16403      fifo_tasks.push(task);
16404    }
16405    for _ in 0..2 {
16406      let fifo_executor = Arc::clone(&executor);
16407      assert!(run_claim_with_deadlock_gate_assertion(
16408        &executor,
16409        &executor.metrics.runnable_polls,
16410        move || {
16411          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16412          fifo_executor.run_one_runnable()
16413        }
16414      ));
16415    }
16416    for task in fifo_tasks {
16417      futures::executor::block_on(task);
16418    }
16419    assert_eq!(
16420      *fifo_order.lock().unwrap(),
16421      ["first", "second"],
16422      "the detector-enabled normal fallback must preserve FIFO order"
16423    );
16424
16425    let lifo_ran = Arc::new(AtomicBool::new(false));
16426    let lifo_task_ran = Arc::clone(&lifo_ran);
16427    let (lifo, lifo_task) = async_task::spawn(
16428      async move {
16429        lifo_task_ran.store(true, Ordering::SeqCst);
16430      },
16431      |_| {},
16432    );
16433    executor.metrics.runnable_scheduled();
16434    let lifo_executor = Arc::clone(&executor);
16435    assert!(run_claim_with_deadlock_gate_assertion(
16436      &executor,
16437      &executor.metrics.runnable_polls,
16438      move || {
16439        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
16440        LIFO_SLOT.with(|slot| {
16441          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
16442        });
16443        lifo_executor.run_one_runnable()
16444      }
16445    ));
16446    futures::executor::block_on(lifo_task);
16447    assert!(lifo_ran.load(Ordering::SeqCst));
16448
16449    executor.begin_shutdown();
16450    executor.wait_until_scheduler_idle();
16451  }
16452
16453  #[cfg(not(target_family = "wasm"))]
16454  #[test]
16455  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
16456    let executor =
16457      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
16458
16459    let fifo_ran = Arc::new(AtomicBool::new(false));
16460    let fifo_job_ran = Arc::clone(&fifo_ran);
16461    executor.metrics.blocking_scheduled();
16462    executor
16463      .blocking_queue
16464      .lock()
16465      .unwrap()
16466      .push(QueuedBlocking {
16467        id: BlockingJobId(u64::MAX - 90),
16468        run: Box::new(move || {
16469          fifo_job_ran.store(true, Ordering::SeqCst);
16470        }),
16471      });
16472    let fifo_executor = Arc::clone(&executor);
16473    assert!(run_claim_with_deadlock_gate_assertion(
16474      &executor,
16475      &executor.metrics.blocking_tasks_started,
16476      move || {
16477        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16478        fifo_executor.run_one_blocking()
16479      }
16480    ));
16481    assert!(fifo_ran.load(Ordering::SeqCst));
16482
16483    let owner = executor.fresh_owner_token();
16484    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
16485    let exact_job = BlockingJobId(u64::MAX - 91);
16486    let exact_ran = Arc::new(AtomicBool::new(false));
16487    let exact_job_ran = Arc::clone(&exact_ran);
16488    executor.metrics.blocking_scheduled();
16489    executor
16490      .blocking_queue
16491      .lock()
16492      .unwrap()
16493      .push(QueuedBlocking {
16494        id: exact_job,
16495        run: Box::new(move || {
16496          exact_job_ran.store(true, Ordering::SeqCst);
16497        }),
16498      });
16499    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
16500    dependency.set_owned(BlockingDependency {
16501      job: exact_job,
16502      owner: Some(owner),
16503    });
16504    let exact_executor = Arc::clone(&executor);
16505    assert!(run_claim_with_deadlock_gate_assertion(
16506      &executor,
16507      &executor.metrics.blocking_tasks_started,
16508      move || {
16509        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
16510        let _owner = BlockingOwnerGuard::enter(Some(owner));
16511        exact_executor.try_owned_blocking_over_cap(&dependency)
16512      }
16513    ));
16514    assert!(exact_ran.load(Ordering::SeqCst));
16515
16516    drop(active_owner);
16517    executor.begin_shutdown();
16518    executor.wait_until_scheduler_idle();
16519  }
16520
16521  #[cfg(not(target_family = "wasm"))]
16522  #[test]
16523  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
16524    // Empty-registry behavior: with nothing registered, `wake_one` must return
16525    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
16526    // the common case, since queue-wakes fire on every schedule while drivers
16527    // park only in rare re-entrant `block_on`). Proxy for "did not take the
16528    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
16529    // anyway, while (control) a wake_one issued with a parker registered must
16530    // take the slow path and block on that held lock until it is released.
16531    use std::sync::mpsc;
16532    use std::time::Duration;
16533
16534    let registry = Arc::new(ParkedDrivers::default());
16535
16536    // Fast path: empty registry -> wake_one completes although the lock is held.
16537    let guard = registry.parked.lock().unwrap();
16538    let (done_tx, done_rx) = mpsc::channel();
16539    let waker_thread = {
16540      let registry = Arc::clone(&registry);
16541      std::thread::spawn(move || {
16542        done_tx.send(registry.wake_one()).unwrap();
16543      })
16544    };
16545    let woke = done_rx
16546      .recv_timeout(Duration::from_secs(5))
16547      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
16548    assert!(!woke, "an empty registry has nobody to wake");
16549    waker_thread.join().unwrap();
16550    drop(guard);
16551
16552    // Control: with a parker registered the SAME held lock must block
16553    // wake_one (slow path). This proves the fast path above genuinely skipped
16554    // the lock rather than wake_one never locking at all.
16555    let parker = Arc::new(DriverParker::default());
16556    registry.register(&parker);
16557    let guard = registry.parked.lock().unwrap();
16558    let (done_tx, done_rx) = mpsc::channel();
16559    let waker_thread = {
16560      let registry = Arc::clone(&registry);
16561      std::thread::spawn(move || {
16562        done_tx.send(registry.wake_one()).unwrap();
16563      })
16564    };
16565    assert!(
16566      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16567      "wake_one with a registered parker must take the slow path (block on the held lock)"
16568    );
16569    drop(guard);
16570    let woke = done_rx
16571      .recv_timeout(Duration::from_secs(5))
16572      .expect("slow-path wake_one must complete once the lock is released");
16573    assert!(
16574      woke,
16575      "the slow path must pop and wake the registered parker"
16576    );
16577    waker_thread.join().unwrap();
16578    // The popped parker received its permit: a park now returns immediately
16579    // instead of sleeping (bounded by the test harness if broken).
16580    parker.park();
16581    assert_eq!(
16582      registry.count.load(Ordering::SeqCst),
16583      0,
16584      "wake_one deregisters what it pops"
16585    );
16586  }
16587
16588  #[cfg(not(target_family = "wasm"))]
16589  #[test]
16590  fn driver_parker_permit_granted_while_running_is_not_lost() {
16591    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
16592    // parked) must be stored as a permit that makes the next `park` return
16593    // immediately; multiple such wakes coalesce into ONE permit.
16594    use std::sync::mpsc;
16595    use std::time::Duration;
16596
16597    let parker = Arc::new(DriverParker::default());
16598    // Two wakes while "running" (nobody parked yet).
16599    parker.unpark();
16600    parker.unpark();
16601
16602    let (tx, rx) = mpsc::channel();
16603    let driver = {
16604      let parker = Arc::clone(&parker);
16605      std::thread::spawn(move || {
16606        parker.park(); // must consume the stored permit without sleeping
16607        tx.send(1).unwrap();
16608        parker.park(); // no permit left: must genuinely sleep
16609        tx.send(2).unwrap();
16610      })
16611    };
16612
16613    assert_eq!(
16614      rx.recv_timeout(Duration::from_secs(5))
16615        .expect("a stored permit must not be lost"),
16616      1
16617    );
16618    assert!(
16619      rx.recv_timeout(Duration::from_millis(300)).is_err(),
16620      "two pre-park wakes must coalesce into one permit (second park must sleep)"
16621    );
16622    parker.unpark();
16623    assert_eq!(
16624      rx.recv_timeout(Duration::from_secs(5))
16625        .expect("unpark must wake a sleeping driver"),
16626      2
16627    );
16628    driver.join().unwrap();
16629  }
16630
16631  #[cfg(not(target_family = "wasm"))]
16632  #[test]
16633  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
16634    use std::sync::mpsc;
16635
16636    let parker = Arc::new(DriverParker::default());
16637    assert!(
16638      Instant::now().checked_add(Duration::MAX).is_none(),
16639      "Duration::MAX must exercise the unrepresentable-deadline path"
16640    );
16641    let (done_tx, done_rx) = mpsc::channel();
16642    let driver = {
16643      let parker = Arc::clone(&parker);
16644      std::thread::spawn(move || {
16645        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
16646      })
16647    };
16648    wait_until("the unbounded timeout driver to sleep", || {
16649      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
16650    });
16651    assert!(
16652      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
16653      "an unrepresentable timeout must not expire immediately"
16654    );
16655
16656    let unparker = {
16657      let parker = Arc::clone(&parker);
16658      std::thread::spawn(move || parker.unpark())
16659    };
16660    assert!(
16661      done_rx
16662        .recv_timeout(Duration::from_secs(1))
16663        .expect("an unrepresentable timeout must remain wakeable"),
16664      "a permit must be reported as a wake"
16665    );
16666    unparker.join().unwrap();
16667    driver.join().unwrap();
16668  }
16669
16670  #[cfg(not(target_family = "wasm"))]
16671  #[test]
16672  fn parked_drivers_wake_one_wakes_exactly_one() {
16673    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
16674    // other stays parked. Targeted wakes must neither wake everyone nor lose
16675    // the wake.
16676    use std::sync::mpsc;
16677    use std::time::Duration;
16678
16679    let registry = Arc::new(ParkedDrivers::default());
16680    let (woke_tx, woke_rx) = mpsc::channel();
16681    let mut drivers = Vec::new();
16682    for index in 0..2 {
16683      let registry = Arc::clone(&registry);
16684      let woke_tx = woke_tx.clone();
16685      drivers.push(std::thread::spawn(move || {
16686        let parker = Arc::new(DriverParker::default());
16687        registry.register(&parker);
16688        parker.park();
16689        registry.deregister(&parker);
16690        woke_tx.send(index).unwrap();
16691      }));
16692    }
16693    wait_until("both drivers registered as parked", || {
16694      registry.count.load(Ordering::SeqCst) == 2
16695    });
16696
16697    assert!(registry.wake_one(), "wake_one must wake a parked driver");
16698    woke_rx
16699      .recv_timeout(Duration::from_secs(5))
16700      .expect("exactly one driver must wake");
16701    assert!(
16702      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16703      "the second driver must stay parked after a single wake_one"
16704    );
16705    assert_eq!(
16706      registry.count.load(Ordering::SeqCst),
16707      1,
16708      "one driver must remain registered"
16709    );
16710
16711    assert!(
16712      registry.wake_one(),
16713      "a second wake_one must wake the remaining driver"
16714    );
16715    woke_rx
16716      .recv_timeout(Duration::from_secs(5))
16717      .expect("the second driver must wake");
16718    for driver in drivers {
16719      driver.join().unwrap();
16720    }
16721  }
16722
16723  #[cfg(not(target_family = "wasm"))]
16724  #[test]
16725  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
16726    use std::sync::mpsc;
16727
16728    struct ReenterRegistryOnDrop {
16729      registry: Arc<ParkedDrivers>,
16730      dropped: mpsc::Sender<()>,
16731    }
16732
16733    #[expect(
16734      clippy::manual_noop_waker,
16735      reason = "the test observes the waker payload's Drop"
16736    )]
16737    impl std::task::Wake for ReenterRegistryOnDrop {
16738      fn wake(self: Arc<Self>) {}
16739    }
16740
16741    impl Drop for ReenterRegistryOnDrop {
16742      fn drop(&mut self) {
16743        let parker = Arc::new(DriverParker::default());
16744        self.registry.register(&parker);
16745        self.registry.deregister(&parker);
16746        self.dropped.send(()).unwrap();
16747      }
16748    }
16749
16750    let registry = Arc::new(ParkedDrivers::default());
16751    let dependency = Arc::new(TaskDependency::default());
16752    let (dropped_tx, dropped_rx) = mpsc::channel();
16753    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
16754      registry: Arc::clone(&registry),
16755      dropped: dropped_tx,
16756    }));
16757    dependency.register_waiter(&waiter);
16758    drop(waiter);
16759
16760    let parker = Arc::new(DriverParker::default());
16761    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16762    drop(dependency);
16763
16764    let retiring_registry = Arc::clone(&registry);
16765    let retiring_parker = Arc::clone(&parker);
16766    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16767    dropped_rx
16768      .recv_timeout(Duration::from_secs(1))
16769      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16770    retiring.join().unwrap();
16771  }
16772
16773  #[cfg(not(target_family = "wasm"))]
16774  #[test]
16775  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16776    use std::sync::mpsc;
16777
16778    let registry = Arc::new(ParkedDrivers::default());
16779    let owner = BlockingOwnerToken {
16780      executor_id: u64::MAX - 70,
16781      frame: u64::MAX - 71,
16782    };
16783    let dependency = Arc::new(TaskDependency::default());
16784    dependency.set_owned(BlockingDependency {
16785      job: BlockingJobId(u64::MAX - 72),
16786      owner: Some(owner),
16787    });
16788    let parker = Arc::new(DriverParker::default());
16789    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16790
16791    let (predicate_tx, predicate_rx) = mpsc::channel();
16792    let (release_tx, release_rx) = mpsc::channel();
16793    let selecting_registry = Arc::clone(&registry);
16794    let selector = std::thread::spawn(move || {
16795      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16796        *slot.borrow_mut() = Some(Box::new(move || {
16797          predicate_tx.send(()).unwrap();
16798          release_rx.recv().unwrap();
16799        }));
16800      });
16801      selecting_registry.wake_one_owner_dependency(owner)
16802    });
16803
16804    predicate_rx
16805      .recv_timeout(Duration::from_secs(2))
16806      .expect("owner selection must reach the dependency predicate");
16807    let registry_was_unlocked = registry.parked.try_lock().is_ok();
16808    release_tx.send(()).unwrap();
16809    assert!(
16810      selector.join().unwrap(),
16811      "the exact-owner parker must still be selected"
16812    );
16813
16814    assert!(
16815      registry_was_unlocked,
16816      "owner dependency predicates must not run while the parked-driver registry is locked"
16817    );
16818    assert!(parker.consume_permit());
16819    assert_eq!(parker.take_owner_handoff(), Some(owner));
16820  }
16821
16822  #[cfg(not(target_family = "wasm"))]
16823  #[test]
16824  fn owner_handoff_skips_newer_untagged_dependency() {
16825    let registry = ParkedDrivers::default();
16826    let owner = BlockingOwnerToken {
16827      executor_id: u64::MAX - 1,
16828      frame: u64::MAX - 2,
16829    };
16830    let owned_dependency = Arc::new(TaskDependency::default());
16831    owned_dependency.set_owned(BlockingDependency {
16832      job: BlockingJobId(u64::MAX - 3),
16833      owner: Some(owner),
16834    });
16835    let untagged_dependency = Arc::new(TaskDependency::default());
16836    untagged_dependency.set_owned(BlockingDependency {
16837      job: BlockingJobId(u64::MAX - 4),
16838      owner: None,
16839    });
16840    let owned = Arc::new(DriverParker::default());
16841    let untagged = Arc::new(DriverParker::default());
16842
16843    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16844    // Register the unrelated dependency last so an unfiltered LIFO retry
16845    // would consume the exact owner handoff.
16846    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16847
16848    assert!(registry.wake_one_owner_dependency(owner));
16849    assert!(
16850      owned.consume_permit(),
16851      "the exact owner dependency must receive the retry"
16852    );
16853    assert!(
16854      !untagged.consume_permit(),
16855      "an untagged dependency must not consume an exact owner handoff"
16856    );
16857    registry.deregister(&untagged);
16858  }
16859
16860  #[cfg(not(target_family = "wasm"))]
16861  #[test]
16862  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16863    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16864    let owner = executor.fresh_owner_token();
16865    let valid_dependency = Arc::new(TaskDependency::default());
16866    valid_dependency.set_owned(BlockingDependency {
16867      job: BlockingJobId(u64::MAX - 45),
16868      owner: Some(owner),
16869    });
16870    let withdrawn_dependency = Arc::new(TaskDependency::default());
16871    withdrawn_dependency.set_owned(BlockingDependency {
16872      job: BlockingJobId(u64::MAX - 46),
16873      owner: Some(owner),
16874    });
16875    let valid = Arc::new(DriverParker::default());
16876    let withdrawn = Arc::new(DriverParker::default());
16877
16878    executor
16879      .parked_drivers
16880      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16881    executor.parked_drivers.register_with_role(
16882      &withdrawn,
16883      true,
16884      Some(Arc::clone(&withdrawn_dependency)),
16885    );
16886    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16887    withdrawn_dependency.clear();
16888
16889    assert_eq!(
16890      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16891      Some(false),
16892      "a selected dependency withdrawn before retry must forward the owner handoff"
16893    );
16894    assert!(
16895      valid.consume_permit(),
16896      "the next live same-owner dependency must receive the forwarded wake"
16897    );
16898    assert_eq!(
16899      valid.take_owner_handoff(),
16900      Some(owner),
16901      "the forwarded wake must retain the exact owner identity"
16902    );
16903  }
16904
16905  #[cfg(not(target_family = "wasm"))]
16906  #[test]
16907  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16908    use std::sync::mpsc;
16909
16910    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16911    let owner = executor.fresh_owner_token();
16912    let fallback_dependency = Arc::new(TaskDependency::default());
16913    fallback_dependency.set_owned(BlockingDependency {
16914      job: BlockingJobId(u64::MAX - 47),
16915      owner: Some(owner),
16916    });
16917    let exiting_dependency = Arc::new(TaskDependency::default());
16918    exiting_dependency.set_owned(BlockingDependency {
16919      job: BlockingJobId(u64::MAX - 48),
16920      owner: Some(owner),
16921    });
16922    let fallback = Arc::new(DriverParker::default());
16923    let exiting = Arc::new(DriverParker::default());
16924
16925    executor.parked_drivers.register_with_role(
16926      &fallback,
16927      true,
16928      Some(Arc::clone(&fallback_dependency)),
16929    );
16930    executor.parked_drivers.register_with_role(
16931      &exiting,
16932      true,
16933      Some(Arc::clone(&exiting_dependency)),
16934    );
16935
16936    let (selected_tx, selected_rx) = mpsc::channel();
16937    let (release_selection_tx, release_selection_rx) = mpsc::channel();
16938    let selector_executor = Arc::clone(&executor);
16939    let selector = std::thread::spawn(move || {
16940      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16941        *slot.borrow_mut() = Some(Box::new(move || {
16942          selected_tx.send(()).unwrap();
16943          release_selection_rx.recv().unwrap();
16944        }));
16945      });
16946      assert!(
16947        selector_executor
16948          .parked_drivers
16949          .wake_one_owner_dependency(owner)
16950      );
16951    });
16952    selected_rx
16953      .recv_timeout(Duration::from_secs(2))
16954      .expect("selection must pause after removing the exact-owner parker");
16955
16956    let (exited_tx, exited_rx) = mpsc::channel();
16957    let exiting_executor = Arc::clone(&executor);
16958    let exiting_driver = Arc::clone(&exiting);
16959    let driver = std::thread::spawn(move || {
16960      exiting_driver.park();
16961      exiting_executor.parked_drivers.deregister(&exiting_driver);
16962      exiting_executor.forward_owner_handoff(&exiting_driver);
16963      exited_tx.send(()).unwrap();
16964    });
16965    exiting.unpark();
16966    exited_rx
16967      .recv_timeout(Duration::from_secs(2))
16968      .expect("the direct future wake must let the selected driver exit");
16969
16970    release_selection_tx.send(()).unwrap();
16971    selector.join().unwrap();
16972    driver.join().unwrap();
16973
16974    assert!(
16975      fallback.consume_permit(),
16976      "an exiting selected driver must forward the owner handoff to the next dependency"
16977    );
16978    assert_eq!(
16979      fallback.take_owner_handoff(),
16980      Some(owner),
16981      "the forwarded handoff must retain the exact owner identity"
16982    );
16983  }
16984
16985  #[cfg(not(target_family = "wasm"))]
16986  #[test]
16987  fn cooperative_panic_forwards_pending_owner_handoff() {
16988    use std::sync::mpsc;
16989
16990    struct PanicAfterWake {
16991      dependency: BlockingDependency,
16992      first_poll: Option<mpsc::Sender<()>>,
16993    }
16994
16995    impl Future for PanicAfterWake {
16996      type Output = ();
16997
16998      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16999        if let Some(first_poll) = self.first_poll.take() {
17000          // This shape never retires its publication -- the driver panics
17001          // instead -- so the returned token is dropped straight away.
17002          let mut published = PublishedBlockingDependencies::default();
17003          record_blocking_dependency(&mut published, self.dependency);
17004          first_poll.send(()).unwrap();
17005          return Poll::Pending;
17006        }
17007        panic!("intentional cooperative driver panic after owner handoff");
17008      }
17009    }
17010
17011    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
17012    let owner = executor.fresh_owner_token();
17013    let fallback_dependency = Arc::new(TaskDependency::default());
17014    fallback_dependency.set_owned(BlockingDependency {
17015      job: BlockingJobId(u64::MAX - 49),
17016      owner: Some(owner),
17017    });
17018    let fallback = Arc::new(DriverParker::default());
17019    executor.parked_drivers.register_with_role(
17020      &fallback,
17021      true,
17022      Some(Arc::clone(&fallback_dependency)),
17023    );
17024
17025    let (first_poll_tx, first_poll_rx) = mpsc::channel();
17026    let (panicked_tx, panicked_rx) = mpsc::channel();
17027    let driver_executor = Arc::clone(&executor);
17028    let driver = std::thread::spawn(move || {
17029      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17030      let mut future = std::pin::pin!(PanicAfterWake {
17031        dependency: BlockingDependency {
17032          job: BlockingJobId(u64::MAX - 50),
17033          owner: Some(owner)
17034        },
17035        first_poll: Some(first_poll_tx),
17036      });
17037      let panicked = catch_unwind(AssertUnwindSafe(|| {
17038        driver_executor.block_on(future.as_mut());
17039      }))
17040      .is_err();
17041      panicked_tx.send(panicked).unwrap();
17042    });
17043
17044    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17045    wait_until("the panicking cooperative driver to park", || {
17046      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17047    });
17048    assert!(
17049      executor.parked_drivers.wake_one_owner_dependency(owner),
17050      "the exact owner handoff must select the cooperative driver"
17051    );
17052    assert!(
17053      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17054      "the test future must unwind from its second poll"
17055    );
17056    driver.join().unwrap();
17057
17058    assert!(
17059      fallback.consume_permit(),
17060      "panic cleanup must forward the unconsumed owner handoff"
17061    );
17062    assert_eq!(
17063      fallback.take_owner_handoff(),
17064      Some(owner),
17065      "panic cleanup must preserve the exact owner identity"
17066    );
17067  }
17068
17069  #[cfg(not(target_family = "wasm"))]
17070  #[test]
17071  fn cooperative_panic_flushes_lifo_slot() {
17072    struct ScheduleThenPanic {
17073      executor: Arc<MultiThreadExecutor>,
17074      ran: Arc<AtomicBool>,
17075    }
17076
17077    impl Future for ScheduleThenPanic {
17078      type Output = ();
17079
17080      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17081        let ran = Arc::clone(&self.ran);
17082        let scheduler = Arc::clone(&self.executor);
17083        let (runnable, task) = async_task::spawn(
17084          async move {
17085            ran.store(true, Ordering::SeqCst);
17086          },
17087          move |runnable| scheduler.schedule(runnable),
17088        );
17089        self.executor.schedule(runnable);
17090        task.detach();
17091        panic!("intentional cooperative driver panic with a populated LIFO slot");
17092      }
17093    }
17094
17095    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
17096    executor
17097      .active_drainers
17098      .store(executor.max_drainers, Ordering::SeqCst);
17099    let ran = Arc::new(AtomicBool::new(false));
17100    let panicked = {
17101      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17102      let mut future = std::pin::pin!(ScheduleThenPanic {
17103        executor: Arc::clone(&executor),
17104        ran: Arc::clone(&ran),
17105      });
17106      catch_unwind(AssertUnwindSafe(|| {
17107        executor.block_on(future.as_mut());
17108      }))
17109      .is_err()
17110    };
17111
17112    assert!(panicked, "the test future must unwind from its first poll");
17113    assert!(
17114      executor.pop_lifo_slot().is_none(),
17115      "panic cleanup must empty the LIFO slot"
17116    );
17117    assert_eq!(
17118      executor.injector.len(),
17119      1,
17120      "panic cleanup must move the slot runnable to the shared FIFO"
17121    );
17122    assert!(
17123      executor.run_one(),
17124      "the flushed runnable must remain executable"
17125    );
17126    assert!(ran.load(Ordering::SeqCst));
17127    executor.active_drainers.store(0, Ordering::SeqCst);
17128  }
17129
17130  #[cfg(not(target_family = "wasm"))]
17131  #[test]
17132  fn cooperative_panic_compensates_absorbed_queue_wake() {
17133    use std::sync::mpsc;
17134
17135    struct PanicAfterWake {
17136      first_poll: Option<mpsc::Sender<()>>,
17137    }
17138
17139    impl Future for PanicAfterWake {
17140      type Output = ();
17141
17142      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17143        if let Some(first_poll) = self.first_poll.take() {
17144          first_poll.send(()).unwrap();
17145          return Poll::Pending;
17146        }
17147        panic!("intentional cooperative driver panic after a queue wake");
17148      }
17149    }
17150
17151    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
17152    executor
17153      .active_drainers
17154      .store(executor.max_drainers, Ordering::SeqCst);
17155    let fallback = Arc::new(DriverParker::default());
17156    executor.parked_drivers.register(&fallback);
17157    let (first_poll_tx, first_poll_rx) = mpsc::channel();
17158    let (panicked_tx, panicked_rx) = mpsc::channel();
17159    let driver_executor = Arc::clone(&executor);
17160    let driver = std::thread::spawn(move || {
17161      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17162      let mut future = std::pin::pin!(PanicAfterWake {
17163        first_poll: Some(first_poll_tx)
17164      });
17165      let panicked = catch_unwind(AssertUnwindSafe(|| {
17166        driver_executor.block_on(future.as_mut());
17167      }))
17168      .is_err();
17169      panicked_tx.send(panicked).unwrap();
17170    });
17171
17172    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17173    wait_until(
17174      "the panicking cooperative driver to park after the fallback",
17175      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
17176    );
17177
17178    let ran = Arc::new(AtomicBool::new(false));
17179    let ran_task = Arc::clone(&ran);
17180    let scheduler = Arc::clone(&executor);
17181    let (runnable, task) = async_task::spawn(
17182      async move {
17183        ran_task.store(true, Ordering::SeqCst);
17184      },
17185      move |runnable| scheduler.schedule(runnable),
17186    );
17187    task.detach();
17188    executor.metrics.runnable_scheduled();
17189    executor.push_fifo_raw(runnable);
17190    assert!(
17191      executor.parked_drivers.wake_one(),
17192      "the queued runnable's wake must be absorbed by the cooperative driver"
17193    );
17194
17195    assert!(
17196      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17197      "the test future must unwind after absorbing the queue wake"
17198    );
17199    driver.join().unwrap();
17200    assert!(
17201      fallback.consume_permit(),
17202      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
17203    );
17204    assert!(
17205      executor.run_one(),
17206      "the queued runnable must remain executable"
17207    );
17208    assert!(ran.load(Ordering::SeqCst));
17209    executor.active_drainers.store(0, Ordering::SeqCst);
17210  }
17211
17212  #[cfg(not(target_family = "wasm"))]
17213  #[test]
17214  fn multi_thread_future_wake_targets_its_own_parked_driver() {
17215    // Future-wake targeting invariant: a future-wake must reach the driver that
17216    // AWAITS that future, not "some parked driver". D1 parks first awaiting
17217    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
17218    // though D2 parked more recently -- a future-wake misrouted through the
17219    // registry (which pops most-recently-parked) would wake D2, leave D1
17220    // parked forever, and trip the bounded timeout. The old notify_all
17221    // broadcast never distinguished drivers, so this pins the new obligation.
17222    use std::sync::mpsc;
17223    use std::time::Duration;
17224
17225    let (done_tx, done_rx) = mpsc::channel();
17226    let runner = std::thread::spawn(move || {
17227      let metrics = Arc::new(RuntimeMetrics::default());
17228      let options = RuntimeOptions {
17229        flavor: RuntimeFlavor::MultiThread,
17230        worker_threads: 2,
17231        max_blocking_tasks: 2,
17232        thread_name_prefix: "wake-target".to_string(),
17233        park_deadline: None,
17234        drain_linger: DEFAULT_DRAIN_LINGER,
17235      };
17236      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17237
17238      let (tx1, rx1) = oneshot::channel::<usize>();
17239      let (tx2, rx2) = oneshot::channel::<usize>();
17240      let t1_done = Arc::new(AtomicBool::new(false));
17241      let t2_done = Arc::new(AtomicBool::new(false));
17242
17243      // T1: parks FIRST, awaiting rx1.
17244      let t1_exec = Arc::clone(&executor);
17245      let t1_flag = Arc::clone(&t1_done);
17246      let t1_body = async move {
17247        let mut inner = std::pin::pin!(async move {
17248          assert_eq!(rx1.await.unwrap(), 7usize);
17249        });
17250        t1_exec.block_on(inner.as_mut());
17251        t1_flag.store(true, Ordering::SeqCst);
17252      };
17253      let sched1 = Arc::clone(&executor);
17254      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
17255      executor.schedule(runnable1);
17256      wait_until("driver 1 parked", || {
17257        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17258      });
17259
17260      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
17261      // `schedule`, so no wake_one can pop the already-parked D1) and a
17262      // drainer is spawned for it on the free second worker.
17263      let t2_exec = Arc::clone(&executor);
17264      let t2_flag = Arc::clone(&t2_done);
17265      let t2_body = async move {
17266        let mut inner = std::pin::pin!(async move {
17267          assert_eq!(rx2.await.unwrap(), 9usize);
17268        });
17269        t2_exec.block_on(inner.as_mut());
17270        t2_flag.store(true, Ordering::SeqCst);
17271      };
17272      let sched2 = Arc::clone(&executor);
17273      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
17274      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17275      executor.push_fifo_raw(runnable2);
17276      executor.ensure_drainer();
17277      wait_until("driver 2 parked", || {
17278        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17279      });
17280
17281      // Complete D1's future. The wake must land on D1 (first-parked), the
17282      // driver that owns rx1's waker -- not on the more recently parked D2.
17283      tx1.send(7usize).unwrap();
17284      wait_until("T1 completed via its own driver's wake", || {
17285        t1_done.load(Ordering::SeqCst)
17286      });
17287      futures::executor::block_on(task1);
17288      assert!(
17289        !t2_done.load(Ordering::SeqCst),
17290        "D2 must still be parked: its future was never completed"
17291      );
17292      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
17293      // linger): its linger parker joins the registry alongside D2 for the
17294      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
17295      // in the window between the cooperative parker's wake-deregistration
17296      // and the drain frame's linger registration, then 2 while lingering,
17297      // then 1 again after expiry -- so a `count == 1` wait can return
17298      // during the transient window and prove nothing about the linger.
17299      // Observe the lifecycle itself instead, via the test counters that
17300      // `drain` maintains: the linger parker must REGISTER, and its idle
17301      // budget must EXPIRE (`drain_linger_expiries` is incremented only
17302      // after the expiry path deregistered the parker, and no further work
17303      // exists to start another episode), after which the registry has
17304      // deterministically settled: EXACTLY the untargeted driver D2 remains
17305      // (a misrouted wake would have left D1 cooperatively parked instead,
17306      // already excluded by the `t1_done` wait above, and a linger parker
17307      // stranded in the registry would read 2 here).
17308      wait_until(
17309        "the retiring drain frame registered its linger parker",
17310        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
17311      );
17312      wait_until("the lingering drainer's idle budget expired", || {
17313        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
17314      });
17315      assert_eq!(
17316        executor.parked_drivers.count.load(Ordering::SeqCst),
17317        1,
17318        "exactly the untargeted driver must remain parked after the linger expired"
17319      );
17320      assert!(
17321        !t2_done.load(Ordering::SeqCst),
17322        "D2 must still be parked after the lingering drainer expired"
17323      );
17324
17325      // Teardown: release D2 as well.
17326      tx2.send(9usize).unwrap();
17327      futures::executor::block_on(task2);
17328      assert!(t2_done.load(Ordering::SeqCst));
17329      let _ = done_tx.send(());
17330    });
17331
17332    match done_rx.recv_timeout(Duration::from_secs(10)) {
17333      Ok(()) => runner.join().unwrap(),
17334      Err(error) => panic!(
17335        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
17336      ),
17337    }
17338  }
17339
17340  #[cfg(not(target_family = "wasm"))]
17341  #[test]
17342  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
17343    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
17344    // with queue work remaining must hand the wake on because it may have
17345    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
17346    // is parked as a cooperative driver, a runnable is pushed without a wake,
17347    // and the test thread forces a ready future through the cooperative branch.
17348    // Exit compensation is the ONLY mechanism left that can wake the parked
17349    // worker to drain the runnable.
17350    use std::sync::mpsc;
17351    use std::time::Duration;
17352
17353    let (done_tx, done_rx) = mpsc::channel();
17354    let runner = std::thread::spawn(move || {
17355      let metrics = Arc::new(RuntimeMetrics::default());
17356      let options = RuntimeOptions {
17357        flavor: RuntimeFlavor::MultiThread,
17358        worker_threads: 1,
17359        max_blocking_tasks: 1,
17360        thread_name_prefix: "wake-compensate".to_string(),
17361        park_deadline: None,
17362        drain_linger: DEFAULT_DRAIN_LINGER,
17363      };
17364      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17365
17366      // Park the sole worker as a cooperative driver awaiting a gate.
17367      let (gate_tx, gate_rx) = oneshot::channel::<()>();
17368      let td_exec = Arc::clone(&executor);
17369      let td_body = async move {
17370        let mut inner = std::pin::pin!(async move {
17371          gate_rx.await.unwrap();
17372        });
17373        td_exec.block_on(inner.as_mut());
17374      };
17375      let sched = Arc::clone(&executor);
17376      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
17377      executor.schedule(td_runnable);
17378      wait_until("the sole driver parked", || {
17379        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17380      });
17381
17382      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
17383      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
17384      let (ran_tx, ran_rx) = mpsc::channel::<()>();
17385      let sched_stranded = Arc::clone(&executor);
17386      let (stranded_runnable, stranded_task) = async_task::spawn(
17387        async move {
17388          ran_tx.send(()).unwrap();
17389        },
17390        move |r| sched_stranded.schedule(r),
17391      );
17392      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17393      executor.push_fifo_raw(stranded_runnable);
17394      stranded_task.detach();
17395
17396      // Drive a ready future through the cooperative branch on THIS thread
17397      // (fake on-pool marker, as in the executor-scoping test). Its first
17398      // poll is Ready, so the loop performs no work itself -- only the exit
17399      // compensation can hand the queued runnable to the parked driver.
17400      {
17401        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17402        let mut ready = std::pin::pin!(async {});
17403        executor.block_on(ready.as_mut());
17404      }
17405
17406      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
17407        "exit compensation must wake the parked driver to run the stranded queued runnable",
17408      );
17409
17410      // Teardown: release the parked driver.
17411      gate_tx.send(()).unwrap();
17412      futures::executor::block_on(td_task);
17413      let _ = done_tx.send(());
17414    });
17415
17416    match done_rx.recv_timeout(Duration::from_secs(10)) {
17417      Ok(()) => runner.join().unwrap(),
17418      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
17419    }
17420  }
17421
17422  #[cfg(not(target_family = "wasm"))]
17423  #[test]
17424  fn cooperative_exit_compensates_blocking_only_residue() {
17425    use std::sync::mpsc;
17426
17427    struct Controlled {
17428      ready: Arc<AtomicBool>,
17429      entered: Option<mpsc::Sender<()>>,
17430    }
17431
17432    impl Future for Controlled {
17433      type Output = ();
17434
17435      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17436        if let Some(entered) = self.entered.take() {
17437          entered.send(()).unwrap();
17438        }
17439        if self.ready.load(Ordering::SeqCst) {
17440          Poll::Ready(())
17441        } else {
17442          Poll::Pending
17443        }
17444      }
17445    }
17446
17447    let options = RuntimeOptions {
17448      flavor: RuntimeFlavor::MultiThread,
17449      worker_threads: 2,
17450      max_blocking_tasks: 1,
17451      thread_name_prefix: "blocking-exit-compensation".to_string(),
17452      park_deadline: None,
17453      drain_linger: DEFAULT_DRAIN_LINGER,
17454    };
17455    let executor =
17456      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17457    let ready = Arc::new(AtomicBool::new(false));
17458    let (entered_tx, entered_rx) = mpsc::channel();
17459    let (returned_tx, returned_rx) = mpsc::channel();
17460    let (release_tx, release_rx) = mpsc::channel();
17461    let task_exec = Arc::clone(&executor);
17462    let task_ready = Arc::clone(&ready);
17463    let scheduler = Arc::clone(&executor);
17464    let (runnable, task) = async_task::spawn(
17465      async move {
17466        let mut future = std::pin::pin!(Controlled {
17467          ready: task_ready,
17468          entered: Some(entered_tx)
17469        });
17470        task_exec.block_on(future.as_mut());
17471        returned_tx.send(()).unwrap();
17472        release_rx.recv().unwrap();
17473      },
17474      move |runnable| scheduler.schedule(runnable),
17475    );
17476    executor.schedule(runnable);
17477    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17478
17479    let timer_exec = Arc::clone(&executor);
17480    let timer_scheduler = Arc::clone(&executor);
17481    let (timer_runnable, timer_task) = async_task::spawn(
17482      async move {
17483        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
17484      },
17485      move |runnable| timer_scheduler.schedule(runnable),
17486    );
17487    executor.schedule(timer_runnable);
17488    wait_until("the dedicated timekeeper parked", || {
17489      executor
17490        .timers
17491        .inner
17492        .lock()
17493        .unwrap()
17494        .timekeeper_parker
17495        .is_some()
17496    });
17497
17498    ready.store(true, Ordering::SeqCst);
17499    let (blocking_tx, blocking_rx) = mpsc::channel();
17500    let blocking = executor.schedule_blocking(move || {
17501      blocking_tx.send(()).unwrap();
17502    });
17503    blocking_rx
17504      .recv_timeout(Duration::from_secs(2))
17505      .expect("blocking-only residue must be serviced by a Rayon worker");
17506    returned_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17507    release_tx.send(()).unwrap();
17508    futures::executor::block_on(task);
17509    futures::executor::block_on(blocking).unwrap();
17510    executor.shutdown_timers();
17511    futures::executor::block_on(timer_task);
17512  }
17513
17514  #[cfg(not(target_family = "wasm"))]
17515  #[test]
17516  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
17517    let options = RuntimeOptions {
17518      flavor: RuntimeFlavor::MultiThread,
17519      worker_threads: 2,
17520      max_blocking_tasks: 1,
17521      thread_name_prefix: "stopped-exit-compensation".to_string(),
17522      park_deadline: None,
17523      drain_linger: DEFAULT_DRAIN_LINGER,
17524    };
17525    let executor =
17526      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17527    let ran = Arc::new(AtomicBool::new(false));
17528    let blocking_ran = Arc::clone(&ran);
17529
17530    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
17531    // become observable while this job remains queued, matching the original
17532    // begin_shutdown ordering window exactly.
17533    executor
17534      .active_drainers
17535      .store(executor.max_drainers, Ordering::SeqCst);
17536    let blocking = executor.schedule_blocking(move || {
17537      blocking_ran.store(true, Ordering::SeqCst);
17538    });
17539    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
17540
17541    executor.stop.begin_shutdown();
17542    executor.compensate_queued_work_wake(true);
17543    let ran_after_stop = ran.load(Ordering::SeqCst);
17544
17545    executor.active_drainers.store(0, Ordering::SeqCst);
17546    executor.begin_shutdown();
17547    let blocking_result = futures::executor::block_on(blocking);
17548
17549    assert!(
17550      !ran_after_stop,
17551      "exit compensation started queued blocking work after stop"
17552    );
17553    assert!(
17554      blocking_result.is_err(),
17555      "shutdown must cancel the queued blocking job"
17556    );
17557  }
17558
17559  #[cfg(not(target_family = "wasm"))]
17560  #[test]
17561  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
17562    // Targeted-wake stress: N rounds of {producer: publish work,
17563    // wake_one()} racing {consumer: check work, register, re-check, park} --
17564    // the exact schedule-vs-park protocol `schedule` and
17565    // `cooperative_block_on` run. This walks the interleaving at every offset
17566    // the OS scheduler produces; a single lost wakeup parks the consumer
17567    // forever with work pending, and the bounded recv_timeout turns that into
17568    // a loud failure instead of a hung suite. (No loom in this repo; std
17569    // threads.)
17570    use std::sync::mpsc;
17571    use std::time::Duration;
17572
17573    const ROUNDS: usize = 20_000;
17574
17575    let (done_tx, done_rx) = mpsc::channel();
17576    let registry = Arc::new(ParkedDrivers::default());
17577    let work = Arc::new(AtomicBool::new(false));
17578
17579    let consumer = {
17580      let registry = Arc::clone(&registry);
17581      let work = Arc::clone(&work);
17582      std::thread::spawn(move || {
17583        let parker = Arc::new(DriverParker::default());
17584        for _ in 0..ROUNDS {
17585          loop {
17586            if work.swap(false, Ordering::SeqCst) {
17587              break;
17588            }
17589            // The cooperative park protocol: register FIRST, then re-check,
17590            // then park (see `ParkedDrivers::wake_one`).
17591            registry.register(&parker);
17592            if work.load(Ordering::SeqCst) {
17593              registry.deregister(&parker);
17594              continue;
17595            }
17596            parker.park();
17597            registry.deregister(&parker);
17598          }
17599        }
17600        done_tx.send(()).unwrap();
17601      })
17602    };
17603
17604    for _ in 0..ROUNDS {
17605      work.store(true, Ordering::SeqCst);
17606      // The schedule side: publish work, then wake one parked driver. When
17607      // wake_one misses (consumer not registered yet), the consumer's
17608      // post-register re-check must see the work instead.
17609      let _ = registry.wake_one();
17610      while work.load(Ordering::SeqCst) {
17611        std::hint::spin_loop();
17612      }
17613    }
17614
17615    done_rx
17616      .recv_timeout(Duration::from_secs(30))
17617      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
17618    consumer.join().unwrap();
17619  }
17620
17621  #[cfg(not(target_family = "wasm"))]
17622  #[test]
17623  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
17624    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
17625    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
17626    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
17627    let options = RuntimeOptions {
17628      flavor: RuntimeFlavor::MultiThread,
17629      worker_threads: 2,
17630      max_blocking_tasks: 2,
17631      thread_name_prefix: "lifo-order".to_string(),
17632      park_deadline: None,
17633      drain_linger: DEFAULT_DRAIN_LINGER,
17634    };
17635    let executor =
17636      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17637    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17638
17639    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
17640    // could race the manual `run_one` calls below).
17641    let old_order = Arc::clone(&order);
17642    let sched = Arc::clone(&executor);
17643    let (old_runnable, old_task) = async_task::spawn(
17644      async move {
17645        old_order.lock().unwrap().push("old");
17646      },
17647      move |r| sched.schedule(r),
17648    );
17649    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17650    executor.push_fifo_raw(old_runnable);
17651    old_task.detach();
17652
17653    // Simulate being on this executor's pool worker: the schedule below must
17654    // take the slot path (which never touches the FIFO nor spawns a drainer).
17655    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17656    let new_order = Arc::clone(&order);
17657    let sched = Arc::clone(&executor);
17658    let (new_runnable, new_task) = async_task::spawn(
17659      async move {
17660        new_order.lock().unwrap().push("new");
17661      },
17662      move |r| sched.schedule(r),
17663    );
17664    executor.schedule(new_runnable);
17665    new_task.detach();
17666    assert_eq!(
17667      executor.injector.len(),
17668      1,
17669      "the slot-path schedule must not push to the shared FIFO"
17670    );
17671
17672    assert!(executor.run_one(), "run_one must pop the slot first");
17673    assert!(executor.run_one(), "run_one must then pop the FIFO");
17674    assert!(!executor.run_one(), "no work must remain");
17675    assert_eq!(
17676      *order.lock().unwrap(),
17677      vec!["new", "old"],
17678      "the slot runnable (newest) must run before older FIFO work"
17679    );
17680  }
17681
17682  #[cfg(not(target_family = "wasm"))]
17683  #[test]
17684  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
17685    let options = RuntimeOptions {
17686      flavor: RuntimeFlavor::MultiThread,
17687      worker_threads: 2,
17688      max_blocking_tasks: 1,
17689      thread_name_prefix: "fifo-owner".to_string(),
17690      park_deadline: None,
17691      drain_linger: DEFAULT_DRAIN_LINGER,
17692    };
17693    let executor =
17694      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17695    let observed_owner = Arc::new(Mutex::new(None));
17696    let observed = Arc::clone(&observed_owner);
17697    let (runnable, task) = async_task::spawn(
17698      async move {
17699        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17700      },
17701      |_| {},
17702    );
17703    task.detach();
17704    executor.metrics.runnable_scheduled();
17705    executor.push_fifo_raw(runnable);
17706    executor.active_drainers.store(1, Ordering::Release);
17707
17708    let owner = executor.fresh_owner_token();
17709    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
17710    Arc::clone(&executor).drain();
17711
17712    assert_eq!(
17713      *observed_owner.lock().unwrap(),
17714      Some(None),
17715      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
17716    );
17717    assert_eq!(
17718      BLOCKING_OWNER.with(std::cell::Cell::get),
17719      Some(owner),
17720      "the ambient blocking owner must be restored after the runnable"
17721    );
17722  }
17723
17724  #[cfg(not(target_family = "wasm"))]
17725  #[test]
17726  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
17727    // Drain integration: with one worker, task P scheduling
17728    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
17729    // jumping ahead of the older queued T1/T2.
17730    use std::sync::mpsc;
17731    use std::time::Duration;
17732
17733    let (done_tx, done_rx) = mpsc::channel();
17734    let runner = std::thread::spawn(move || {
17735      let options = RuntimeOptions {
17736        flavor: RuntimeFlavor::MultiThread,
17737        worker_threads: 1,
17738        max_blocking_tasks: 1,
17739        thread_name_prefix: "lifo-drain".to_string(),
17740        park_deadline: None,
17741        drain_linger: DEFAULT_DRAIN_LINGER,
17742      };
17743      let executor =
17744        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17745      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17746
17747      let p_exec = Arc::clone(&executor);
17748      let p_order = Arc::clone(&order);
17749      let p_body = async move {
17750        p_order.lock().unwrap().push("P");
17751        // Scheduled from the pool worker mid-drain: must land in the slot.
17752        let c_order = Arc::clone(&p_order);
17753        let sched = Arc::clone(&p_exec);
17754        let (c_runnable, c_task) = async_task::spawn(
17755          async move {
17756            c_order.lock().unwrap().push("C");
17757          },
17758          move |r| sched.schedule(r),
17759        );
17760        p_exec.schedule(c_runnable);
17761        c_task.detach();
17762      };
17763
17764      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
17765      // mirroring the `runnable_scheduled` accounting a real schedule does so
17766      // the queued-runnables counter stays balanced.
17767      let mut tasks = Vec::new();
17768      let sched = Arc::clone(&executor);
17769      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
17770      executor.metrics.runnable_scheduled();
17771      executor.push_fifo_raw(p_runnable);
17772      tasks.push(p_task);
17773      for name in ["T1", "T2"] {
17774        let t_order = Arc::clone(&order);
17775        let sched = Arc::clone(&executor);
17776        let (t_runnable, t_task) = async_task::spawn(
17777          async move {
17778            t_order.lock().unwrap().push(name);
17779          },
17780          move |r| sched.schedule(r),
17781        );
17782        executor.metrics.runnable_scheduled();
17783        executor.push_fifo_raw(t_runnable);
17784        tasks.push(t_task);
17785      }
17786      executor.ensure_drainer();
17787
17788      for task in tasks {
17789        futures::executor::block_on(task);
17790      }
17791      wait_until("the detached child C ran", || {
17792        order.lock().unwrap().len() == 4
17793      });
17794      assert_eq!(
17795        *order.lock().unwrap(),
17796        vec!["P", "C", "T1", "T2"],
17797        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
17798      );
17799      let _ = done_tx.send(());
17800    });
17801
17802    match done_rx.recv_timeout(Duration::from_secs(10)) {
17803      Ok(()) => runner.join().unwrap(),
17804      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17805    }
17806  }
17807
17808  #[cfg(not(target_family = "wasm"))]
17809  #[test]
17810  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17811    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
17812    // NEWEST takes the slot; the displaced occupant must land on the shared
17813    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
17814    // the displacement wake cannot spawn a real drainer racing the manual
17815    // `run_one` calls.
17816    let options = RuntimeOptions {
17817      flavor: RuntimeFlavor::MultiThread,
17818      worker_threads: 2,
17819      max_blocking_tasks: 2,
17820      thread_name_prefix: "lifo-displace".to_string(),
17821      park_deadline: None,
17822      drain_linger: DEFAULT_DRAIN_LINGER,
17823    };
17824    let executor =
17825      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17826    executor
17827      .active_drainers
17828      .store(executor.max_drainers, Ordering::SeqCst);
17829    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17830
17831    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17832    for name in ["a", "b"] {
17833      let n_order = Arc::clone(&order);
17834      let sched = Arc::clone(&executor);
17835      let (runnable, task) = async_task::spawn(
17836        async move {
17837          n_order.lock().unwrap().push(name);
17838        },
17839        move |r| sched.schedule(r),
17840      );
17841      executor.schedule(runnable);
17842      task.detach();
17843    }
17844    assert_eq!(
17845      executor.injector.len(),
17846      1,
17847      "the displaced occupant (a) must have been pushed to the shared FIFO"
17848    );
17849
17850    assert!(executor.run_one(), "run_one must pop the slot (b) first");
17851    assert!(
17852      executor.run_one(),
17853      "run_one must then pop the displaced (a) from the FIFO"
17854    );
17855    assert!(!executor.run_one(), "no work must remain");
17856    assert_eq!(
17857      *order.lock().unwrap(),
17858      vec!["b", "a"],
17859      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17860    );
17861    executor.active_drainers.store(0, Ordering::SeqCst);
17862  }
17863
17864  #[cfg(not(target_family = "wasm"))]
17865  #[test]
17866  fn lifo_slot_is_executor_scoped() {
17867    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
17868    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
17869    // scheduling onto executor B must bypass the slot (B's FIFO); and with
17870    // A's runnable IN the slot, B must neither pop nor displace it.
17871    let options = RuntimeOptions {
17872      flavor: RuntimeFlavor::MultiThread,
17873      worker_threads: 2,
17874      max_blocking_tasks: 2,
17875      thread_name_prefix: "lifo-scope".to_string(),
17876      park_deadline: None,
17877      drain_linger: DEFAULT_DRAIN_LINGER,
17878    };
17879    let exec1 =
17880      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17881    let exec2 =
17882      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17883    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17884    exec1
17885      .active_drainers
17886      .store(exec1.max_drainers, Ordering::SeqCst);
17887    exec2
17888      .active_drainers
17889      .store(exec2.max_drainers, Ordering::SeqCst);
17890    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17891
17892    // As exec1's worker: r1 goes to the slot, tagged exec1.
17893    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17894    let r1_order = Arc::clone(&order);
17895    let sched = Arc::clone(&exec1);
17896    let (r1, t1) = async_task::spawn(
17897      async move {
17898        r1_order.lock().unwrap().push("one");
17899      },
17900      move |r| sched.schedule(r),
17901    );
17902    exec1.schedule(r1);
17903    t1.detach();
17904    assert!(
17905      exec1.injector.is_empty(),
17906      "r1 must sit in the slot, not exec1's FIFO"
17907    );
17908
17909    {
17910      // Nested: now act as exec2's worker while exec1's runnable occupies the
17911      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
17912      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17913      let r2_order = Arc::clone(&order);
17914      let sched = Arc::clone(&exec2);
17915      let (r2, t2) = async_task::spawn(
17916        async move {
17917          r2_order.lock().unwrap().push("two");
17918        },
17919        move |r| sched.schedule(r),
17920      );
17921      exec2.schedule(r2);
17922      t2.detach();
17923      assert_eq!(
17924        exec2.injector.len(),
17925        1,
17926        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17927      );
17928      assert!(
17929        exec2.pop_lifo_slot().is_none(),
17930        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17931      );
17932      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17933    }
17934
17935    // Back as exec1's worker: its slot entry is intact and runnable.
17936    assert!(
17937      exec1.run_one(),
17938      "exec1's slot entry must have survived the foreign activity"
17939    );
17940    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17941    exec1.active_drainers.store(0, Ordering::SeqCst);
17942    exec2.active_drainers.store(0, Ordering::SeqCst);
17943  }
17944
17945  #[cfg(not(target_family = "wasm"))]
17946  #[test]
17947  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17948    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
17949    // next FROM the pool worker (every link lands in the slot) crosses
17950    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
17951    // flushed to the FIFO before `finish_draining` (so its re-check sees the
17952    // runnable and re-arms a drainer); a missing flush strands the chain at
17953    // the first budget boundary (lost task + queued_runnables leak).
17954    use std::sync::mpsc;
17955    use std::time::Duration;
17956
17957    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
17958
17959    fn spawn_link(
17960      executor: &Arc<MultiThreadExecutor>,
17961      remaining: u64,
17962      counter: Arc<AtomicU64>,
17963      done: mpsc::Sender<()>,
17964    ) {
17965      let body_exec = Arc::clone(executor);
17966      let body = async move {
17967        counter.fetch_add(1, Ordering::SeqCst);
17968        if remaining > 0 {
17969          spawn_link(&body_exec, remaining - 1, counter, done);
17970        } else {
17971          done.send(()).unwrap();
17972        }
17973      };
17974      let sched = Arc::clone(executor);
17975      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17976      executor.schedule(runnable);
17977      task.detach();
17978    }
17979
17980    let (done_tx, done_rx) = mpsc::channel();
17981    let runner = std::thread::spawn(move || {
17982      let metrics = Arc::new(RuntimeMetrics::default());
17983      let options = RuntimeOptions {
17984        flavor: RuntimeFlavor::MultiThread,
17985        worker_threads: 1,
17986        max_blocking_tasks: 1,
17987        thread_name_prefix: "lifo-budget".to_string(),
17988        park_deadline: None,
17989        drain_linger: DEFAULT_DRAIN_LINGER,
17990      };
17991      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
17992
17993      let counter = Arc::new(AtomicU64::new(0));
17994      let (chain_tx, chain_rx) = mpsc::channel();
17995      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
17996
17997      chain_rx
17998        .recv_timeout(Duration::from_secs(10))
17999        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
18000      assert_eq!(
18001        counter.load(Ordering::SeqCst),
18002        CHAIN,
18003        "every chain link must have run"
18004      );
18005      wait_until("runnable counters settle to zero (no slot leak)", || {
18006        metrics.queued_runnables.load(Ordering::Relaxed) == 0
18007          && metrics.active_runnables.load(Ordering::Relaxed) == 0
18008      });
18009      let _ = done_tx.send(());
18010    });
18011
18012    match done_rx.recv_timeout(Duration::from_secs(15)) {
18013      Ok(()) => runner.join().unwrap(),
18014      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
18015    }
18016  }
18017
18018  #[cfg(not(target_family = "wasm"))]
18019  #[test]
18020  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
18021    // Direct flush primitive test: pin the flush behavior used at drain
18022    // exits, before cooperative parks and by the unwind guard -- a
18023    // slot-resident runnable moves to the shared FIFO with its task still
18024    // completable, never dropped. (The pre-park call is defensive: `run_one`
18025    // pops the slot before any park is reachable, so this primitive is its
18026    // only observable seam.)
18027    let options = RuntimeOptions {
18028      flavor: RuntimeFlavor::MultiThread,
18029      worker_threads: 2,
18030      max_blocking_tasks: 2,
18031      thread_name_prefix: "lifo-flush".to_string(),
18032      park_deadline: None,
18033      drain_linger: DEFAULT_DRAIN_LINGER,
18034    };
18035    let executor =
18036      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18037    executor
18038      .active_drainers
18039      .store(executor.max_drainers, Ordering::SeqCst);
18040    let ran = Arc::new(AtomicBool::new(false));
18041
18042    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18043    let ran_flag = Arc::clone(&ran);
18044    let sched = Arc::clone(&executor);
18045    let (runnable, task) = async_task::spawn(
18046      async move {
18047        ran_flag.store(true, Ordering::SeqCst);
18048      },
18049      move |r| sched.schedule(r),
18050    );
18051    executor.schedule(runnable);
18052    task.detach();
18053    assert!(
18054      executor.injector.is_empty(),
18055      "the runnable must start in the slot"
18056    );
18057
18058    executor.flush_lifo_slot();
18059    assert!(
18060      executor.pop_lifo_slot().is_none(),
18061      "the slot must be empty after a flush"
18062    );
18063    assert_eq!(
18064      executor.injector.len(),
18065      1,
18066      "the flushed runnable must be on the shared FIFO"
18067    );
18068    assert!(executor.run_one(), "the flushed runnable must still run");
18069    assert!(
18070      ran.load(Ordering::SeqCst),
18071      "the flushed task must complete, not be dropped"
18072    );
18073    executor.active_drainers.store(0, Ordering::SeqCst);
18074  }
18075
18076  #[cfg(not(target_family = "wasm"))]
18077  #[test]
18078  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
18079    // Blocking-owner isolation must cover the LIFO slot path: a runnable
18080    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
18081    // borrow the driving owner frame's over-cap privilege; the ambient owner
18082    // is restored afterwards.
18083    let options = RuntimeOptions {
18084      flavor: RuntimeFlavor::MultiThread,
18085      worker_threads: 2,
18086      max_blocking_tasks: 2,
18087      thread_name_prefix: "lifo-owner".to_string(),
18088      park_deadline: None,
18089      drain_linger: DEFAULT_DRAIN_LINGER,
18090    };
18091    let executor =
18092      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18093    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
18094
18095    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18096
18097    // Install the slot entry from RUNNABLE context (no ambient owner), the
18098    // only context that may use the slot because schedules issued under an
18099    // owner frame are diverted to the shared FIFO.
18100    let observed_slot = Arc::clone(&observed);
18101    let sched = Arc::clone(&executor);
18102    let (runnable, task) = async_task::spawn(
18103      async move {
18104        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
18105      },
18106      move |r| sched.schedule(r),
18107    );
18108    executor.schedule(runnable);
18109    task.detach();
18110    assert!(
18111      executor.injector.is_empty(),
18112      "the runnable must sit in the slot"
18113    );
18114
18115    // The POP can still happen while an owner frame is ambient (a cooperative
18116    // `block_on` entered from a blocking closure drives `run_one` with the
18117    // closure's token on the stack); the slot branch must clear it.
18118    let token = executor.fresh_owner_token();
18119    let _owner = BlockingOwnerGuard::enter(Some(token));
18120    assert!(executor.run_one(), "the slot runnable must run");
18121    assert_eq!(
18122      *observed.lock().unwrap(),
18123      Some(None),
18124      "a slot runnable must observe a CLEARED blocking owner"
18125    );
18126    assert_eq!(
18127      BLOCKING_OWNER.with(std::cell::Cell::get),
18128      Some(token),
18129      "the ambient owner frame must be restored after the slot runnable"
18130    );
18131  }
18132
18133  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
18134  // each other on every poll. Scheduled from a pool worker, each wake lands in
18135  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
18136  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
18137  // its partner's stored waker so both complete.
18138  #[cfg(not(target_family = "wasm"))]
18139  struct PingPongShared {
18140    count: AtomicU64,
18141    stop: AtomicBool,
18142    stop_after: u64,
18143    wakers: Mutex<[Option<std::task::Waker>; 2]>,
18144  }
18145
18146  #[cfg(not(target_family = "wasm"))]
18147  struct PingPong {
18148    shared: Arc<PingPongShared>,
18149    me: usize,
18150  }
18151
18152  #[cfg(not(target_family = "wasm"))]
18153  impl Future for PingPong {
18154    type Output = ();
18155    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18156      let shared = &self.shared;
18157      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
18158      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
18159        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
18160        if let Some(partner) = partner {
18161          partner.wake();
18162        }
18163        return Poll::Ready(());
18164      }
18165      let mut wakers = shared.wakers.lock().unwrap();
18166      wakers[self.me] = Some(cx.waker().clone());
18167      if let Some(partner) = wakers[1 - self.me].take() {
18168        drop(wakers);
18169        partner.wake();
18170      }
18171      Poll::Pending
18172    }
18173  }
18174
18175  #[cfg(not(target_family = "wasm"))]
18176  fn spawn_ping_pong_pair(
18177    executor: &Arc<MultiThreadExecutor>,
18178    shared: &Arc<PingPongShared>,
18179  ) -> Vec<Task<()>> {
18180    let mut tasks = Vec::new();
18181    for me in 0..2 {
18182      let sched = Arc::clone(executor);
18183      let (runnable, task) = async_task::spawn(
18184        PingPong {
18185          shared: Arc::clone(shared),
18186          me,
18187        },
18188        move |r| sched.schedule(r),
18189      );
18190      executor.schedule(runnable);
18191      tasks.push(task);
18192    }
18193    tasks
18194  }
18195
18196  #[cfg(not(target_family = "wasm"))]
18197  #[test]
18198  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
18199    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
18200    // a hot wake/await pair holds a worker for at most one budget window
18201    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
18202    // a third task queued behind an unbounded ping-pong pair must still run;
18203    // if slot pops bypassed the budget the drain would never exit and T3
18204    // would starve forever (caught by the bounded timeout).
18205    use std::sync::mpsc;
18206    use std::time::Duration;
18207
18208    let (done_tx, done_rx) = mpsc::channel();
18209    let runner = std::thread::spawn(move || {
18210      let options = RuntimeOptions {
18211        flavor: RuntimeFlavor::MultiThread,
18212        worker_threads: 1,
18213        max_blocking_tasks: 0,
18214        thread_name_prefix: "lifo-streak".to_string(),
18215        park_deadline: None,
18216        drain_linger: DEFAULT_DRAIN_LINGER,
18217      };
18218      let executor =
18219        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18220
18221      let shared = Arc::new(PingPongShared {
18222        count: AtomicU64::new(0),
18223        stop: AtomicBool::new(false),
18224        stop_after: u64::MAX,
18225        wakers: Mutex::new([None, None]),
18226      });
18227      let pair = spawn_ping_pong_pair(&executor, &shared);
18228
18229      // T3: queued on the shared FIFO behind the hot pair.
18230      let (t3_tx, t3_rx) = mpsc::channel::<()>();
18231      let sched = Arc::clone(&executor);
18232      let (t3_runnable, t3_task) = async_task::spawn(
18233        async move {
18234          t3_tx.send(()).unwrap();
18235        },
18236        move |r| sched.schedule(r),
18237      );
18238      executor.schedule(t3_runnable);
18239
18240      t3_rx
18241        .recv_timeout(Duration::from_secs(10))
18242        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
18243      assert!(
18244        shared.count.load(Ordering::SeqCst) >= 32,
18245        "the pair must have been bouncing through the slot before T3 got its turn"
18246      );
18247
18248      // Teardown: stop the pair and release whichever side is waiting.
18249      shared.stop.store(true, Ordering::SeqCst);
18250      let stored: Vec<_> = shared
18251        .wakers
18252        .lock()
18253        .unwrap()
18254        .iter_mut()
18255        .filter_map(Option::take)
18256        .collect();
18257      for waker in stored {
18258        waker.wake();
18259      }
18260      for task in pair {
18261        futures::executor::block_on(task);
18262      }
18263      futures::executor::block_on(t3_task);
18264      let _ = done_tx.send(());
18265    });
18266
18267    match done_rx.recv_timeout(Duration::from_secs(15)) {
18268      Ok(()) => runner.join().unwrap(),
18269      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
18270    }
18271  }
18272
18273  #[cfg(not(target_family = "wasm"))]
18274  #[test]
18275  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
18276    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
18277    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
18278    // pair must not turn that priority into a permanent wedge. The budget
18279    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
18280    // blocking queue, and the queued blocking job completes once the pair
18281    // finishes its (long) run on the SOLE worker.
18282    use std::sync::mpsc;
18283    use std::time::Duration;
18284
18285    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
18286
18287    let (done_tx, done_rx) = mpsc::channel();
18288    let runner = std::thread::spawn(move || {
18289      let options = RuntimeOptions {
18290        flavor: RuntimeFlavor::MultiThread,
18291        worker_threads: 1,
18292        max_blocking_tasks: 1,
18293        thread_name_prefix: "lifo-blocking".to_string(),
18294        park_deadline: None,
18295        drain_linger: DEFAULT_DRAIN_LINGER,
18296      };
18297      let executor =
18298        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18299
18300      let shared = Arc::new(PingPongShared {
18301        count: AtomicU64::new(0),
18302        stop: AtomicBool::new(false),
18303        stop_after: BOUNCES,
18304        wakers: Mutex::new([None, None]),
18305      });
18306      let pair = spawn_ping_pong_pair(&executor, &shared);
18307
18308      // Queued while the pair keeps the slot hot on the only worker.
18309      let blocking = executor.schedule_blocking(|| 11usize);
18310
18311      assert_eq!(
18312        futures::executor::block_on(blocking).unwrap(),
18313        11,
18314        "the queued blocking job must complete despite the hot LIFO pair"
18315      );
18316      for task in pair {
18317        futures::executor::block_on(task);
18318      }
18319      assert!(
18320        shared.count.load(Ordering::SeqCst) >= BOUNCES,
18321        "the pair must have actually run its full bounce quota"
18322      );
18323      let _ = done_tx.send(());
18324    });
18325
18326    match done_rx.recv_timeout(Duration::from_secs(15)) {
18327      Ok(()) => runner.join().unwrap(),
18328      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
18329    }
18330  }
18331
18332  #[cfg(not(target_family = "wasm"))]
18333  #[test]
18334  fn infinite_hot_runnable_yields_to_blocking_fifo() {
18335    use std::sync::mpsc;
18336
18337    struct HotFuture {
18338      stop: Arc<AtomicBool>,
18339    }
18340
18341    impl Future for HotFuture {
18342      type Output = ();
18343
18344      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18345        if self.stop.load(Ordering::SeqCst) {
18346          Poll::Ready(())
18347        } else {
18348          cx.waker().wake_by_ref();
18349          Poll::Pending
18350        }
18351      }
18352    }
18353
18354    let options = RuntimeOptions {
18355      flavor: RuntimeFlavor::MultiThread,
18356      worker_threads: 2,
18357      max_blocking_tasks: 1,
18358      thread_name_prefix: "blocking-fairness".to_string(),
18359      park_deadline: None,
18360      drain_linger: DEFAULT_DRAIN_LINGER,
18361    };
18362    let executor =
18363      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18364    let stop = Arc::new(AtomicBool::new(false));
18365    let scheduler = Arc::clone(&executor);
18366    let (runnable, task) = async_task::spawn(
18367      HotFuture {
18368        stop: Arc::clone(&stop),
18369      },
18370      move |r| scheduler.schedule(r),
18371    );
18372    task.detach();
18373    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
18374    executor
18375      .blocking_queue
18376      .lock()
18377      .unwrap()
18378      .push(QueuedBlocking {
18379        id: BlockingJobId(u64::MAX),
18380        run: Box::new(move || blocking_tx.send(()).unwrap()),
18381      });
18382
18383    let _driver = OnPoolWorkerGuard::enter(executor.id);
18384    executor.schedule(runnable);
18385    let mut runnable_streak = 0;
18386    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
18387      assert!(executor.run_one_fair(&mut runnable_streak));
18388    }
18389    blocking_rx
18390      .try_recv()
18391      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
18392
18393    stop.store(true, Ordering::SeqCst);
18394    assert!(executor.run_one_fair(&mut runnable_streak));
18395  }
18396
18397  #[cfg(not(target_family = "wasm"))]
18398  #[test]
18399  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
18400    use std::sync::mpsc;
18401
18402    struct HotFuture {
18403      stop: Arc<AtomicBool>,
18404    }
18405
18406    impl Future for HotFuture {
18407      type Output = ();
18408
18409      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18410        if self.stop.load(Ordering::SeqCst) {
18411          Poll::Ready(())
18412        } else {
18413          cx.waker().wake_by_ref();
18414          Poll::Pending
18415        }
18416      }
18417    }
18418
18419    let options = RuntimeOptions {
18420      flavor: RuntimeFlavor::MultiThread,
18421      worker_threads: 1,
18422      max_blocking_tasks: 1,
18423      thread_name_prefix: "exact-dependency-fairness".to_string(),
18424      park_deadline: None,
18425      drain_linger: DEFAULT_DRAIN_LINGER,
18426    };
18427    let executor =
18428      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18429    let stop = Arc::new(AtomicBool::new(false));
18430    let (queued_tx, queued_rx) = mpsc::channel();
18431    let (exact_tx, exact_rx) = mpsc::channel();
18432    let owner_executor = Arc::clone(&executor);
18433    let owner_stop = Arc::clone(&stop);
18434    let owner = executor.schedule_blocking(move || {
18435      let scheduler = Arc::clone(&owner_executor);
18436      let hot_stop = Arc::clone(&owner_stop);
18437      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
18438        scheduler.schedule(runnable);
18439      });
18440      owner_executor.schedule(runnable);
18441
18442      let exact_stop = Arc::clone(&owner_stop);
18443      let exact = owner_executor.schedule_blocking(move || {
18444        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
18445        41usize
18446      });
18447      queued_tx.send(()).unwrap();
18448
18449      let mut value = None;
18450      {
18451        let mut future = std::pin::pin!(async {
18452          value = Some(exact.await.unwrap());
18453          hot_task.await;
18454        });
18455        assert_eq!(
18456          owner_executor.block_on(future.as_mut()),
18457          BlockOnOutcome::Completed
18458        );
18459      }
18460      value.unwrap()
18461    });
18462
18463    queued_rx
18464      .recv_timeout(Duration::from_secs(2))
18465      .expect("the exact dependency must be queued behind the saturated owner");
18466    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
18467    stop.store(true, Ordering::SeqCst);
18468    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
18469    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
18470    assert_eq!(
18471      exact_before_stop,
18472      Some(true),
18473      "a hot local runnable must yield to the exact dependency before it is stopped"
18474    );
18475  }
18476
18477  #[cfg(not(target_family = "wasm"))]
18478  #[test]
18479  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
18480    use std::sync::mpsc;
18481
18482    struct BlockingPoll {
18483      started: Option<mpsc::Sender<()>>,
18484      released: Arc<(Mutex<bool>, Condvar)>,
18485    }
18486
18487    impl Future for BlockingPoll {
18488      type Output = ();
18489
18490      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18491        if let Some(started) = self.started.take() {
18492          started.send(()).unwrap();
18493        }
18494        let (released, changed) = &*self.released;
18495        let mut released = released
18496          .lock()
18497          .unwrap_or_else(std::sync::PoisonError::into_inner);
18498        while !*released {
18499          released = changed
18500            .wait(released)
18501            .unwrap_or_else(std::sync::PoisonError::into_inner);
18502        }
18503        Poll::Ready(())
18504      }
18505    }
18506
18507    let options = RuntimeOptions {
18508      flavor: RuntimeFlavor::MultiThread,
18509      worker_threads: 3,
18510      max_blocking_tasks: 2,
18511      thread_name_prefix: "exact-before-last-slot".to_string(),
18512      park_deadline: None,
18513      drain_linger: DEFAULT_DRAIN_LINGER,
18514    };
18515    let executor =
18516      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18517
18518    let released = Arc::new((Mutex::new(false), Condvar::new()));
18519    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
18520    let mut blockers = Vec::new();
18521    for _ in 0..2 {
18522      let scheduler = Arc::clone(&executor);
18523      let blocker_released = Arc::clone(&released);
18524      let started = blocker_started_tx.clone();
18525      let (runnable, task) = async_task::spawn(
18526        BlockingPoll {
18527          started: Some(started),
18528          released: blocker_released,
18529        },
18530        move |runnable| scheduler.schedule(runnable),
18531      );
18532      executor.schedule(runnable);
18533      blockers.push(task);
18534    }
18535    drop(blocker_started_tx);
18536    for _ in 0..2 {
18537      blocker_started_rx
18538        .recv_timeout(Duration::from_secs(2))
18539        .expect("two workers must be gated before the owner starts");
18540    }
18541
18542    let dependency_started = Arc::new(AtomicBool::new(false));
18543    let owner_executor = Arc::clone(&executor);
18544    let owner_dependency_started = Arc::clone(&dependency_started);
18545    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
18546    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
18547    let owner_signal = Arc::clone(&dependency_signal);
18548    let owner = executor.schedule_blocking(move || {
18549      let unrelated_signal = Arc::clone(&owner_signal);
18550      let unrelated_started = Arc::clone(&owner_dependency_started);
18551      let unrelated = owner_executor.schedule_blocking(move || {
18552        let observed = unrelated_started.load(Ordering::SeqCst);
18553        unrelated_observed_tx.send(observed).unwrap();
18554        let (ready, changed) = &*unrelated_signal;
18555        let mut ready = ready
18556          .lock()
18557          .unwrap_or_else(std::sync::PoisonError::into_inner);
18558        while !*ready {
18559          ready = changed
18560            .wait(ready)
18561            .unwrap_or_else(std::sync::PoisonError::into_inner);
18562        }
18563      });
18564
18565      let exact_signal = Arc::clone(&owner_signal);
18566      let exact_started = Arc::clone(&owner_dependency_started);
18567      let exact = owner_executor.schedule_blocking(move || {
18568        exact_started.store(true, Ordering::SeqCst);
18569        let (ready, changed) = &*exact_signal;
18570        *ready
18571          .lock()
18572          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18573        changed.notify_all();
18574        41usize
18575      });
18576
18577      let mut output = None;
18578      {
18579        let mut future = std::pin::pin!(async {
18580          output = Some(exact.await.unwrap());
18581          unrelated.await.unwrap();
18582        });
18583        assert_eq!(
18584          owner_executor.block_on(future.as_mut()),
18585          BlockOnOutcome::Completed
18586        );
18587      }
18588      output.unwrap()
18589    });
18590
18591    let (owner_done_tx, owner_done_rx) = mpsc::channel();
18592    let owner_waiter = std::thread::spawn(move || {
18593      owner_done_tx
18594        .send(futures::executor::block_on(owner))
18595        .unwrap();
18596    });
18597    let (completed_while_gated, owner_result) =
18598      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
18599        Ok(result) => (true, result),
18600        Err(_) => {
18601          // Let a broken implementation recover so the regression fails
18602          // deterministically instead of leaving blocked Rayon workers behind.
18603          let (released, changed) = &*released;
18604          *released
18605            .lock()
18606            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18607          changed.notify_all();
18608          (
18609            false,
18610            owner_done_rx
18611              .recv_timeout(Duration::from_secs(2))
18612              .expect("releasing the other workers must let the broken ordering recover"),
18613          )
18614        }
18615      };
18616    let unrelated_observed = unrelated_observed_rx
18617      .recv_timeout(Duration::from_secs(2))
18618      .expect("the unrelated FIFO job must eventually run");
18619
18620    {
18621      let (released, changed) = &*released;
18622      *released
18623        .lock()
18624        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18625      changed.notify_all();
18626    }
18627    owner_waiter.join().unwrap();
18628    for blocker in blockers {
18629      futures::executor::block_on(blocker);
18630    }
18631    executor.begin_shutdown();
18632    executor.wait_until_scheduler_idle();
18633
18634    assert!(
18635      completed_while_gated,
18636      "the exact dependency must complete while unrelated workers remain gated"
18637    );
18638    assert_eq!(owner_result.unwrap(), 41);
18639    assert!(
18640      unrelated_observed,
18641      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
18642    );
18643  }
18644
18645  #[cfg(not(target_family = "wasm"))]
18646  #[test]
18647  fn blocking_saturation_preserves_a_runnable_execution_lane() {
18648    use std::{sync::mpsc, time::Duration};
18649
18650    let options = RuntimeOptions {
18651      flavor: RuntimeFlavor::MultiThread,
18652      worker_threads: 2,
18653      max_blocking_tasks: 2,
18654      thread_name_prefix: "blocking-reserve".to_string(),
18655      park_deadline: None,
18656      drain_linger: DEFAULT_DRAIN_LINGER,
18657    }
18658    .validate()
18659    .expect("production options must reserve a runnable lane");
18660    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
18661    let executor =
18662      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18663    let (release_tx, release_rx) = mpsc::channel();
18664    let (started_tx, started_rx) = mpsc::channel();
18665    let first = executor.schedule_blocking(move || {
18666      started_tx.send(()).unwrap();
18667      release_rx.recv().unwrap();
18668    });
18669    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
18670
18671    let (queued_tx, queued_rx) = mpsc::channel();
18672    let second = executor.schedule_blocking(move || {
18673      queued_tx.send(()).unwrap();
18674    });
18675    assert!(
18676      queued_rx.try_recv().is_err(),
18677      "the second blocking job must remain queued while the sole blocking slot is occupied"
18678    );
18679
18680    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
18681    let scheduler = Arc::clone(&executor);
18682    let (runnable, task) = async_task::spawn(
18683      async move {
18684        runnable_tx.send(()).unwrap();
18685      },
18686      move |r| scheduler.schedule(r),
18687    );
18688    task.detach();
18689    executor.schedule(runnable);
18690    runnable_rx
18691      .recv_timeout(Duration::from_secs(1))
18692      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
18693
18694    release_tx.send(()).unwrap();
18695    futures::executor::block_on(first).unwrap();
18696    futures::executor::block_on(second).unwrap();
18697    queued_rx
18698      .recv_timeout(Duration::from_secs(1))
18699      .expect("queued blocking work must run after the occupied slot is released");
18700  }
18701
18702  #[cfg(not(target_family = "wasm"))]
18703  #[test]
18704  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
18705    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
18706    // schedule same-executor work, which lands in THIS worker's LIFO slot --
18707    // no FIFO push, no wake. The Ready exit of
18708    // `cooperative_block_on` must flush the slot: the caller may wait on that
18709    // child synchronously (and the enclosing drain frame can be arbitrarily
18710    // far away -- here it does not exist at all, via the fake on-pool
18711    // marker), so a stranded slot entry is an invisible, dead task.
18712    use std::sync::mpsc;
18713    use std::time::Duration;
18714
18715    struct SpawnOnReady {
18716      executor: Arc<MultiThreadExecutor>,
18717      child_tx: Option<mpsc::Sender<u64>>,
18718    }
18719    impl Future for SpawnOnReady {
18720      type Output = ();
18721      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18722        let child_tx = self.child_tx.take().expect("polled once");
18723        let sched = Arc::clone(&self.executor);
18724        let (runnable, task) = async_task::spawn(
18725          async move {
18726            child_tx.send(21).unwrap();
18727          },
18728          move |r| sched.schedule(r),
18729        );
18730        // On-pool + same executor + no ambient owner: lands in the slot.
18731        self.executor.schedule(runnable);
18732        task.detach();
18733        Poll::Ready(())
18734      }
18735    }
18736
18737    let (done_tx, done_rx) = mpsc::channel();
18738    let runner = std::thread::spawn(move || {
18739      let options = RuntimeOptions {
18740        flavor: RuntimeFlavor::MultiThread,
18741        worker_threads: 2,
18742        max_blocking_tasks: 2,
18743        thread_name_prefix: "exit-flush".to_string(),
18744        park_deadline: None,
18745        drain_linger: DEFAULT_DRAIN_LINGER,
18746      };
18747      let executor =
18748        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18749
18750      let (child_tx, child_rx) = mpsc::channel::<u64>();
18751      {
18752        // The test-only on-pool marker runs the cooperative branch on THIS
18753        // thread, with NO enclosing drain loop to bail the slot out.
18754        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18755        let mut future = std::pin::pin!(SpawnOnReady {
18756          executor: Arc::clone(&executor),
18757          child_tx: Some(child_tx)
18758        });
18759        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
18760      }
18761
18762      // Synchronous bounded wait, NOT via the executor: only the exit flush
18763      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
18764      // complete the child.
18765      let value = child_rx
18766        .recv_timeout(Duration::from_secs(5))
18767        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
18768      assert_eq!(value, 21);
18769      let _ = done_tx.send(());
18770    });
18771
18772    match done_rx.recv_timeout(Duration::from_secs(10)) {
18773      Ok(()) => runner.join().unwrap(),
18774      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
18775    }
18776  }
18777
18778  /// Perpetual same-executor chain: each link schedules its successor from
18779  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
18780  /// raised. `hops` counts executed links.
18781  #[cfg(not(target_family = "wasm"))]
18782  fn spawn_hot_chain(
18783    executor: &Arc<MultiThreadExecutor>,
18784    stop: Arc<AtomicBool>,
18785    hops: Arc<AtomicU64>,
18786  ) {
18787    if stop.load(Ordering::SeqCst) {
18788      return;
18789    }
18790    let link_exec = Arc::clone(executor);
18791    let body = async move {
18792      hops.fetch_add(1, Ordering::SeqCst);
18793      spawn_hot_chain(&link_exec, stop, hops);
18794    };
18795    let sched = Arc::clone(executor);
18796    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
18797    executor.schedule(runnable);
18798    task.detach();
18799  }
18800
18801  #[cfg(not(target_family = "wasm"))]
18802  #[test]
18803  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
18804    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
18805    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
18806    // the FIFO, so a hot chain (every link schedules its successor into the
18807    // slot) keeps run_one succeeding forever and the FIFO is never reached
18808    // from this thread. On the direct 1-worker test topology a FIFO task that
18809    // completes the awaited future then never runs: deadlock. The budgeted
18810    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
18811    // gets its turn.
18812    use std::sync::mpsc;
18813    use std::time::Duration;
18814
18815    let (done_tx, done_rx) = mpsc::channel();
18816    let runner = std::thread::spawn(move || {
18817      let options = RuntimeOptions {
18818        flavor: RuntimeFlavor::MultiThread,
18819        worker_threads: 1,
18820        // This locality test intentionally bypasses RuntimeOptions::validate
18821        // to isolate one physical worker without admitting blocking work.
18822        max_blocking_tasks: 0,
18823        thread_name_prefix: "coop-budget".to_string(),
18824        park_deadline: None,
18825        drain_linger: DEFAULT_DRAIN_LINGER,
18826      };
18827      let executor =
18828        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18829
18830      let stop = Arc::new(AtomicBool::new(false));
18831      let hops = Arc::new(AtomicU64::new(0));
18832
18833      // M runs on the sole worker: it queues F (the task that completes the
18834      // awaited future) on the FIFO, heats the slot with the chain, then
18835      // block_on's the value F produces.
18836      let m_exec = Arc::clone(&executor);
18837      let m_stop = Arc::clone(&stop);
18838      let m_hops = Arc::clone(&hops);
18839      let m_body = async move {
18840        let (tx, rx) = oneshot::channel::<u64>();
18841        // Schedule F first: it takes the slot momentarily...
18842        let sched_f = Arc::clone(&m_exec);
18843        let (f_runnable, f_task) = async_task::spawn(
18844          async move {
18845            let _ = tx.send(7);
18846          },
18847          move |r| sched_f.schedule(r),
18848        );
18849        m_exec.schedule(f_runnable);
18850        f_task.detach();
18851        // ...then the chain starter displaces it to the FIFO and keeps the
18852        // slot hot from here on.
18853        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18854
18855        let mut inner = std::pin::pin!(async move {
18856          assert_eq!(
18857            rx.await.unwrap(),
18858            7,
18859            "F must run and complete the awaited future"
18860          );
18861        });
18862        m_exec.block_on(inner.as_mut());
18863        m_stop.store(true, Ordering::SeqCst);
18864      };
18865      let sched_m = Arc::clone(&executor);
18866      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18867      executor.schedule(m_runnable);
18868
18869      futures::executor::block_on(m_task);
18870      assert!(
18871        hops.load(Ordering::SeqCst) >= 64,
18872        "the chain must have been genuinely hot (>= one budget window) before F ran"
18873      );
18874      let _ = done_tx.send(());
18875    });
18876
18877    match done_rx.recv_timeout(Duration::from_secs(10)) {
18878      Ok(()) => runner.join().unwrap(),
18879      Err(error) => panic!(
18880        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18881      ),
18882    }
18883  }
18884
18885  #[cfg(not(target_family = "wasm"))]
18886  #[test]
18887  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18888    use std::sync::mpsc;
18889
18890    struct RefillLifo {
18891      executor: Arc<MultiThreadExecutor>,
18892      fifo_ran: Arc<AtomicBool>,
18893    }
18894
18895    impl Future for RefillLifo {
18896      type Output = ();
18897
18898      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18899        if self.fifo_ran.load(Ordering::SeqCst) {
18900          return Poll::Ready(());
18901        }
18902        let scheduler = Arc::clone(&self.executor);
18903        let (runnable, task) =
18904          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18905        self.executor.schedule(runnable);
18906        task.detach();
18907        Poll::Pending
18908      }
18909    }
18910
18911    let (done_tx, done_rx) = mpsc::channel();
18912    let runner = std::thread::spawn(move || {
18913      let options = RuntimeOptions {
18914        flavor: RuntimeFlavor::MultiThread,
18915        worker_threads: 1,
18916        max_blocking_tasks: 0,
18917        thread_name_prefix: "forced-fifo".to_string(),
18918        park_deadline: None,
18919        drain_linger: DEFAULT_DRAIN_LINGER,
18920      };
18921      let executor =
18922        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18923      executor
18924        .active_drainers
18925        .store(executor.max_drainers, Ordering::SeqCst);
18926
18927      let fifo_ran = Arc::new(AtomicBool::new(false));
18928      let fifo_flag = Arc::clone(&fifo_ran);
18929      let scheduler = Arc::clone(&executor);
18930      let (fifo, task) = async_task::spawn(
18931        async move {
18932          fifo_flag.store(true, Ordering::SeqCst);
18933        },
18934        move |runnable| scheduler.schedule(runnable),
18935      );
18936      task.detach();
18937      executor.metrics.runnable_scheduled();
18938      executor.push_fifo_raw(fifo);
18939
18940      {
18941        let _driver = OnPoolWorkerGuard::enter(executor.id);
18942        let mut future = std::pin::pin!(RefillLifo {
18943          executor: Arc::clone(&executor),
18944          fifo_ran: Arc::clone(&fifo_ran)
18945        });
18946        executor.block_on(future.as_mut());
18947        while executor.run_one() {}
18948      }
18949      executor.active_drainers.store(0, Ordering::SeqCst);
18950      assert!(fifo_ran.load(Ordering::SeqCst));
18951      done_tx.send(()).unwrap();
18952    });
18953    done_rx
18954      .recv_timeout(Duration::from_secs(3))
18955      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18956    runner.join().unwrap();
18957  }
18958
18959  #[cfg(not(target_family = "wasm"))]
18960  #[test]
18961  fn blocking_closure_spawn_is_not_stranded_in_slot() {
18962    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
18963    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
18964    // same-executor task and then waits for it synchronously would deadlock if
18965    // the child were slotted because the slot only drains after the closure
18966    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
18967    // push to the shared FIFO with a wake.
18968    use std::sync::mpsc;
18969    use std::time::Duration;
18970
18971    let (done_tx, done_rx) = mpsc::channel();
18972    let runner = std::thread::spawn(move || {
18973      let options = RuntimeOptions {
18974        flavor: RuntimeFlavor::MultiThread,
18975        worker_threads: 2,
18976        max_blocking_tasks: 2,
18977        thread_name_prefix: "blk-spawn-wait".to_string(),
18978        park_deadline: None,
18979        drain_linger: DEFAULT_DRAIN_LINGER,
18980      };
18981      let executor =
18982        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18983
18984      // Closure spawns a child and BLOCKS on the channel the child must fill.
18985      // A spare worker exists; only a real FIFO push + wake can reach it.
18986      let blk_exec = Arc::clone(&executor);
18987      let handle = executor.schedule_blocking(move || {
18988        let (tx, rx) = mpsc::channel::<u64>();
18989        let sched = Arc::clone(&blk_exec);
18990        let (runnable, task) = async_task::spawn(
18991          async move {
18992            tx.send(21).unwrap();
18993          },
18994          move |r| sched.schedule(r),
18995        );
18996        blk_exec.schedule(runnable);
18997        task.detach();
18998        rx.recv_timeout(Duration::from_secs(5))
18999          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
19000      });
19001      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
19002
19003      // Variant: the closure exits WITHOUT waiting; the child must still
19004      // complete promptly (no stranding on the closure-exit path either).
19005      let (v_tx, v_rx) = mpsc::channel::<u64>();
19006      let v_exec = Arc::clone(&executor);
19007      let v_handle = executor.schedule_blocking(move || {
19008        let sched = Arc::clone(&v_exec);
19009        let (runnable, task) = async_task::spawn(
19010          async move {
19011            v_tx.send(42).unwrap();
19012          },
19013          move |r| sched.schedule(r),
19014        );
19015        v_exec.schedule(runnable);
19016        task.detach();
19017        1u64
19018      });
19019      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
19020      assert_eq!(
19021        v_rx
19022          .recv_timeout(Duration::from_secs(5))
19023          .expect("fire-and-forget child must complete"),
19024        42
19025      );
19026      let _ = done_tx.send(());
19027    });
19028
19029    match done_rx.recv_timeout(Duration::from_secs(15)) {
19030      Ok(()) => runner.join().unwrap(),
19031      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
19032    }
19033  }
19034
19035  #[cfg(not(target_family = "wasm"))]
19036  #[test]
19037  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
19038    // Saturated nested-blocking invariant: a blocking closure re-enters
19039    // `block_on` and awaits another `spawn_blocking` while
19040    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
19041    // The driver must not park while still holding its blocking slot, because
19042    // the inner job would then have no capacity to run.
19043    use std::sync::{Barrier, mpsc};
19044    use std::time::Duration;
19045
19046    let (done_tx, done_rx) = mpsc::channel();
19047    let runner = std::thread::spawn(move || {
19048      let metrics = Arc::new(RuntimeMetrics::default());
19049      let options = RuntimeOptions {
19050        flavor: RuntimeFlavor::MultiThread,
19051        worker_threads: 2,
19052        max_blocking_tasks: 2,
19053        thread_name_prefix: "rd1-nested-blk".to_string(),
19054        park_deadline: None,
19055        drain_linger: DEFAULT_DRAIN_LINGER,
19056      };
19057      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19058
19059      let outer_count = 2usize; // saturates max_blocking_tasks
19060      let barrier = Arc::new(Barrier::new(outer_count));
19061
19062      let mut handles = Vec::new();
19063      for _ in 0..outer_count {
19064        let exec = Arc::clone(&executor);
19065        let barrier = Arc::clone(&barrier);
19066        let handle = executor.schedule_blocking(move || {
19067          // Both outer blocking jobs hold a slot before either nests.
19068          barrier.wait();
19069          let inner_exec = Arc::clone(&exec);
19070          let mut value = 0usize;
19071          {
19072            let mut inner = std::pin::pin!(async {
19073              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19074            });
19075            exec.block_on(inner.as_mut());
19076          }
19077          value
19078        });
19079        handles.push(handle);
19080      }
19081
19082      for handle in handles {
19083        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
19084      }
19085      let _ = done_tx.send(());
19086    });
19087
19088    match done_rx.recv_timeout(Duration::from_secs(10)) {
19089      Ok(()) => runner.join().unwrap(),
19090      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
19091    }
19092  }
19093
19094  #[cfg(not(target_family = "wasm"))]
19095  #[test]
19096  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
19097    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
19098    let owner = executor.fresh_owner_token();
19099    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19100    let job = BlockingJobId(u64::MAX - 5);
19101    let dependency = TaskDependency::default();
19102    dependency.set_owned(BlockingDependency { job, owner: None });
19103    let ran = Arc::new(AtomicBool::new(false));
19104    let job_ran = Arc::clone(&ran);
19105    executor
19106      .blocking_queue
19107      .lock()
19108      .unwrap()
19109      .push(QueuedBlocking {
19110        id: job,
19111        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
19112      });
19113
19114    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19115    assert!(
19116      !executor.try_owned_blocking_over_cap(&dependency),
19117      "an untagged worker must not infer ancestry from a sole active owner"
19118    );
19119    assert!(!ran.load(Ordering::SeqCst));
19120    assert!(
19121      executor
19122        .blocking_queue
19123        .lock()
19124        .unwrap()
19125        .jobs
19126        .contains_key(&job),
19127      "rejected owner inference must leave the exact job queued"
19128    );
19129  }
19130
19131  #[cfg(not(target_family = "wasm"))]
19132  #[test]
19133  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
19134    use std::sync::{Barrier, mpsc};
19135
19136    let options = RuntimeOptions {
19137      flavor: RuntimeFlavor::MultiThread,
19138      worker_threads: 2,
19139      max_blocking_tasks: 2,
19140      thread_name_prefix: "exact-owner-frame".to_string(),
19141      park_deadline: None,
19142      drain_linger: DEFAULT_DRAIN_LINGER,
19143    };
19144    let executor =
19145      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19146    let owners_saturated = Arc::new(Barrier::new(3));
19147    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
19148    let begin_lending = Arc::new(Barrier::new(3));
19149    let release_dependencies = Arc::new(Barrier::new(3));
19150    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
19151    let mut owners = Vec::new();
19152
19153    for owner_index in 0..2 {
19154      let owner_executor = Arc::clone(&executor);
19155      let owners_saturated = Arc::clone(&owners_saturated);
19156      let dependency_queued_tx = dependency_queued_tx.clone();
19157      let begin_lending = Arc::clone(&begin_lending);
19158      let release_dependencies = Arc::clone(&release_dependencies);
19159      let dependency_started_tx = dependency_started_tx.clone();
19160      owners.push(executor.schedule_blocking(move || {
19161        let owner_thread = std::thread::current().id();
19162        owners_saturated.wait();
19163        let child_started = dependency_started_tx.clone();
19164        let child_release = Arc::clone(&release_dependencies);
19165        let child = owner_executor.schedule_blocking(move || {
19166          assert_eq!(
19167            std::thread::current().id(),
19168            owner_thread,
19169            "an owner frame must execute only its own exact dependency on its lent lane"
19170          );
19171          child_started.send(owner_index).unwrap();
19172          child_release.wait();
19173          owner_index
19174        });
19175        dependency_queued_tx.send(()).unwrap();
19176        begin_lending.wait();
19177
19178        let mut result = None;
19179        {
19180          let mut future = std::pin::pin!(async {
19181            result = Some(child.await.unwrap());
19182          });
19183          assert_eq!(
19184            owner_executor.block_on(future.as_mut()),
19185            BlockOnOutcome::Completed
19186          );
19187        }
19188        result.unwrap()
19189      }));
19190    }
19191    drop(dependency_queued_tx);
19192    drop(dependency_started_tx);
19193    owners_saturated.wait();
19194
19195    for _ in 0..2 {
19196      dependency_queued_rx
19197        .recv_timeout(Duration::from_secs(2))
19198        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
19199    }
19200    let (unrelated_tx, unrelated_rx) = mpsc::channel();
19201    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
19202    begin_lending.wait();
19203
19204    let mut started = Vec::new();
19205    for _ in 0..2 {
19206      started.push(
19207        dependency_started_rx
19208          .recv_timeout(Duration::from_secs(2))
19209          .expect("each owner must service its own exact dependency"),
19210      );
19211    }
19212    started.sort_unstable();
19213    assert_eq!(started, vec![0, 1]);
19214    assert!(
19215      unrelated_rx.try_recv().is_err(),
19216      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
19217    );
19218
19219    release_dependencies.wait();
19220    for (owner_index, owner) in owners.into_iter().enumerate() {
19221      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
19222    }
19223    unrelated_rx
19224      .recv_timeout(Duration::from_secs(2))
19225      .expect("the unrelated job must run after an owner releases a counted lane");
19226    futures::executor::block_on(unrelated).unwrap();
19227  }
19228
19229  #[cfg(not(target_family = "wasm"))]
19230  #[test]
19231  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
19232    use std::sync::{Mutex, mpsc};
19233
19234    const DEPENDENCY_COUNT: usize = 4;
19235
19236    let options = RuntimeOptions {
19237      flavor: RuntimeFlavor::MultiThread,
19238      worker_threads: 3,
19239      max_blocking_tasks: 1,
19240      thread_name_prefix: "parked-unrelated-worker".to_string(),
19241      park_deadline: None,
19242      drain_linger: DEFAULT_DRAIN_LINGER,
19243    };
19244    let executor =
19245      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19246    let (parked_tx, parked_rx) = mpsc::channel();
19247    let (release_parked_tx, release_parked_rx) = mpsc::channel();
19248    executor.pool.spawn(move || {
19249      parked_tx.send(()).unwrap();
19250      release_parked_rx.recv().unwrap();
19251    });
19252    parked_rx
19253      .recv_timeout(Duration::from_secs(2))
19254      .expect("the unrelated Rayon worker must remain occupied");
19255
19256    let owner_executor = Arc::clone(&executor);
19257    let owner = executor.schedule_blocking(move || {
19258      let owner = BLOCKING_OWNER
19259        .with(std::cell::Cell::get)
19260        .expect("the blocking closure must carry its exact owner frame");
19261      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
19262      let results = Arc::new(Mutex::new(Vec::new()));
19263      let descendant_results = Arc::clone(&results);
19264      let descendant_executor = Arc::clone(&owner_executor);
19265      rayon::scope(move |scope| {
19266        scope.spawn(move |_| {
19267          assert_eq!(
19268            BLOCKING_OWNER.with(std::cell::Cell::get),
19269            None,
19270            "the parked owner must force its descendant onto the remaining worker"
19271          );
19272          let _owner = BlockingOwnerGuard::enter(Some(owner));
19273          descendant_started_tx.send(()).unwrap();
19274          for value in 0..DEPENDENCY_COUNT {
19275            let handle = descendant_executor.schedule_blocking(move || value);
19276            let mut result = None;
19277            {
19278              let mut future = std::pin::pin!(async {
19279                result = Some(handle.await.unwrap());
19280              });
19281              assert_eq!(
19282                descendant_executor.block_on(future.as_mut()),
19283                BlockOnOutcome::Completed
19284              );
19285            }
19286            descendant_results.lock().unwrap().push(result.unwrap());
19287          }
19288        });
19289        descendant_started_rx
19290          .recv_timeout(Duration::from_secs(2))
19291          .expect("the nested descendant must start on the only unoccupied worker");
19292      });
19293      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19294    });
19295
19296    assert_eq!(
19297      futures::executor::block_on(owner).unwrap(),
19298      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
19299    );
19300
19301    let (idle_tx, idle_rx) = mpsc::channel();
19302    let idle_executor = Arc::clone(&executor);
19303    let idle_waiter = std::thread::spawn(move || {
19304      idle_executor.begin_shutdown();
19305      idle_executor.wait_until_scheduler_idle();
19306      idle_tx.send(()).unwrap();
19307    });
19308    idle_rx
19309      .recv_timeout(Duration::from_secs(2))
19310      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
19311
19312    release_parked_tx.send(()).unwrap();
19313    idle_waiter.join().unwrap();
19314  }
19315
19316  #[cfg(not(target_family = "wasm"))]
19317  #[test]
19318  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
19319    use std::sync::{Condvar, Mutex, mpsc};
19320
19321    let options = RuntimeOptions {
19322      flavor: RuntimeFlavor::MultiThread,
19323      worker_threads: 3,
19324      max_blocking_tasks: 1,
19325      thread_name_prefix: "same-owner-rearm".to_string(),
19326      park_deadline: None,
19327      drain_linger: DEFAULT_DRAIN_LINGER,
19328    };
19329    let executor =
19330      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19331    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
19332    let (job_started_tx, job_started_rx) = mpsc::channel();
19333    let owner_executor = Arc::clone(&executor);
19334    let owner_releases = Arc::clone(&releases);
19335    let owner = executor.schedule_blocking(move || {
19336      let owner = BLOCKING_OWNER
19337        .with(std::cell::Cell::get)
19338        .expect("the blocking closure must carry its exact owner frame");
19339      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
19340      let results = Arc::new(Mutex::new(Vec::new()));
19341      let scope_results = Arc::clone(&results);
19342      rayon::scope(move |scope| {
19343        for index in 0..2 {
19344          let descendant_ready_tx = descendant_ready_tx.clone();
19345          let descendant_executor = Arc::clone(&owner_executor);
19346          let descendant_results = Arc::clone(&scope_results);
19347          let job_started_tx = job_started_tx.clone();
19348          let releases = Arc::clone(&owner_releases);
19349          scope.spawn(move |_| {
19350            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19351            let _owner = BlockingOwnerGuard::enter(Some(owner));
19352            descendant_ready_tx.send(()).unwrap();
19353            let handle = descendant_executor.schedule_blocking(move || {
19354              job_started_tx.send(index).unwrap();
19355              let (released, changed) = &*releases;
19356              let mut state = released.lock().unwrap();
19357              while !state[index] {
19358                state = changed.wait(state).unwrap();
19359              }
19360              index
19361            });
19362            let mut result = None;
19363            {
19364              let mut future = std::pin::pin!(async {
19365                result = Some(handle.await.unwrap());
19366              });
19367              assert_eq!(
19368                descendant_executor.block_on(future.as_mut()),
19369                BlockOnOutcome::Completed
19370              );
19371            }
19372            descendant_results.lock().unwrap().push(result.unwrap());
19373          });
19374        }
19375        drop(descendant_ready_tx);
19376        for _ in 0..2 {
19377          descendant_ready_rx
19378            .recv_timeout(Duration::from_secs(2))
19379            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
19380        }
19381      });
19382      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19383    });
19384
19385    let first = job_started_rx
19386      .recv_timeout(Duration::from_secs(2))
19387      .expect("one exact dependency must borrow the owner lane");
19388    assert!(
19389      job_started_rx
19390        .recv_timeout(Duration::from_millis(100))
19391        .is_err(),
19392      "one owner frame must not service two dependencies concurrently"
19393    );
19394    let unrelated_parker = Arc::new(DriverParker::default());
19395    executor
19396      .parked_drivers
19397      .register_with_role(&unrelated_parker, true, None);
19398    {
19399      let (released, changed) = &*releases;
19400      released.lock().unwrap()[first] = true;
19401      changed.notify_all();
19402    }
19403
19404    let second = job_started_rx
19405      .recv_timeout(Duration::from_secs(2))
19406      .expect("completion must rearm one parked same-owner dependency");
19407    assert_ne!(second, first);
19408    assert!(
19409      !unrelated_parker.consume_permit(),
19410      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
19411    );
19412    executor.parked_drivers.deregister(&unrelated_parker);
19413    {
19414      let (released, changed) = &*releases;
19415      released.lock().unwrap()[second] = true;
19416      changed.notify_all();
19417    }
19418
19419    let mut results = futures::executor::block_on(owner).unwrap();
19420    results.sort_unstable();
19421    assert_eq!(results, vec![0, 1]);
19422  }
19423
19424  #[cfg(not(target_family = "wasm"))]
19425  #[test]
19426  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
19427    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
19428    let owner = executor.fresh_owner_token();
19429    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19430    let failed_dependency = Arc::new(TaskDependency::default());
19431    failed_dependency.set_owned(BlockingDependency {
19432      job: BlockingJobId(u64::MAX - 21),
19433      owner: Some(owner),
19434    });
19435    let parked_dependency = Arc::new(TaskDependency::default());
19436    parked_dependency.set_owned(BlockingDependency {
19437      job: BlockingJobId(u64::MAX - 22),
19438      owner: Some(owner),
19439    });
19440    let parker = Arc::new(DriverParker::default());
19441
19442    let queue = executor.blocking_queue.lock().unwrap();
19443    let claimant_executor = Arc::clone(&executor);
19444    let claimant_dependency = Arc::clone(&failed_dependency);
19445    let claimant = std::thread::spawn(move || {
19446      let _owner = BlockingOwnerGuard::enter(Some(owner));
19447      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
19448    });
19449    wait_until("the failed claimant to reserve the owner lane", || {
19450      !executor.active_blocking_owners.is_available(owner)
19451    });
19452    executor
19453      .parked_drivers
19454      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
19455    failed_dependency.clear();
19456    drop(queue);
19457
19458    assert!(
19459      !claimant.join().unwrap(),
19460      "the cancelled dependency must not execute"
19461    );
19462    assert!(
19463      parker.consume_permit(),
19464      "releasing a failed exact claim must hand the owner lane to another parked dependency"
19465    );
19466  }
19467
19468  #[cfg(not(target_family = "wasm"))]
19469  #[test]
19470  fn exact_owner_lending_does_not_start_blocking_after_stop() {
19471    use std::sync::mpsc;
19472
19473    let options = RuntimeOptions {
19474      flavor: RuntimeFlavor::MultiThread,
19475      worker_threads: 2,
19476      max_blocking_tasks: 1,
19477      thread_name_prefix: "stopped-exact-owner".to_string(),
19478      park_deadline: None,
19479      drain_linger: DEFAULT_DRAIN_LINGER,
19480    };
19481    let executor =
19482      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19483    let ran = Arc::new(AtomicBool::new(false));
19484    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
19485    let (release_claim_tx, release_claim_rx) = mpsc::channel();
19486    let owner_executor = Arc::clone(&executor);
19487    let child_ran = Arc::clone(&ran);
19488    let owner = executor.schedule_blocking(move || {
19489      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
19490        *slot.borrow_mut() = Some(Box::new(move || {
19491          claim_entered_tx.send(()).unwrap();
19492          release_claim_rx.recv().unwrap();
19493        }));
19494      });
19495      let child = owner_executor.schedule_blocking(move || {
19496        child_ran.store(true, Ordering::SeqCst);
19497      });
19498      let mut future = std::pin::pin!(async move {
19499        let _ = child.await;
19500      });
19501      owner_executor.block_on(future.as_mut())
19502    });
19503
19504    claim_entered_rx
19505      .recv_timeout(Duration::from_secs(2))
19506      .expect("the cooperative driver must pause before the exact-owner queue claim");
19507    assert_eq!(
19508      executor.blocking_queue.lock().unwrap().jobs.len(),
19509      1,
19510      "the exact dependency must remain queued before stop publication"
19511    );
19512
19513    // Pin the reported race: the cooperative driver already passed its loop's
19514    // stop check, then shutdown publishes stop before the exact queue claim.
19515    executor.stop.begin_shutdown();
19516    release_claim_tx.send(()).unwrap();
19517
19518    assert_eq!(
19519      futures::executor::block_on(owner).unwrap(),
19520      BlockOnOutcome::Stopped,
19521      "the cooperative driver must stop instead of executing its queued dependency"
19522    );
19523    assert!(
19524      !ran.load(Ordering::SeqCst),
19525      "exact-owner lending started blocking work after stop"
19526    );
19527    assert_eq!(
19528      executor.blocking_queue.lock().unwrap().jobs.len(),
19529      1,
19530      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
19531    );
19532
19533    executor.begin_shutdown();
19534    executor.wait_until_scheduler_idle();
19535    assert!(executor.blocking_queue.lock().unwrap().is_empty());
19536    assert!(!ran.load(Ordering::SeqCst));
19537  }
19538
19539  #[cfg(not(target_family = "wasm"))]
19540  #[test]
19541  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
19542    const DEPENDENCY_COUNT: usize = 1024;
19543
19544    let options = RuntimeOptions {
19545      flavor: RuntimeFlavor::MultiThread,
19546      worker_threads: 2,
19547      max_blocking_tasks: 1,
19548      thread_name_prefix: "linear-owner-lending".to_string(),
19549      park_deadline: None,
19550      drain_linger: DEFAULT_DRAIN_LINGER,
19551    };
19552    let executor =
19553      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19554    let completed = Arc::new(AtomicUsize::new(0));
19555
19556    for index in 0..DEPENDENCY_COUNT {
19557      let owner = executor.fresh_owner_token();
19558      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19559      let dependency = TaskDependency::new(u64::MAX, Some(owner));
19560      let blocking = BlockingDependency {
19561        job: BlockingJobId(u64::MAX - index as u64),
19562        owner: Some(owner),
19563      };
19564      dependency.set_owned(blocking);
19565      let completed = Arc::clone(&completed);
19566      executor
19567        .blocking_queue
19568        .lock()
19569        .unwrap()
19570        .push(QueuedBlocking {
19571          id: blocking.job,
19572          run: Box::new(move || {
19573            completed.fetch_add(1, Ordering::SeqCst);
19574          }),
19575        });
19576
19577      let _owner = BlockingOwnerGuard::enter(Some(owner));
19578      assert!(executor.try_owned_blocking_over_cap(&dependency));
19579    }
19580
19581    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
19582    assert_eq!(
19583      executor.owner_lending_attempts.load(Ordering::Relaxed),
19584      DEPENDENCY_COUNT,
19585      "targeted lending must perform one O(1) claim attempt per exact dependency"
19586    );
19587    let queue = executor.blocking_queue.lock().unwrap();
19588    assert!(queue.is_empty());
19589    assert_eq!(queue.head, None);
19590    assert_eq!(queue.tail, None);
19591  }
19592
19593  #[cfg(not(target_family = "wasm"))]
19594  #[test]
19595  fn one_dependency_interspersed_owner_publications_stays_linear() {
19596    const PUBLICATION_COUNT: usize = 1024;
19597
19598    let executor = multi_thread_executor(2, None, "linear-publication-lending");
19599    let owner = executor.fresh_owner_token();
19600    let wrong_owner = executor.fresh_owner_token();
19601    let dependency = TaskDependency::new(u64::MAX, Some(owner));
19602    let completed = Arc::new(AtomicUsize::new(0));
19603    for index in 0..PUBLICATION_COUNT {
19604      let cancelled_source = TaskDependency::default();
19605      cancelled_source.set_owned(BlockingDependency {
19606        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
19607        owner: Some(owner),
19608      });
19609      dependency.set_borrowed(
19610        &cancelled_source
19611          .get()
19612          .expect("the source publication must initially be live"),
19613      );
19614      cancelled_source.clear();
19615
19616      dependency.set_owned(BlockingDependency {
19617        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
19618        owner: Some(wrong_owner),
19619      });
19620      let blocking = BlockingDependency {
19621        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
19622        owner: Some(owner),
19623      };
19624      dependency.set_owned(blocking);
19625      let completed = Arc::clone(&completed);
19626      executor
19627        .blocking_queue
19628        .lock()
19629        .unwrap()
19630        .push(QueuedBlocking {
19631          id: blocking.job,
19632          run: Box::new(move || {
19633            completed.fetch_add(1, Ordering::SeqCst);
19634          }),
19635        });
19636    }
19637    dependency.reset_entry_visits();
19638
19639    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19640    let _owner = BlockingOwnerGuard::enter(Some(owner));
19641    for _ in 0..PUBLICATION_COUNT {
19642      assert!(
19643        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19644        "the owner-handoff predicate must find the next exact publication"
19645      );
19646      assert!(
19647        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19648        "the park-availability predicate must find the next serviceable publication"
19649      );
19650      assert!(executor.try_owned_blocking_over_cap(&dependency));
19651    }
19652
19653    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
19654    assert!(
19655      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19656      "wrong-owner publications must not satisfy an exact handoff"
19657    );
19658    assert!(
19659      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19660      "wrong-owner publications must not make the owner's lane appear serviceable"
19661    );
19662    assert!(
19663      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
19664      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
19665      dependency.entry_visits()
19666    );
19667    assert!(executor.blocking_queue.lock().unwrap().is_empty());
19668  }
19669
19670  #[cfg(not(target_family = "wasm"))]
19671  #[test]
19672  fn binding_unowned_dependency_updates_exact_owner_predicate() {
19673    let owner = BlockingOwnerToken {
19674      executor_id: u64::MAX - 200,
19675      frame: u64::MAX - 201,
19676    };
19677    let dependency = TaskDependency::default();
19678    let blocking = BlockingDependency {
19679      job: BlockingJobId(u64::MAX - 202),
19680      owner: None,
19681    };
19682    dependency.set_owned(blocking);
19683
19684    assert!(
19685      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19686      "an unowned publication must not satisfy an exact owner handoff"
19687    );
19688    assert!(
19689      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19690      "an unowned publication is available to the ambient owner"
19691    );
19692
19693    let selected = dependency
19694      .select_for_owner(owner)
19695      .expect("the owner must bind the unowned publication");
19696    assert_eq!(selected.publication.dependency.owner, Some(owner));
19697    assert!(
19698      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19699      "binding must immediately publish the entry to exact-owner predicates"
19700    );
19701    assert!(dependency.try_claim(&selected));
19702    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19703  }
19704
19705  #[cfg(not(target_family = "wasm"))]
19706  #[test]
19707  fn owner_selection_preserves_unowned_and_exact_publication_order() {
19708    let owner = BlockingOwnerToken {
19709      executor_id: u64::MAX - 210,
19710      frame: u64::MAX - 211,
19711    };
19712    let dependency = TaskDependency::default();
19713    let first = BlockingDependency {
19714      job: BlockingJobId(u64::MAX - 212),
19715      owner: None,
19716    };
19717    let second = BlockingDependency {
19718      job: BlockingJobId(u64::MAX - 213),
19719      owner: Some(owner),
19720    };
19721    dependency.set_owned(first);
19722    dependency.set_owned(second);
19723
19724    let selected = dependency
19725      .select_for_owner(owner)
19726      .expect("the first publication must be found");
19727    assert_eq!(
19728      selected.publication.dependency,
19729      BlockingDependency {
19730        owner: Some(owner),
19731        ..first
19732      }
19733    );
19734    assert!(dependency.try_claim(&selected));
19735
19736    let selected = dependency
19737      .select_for_owner(owner)
19738      .expect("the second publication must remain eligible");
19739    assert_eq!(selected.publication.dependency, second);
19740  }
19741
19742  #[cfg(not(target_family = "wasm"))]
19743  #[test]
19744  fn blocking_queue_exact_removal_reclaims_fifo_links() {
19745    const JOB_COUNT: u64 = 4096;
19746
19747    let mut queue = BlockingQueue {
19748      closed: false,
19749      head: None,
19750      tail: None,
19751      jobs: FxHashMap::default(),
19752    };
19753    for id in 1..=JOB_COUNT {
19754      queue.push(QueuedBlocking {
19755        id: BlockingJobId(id),
19756        run: Box::new(|| {}),
19757      });
19758    }
19759    for id in (2..=JOB_COUNT).step_by(2) {
19760      drop(
19761        queue
19762          .remove(BlockingJobId(id))
19763          .expect("even job must remain indexed"),
19764      );
19765    }
19766    for id in (1..=JOB_COUNT).step_by(2) {
19767      drop(
19768        queue
19769          .remove(BlockingJobId(id))
19770          .expect("odd job must remain indexed"),
19771      );
19772    }
19773    assert!(queue.jobs.is_empty());
19774    assert_eq!(queue.head, None);
19775    assert_eq!(queue.tail, None);
19776
19777    let order = Arc::new(Mutex::new(Vec::new()));
19778    for id in 1..=3 {
19779      let order = Arc::clone(&order);
19780      queue.push(QueuedBlocking {
19781        id: BlockingJobId(id),
19782        run: Box::new(move || order.lock().unwrap().push(id)),
19783      });
19784    }
19785    drop(
19786      queue
19787        .remove(BlockingJobId(2))
19788        .expect("middle job must be removable"),
19789    );
19790    queue.pop_front().expect("FIFO head must remain linked")();
19791    queue.pop_front().expect("FIFO tail must remain linked")();
19792    assert!(queue.pop_front().is_none());
19793    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
19794    assert_eq!(queue.head, None);
19795    assert_eq!(queue.tail, None);
19796  }
19797
19798  #[cfg(not(target_family = "wasm"))]
19799  #[test]
19800  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
19801    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
19802
19803    if std::env::var_os(CHILD_ENV).is_some() {
19804      configure(RuntimeOptions {
19805        flavor: RuntimeFlavor::MultiThread,
19806        worker_threads: 2,
19807        max_blocking_tasks: 1,
19808        thread_name_prefix: "dependency-lending-metrics".to_string(),
19809        park_deadline: None,
19810        drain_linger: DEFAULT_DRAIN_LINGER,
19811      })
19812      .unwrap();
19813
19814      let outer = spawn_blocking(|| {
19815        let inner = spawn_blocking(|| 42usize);
19816        block_on(inner).unwrap()
19817      });
19818      assert_eq!(block_on(outer).unwrap(), 42);
19819
19820      // Result delivery wakes the joiner from inside the blocking closure,
19821      // immediately before the scheduler-owned metrics guard retires. Use
19822      // shutdown quiescence before sampling completed/live counters so this
19823      // test does not race that final bookkeeping.
19824      shutdown().unwrap();
19825      let snapshot = metrics();
19826      assert_eq!(snapshot.blocking_tasks_started, 2);
19827      assert_eq!(snapshot.blocking_tasks_completed, 2);
19828      assert_eq!(snapshot.active_blocking_tasks, 0);
19829      assert_eq!(
19830        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19831        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19832      );
19833      return;
19834    }
19835
19836    let output = std::process::Command::new(std::env::current_exe().unwrap())
19837      .arg("--exact")
19838      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19839      .arg("--nocapture")
19840      .env(CHILD_ENV, "1")
19841      .output()
19842      .expect("the dependency-lending metrics subprocess must start");
19843    assert!(
19844      output.status.success(),
19845      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19846      output.status.code(),
19847      String::from_utf8_lossy(&output.stdout),
19848      String::from_utf8_lossy(&output.stderr)
19849    );
19850  }
19851
19852  #[cfg(not(target_family = "wasm"))]
19853  #[test]
19854  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19855    use std::sync::mpsc;
19856
19857    let (done_tx, done_rx) = mpsc::channel();
19858    let runner = std::thread::spawn(move || {
19859      let options = RuntimeOptions {
19860        flavor: RuntimeFlavor::MultiThread,
19861        worker_threads: 2,
19862        max_blocking_tasks: 1,
19863        thread_name_prefix: "exact-blocking-job".to_string(),
19864        park_deadline: None,
19865        drain_linger: DEFAULT_DRAIN_LINGER,
19866      };
19867      let executor =
19868        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19869      let outer_exec = Arc::clone(&executor);
19870      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19871      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19872      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19873      let outer = executor.schedule_blocking(move || {
19874        let sibling = outer_exec.schedule_blocking(move || {
19875          sibling_started_tx.send(()).unwrap();
19876          release_sibling_rx.recv().unwrap();
19877        });
19878        let awaited = outer_exec.schedule_blocking(move || {
19879          awaited_ran_tx.send(()).unwrap();
19880          17usize
19881        });
19882        let mut value = None;
19883        {
19884          let mut future = std::pin::pin!(async {
19885            value = Some(awaited.await.unwrap());
19886          });
19887          outer_exec.block_on(future.as_mut());
19888        }
19889        (sibling, value.unwrap())
19890      });
19891
19892      awaited_ran_rx
19893        .recv_timeout(Duration::from_secs(2))
19894        .expect("the exact awaited job must bypass the earlier detached sibling");
19895      let (sibling, value) = futures::executor::block_on(outer).unwrap();
19896      assert_eq!(value, 17);
19897      sibling_started_rx
19898        .recv_timeout(Duration::from_secs(2))
19899        .expect("the sibling must run after the owner releases its counted slot");
19900      release_sibling_tx.send(()).unwrap();
19901      futures::executor::block_on(sibling).unwrap();
19902      done_tx.send(()).unwrap();
19903    });
19904    done_rx
19905      .recv_timeout(Duration::from_secs(5))
19906      .expect("same-owner sibling selection deadlocked the exact awaited job");
19907    runner.join().unwrap();
19908  }
19909
19910  #[cfg(not(target_family = "wasm"))]
19911  #[test]
19912  fn blocking_dependency_propagates_through_async_join_lineage() {
19913    use std::sync::mpsc;
19914
19915    let (done_tx, done_rx) = mpsc::channel();
19916    let runner = std::thread::spawn(move || {
19917      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19918      let owner_controller = Arc::clone(&controller);
19919      let (child_tx, child_rx) = mpsc::channel();
19920      let outer = controller
19921        .try_spawn_blocking(move || {
19922          let async_controller = Arc::clone(&owner_controller);
19923          let async_child = owner_controller
19924            .try_spawn(async move {
19925              async_controller
19926                .try_spawn_blocking(move || {
19927                  child_tx.send(()).unwrap();
19928                  23usize
19929                })
19930                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19931                .await
19932                .unwrap()
19933            })
19934            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19935          let mut value = None;
19936          {
19937            let mut future = std::pin::pin!(async {
19938              value = Some(async_child.await.unwrap());
19939            });
19940            owner_controller.block_on(future.as_mut());
19941          }
19942          value.unwrap()
19943        })
19944        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19945
19946      child_rx
19947        .recv_timeout(Duration::from_secs(2))
19948        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19949      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19950      controller.shutdown().unwrap();
19951      done_tx.send(()).unwrap();
19952    });
19953    done_rx
19954      .recv_timeout(Duration::from_secs(5))
19955      .expect("blocking dependency lineage deadlocked");
19956    runner.join().unwrap();
19957  }
19958
19959  #[cfg(not(target_family = "wasm"))]
19960  #[test]
19961  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19962    use std::sync::mpsc;
19963
19964    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19965    let outer_controller = Arc::clone(&controller);
19966    let (child_queued_tx, child_queued_rx) = mpsc::channel();
19967    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19968    let (release_outer_tx, release_outer_rx) = oneshot::channel();
19969    let outer = controller
19970      .try_spawn_blocking(move || {
19971        let child_controller = Arc::clone(&outer_controller);
19972        let child = outer_controller
19973          .try_spawn(async move {
19974            let blocking = child_controller
19975              .try_spawn_blocking(move || {
19976                unrelated_ran_tx.send(()).unwrap();
19977                31usize
19978              })
19979              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
19980            child_queued_tx.send(()).unwrap();
19981            blocking.await.unwrap()
19982          })
19983          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
19984        let mut released = false;
19985        {
19986          let mut future = std::pin::pin!(async {
19987            release_outer_rx.await.unwrap();
19988            released = true;
19989          });
19990          outer_controller.block_on(future.as_mut());
19991        }
19992        assert!(released);
19993        child
19994      })
19995      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19996
19997    child_queued_rx
19998      .recv_timeout(Duration::from_secs(2))
19999      .expect("the cooperative driver must service the unrelated async runnable");
20000    assert!(
20001      unrelated_ran_rx
20002        .recv_timeout(Duration::from_millis(200))
20003        .is_err(),
20004      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
20005    );
20006
20007    release_outer_tx.send(()).unwrap();
20008    let child = futures::executor::block_on(outer).unwrap();
20009    unrelated_ran_rx
20010      .recv_timeout(Duration::from_secs(2))
20011      .expect("the unrelated blocking job must run after the owner releases its counted slot");
20012    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
20013    controller.shutdown().unwrap();
20014  }
20015
20016  #[cfg(not(target_family = "wasm"))]
20017  #[test]
20018  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
20019    let parent = Arc::new(TaskDependency::default());
20020    let _context = TaskDependencyGuard::enter(&parent);
20021    let dependency = BlockingDependency {
20022      job: BlockingJobId(u64::MAX),
20023      owner: None,
20024    };
20025    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20026    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20027      receiver,
20028      dependency,
20029      published: PublishedBlockingDependencies::default(),
20030    });
20031    let waker = futures::task::noop_waker();
20032    let mut cx = Context::from_waker(&waker);
20033
20034    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20035    assert_eq!(parent.get_dependency(), Some(dependency));
20036    drop(handle);
20037    assert_eq!(
20038      parent.get_dependency(),
20039      None,
20040      "an abandoned JoinHandle must clear stale dependency lineage"
20041    );
20042    drop(sender);
20043  }
20044
20045  #[cfg(not(target_family = "wasm"))]
20046  #[test]
20047  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
20048    let owner = BlockingOwnerToken {
20049      executor_id: u64::MAX - 40,
20050      frame: u64::MAX - 41,
20051    };
20052    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20053    let _context = TaskDependencyGuard::enter(&parent);
20054    let dependency = BlockingDependency {
20055      job: BlockingJobId(u64::MAX - 42),
20056      owner: None,
20057    };
20058    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20059    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20060      receiver,
20061      dependency,
20062      published: PublishedBlockingDependencies::default(),
20063    });
20064    let waker = futures::task::noop_waker();
20065    let mut cx = Context::from_waker(&waker);
20066
20067    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20068    assert_eq!(
20069      parent.get_dependency(),
20070      Some(BlockingDependency {
20071        owner: Some(owner),
20072        ..dependency
20073      }),
20074      "the ambient task owner must enrich an untagged direct blocking dependency"
20075    );
20076    drop(handle);
20077    assert_eq!(
20078      parent.get_dependency(),
20079      None,
20080      "detach cleanup must match the stable job id after owner enrichment"
20081    );
20082    drop(sender);
20083  }
20084
20085  #[cfg(not(target_family = "wasm"))]
20086  #[test]
20087  fn one_poll_preserves_every_observed_blocking_dependency() {
20088    let parent = Arc::new(TaskDependency::default());
20089    let _context = TaskDependencyGuard::enter(&parent);
20090    let first = BlockingDependency {
20091      job: BlockingJobId(u64::MAX - 60),
20092      owner: None,
20093    };
20094    let second = BlockingDependency {
20095      job: BlockingJobId(u64::MAX - 61),
20096      owner: None,
20097    };
20098    let (first_sender, first_receiver) =
20099      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20100    let (second_sender, second_receiver) =
20101      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20102    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
20103      receiver: first_receiver,
20104      dependency: first,
20105      published: PublishedBlockingDependencies::default(),
20106    });
20107    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
20108      receiver: second_receiver,
20109      dependency: second,
20110      published: PublishedBlockingDependencies::default(),
20111    });
20112    let waker = futures::task::noop_waker();
20113    let mut cx = Context::from_waker(&waker);
20114
20115    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
20116    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
20117    assert_eq!(
20118      parent.get_dependencies(),
20119      vec![first, second],
20120      "publishing a later dependency in one user poll must not replace an earlier dependency"
20121    );
20122
20123    drop(first_handle);
20124    drop(second_handle);
20125    drop(first_sender);
20126    drop(second_sender);
20127  }
20128
20129  #[cfg(not(target_family = "wasm"))]
20130  #[test]
20131  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
20132    let owner = BlockingOwnerToken {
20133      executor_id: u64::MAX - 900,
20134      frame: u64::MAX - 901,
20135    };
20136    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20137    let _context = TaskDependencyGuard::enter(&parent);
20138    let waker = futures::task::noop_waker();
20139    let mut cx = Context::from_waker(&waker);
20140
20141    let j1 = BlockingDependency {
20142      job: BlockingJobId(u64::MAX - 910),
20143      owner: Some(owner),
20144    };
20145    let j2 = BlockingDependency {
20146      job: BlockingJobId(u64::MAX - 911),
20147      owner: Some(owner),
20148    };
20149
20150    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20151    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20152
20153    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20154      receiver: r1,
20155      dependency: j1,
20156      published: PublishedBlockingDependencies::default(),
20157    });
20158    // TWO Pending polls in one pass. Each publishes, so without coalescing the
20159    // handle leaves two live entries for J1 and can retire only the newer one.
20160    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20161    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20162    let entries_after_double_poll = parent
20163      .state
20164      .lock()
20165      .unwrap_or_else(std::sync::PoisonError::into_inner)
20166      .current
20167      .len();
20168    drop(h1);
20169
20170    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20171      receiver: r2,
20172      dependency: j2,
20173      published: PublishedBlockingDependencies::default(),
20174    });
20175    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20176
20177    assert_eq!(
20178      entries_after_double_poll, 1,
20179      "a repeat Pending poll must supersede the handle's own previous publication"
20180    );
20181    let selected = parent
20182      .select_for_owner(owner)
20183      .expect("a live dependency must be selectable");
20184    assert_eq!(
20185      selected.publication.dependency.job, j2.job,
20186      "a DETACHED job must not be selected for owner lending ahead of the live one"
20187    );
20188    drop((s1, s2));
20189  }
20190
20191  #[cfg(not(target_family = "wasm"))]
20192  #[test]
20193  fn handle_polled_under_a_nested_frame_retires_both_publications() {
20194    let owner = BlockingOwnerToken {
20195      executor_id: u64::MAX - 920,
20196      frame: u64::MAX - 921,
20197    };
20198    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20199    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20200    let waker = futures::task::noop_waker();
20201    let mut cx = Context::from_waker(&waker);
20202
20203    let j1 = BlockingDependency {
20204      job: BlockingJobId(u64::MAX - 930),
20205      owner: Some(owner),
20206    };
20207    let j2 = BlockingDependency {
20208      job: BlockingJobId(u64::MAX - 931),
20209      owner: Some(owner),
20210    };
20211    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20212    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20213    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20214      receiver: r1,
20215      dependency: j1,
20216      published: Default::default(),
20217    });
20218
20219    {
20220      let _outer_ctx = TaskDependencyGuard::enter(&outer);
20221      // A handle can publish into more than one frame: once here, and again
20222      // under a nested re-entrant frame. Detaching must retire BOTH, or the
20223      // forgotten entry keeps a detached job selectable for owner lending.
20224      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20225      {
20226        let _inner_ctx = TaskDependencyGuard::enter(&inner);
20227        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20228      }
20229      drop(h1);
20230      assert!(
20231        outer.get_dependencies().is_empty(),
20232        "detaching must retire the publication made in the outer frame too"
20233      );
20234      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20235        receiver: r2,
20236        dependency: j2,
20237        published: Default::default(),
20238      });
20239      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20240      let selected = outer.select_for_owner(owner).expect("selectable");
20241      assert_eq!(
20242        selected.publication.dependency.job, j2.job,
20243        "a detached job published in an outer frame must not outrank a live one"
20244      );
20245      drop(h2);
20246    }
20247    drop((s1, s2));
20248  }
20249
20250  #[cfg(not(target_family = "wasm"))]
20251  #[test]
20252  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
20253    const FRAME_COUNT: usize = 64;
20254    let waker = futures::task::noop_waker();
20255    let mut cx = Context::from_waker(&waker);
20256    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20257    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20258      receiver,
20259      dependency: BlockingDependency {
20260        job: BlockingJobId(u64::MAX - 940),
20261        owner: None,
20262      },
20263      published: PublishedBlockingDependencies::default(),
20264    });
20265    let mut frames = Vec::new();
20266    for _ in 0..FRAME_COUNT {
20267      let frame = Arc::new(TaskDependency::default());
20268      frames.push(Arc::downgrade(&frame));
20269      let _context = TaskDependencyGuard::enter(&frame);
20270      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20271    }
20272    // A handle carried through a sequence of completed frames must not keep
20273    // any of them alive: it remembers each publication by `Weak`, and prunes
20274    // the expired tokens on its next publication, so the history stays at the
20275    // live frame count instead of growing with every frame ever seen.
20276    let retained = frames
20277      .iter()
20278      .filter(|frame| frame.strong_count() > 0)
20279      .count();
20280    assert_eq!(
20281      retained, 0,
20282      "a completed frame must not be kept alive by a pending handle's publication history"
20283    );
20284    drop(handle);
20285    drop(sender);
20286  }
20287
20288  #[cfg(not(target_family = "wasm"))]
20289  #[test]
20290  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
20291    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
20292    // sequence, so H1 must keep the position its FIRST poll gave it: if
20293    // re-polling appended a replacement at the tail, H2 would be lent first,
20294    // and an H2 that waits on H1 would deadlock the owner's single lane.
20295    let owner = BlockingOwnerToken {
20296      executor_id: u64::MAX - 950,
20297      frame: u64::MAX - 951,
20298    };
20299    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20300    let _context = TaskDependencyGuard::enter(&parent);
20301    let waker = futures::task::noop_waker();
20302    let mut cx = Context::from_waker(&waker);
20303
20304    let j1 = BlockingDependency {
20305      job: BlockingJobId(u64::MAX - 960),
20306      owner: Some(owner),
20307    };
20308    let j2 = BlockingDependency {
20309      job: BlockingJobId(u64::MAX - 961),
20310      owner: Some(owner),
20311    };
20312    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20313    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20314    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20315      receiver: r1,
20316      dependency: j1,
20317      published: PublishedBlockingDependencies::default(),
20318    });
20319    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20320      receiver: r2,
20321      dependency: j2,
20322      published: PublishedBlockingDependencies::default(),
20323    });
20324
20325    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20326    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20327    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20328
20329    let selected = parent
20330      .select_for_owner(owner)
20331      .expect("a live dependency must be selectable");
20332    assert_eq!(
20333      selected.publication.dependency.job, j1.job,
20334      "re-polling a handle must not push its dependency behind one published later"
20335    );
20336    drop((h1, h2, s1, s2));
20337  }
20338
20339  #[cfg(not(target_family = "wasm"))]
20340  #[test]
20341  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
20342    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
20343    // `TaskDependency`) but each drains `current` at the start of its poll,
20344    // which is what a real task poll does. A drained frame no longer holds the
20345    // entry, so its token stops standing and is dropped on the next
20346    // publication: the history tracks frames that still hold an entry, not
20347    // every frame the handle has ever been polled under.
20348    const FRAME_COUNT: usize = 256;
20349    let waker = futures::task::noop_waker();
20350    let mut cx = Context::from_waker(&waker);
20351    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20352    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20353      receiver,
20354      dependency: BlockingDependency {
20355        job: BlockingJobId(u64::MAX - 970),
20356        owner: None,
20357      },
20358      published: PublishedBlockingDependencies::default(),
20359    });
20360    let mut retained = Vec::new();
20361    for _ in 0..FRAME_COUNT {
20362      let frame = Arc::new(TaskDependency::default());
20363      retained.push(Arc::clone(&frame));
20364      let _context = TaskDependencyGuard::enter(&frame);
20365      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20366      // The frame's next poll drains what the previous one published.
20367      frame.begin_poll();
20368    }
20369    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
20370      panic!("the handle must still be a blocking handle");
20371    };
20372    let remembered = usize::from(published.first.is_some()) + published.rest.len();
20373    assert!(
20374      remembered <= 2,
20375      "a drained frame must not stay in the publication history, remembered={remembered}"
20376    );
20377    drop(handle);
20378    drop(sender);
20379  }
20380
20381  #[cfg(not(target_family = "wasm"))]
20382  #[test]
20383  fn mass_detach_of_pending_blocking_handles_stays_linear() {
20384    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
20385    // poll pass -- so `begin_poll` never runs to drain `current` and all N
20386    // entries are simultaneously registered and still pending -- and are then
20387    // dropped together. Retiring a handle's own entry is O(1) and
20388    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
20389    //
20390    // This is the shape that made an earlier attempt at this optimisation
20391    // quadratic: it replaced the job index with a scan of `current` on every
20392    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
20393    // the dependency mutex.
20394    const HANDLE_COUNT: usize = 512;
20395
20396    let parent = Arc::new(TaskDependency::default());
20397    let _context = TaskDependencyGuard::enter(&parent);
20398    let waker = futures::task::noop_waker();
20399    let mut cx = Context::from_waker(&waker);
20400
20401    let mut senders = Vec::with_capacity(HANDLE_COUNT);
20402    let mut handles = Vec::with_capacity(HANDLE_COUNT);
20403    for index in 0..HANDLE_COUNT {
20404      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20405      senders.push(sender);
20406      handles.push(JoinHandle(JoinHandleInner::Blocking {
20407        receiver,
20408        dependency: BlockingDependency {
20409          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
20410          owner: None,
20411        },
20412        published: PublishedBlockingDependencies::default(),
20413      }));
20414    }
20415    for handle in &mut handles {
20416      assert!(Pin::new(handle).poll(&mut cx).is_pending());
20417    }
20418    assert_eq!(
20419      parent
20420        .state
20421        .lock()
20422        .unwrap_or_else(std::sync::PoisonError::into_inner)
20423        .current
20424        .len(),
20425      HANDLE_COUNT,
20426      "every handle must be registered and still pending before the detach"
20427    );
20428
20429    parent.reset_entry_visits();
20430    drop(handles);
20431
20432    assert!(
20433      parent.entry_visits() <= HANDLE_COUNT * 4,
20434      "mass-detach must visit O(n) entries across n handles, visits={}",
20435      parent.entry_visits()
20436    );
20437    assert_eq!(
20438      parent.get_dependency(),
20439      None,
20440      "every detached handle must have retired its own publication"
20441    );
20442    drop(senders);
20443  }
20444
20445  #[cfg(not(target_family = "wasm"))]
20446  #[test]
20447  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
20448    use std::sync::mpsc;
20449
20450    struct ReenterDependencyContext {
20451      dependency: Arc<TaskDependency>,
20452      woke: mpsc::Sender<()>,
20453    }
20454
20455    impl std::task::Wake for ReenterDependencyContext {
20456      fn wake(self: Arc<Self>) {
20457        let _context = TaskDependencyGuard::enter(&self.dependency);
20458        self.woke.send(()).unwrap();
20459      }
20460    }
20461
20462    let parent = Arc::new(TaskDependency::default());
20463    let nested = Arc::new(TaskDependency::default());
20464    let (woke_tx, woke_rx) = mpsc::channel();
20465    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
20466      dependency: Arc::clone(&nested),
20467      woke: woke_tx,
20468    }));
20469    parent.register_waiter(&waiter);
20470    let blocking = BlockingDependency {
20471      job: BlockingJobId(u64::MAX - 62),
20472      owner: None,
20473    };
20474
20475    let _context = TaskDependencyGuard::enter(&parent);
20476    let mut published = PublishedBlockingDependencies::default();
20477    record_blocking_dependency(&mut published, blocking);
20478    woke_rx
20479      .recv_timeout(Duration::from_secs(1))
20480      .expect("dependency notification must permit reentrant TLS context access");
20481
20482    parent.register_waiter(&waiter);
20483    clear_blocking_dependency(published);
20484    woke_rx
20485      .recv_timeout(Duration::from_secs(1))
20486      .expect("dependency clearing must permit reentrant TLS context access");
20487  }
20488
20489  #[cfg(not(target_family = "wasm"))]
20490  #[test]
20491  fn dependency_free_begin_poll_does_not_lock_state() {
20492    use std::sync::mpsc;
20493
20494    let dependency = Arc::new(TaskDependency::default());
20495    let state = dependency.state.lock().unwrap();
20496    let (completed_tx, completed_rx) = mpsc::channel();
20497    let polling_dependency = Arc::clone(&dependency);
20498    let polling = std::thread::spawn(move || {
20499      polling_dependency.begin_poll();
20500      completed_tx.send(()).unwrap();
20501    });
20502
20503    let completed_without_lock = completed_rx
20504      .recv_timeout(Duration::from_millis(200))
20505      .is_ok();
20506    drop(state);
20507    polling.join().unwrap();
20508    assert!(
20509      completed_without_lock,
20510      "a task with no published dependency must skip the dependency-state mutex"
20511    );
20512  }
20513
20514  #[cfg(not(target_family = "wasm"))]
20515  #[test]
20516  fn deep_dependency_liveness_chain_is_iterative() {
20517    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
20518
20519    if std::env::var_os(CHILD_ENV).is_some() {
20520      const DEPTH: usize = 100_000;
20521
20522      let mut links = Vec::with_capacity(DEPTH + 1);
20523      links.push(DependencyLink::root());
20524      for _ in 0..DEPTH {
20525        links.push(DependencyLink::derived(links.last().unwrap()));
20526      }
20527      assert!(links.last().unwrap().is_live());
20528      links[0].cancel();
20529      assert!(!links.last().unwrap().is_live());
20530      return;
20531    }
20532
20533    let output = std::process::Command::new(std::env::current_exe().unwrap())
20534      .arg("--exact")
20535      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
20536      .arg("--nocapture")
20537      .env(CHILD_ENV, "1")
20538      .output()
20539      .expect("the subprocess test must start");
20540    assert!(
20541      output.status.success(),
20542      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
20543      output.status.code(),
20544      String::from_utf8_lossy(&output.stdout),
20545      String::from_utf8_lossy(&output.stderr)
20546    );
20547  }
20548
20549  #[cfg(not(target_family = "wasm"))]
20550  #[test]
20551  fn task_dependency_set_contains_panicking_waiter() {
20552    struct PanicWake;
20553    impl std::task::Wake for PanicWake {
20554      fn wake(self: Arc<Self>) {
20555        panic!("intentional TaskDependency::set waiter panic");
20556      }
20557    }
20558
20559    let dependency = TaskDependency::default();
20560    let blocking = BlockingDependency {
20561      job: BlockingJobId(u64::MAX),
20562      owner: None,
20563    };
20564    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20565
20566    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
20567    assert!(
20568      result.is_ok(),
20569      "set notification must not unwind its caller"
20570    );
20571    assert_eq!(
20572      dependency.get_dependency(),
20573      Some(blocking),
20574      "set must commit before notifying its waiter"
20575    );
20576  }
20577
20578  #[cfg(not(target_family = "wasm"))]
20579  #[test]
20580  fn task_dependency_clear_contains_panicking_waiter() {
20581    struct PanicWake;
20582    impl std::task::Wake for PanicWake {
20583      fn wake(self: Arc<Self>) {
20584        panic!("intentional TaskDependency::clear waiter panic");
20585      }
20586    }
20587
20588    let dependency = TaskDependency::default();
20589    dependency.set_owned(BlockingDependency {
20590      job: BlockingJobId(u64::MAX),
20591      owner: None,
20592    });
20593    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20594
20595    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
20596    assert!(
20597      result.is_ok(),
20598      "clear notification must not unwind its caller"
20599    );
20600    assert_eq!(
20601      dependency.get_dependency(),
20602      None,
20603      "clear must commit before notifying its waiter"
20604    );
20605  }
20606
20607  #[cfg(not(target_family = "wasm"))]
20608  #[test]
20609  fn task_dependency_clear_if_contains_panicking_waiter() {
20610    struct PanicWake;
20611    impl std::task::Wake for PanicWake {
20612      fn wake(self: Arc<Self>) {
20613        panic!("intentional TaskDependency::clear_if waiter panic");
20614      }
20615    }
20616
20617    let dependency = TaskDependency::default();
20618    let blocking = BlockingDependency {
20619      job: BlockingJobId(u64::MAX),
20620      owner: None,
20621    };
20622    let sequence = dependency.set_owned(blocking);
20623    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20624
20625    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
20626    assert!(
20627      result.is_ok(),
20628      "retire notification must not unwind its caller"
20629    );
20630    assert_eq!(
20631      dependency.get_dependency(),
20632      None,
20633      "retire must commit before notifying its waiter"
20634    );
20635  }
20636
20637  #[cfg(not(target_family = "wasm"))]
20638  #[test]
20639  fn task_dependency_waiter_replacement_contains_panicking_drop() {
20640    use std::sync::mpsc;
20641
20642    const GENERATION: u64 = u64::MAX - 20;
20643
20644    struct PanicOnDropWake {
20645      dropped: mpsc::Sender<Option<u64>>,
20646    }
20647
20648    #[expect(
20649      clippy::manual_noop_waker,
20650      reason = "the test observes the waker payload's Drop"
20651    )]
20652    impl std::task::Wake for PanicOnDropWake {
20653      fn wake(self: Arc<Self>) {}
20654    }
20655
20656    impl Drop for PanicOnDropWake {
20657      fn drop(&mut self) {
20658        self
20659          .dropped
20660          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20661          .unwrap();
20662        panic!("intentional replaced dependency waiter destructor panic");
20663      }
20664    }
20665
20666    let dependency = TaskDependency::new(GENERATION, None);
20667    let (dropped_tx, dropped_rx) = mpsc::channel();
20668    let waiter = Waker::from(Arc::new(PanicOnDropWake {
20669      dropped: dropped_tx,
20670    }));
20671    dependency.register_waiter(&waiter);
20672    drop(waiter);
20673
20674    let replacement = futures::task::noop_waker();
20675    let result = catch_unwind(AssertUnwindSafe(|| {
20676      dependency.register_waiter(&replacement)
20677    }));
20678    assert!(
20679      result.is_ok(),
20680      "replacing a hostile waiter must not unwind registration"
20681    );
20682    assert_eq!(
20683      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20684      Some(GENERATION),
20685      "the replaced waiter must be destroyed under its runtime generation"
20686    );
20687  }
20688
20689  #[cfg(not(target_family = "wasm"))]
20690  #[test]
20691  fn task_dependency_retirement_contains_stored_waiter_drop() {
20692    use std::sync::mpsc;
20693
20694    const GENERATION: u64 = u64::MAX - 21;
20695
20696    struct PanicOnDropWake {
20697      dropped: mpsc::Sender<Option<u64>>,
20698    }
20699
20700    #[expect(
20701      clippy::manual_noop_waker,
20702      reason = "the test observes the waker payload's Drop"
20703    )]
20704    impl std::task::Wake for PanicOnDropWake {
20705      fn wake(self: Arc<Self>) {}
20706    }
20707
20708    impl Drop for PanicOnDropWake {
20709      fn drop(&mut self) {
20710        self
20711          .dropped
20712          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20713          .unwrap();
20714        panic!("intentional stored dependency waiter destructor panic");
20715      }
20716    }
20717
20718    let dependency = TaskDependency::new(GENERATION, None);
20719    let (dropped_tx, dropped_rx) = mpsc::channel();
20720    let waiter = Waker::from(Arc::new(PanicOnDropWake {
20721      dropped: dropped_tx,
20722    }));
20723    dependency.register_waiter(&waiter);
20724    drop(waiter);
20725
20726    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
20727    assert!(
20728      result.is_ok(),
20729      "retiring a hostile stored waiter must not unwind"
20730    );
20731    assert_eq!(
20732      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20733      Some(GENERATION),
20734      "the retained waiter must be destroyed under its runtime generation"
20735    );
20736  }
20737
20738  #[cfg(not(target_family = "wasm"))]
20739  #[test]
20740  fn detaching_task_clears_dependency_waiter_parent_retention() {
20741    struct RetainingWake {
20742      retained_parent: Arc<()>,
20743    }
20744
20745    impl std::task::Wake for RetainingWake {
20746      fn wake(self: Arc<Self>) {
20747        let _ = &self.retained_parent;
20748      }
20749    }
20750
20751    let dependency = Arc::new(TaskDependency::default());
20752    let retained_parent = Arc::new(());
20753    let retained_parent_weak = Arc::downgrade(&retained_parent);
20754    let waiter = Waker::from(Arc::new(RetainingWake {
20755      retained_parent: Arc::clone(&retained_parent),
20756    }));
20757    dependency.register_waiter(&waiter);
20758    drop(waiter);
20759    drop(retained_parent);
20760    assert!(
20761      retained_parent_weak.upgrade().is_some(),
20762      "the stored dependency waiter must retain its parent before detachment"
20763    );
20764
20765    let (runnable, task) = async_task::spawn(
20766      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20767      |_| {},
20768    );
20769    drop(JoinHandle(JoinHandleInner::Task {
20770      task: task.fallible(),
20771      awaiter: None,
20772      dependency: Arc::clone(&dependency),
20773    }));
20774    assert!(
20775      retained_parent_weak.upgrade().is_none(),
20776      "detachment must clear the dependency waiter instead of retaining its parent task"
20777    );
20778    drop(runnable);
20779  }
20780
20781  #[cfg(not(target_family = "wasm"))]
20782  #[test]
20783  fn detaching_task_clears_many_parent_publications_linearly() {
20784    const PUBLICATION_COUNT: usize = 1024;
20785
20786    struct CountWake(Arc<AtomicUsize>);
20787
20788    impl std::task::Wake for CountWake {
20789      fn wake(self: Arc<Self>) {
20790        self.0.fetch_add(1, Ordering::SeqCst);
20791      }
20792    }
20793
20794    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
20795      dependency
20796        .state
20797        .lock()
20798        .unwrap_or_else(std::sync::PoisonError::into_inner)
20799        .current
20800        .back()
20801        .expect("the sentinel publication must be appended")
20802        .publication
20803        .clone()
20804    }
20805
20806    let owner = BlockingOwnerToken {
20807      executor_id: u64::MAX - 300,
20808      frame: u64::MAX - 301,
20809    };
20810    let other_owner = BlockingOwnerToken {
20811      executor_id: u64::MAX - 302,
20812      frame: u64::MAX - 303,
20813    };
20814    let child = Arc::new(TaskDependency::default());
20815    for index in 0..PUBLICATION_COUNT {
20816      child.set_owned(BlockingDependency {
20817        job: BlockingJobId(1_000_000 + index as u64),
20818        owner: (index % 2 == 0).then_some(owner),
20819      });
20820    }
20821    let child_publications = child.snapshot_all_live();
20822    let source = child_publications[0].clone();
20823
20824    let parent = Arc::new(TaskDependency::default());
20825    let _context = TaskDependencyGuard::enter(&parent);
20826    let (runnable, task) = async_task::spawn(
20827      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20828      |_| {},
20829    );
20830    let mut handle = JoinHandle(JoinHandleInner::Task {
20831      task: task.fallible(),
20832      awaiter: None,
20833      dependency: Arc::clone(&child),
20834    });
20835    let waker = futures::task::noop_waker();
20836    let mut cx = Context::from_waker(&waker);
20837    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20838    assert_eq!(
20839      parent.get_dependencies().len(),
20840      PUBLICATION_COUNT,
20841      "polling the child handle must propagate every current publication"
20842    );
20843
20844    let mut wrong_claim_source = source.clone();
20845    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
20846    parent.set_borrowed(&wrong_claim_source);
20847    let wrong_claim_publication = latest_publication(&parent);
20848
20849    let mut wrong_job_source = source.clone();
20850    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
20851    parent.set_borrowed(&wrong_job_source);
20852    let wrong_job_publication = latest_publication(&parent);
20853
20854    let wrong_immediate_source = source.derived(None);
20855    parent.set_borrowed(&wrong_immediate_source);
20856    let wrong_immediate_publication = latest_publication(&parent);
20857
20858    let wakes = Arc::new(AtomicUsize::new(0));
20859    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
20860    parent.reset_entry_visits();
20861    drop(handle);
20862
20863    assert_eq!(
20864      wakes.load(Ordering::SeqCst),
20865      1,
20866      "one batched parent cleanup must emit one waiter notification"
20867    );
20868    assert!(
20869      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
20870      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
20871      parent.entry_visits()
20872    );
20873    assert!(
20874      wrong_claim_publication.is_live(),
20875      "claim identity must remain part of the match"
20876    );
20877    assert!(
20878      wrong_job_publication.is_live(),
20879      "job identity must remain part of the match"
20880    );
20881    assert!(
20882      wrong_immediate_publication.is_live(),
20883      "only an exact immediate-parent link may be retired"
20884    );
20885
20886    {
20887      let state = parent
20888        .state
20889        .lock()
20890        .unwrap_or_else(std::sync::PoisonError::into_inner);
20891      assert_eq!(state.current.len(), 3);
20892      // The `job_sequences` index is gone -- a blocking handle retires its own
20893      // entry by sequence -- so "the index retains no stale sequence" is
20894      // structurally impossible rather than something to assert. The
20895      // substantive property that assertion protected, that only the three
20896      // NON-matching sentinels survived the clear, is checked directly on the
20897      // authoritative structure.
20898      assert!(
20899        state
20900          .current
20901          .iter()
20902          .all(|entry| entry.publication.is_live()),
20903        "only the three non-matching sentinels may remain, all still live"
20904      );
20905      assert!(state.unowned_sequences.is_empty());
20906      assert_eq!(state.owner_sequences.len(), 1);
20907      assert_eq!(
20908        state.owner_sequences.get(&owner).map(VecDeque::len),
20909        Some(3)
20910      );
20911    }
20912    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
20913    assert!(
20914      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
20915      "cancelled unowned publications must not survive in availability predicates"
20916    );
20917    drop(runnable);
20918  }
20919
20920  #[cfg(not(target_family = "wasm"))]
20921  #[test]
20922  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
20923    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
20924
20925    if std::env::var_os(CHILD_ENV).is_some() {
20926      struct PanicWake;
20927      impl std::task::Wake for PanicWake {
20928        fn wake(self: Arc<Self>) {
20929          panic!("intentional dependency waker panic");
20930        }
20931      }
20932
20933      struct PanicOnDrop;
20934      impl Drop for PanicOnDrop {
20935        fn drop(&mut self) {
20936          panic!("intentional buffered blocking result destructor panic");
20937        }
20938      }
20939
20940      let parent = Arc::new(TaskDependency::default());
20941      let dependency = BlockingDependency {
20942        job: BlockingJobId(u64::MAX - 1),
20943        owner: None,
20944      };
20945      // The handle is built by hand rather than by polling, so hand it the
20946      // publication token a Pending poll would have recorded; detach retires
20947      // the entry by sequence.
20948      let sequence = parent.set_owned(dependency);
20949      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
20950      let _context = TaskDependencyGuard::enter(&parent);
20951      let (sender, receiver) = oneshot::channel();
20952      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
20953        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
20954      ));
20955      drop(JoinHandle(JoinHandleInner::Blocking {
20956        receiver,
20957        dependency,
20958        published: PublishedBlockingDependencies {
20959          first: Some(PublishedBlockingDependency {
20960            state: Arc::downgrade(&parent),
20961            sequence,
20962          }),
20963          rest: Vec::new(),
20964        },
20965      }));
20966      assert_eq!(parent.get_dependency(), None);
20967      return;
20968    }
20969
20970    let output = std::process::Command::new(std::env::current_exe().unwrap())
20971      .arg("--exact")
20972      .arg(
20973        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
20974      )
20975      .arg("--nocapture")
20976      .env(CHILD_ENV, "1")
20977      .output()
20978      .expect("the subprocess test must start");
20979    assert!(
20980      output.status.success(),
20981      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
20982      output.status.code(),
20983      String::from_utf8_lossy(&output.stdout),
20984      String::from_utf8_lossy(&output.stderr)
20985    );
20986  }
20987
20988  #[cfg(not(target_family = "wasm"))]
20989  #[test]
20990  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
20991    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
20992    // that does NOT own a counted blocking slot and re-enters `block_on`
20993    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
20994    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
20995    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
20996    // driver could use the over-cap fallback, the inner jobs would run while
20997    // both holders still occupy slots and push peak active blocking above 2.
20998    // The cap must be honored AND everything must still complete.
20999    use std::sync::{Barrier, mpsc};
21000    use std::time::Duration;
21001
21002    let (done_tx, done_rx) = mpsc::channel();
21003    let peak_out = Arc::new(Mutex::new(None));
21004    let peak_slot = Arc::clone(&peak_out);
21005    let runner = std::thread::spawn(move || {
21006      let metrics = Arc::new(RuntimeMetrics::default());
21007      let options = RuntimeOptions {
21008        flavor: RuntimeFlavor::MultiThread,
21009        worker_threads: 4,
21010        max_blocking_tasks: 2,
21011        thread_name_prefix: "rd1-cap".to_string(),
21012        park_deadline: None,
21013        drain_linger: DEFAULT_DRAIN_LINGER,
21014      };
21015      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21016
21017      let holder_count = 2usize; // saturates max_blocking_tasks
21018      let driver_count = 2usize;
21019
21020      // Holders signal once they hold a counted slot, then wait to be released.
21021      let (held_tx, held_rx) = mpsc::channel();
21022      let release = Arc::new(Barrier::new(holder_count + 1));
21023
21024      let mut holder_handles = Vec::new();
21025      for _ in 0..holder_count {
21026        let held_tx = held_tx.clone();
21027        let release = Arc::clone(&release);
21028        let handle = executor.schedule_blocking(move || {
21029          held_tx.send(()).unwrap();
21030          release.wait();
21031          0usize
21032        });
21033        holder_handles.push(handle);
21034      }
21035      // Wait until both holders hold their slots (cap fully saturated).
21036      for _ in 0..holder_count {
21037        held_rx.recv().unwrap();
21038      }
21039
21040      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
21041      // an inner spawn_blocking.
21042      let mut driver_tasks = Vec::new();
21043      for _ in 0..driver_count {
21044        let exec = Arc::clone(&executor);
21045        let body = async move {
21046          let inner_exec = Arc::clone(&exec);
21047          let mut inner = std::pin::pin!(async move {
21048            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21049            assert_eq!(v, 5);
21050          });
21051          exec.block_on(inner.as_mut());
21052        };
21053        let scheduler = Arc::clone(&executor);
21054        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
21055        executor.schedule(runnable);
21056        driver_tasks.push(task);
21057      }
21058
21059      // Let the driver tasks reach block_on. They must park until a holder
21060      // frees a slot instead of running their inner jobs over the cap.
21061      std::thread::sleep(Duration::from_millis(300));
21062
21063      // Release the holders so the inner jobs can run within the cap.
21064      release.wait();
21065
21066      for task in driver_tasks {
21067        futures::executor::block_on(task);
21068      }
21069      for handle in holder_handles {
21070        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21071      }
21072
21073      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21074      let _ = done_tx.send(());
21075    });
21076
21077    match done_rx.recv_timeout(Duration::from_secs(10)) {
21078      Ok(()) => runner.join().unwrap(),
21079      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
21080    }
21081    let peak = peak_out.lock().unwrap().expect("peak captured");
21082    assert!(
21083      peak <= 2,
21084      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21085    );
21086  }
21087
21088  #[cfg(not(target_family = "wasm"))]
21089  #[test]
21090  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
21091    // Blocking-owner isolation invariant: a counted blocking OWNER that
21092    // re-enters cooperative `block_on` can drive a plain queued runnable via
21093    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
21094    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
21095    // the cap is saturated, the inner job must wait for a slot instead of
21096    // borrowing the owner's token.
21097    //
21098    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
21099    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
21100    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
21101    // drainers, no replacement drainer can be spawned, so O itself runs R via
21102    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
21103    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
21104    // runs within the cap and the peak remains 2. Everything must still
21105    // complete.
21106    use std::sync::{Barrier, mpsc};
21107    use std::time::Duration;
21108
21109    let (done_tx, done_rx) = mpsc::channel();
21110    let peak_out = Arc::new(Mutex::new(None));
21111    let peak_slot = Arc::clone(&peak_out);
21112    let runner = std::thread::spawn(move || {
21113      let metrics = Arc::new(RuntimeMetrics::default());
21114      let options = RuntimeOptions {
21115        flavor: RuntimeFlavor::MultiThread,
21116        worker_threads: 2,
21117        max_blocking_tasks: 2,
21118        thread_name_prefix: "rd1-owner-runnable".to_string(),
21119        park_deadline: None,
21120        drain_linger: DEFAULT_DRAIN_LINGER,
21121      };
21122      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21123
21124      let saturated = Arc::new(Barrier::new(2));
21125      let release = Arc::new(Barrier::new(2));
21126
21127      // Holder H2: holds the second counted slot until released. Not the driver.
21128      let h2_saturated = Arc::clone(&saturated);
21129      let h2_release = Arc::clone(&release);
21130      let h2 = executor.schedule_blocking(move || {
21131        h2_saturated.wait();
21132        h2_release.wait();
21133        0usize
21134      });
21135
21136      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
21137      // spawns plain runnable R and drives it cooperatively via `block_on`.
21138      let o_exec = Arc::clone(&executor);
21139      let o_saturated = Arc::clone(&saturated);
21140      let o = executor.schedule_blocking(move || {
21141        o_saturated.wait(); // both slots held -> cap saturated
21142
21143        let r_exec = Arc::clone(&o_exec);
21144        let body = async move {
21145          let inner_exec = Arc::clone(&r_exec);
21146          let mut inner = std::pin::pin!(async move {
21147            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21148            assert_eq!(v, 5);
21149          });
21150          r_exec.block_on(inner.as_mut());
21151        };
21152        let sched = Arc::clone(&o_exec);
21153        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21154        o_exec.schedule(runnable);
21155
21156        let mut wait_r = std::pin::pin!(async move {
21157          task.await;
21158        });
21159        o_exec.block_on(wait_r.as_mut());
21160        1usize
21161      });
21162
21163      // Give O time to drive R into its inner `block_on`; R must be parked
21164      // waiting for a slot.
21165      std::thread::sleep(Duration::from_millis(300));
21166      // Release H2 so the inner job can run within the cap.
21167      release.wait();
21168
21169      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21170      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
21171
21172      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21173      let _ = done_tx.send(());
21174    });
21175
21176    match done_rx.recv_timeout(Duration::from_secs(10)) {
21177      Ok(()) => runner.join().unwrap(),
21178      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
21179    }
21180    let peak = peak_out.lock().unwrap().expect("peak captured");
21181    assert!(
21182      peak <= 2,
21183      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21184    );
21185  }
21186
21187  #[cfg(not(target_family = "wasm"))]
21188  #[test]
21189  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
21190    // The over-cap fallback must select the exact pending JoinHandle job, not
21191    // the front of the shared blocking queue.
21192    //
21193    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
21194    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
21195    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
21196    // The exact dependency must skip U and run D. Because U can only run once
21197    // a real slot frees after O completes, D must STRICTLY precede U.
21198    use std::sync::{Barrier, mpsc};
21199
21200    let (done_tx, done_rx) = mpsc::channel();
21201    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21202    let order_read = Arc::clone(&order_out);
21203    let runner = std::thread::spawn(move || {
21204      let metrics = Arc::new(RuntimeMetrics::default());
21205      let options = RuntimeOptions {
21206        flavor: RuntimeFlavor::MultiThread,
21207        worker_threads: 2,
21208        max_blocking_tasks: 2,
21209        thread_name_prefix: "rd1-unrelated".to_string(),
21210        park_deadline: None,
21211        drain_linger: DEFAULT_DRAIN_LINGER,
21212      };
21213      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21214
21215      // O and H signal once they each hold a counted slot (cap saturated), then
21216      // wait so U is queued before O nests. H stays held until the very end.
21217      let (held_tx, held_rx) = mpsc::channel();
21218      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
21219      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
21220
21221      // Holder H: holds slot 2 for the whole test.
21222      let h_held = held_tx.clone();
21223      let h_rel = Arc::clone(&h_release);
21224      let h = executor.schedule_blocking(move || {
21225        h_held.send(()).unwrap();
21226        h_rel.wait();
21227        0usize
21228      });
21229
21230      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
21231      // its OWN descendant D.
21232      let o_exec = Arc::clone(&executor);
21233      let o_ready = Arc::clone(&u_ready);
21234      let o_order = Arc::clone(&order_out);
21235      let o = executor.schedule_blocking(move || {
21236        held_tx.send(()).unwrap();
21237        o_ready.wait(); // wait until U is queued ahead of D
21238        let inner_exec = Arc::clone(&o_exec);
21239        let d_order = Arc::clone(&o_order);
21240        let mut inner = std::pin::pin!(async move {
21241          let v = inner_exec
21242            .schedule_blocking(move || {
21243              d_order.lock().unwrap().push("D");
21244              5usize
21245            })
21246            .await
21247            .unwrap();
21248          assert_eq!(v, 5);
21249        });
21250        o_exec.block_on(inner.as_mut());
21251        1usize
21252      });
21253
21254      // Wait until BOTH O and H hold their slots -> cap saturated.
21255      held_rx.recv().unwrap();
21256      held_rx.recv().unwrap();
21257
21258      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
21259      let u_order = Arc::clone(&order_out);
21260      let u = executor.schedule_blocking(move || {
21261        u_order.lock().unwrap().push("U");
21262        2usize
21263      });
21264
21265      // Release O to nest now that U is queued ahead of D.
21266      u_ready.wait();
21267
21268      // O makes progress by running D over cap; U only runs after O frees a slot.
21269      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21270      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
21271
21272      // Release H and finish.
21273      h_release.wait();
21274      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
21275
21276      let _ = done_tx.send(());
21277    });
21278
21279    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
21280      Ok(()) => runner.join().unwrap(),
21281      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
21282    }
21283    let order = order_read.lock().unwrap().clone();
21284    assert_eq!(
21285      order,
21286      vec!["D", "U"],
21287      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
21288    );
21289  }
21290
21291  #[cfg(not(target_family = "wasm"))]
21292  #[test]
21293  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
21294    use std::sync::{Barrier, mpsc};
21295
21296    let opts = RuntimeOptions {
21297      flavor: RuntimeFlavor::MultiThread,
21298      worker_threads: 2,
21299      max_blocking_tasks: 2,
21300      thread_name_prefix: "rd1-scope".to_string(),
21301      park_deadline: None,
21302      drain_linger: DEFAULT_DRAIN_LINGER,
21303    };
21304    let exec1 =
21305      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21306    let exec2 =
21307      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21308    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21309
21310    let holder_count = opts.max_blocking_tasks;
21311    let (held_tx, held_rx) = mpsc::channel();
21312    let release = Arc::new(Barrier::new(holder_count + 1));
21313    let mut holder_handles = Vec::new();
21314    for _ in 0..holder_count {
21315      let held_tx = held_tx.clone();
21316      let release = Arc::clone(&release);
21317      holder_handles.push(exec2.schedule_blocking(move || {
21318        held_tx.send(()).unwrap();
21319        release.wait();
21320        0usize
21321      }));
21322    }
21323    for _ in 0..holder_count {
21324      held_rx.recv().unwrap();
21325    }
21326    assert_eq!(
21327      exec2.active_blocking.load(Ordering::Acquire),
21328      opts.max_blocking_tasks
21329    );
21330
21331    let exact = BlockingJobId(u64::MAX);
21332    let ran = Arc::new(AtomicBool::new(false));
21333    let ran_job = Arc::clone(&ran);
21334    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
21335      id: exact,
21336      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21337    });
21338    let dependency = TaskDependency::default();
21339
21340    let token1 = exec1.fresh_owner_token();
21341    {
21342      let _owner = BlockingOwnerGuard::enter(Some(token1));
21343      dependency.set_owned(BlockingDependency {
21344        job: exact,
21345        owner: Some(token1),
21346      });
21347      assert!(
21348        !exec2.try_owned_blocking_over_cap(&dependency),
21349        "a foreign-executor ambient token must fail the executor gate"
21350      );
21351    }
21352
21353    let token2 = exec2.fresh_owner_token();
21354    {
21355      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
21356      let _owner = BlockingOwnerGuard::enter(Some(token2));
21357      dependency.set_owned(BlockingDependency {
21358        job: BlockingJobId(u64::MAX - 1),
21359        owner: Some(token2),
21360      });
21361      assert!(
21362        !exec2.try_owned_blocking_over_cap(&dependency),
21363        "a foreign or unrelated dependency id must fail the exact-job gate"
21364      );
21365      dependency.set_owned(BlockingDependency {
21366        job: BlockingJobId(exact.0 - 2),
21367        owner: Some(token2),
21368      });
21369      assert!(
21370        !exec2.try_owned_blocking_over_cap(&dependency),
21371        "a different job id must not consume the queued dependency"
21372      );
21373      dependency.set_owned(BlockingDependency {
21374        job: exact,
21375        owner: Some(token2),
21376      });
21377      assert!(
21378        exec2.try_owned_blocking_over_cap(&dependency),
21379        "the exact awaited job must run over cap"
21380      );
21381    }
21382    assert!(
21383      ran.load(Ordering::SeqCst),
21384      "the exact dependency must have run"
21385    );
21386
21387    release.wait();
21388    for handle in holder_handles {
21389      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21390    }
21391  }
21392
21393  #[cfg(not(target_family = "wasm"))]
21394  #[test]
21395  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
21396    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
21397    let first_owner = executor.fresh_owner_token();
21398    let second_owner = executor.fresh_owner_token();
21399    let _first_owner =
21400      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
21401    let _second_owner =
21402      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
21403    let exact = BlockingJobId(u64::MAX - 30);
21404    let dependency = TaskDependency::default();
21405    dependency.set_owned(BlockingDependency {
21406      job: exact,
21407      owner: None,
21408    });
21409    let ran = Arc::new(AtomicBool::new(false));
21410    let ran_job = Arc::clone(&ran);
21411    executor
21412      .blocking_queue
21413      .lock()
21414      .unwrap()
21415      .push(QueuedBlocking {
21416        id: exact,
21417        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21418      });
21419
21420    assert!(
21421      !executor.try_owned_blocking_over_cap(&dependency),
21422      "an untagged driver must not choose between multiple active owner frames"
21423    );
21424    assert!(!ran.load(Ordering::SeqCst));
21425    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
21426    assert!(
21427      queued.is_some(),
21428      "ambiguous ownership must leave the exact job queued"
21429    );
21430  }
21431
21432  #[cfg(not(target_family = "wasm"))]
21433  #[test]
21434  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
21435    let executor = multi_thread_executor(2, None, "live-parked-dependency");
21436    let owner = executor.fresh_owner_token();
21437    let dependency = TaskDependency::new(u64::MAX, Some(owner));
21438    let stale = BlockingJobId(u64::MAX - 10);
21439    let live = BlockingJobId(u64::MAX - 9);
21440    dependency.set_owned(BlockingDependency {
21441      job: stale,
21442      owner: Some(owner),
21443    });
21444    dependency.begin_poll();
21445    dependency.set_owned(BlockingDependency {
21446      job: live,
21447      owner: Some(owner),
21448    });
21449
21450    let stale_ran = Arc::new(AtomicBool::new(false));
21451    let stale_job_ran = Arc::clone(&stale_ran);
21452    let live_ran = Arc::new(AtomicBool::new(false));
21453    let live_job_ran = Arc::clone(&live_ran);
21454    {
21455      let mut queue = executor
21456        .blocking_queue
21457        .lock()
21458        .unwrap_or_else(std::sync::PoisonError::into_inner);
21459      queue.push(QueuedBlocking {
21460        id: stale,
21461        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
21462      });
21463      queue.push(QueuedBlocking {
21464        id: live,
21465        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
21466      });
21467    }
21468
21469    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21470    let _owner = BlockingOwnerGuard::enter(Some(owner));
21471    assert!(
21472      executor.try_owned_blocking_over_cap(&dependency),
21473      "the owner lane must claim the currently published dependency"
21474    );
21475    assert!(
21476      live_ran.load(Ordering::SeqCst),
21477      "the live dependency must run"
21478    );
21479    assert!(
21480      !stale_ran.load(Ordering::SeqCst),
21481      "the stale registered id must remain queued"
21482    );
21483    let remaining = executor
21484      .blocking_queue
21485      .lock()
21486      .unwrap_or_else(std::sync::PoisonError::into_inner)
21487      .jobs
21488      .keys()
21489      .copied()
21490      .collect::<Vec<_>>();
21491    assert_eq!(remaining, vec![stale]);
21492  }
21493
21494  #[cfg(not(target_family = "wasm"))]
21495  #[test]
21496  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
21497    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
21498    let owner = executor.fresh_owner_token();
21499    let child = TaskDependency::new_task(u64::MAX);
21500    let parent = TaskDependency::new(u64::MAX, Some(owner));
21501    let exact = BlockingDependency {
21502      job: BlockingJobId(u64::MAX - 31),
21503      owner: Some(owner),
21504    };
21505
21506    child.set_owned(exact);
21507    let stale = child
21508      .get()
21509      .expect("the child must publish its direct dependency");
21510    child.begin_poll();
21511    parent.set_borrowed(&stale);
21512
21513    let ran = Arc::new(AtomicBool::new(false));
21514    let ran_job = Arc::clone(&ran);
21515    executor
21516      .blocking_queue
21517      .lock()
21518      .unwrap()
21519      .push(QueuedBlocking {
21520        id: exact.job,
21521        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21522      });
21523    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21524    let _owner = BlockingOwnerGuard::enter(Some(owner));
21525    assert!(
21526      !executor.try_owned_blocking_over_cap(&parent),
21527      "a publication copied before child cancellation must remain cancelled after parent install"
21528    );
21529    assert!(!ran.load(Ordering::SeqCst));
21530
21531    child.set_owned(exact);
21532    parent.set_borrowed(
21533      &child
21534        .get()
21535        .expect("the replacement publication must be live"),
21536    );
21537    assert!(
21538      executor.try_owned_blocking_over_cap(&parent),
21539      "a fresh child publication must still authorize its exact queued job"
21540    );
21541    assert!(ran.load(Ordering::SeqCst));
21542  }
21543
21544  #[cfg(not(target_family = "wasm"))]
21545  #[test]
21546  fn intermediate_repoll_does_not_cancel_child_source_claim() {
21547    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
21548    let owner = executor.fresh_owner_token();
21549    let child = TaskDependency::new_task(u64::MAX);
21550    let intermediate = TaskDependency::new_task(u64::MAX);
21551    let parent = TaskDependency::new(u64::MAX, Some(owner));
21552    let exact = BlockingDependency {
21553      job: BlockingJobId(u64::MAX - 43),
21554      owner: Some(owner),
21555    };
21556
21557    child.set_owned(exact);
21558    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
21559    parent.set_borrowed(
21560      &intermediate
21561        .get()
21562        .expect("the intermediate publication must be live"),
21563    );
21564
21565    intermediate.begin_poll();
21566    assert!(
21567      child.get().is_some(),
21568      "withdrawing an intermediate hop must not cancel the child's source claim"
21569    );
21570    assert!(
21571      parent.get().is_none(),
21572      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
21573    );
21574
21575    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
21576    parent.set_borrowed(
21577      &intermediate
21578        .get()
21579        .expect("the intermediate must republish the still-live source"),
21580    );
21581    let ran = Arc::new(AtomicBool::new(false));
21582    let ran_job = Arc::clone(&ran);
21583    executor
21584      .blocking_queue
21585      .lock()
21586      .unwrap()
21587      .push(QueuedBlocking {
21588        id: exact.job,
21589        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21590      });
21591
21592    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21593    let _owner = BlockingOwnerGuard::enter(Some(owner));
21594    assert!(
21595      executor.try_owned_blocking_over_cap(&parent),
21596      "a fresh chain through the repolled intermediate must retain the exact source claim"
21597    );
21598    assert!(ran.load(Ordering::SeqCst));
21599  }
21600
21601  #[cfg(not(target_family = "wasm"))]
21602  #[test]
21603  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
21604    use std::sync::mpsc;
21605
21606    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
21607    let owner = executor.fresh_owner_token();
21608    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21609    let child = Arc::new(TaskDependency::new_task(u64::MAX));
21610    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21611    let exact = BlockingDependency {
21612      job: BlockingJobId(u64::MAX - 44),
21613      owner: Some(owner),
21614    };
21615    child.set_owned(exact);
21616    parent.set_borrowed(&child.get().expect("the child source must be live"));
21617
21618    let ran = Arc::new(AtomicBool::new(false));
21619    let ran_job = Arc::clone(&ran);
21620    executor
21621      .blocking_queue
21622      .lock()
21623      .unwrap()
21624      .push(QueuedBlocking {
21625        id: exact.job,
21626        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21627      });
21628
21629    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
21630    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21631    let claimant_executor = Arc::clone(&executor);
21632    let claimant_parent = Arc::clone(&parent);
21633    let claimant = std::thread::spawn(move || {
21634      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
21635        *slot.borrow_mut() = Some(Box::new(move || {
21636          claim_entered_tx.send(()).unwrap();
21637          release_claim_rx.recv().unwrap();
21638        }));
21639      });
21640      let _owner = BlockingOwnerGuard::enter(Some(owner));
21641      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
21642    });
21643    claim_entered_rx
21644      .recv_timeout(Duration::from_secs(2))
21645      .expect("the exact claimant must hold the shared transition lock");
21646
21647    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
21648    let withdrawing_child = Arc::clone(&child);
21649    let withdrawal = std::thread::spawn(move || {
21650      withdrawing_child.begin_poll();
21651      withdrawn_tx.send(()).unwrap();
21652    });
21653    let withdrew_before_claim = withdrawn_rx
21654      .recv_timeout(Duration::from_millis(200))
21655      .is_ok();
21656    release_claim_tx.send(()).unwrap();
21657
21658    assert!(
21659      claimant.join().unwrap(),
21660      "the claim that linearized first must execute the exact job"
21661    );
21662    withdrawal.join().unwrap();
21663    assert!(
21664      !withdrew_before_claim,
21665      "source withdrawal must not interleave after claim validation but before claim commit"
21666    );
21667    assert!(ran.load(Ordering::SeqCst));
21668  }
21669
21670  #[cfg(not(target_family = "wasm"))]
21671  #[test]
21672  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
21673    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
21674    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
21675    // executor whose worker set the marker. A worker of executor A that re-enters
21676    // `block_on` on executor B must take the PARKING branch instead -- it owns
21677    // none of B's accounting, so driving B's queue would make it an unaccounted
21678    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
21679    //
21680    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
21681    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
21682    // `block_on` on each executor with the SAME probe shape and observe whether
21683    // the cooperative `run_one` driver actually ran a queued runnable.
21684    //
21685    // The probe future returns `Pending` once (self-waking so the parking branch
21686    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
21687    // `run_one` at least once before the future completes. A single runnable is
21688    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
21689    // no background drainer is ever spawned to steal it). After `block_on`
21690    // returns: the cooperative branch will have driven that runnable
21691    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
21692    // (`runnable_polls == 0`, still queued).
21693    struct PendThenReady {
21694      polls: usize,
21695    }
21696    impl Future for PendThenReady {
21697      type Output = ();
21698      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21699        self.polls += 1;
21700        if self.polls >= 2 {
21701          Poll::Ready(())
21702        } else {
21703          // Self-wake so the parking branch (`futures::executor::block_on`)
21704          // re-polls to completion without any external waker; the cooperative
21705          // branch re-polls unconditionally each loop iteration regardless.
21706          cx.waker().wake_by_ref();
21707          Poll::Pending
21708        }
21709      }
21710    }
21711
21712    // Push one already-resolvable runnable directly onto `target`'s queue (no
21713    // `schedule`, so no drainer is spawned) and detach its task.
21714    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
21715      let scheduler = Arc::clone(target);
21716      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
21717      target.push_fifo_raw(runnable);
21718      task.detach();
21719    }
21720
21721    let opts = RuntimeOptions {
21722      flavor: RuntimeFlavor::MultiThread,
21723      worker_threads: 2,
21724      max_blocking_tasks: 2,
21725      thread_name_prefix: "rd-onpool-scope".to_string(),
21726      park_deadline: None,
21727      drain_linger: DEFAULT_DRAIN_LINGER,
21728    };
21729    let exec1 =
21730      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21731    let exec2 =
21732      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21733    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21734
21735    // Simulate being on exec1's pool worker for the whole scenario.
21736    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
21737
21738    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
21739    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
21740    // touch its queue.
21741    enqueue_runnable(&exec2);
21742    {
21743      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21744      exec2.block_on(probe.as_mut());
21745    }
21746    assert_eq!(
21747      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
21748      0,
21749      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
21750    );
21751    assert_eq!(
21752      exec2.injector.len(),
21753      1,
21754      "the foreign call must leave exec2's runnable untouched on its queue"
21755    );
21756
21757    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
21758    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
21759    // runnable via `run_one`.
21760    enqueue_runnable(&exec1);
21761    {
21762      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21763      exec1.block_on(probe.as_mut());
21764    }
21765    assert_eq!(
21766      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
21767      1,
21768      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
21769    );
21770    assert!(
21771      exec1.injector.is_empty(),
21772      "the cooperative branch must have drained exec1's queued runnable"
21773    );
21774
21775    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
21776    // (steal-drain replaces the former `queue.lock().clear()`).
21777    while exec2.steal_one().is_some() {}
21778  }
21779
21780  #[cfg(not(target_family = "wasm"))]
21781  #[test]
21782  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
21783    // A long-lived blocking consumer may consume the entire configured
21784    // blocking allowance, but it must not consume the reserve lane required
21785    // by the async producers that feed it.
21786    use std::sync::mpsc;
21787    use std::time::Duration;
21788
21789    const PRODUCER_COUNT: usize = 8;
21790
21791    fn schedule_pool_workload(
21792      executor: &Arc<MultiThreadExecutor>,
21793      tx: &mpsc::Sender<Option<usize>>,
21794      count: usize,
21795    ) {
21796      for i in 0..count {
21797        let tx = tx.clone();
21798        let scheduler = Arc::clone(executor);
21799        let (runnable, task) =
21800          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
21801            scheduler.schedule(r);
21802          });
21803        executor.schedule(runnable);
21804        task.detach();
21805      }
21806      let tx = tx.clone();
21807      let scheduler = Arc::clone(executor);
21808      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
21809        scheduler.schedule(r)
21810      });
21811      executor.schedule(runnable);
21812      task.detach();
21813    }
21814
21815    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
21816    let (done_tx, done_rx) = mpsc::sync_channel(1);
21817    let runner = std::thread::spawn(move || {
21818      let options = RuntimeOptions {
21819        flavor: RuntimeFlavor::MultiThread,
21820        worker_threads: 1,
21821        max_blocking_tasks: 1,
21822        thread_name_prefix: "rd2-pre-fix".to_string(),
21823        park_deadline: None,
21824        drain_linger: DEFAULT_DRAIN_LINGER,
21825      }
21826      .validate()
21827      .expect("production MultiThread options must provide a reserve lane");
21828      let executor =
21829        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21830
21831      let (tx, rx) = mpsc::channel::<Option<usize>>();
21832      let (started_tx, started_rx) = mpsc::sync_channel(1);
21833      let handle = executor.schedule_blocking(move || {
21834        started_tx.send(()).unwrap();
21835        let mut sum = 0usize;
21836        while let Ok(msg) = rx.recv() {
21837          match msg {
21838            Some(value) => sum += value,
21839            None => break,
21840          }
21841        }
21842        sum
21843      });
21844      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21845      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
21846      let sum = futures::executor::block_on(handle).unwrap();
21847      done_tx.send(sum).unwrap();
21848    });
21849
21850    match done_rx.recv_timeout(Duration::from_secs(10)) {
21851      Ok(sum) => {
21852        runner.join().unwrap();
21853        assert_eq!(sum, expected_sum);
21854      }
21855      Err(error) => {
21856        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
21857      }
21858    }
21859  }
21860
21861  #[test]
21862  fn current_thread_blocking_work_completes_inline() {
21863    let metrics = Arc::new(RuntimeMetrics::default());
21864    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
21865      CurrentThreadExecutor::new(Arc::clone(&metrics)),
21866    )));
21867    let registration = backend.work.try_register_work().unwrap();
21868
21869    let value =
21870      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
21871        .expect("blocking task should complete");
21872
21873    assert_eq!(value, 7);
21874    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
21875    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
21876  }
21877
21878  #[cfg(not(target_family = "wasm"))]
21879  #[test]
21880  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
21881    use std::sync::mpsc;
21882
21883    let controller = Arc::new(current_thread_controller(
21884      "current-thread-blocking-admission",
21885    ));
21886    let backend = controller.backend();
21887    let (first_started_tx, first_started_rx) = mpsc::channel();
21888    let (release_first_tx, release_first_rx) = mpsc::channel();
21889    let first_controller = Arc::clone(&controller);
21890    let first = std::thread::spawn(move || {
21891      let handle = first_controller
21892        .try_spawn_blocking(move || {
21893          first_started_tx.send(()).unwrap();
21894          release_first_rx.recv().unwrap();
21895          1usize
21896        })
21897        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
21898      futures::executor::block_on(handle).unwrap()
21899    });
21900    first_started_rx
21901      .recv_timeout(Duration::from_secs(1))
21902      .unwrap();
21903
21904    let (second_started_tx, second_started_rx) = mpsc::channel();
21905    let second_controller = Arc::clone(&controller);
21906    let second = std::thread::spawn(move || {
21907      let handle = second_controller
21908        .try_spawn_blocking(move || {
21909          second_started_tx.send(()).unwrap();
21910          2usize
21911        })
21912        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
21913      futures::executor::block_on(handle).unwrap()
21914    });
21915
21916    wait_until("both CurrentThread blocking calls to be accepted", || {
21917      backend
21918        .work
21919        .state
21920        .lock()
21921        .unwrap_or_else(std::sync::PoisonError::into_inner)
21922        .active
21923        == 2
21924    });
21925    assert!(
21926      second_started_rx
21927        .recv_timeout(Duration::from_millis(100))
21928        .is_err(),
21929      "CurrentThread must not execute two blocking closures concurrently"
21930    );
21931
21932    release_first_tx.send(()).unwrap();
21933    second_started_rx
21934      .recv_timeout(Duration::from_secs(1))
21935      .expect("the waiting blocking call must acquire the released lane");
21936    assert_eq!(first.join().unwrap(), 1);
21937    assert_eq!(second.join().unwrap(), 2);
21938    assert_eq!(
21939      controller
21940        .metrics
21941        .blocking_tasks_started
21942        .load(Ordering::Relaxed),
21943      2
21944    );
21945    assert_eq!(
21946      controller
21947        .metrics
21948        .blocking_tasks_completed
21949        .load(Ordering::Relaxed),
21950      2
21951    );
21952    assert_eq!(
21953      controller
21954        .metrics
21955        .active_blocking_tasks
21956        .load(Ordering::Relaxed),
21957      0
21958    );
21959    assert_eq!(
21960      controller
21961        .metrics
21962        .max_active_blocking_tasks
21963        .load(Ordering::Relaxed),
21964      1,
21965      "the public active-lane metric must remain within CurrentThread's reported capacity"
21966    );
21967    controller.shutdown().unwrap();
21968  }
21969
21970  #[cfg(not(target_family = "wasm"))]
21971  #[test]
21972  fn current_thread_nested_blocking_reuses_the_active_lane() {
21973    use std::sync::mpsc;
21974
21975    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
21976    let (done_tx, done_rx) = mpsc::channel();
21977    let runner_controller = Arc::clone(&controller);
21978    let runner = std::thread::spawn(move || {
21979      let nested_controller = Arc::clone(&runner_controller);
21980      let outer = runner_controller
21981        .try_spawn_blocking(move || {
21982          let inner = nested_controller
21983            .try_spawn_blocking(|| 41usize)
21984            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
21985          futures::executor::block_on(inner).unwrap() + 1
21986        })
21987        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
21988      done_tx
21989        .send(futures::executor::block_on(outer).unwrap())
21990        .unwrap();
21991    });
21992
21993    assert_eq!(
21994      done_rx
21995        .recv_timeout(Duration::from_secs(2))
21996        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
21997      42
21998    );
21999    runner.join().unwrap();
22000    assert_eq!(
22001      controller
22002        .metrics
22003        .blocking_tasks_started
22004        .load(Ordering::Relaxed),
22005      2
22006    );
22007    assert_eq!(
22008      controller
22009        .metrics
22010        .blocking_tasks_completed
22011        .load(Ordering::Relaxed),
22012      2
22013    );
22014    assert_eq!(
22015      controller
22016        .metrics
22017        .active_blocking_tasks
22018        .load(Ordering::Relaxed),
22019      0
22020    );
22021    assert_eq!(
22022      controller
22023        .metrics
22024        .max_active_blocking_tasks
22025        .load(Ordering::Relaxed),
22026      1,
22027      "nested work borrows the outer lane instead of reporting a second physical lane"
22028    );
22029    controller.shutdown().unwrap();
22030  }
22031
22032  #[cfg(not(target_family = "wasm"))]
22033  #[test]
22034  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22035    use std::sync::mpsc;
22036
22037    struct HotFuture {
22038      stop: Arc<AtomicBool>,
22039    }
22040
22041    impl Future for HotFuture {
22042      type Output = ();
22043
22044      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22045        if self.stop.load(Ordering::SeqCst) {
22046          Poll::Ready(())
22047        } else {
22048          cx.waker().wake_by_ref();
22049          Poll::Pending
22050        }
22051      }
22052    }
22053
22054    let stop = Arc::new(AtomicBool::new(false));
22055    let (ready_tx, ready_rx) = mpsc::channel();
22056    let (exact_tx, exact_rx) = mpsc::channel();
22057    let (done_tx, done_rx) = mpsc::channel();
22058    let runner_stop = Arc::clone(&stop);
22059    let runner = std::thread::spawn(move || {
22060      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22061        RuntimeMetrics::default(),
22062      )));
22063      let owner = executor.fresh_owner_token();
22064      let job = BlockingJobId(u64::MAX - 70);
22065      let (result_tx, result_rx) = oneshot::channel();
22066      {
22067        let mut state = executor
22068          .blocking_admission
22069          .state
22070          .lock()
22071          .unwrap_or_else(std::sync::PoisonError::into_inner);
22072        state.owner = Some(owner);
22073        let exact_stop = Arc::clone(&runner_stop);
22074        state.queue.push(QueuedBlocking {
22075          id: job,
22076          run: Box::new(move || {
22077            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22078            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
22079          }),
22080        });
22081      }
22082      let exact = JoinHandle(JoinHandleInner::Blocking {
22083        receiver: result_rx,
22084        dependency: BlockingDependency {
22085          job,
22086          owner: Some(owner),
22087        },
22088        published: PublishedBlockingDependencies::default(),
22089      });
22090      let _owner = executor.enter_blocking_owner(owner, false);
22091
22092      let scheduler = Arc::clone(&executor);
22093      let hot_stop = Arc::clone(&runner_stop);
22094      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22095        scheduler.schedule(runnable);
22096      });
22097      executor.schedule(runnable);
22098      ready_tx.send(()).unwrap();
22099
22100      let mut value = None;
22101      {
22102        let mut future = std::pin::pin!(async {
22103          value = Some(exact.await.unwrap());
22104          hot_task.await;
22105        });
22106        assert_eq!(
22107          executor.block_on(future.as_mut()),
22108          BlockOnOutcome::Completed
22109        );
22110      }
22111      executor
22112        .blocking_admission
22113        .state
22114        .lock()
22115        .unwrap_or_else(std::sync::PoisonError::into_inner)
22116        .owner = None;
22117      done_tx.send(value.unwrap()).unwrap();
22118    });
22119
22120    ready_rx
22121      .recv_timeout(Duration::from_secs(2))
22122      .expect("the fairness driver must start");
22123    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22124    stop.store(true, Ordering::SeqCst);
22125    assert_eq!(
22126      done_rx
22127        .recv_timeout(Duration::from_secs(2))
22128        .expect("the CurrentThread driver must retire"),
22129      41
22130    );
22131    runner.join().unwrap();
22132    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22133    assert_eq!(
22134      exact_before_stop,
22135      Some(true),
22136      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
22137    );
22138  }
22139
22140  #[cfg(not(target_family = "wasm"))]
22141  #[test]
22142  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
22143    use std::sync::mpsc;
22144
22145    let controller = Arc::new(current_thread_controller(
22146      "current-thread-cross-driver-blocking",
22147    ));
22148    let backend = controller.backend();
22149    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22150      panic!("configured CurrentThread backend must create a CurrentThread executor");
22151    };
22152    let executor = Arc::clone(executor);
22153    drop(backend);
22154
22155    let (release_driver_tx, release_driver_rx) = oneshot::channel();
22156    let (driver_started_tx, driver_started_rx) = mpsc::channel();
22157    let driver_controller = Arc::clone(&controller);
22158    let driver = std::thread::spawn(move || {
22159      driver_started_tx.send(std::thread::current().id()).unwrap();
22160      block_on_with_controller(&driver_controller, async {
22161        release_driver_rx
22162          .await
22163          .expect("driver release sender must remain alive");
22164      });
22165    });
22166    let driver_thread = driver_started_rx
22167      .recv_timeout(Duration::from_secs(1))
22168      .unwrap();
22169    wait_until("the secondary CurrentThread driver to park", || {
22170      executor
22171        .stop
22172        .parkers
22173        .lock()
22174        .unwrap_or_else(std::sync::PoisonError::into_inner)
22175        .iter()
22176        .filter_map(Weak::upgrade)
22177        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22178    });
22179
22180    let (child_polled_tx, child_polled_rx) = mpsc::channel();
22181    let (release_child_tx, release_child_rx) = mpsc::channel();
22182    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
22183    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
22184    let (owner_done_tx, owner_done_rx) = mpsc::channel();
22185    let owner_controller = Arc::clone(&controller);
22186    let owner = std::thread::spawn(move || {
22187      let owner_thread = std::thread::current().id();
22188      let await_controller = Arc::clone(&owner_controller);
22189      let child_controller = Arc::clone(&owner_controller);
22190      let outer = owner_controller
22191        .try_spawn_blocking(move || {
22192          let spawn_controller = Arc::clone(&child_controller);
22193          let child = spawn_controller
22194            .try_spawn(async move {
22195              child_polled_tx.send(std::thread::current().id()).unwrap();
22196              release_child_rx.recv().unwrap();
22197              child_controller
22198                .try_spawn_blocking(move || {
22199                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
22200                  41usize
22201                })
22202                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
22203                .await
22204                .unwrap()
22205                + 1
22206            })
22207            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
22208
22209          assert_eq!(
22210            child_polled_rx
22211              .recv_timeout(Duration::from_secs(1))
22212              .unwrap(),
22213            driver_thread,
22214            "the secondary driver must own the child poll that reaches blocking admission"
22215          );
22216          owner_awaiting_tx.send(owner_thread).unwrap();
22217          block_on_with_controller(&await_controller, child).unwrap()
22218        })
22219        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22220      owner_done_tx
22221        .send(futures::executor::block_on(outer))
22222        .unwrap();
22223    });
22224
22225    let owner_thread = owner_awaiting_rx
22226      .recv_timeout(Duration::from_secs(1))
22227      .unwrap();
22228    wait_until(
22229      "the blocking owner to park while awaiting the cross-driver child",
22230      || {
22231        executor
22232          .stop
22233          .parkers
22234          .lock()
22235          .unwrap_or_else(std::sync::PoisonError::into_inner)
22236          .iter()
22237          .filter_map(Weak::upgrade)
22238          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22239      },
22240    );
22241    release_child_tx.send(()).unwrap();
22242
22243    assert_eq!(
22244      blocking_ran_rx
22245        .recv_timeout(Duration::from_secs(2))
22246        .unwrap(),
22247      owner_thread,
22248      "the exact descendant must be serviced by the lane-owning driver"
22249    );
22250    assert_eq!(
22251      owner_done_rx
22252        .recv_timeout(Duration::from_secs(2))
22253        .expect("cross-driver blocking dependency must not deadlock")
22254        .unwrap(),
22255      42
22256    );
22257    release_driver_tx.send(()).unwrap();
22258    owner.join().unwrap();
22259    driver.join().unwrap();
22260    controller.shutdown().unwrap();
22261    assert_eq!(
22262      controller
22263        .metrics
22264        .blocking_tasks_started
22265        .load(Ordering::Relaxed),
22266      2
22267    );
22268    assert_eq!(
22269      controller
22270        .metrics
22271        .blocking_tasks_completed
22272        .load(Ordering::Relaxed),
22273      2
22274    );
22275    assert_eq!(
22276      controller
22277        .metrics
22278        .active_blocking_tasks
22279        .load(Ordering::Relaxed),
22280      0
22281    );
22282    assert_eq!(
22283      controller
22284        .metrics
22285        .max_active_blocking_tasks
22286        .load(Ordering::Relaxed),
22287      1,
22288      "cross-driver dependency service must reuse the owner's physical lane"
22289    );
22290  }
22291
22292  #[cfg(not(target_family = "wasm"))]
22293  #[test]
22294  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
22295    use std::sync::mpsc;
22296
22297    let controller = Arc::new(current_thread_controller(
22298      "current-thread-blocking-stop-race",
22299    ));
22300    let backend = controller.backend();
22301    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22302      panic!("configured CurrentThread backend must create a CurrentThread executor");
22303    };
22304    let executor = Arc::clone(executor);
22305    drop(backend);
22306
22307    let ran = Arc::new(AtomicBool::new(false));
22308    let blocking_ran = Arc::clone(&ran);
22309    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
22310    let (release_admission_tx, release_admission_rx) = mpsc::channel();
22311    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
22312    let submit_controller = Arc::clone(&controller);
22313    let submitter = std::thread::spawn(move || {
22314      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22315        *slot.borrow_mut() = Some(Box::new(move || {
22316          admission_locked_tx.send(()).unwrap();
22317          release_admission_rx.recv().unwrap();
22318        }));
22319      });
22320      let handle = submit_controller
22321        .try_spawn_blocking(move || {
22322          blocking_ran.store(true, Ordering::SeqCst);
22323        })
22324        .unwrap_or_else(|_| {
22325          panic!("the submission linearized before shutdown and must be accepted")
22326        });
22327      blocking_result_tx
22328        .send(futures::executor::block_on(handle))
22329        .unwrap();
22330    });
22331    admission_locked_rx
22332      .recv_timeout(Duration::from_secs(2))
22333      .expect("the blocking submitter must pause while holding the admission lock");
22334
22335    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
22336    let shutdown_controller = Arc::clone(&controller);
22337    let shutdown = std::thread::spawn(move || {
22338      shutdown_result_tx
22339        .send(shutdown_controller.shutdown())
22340        .unwrap();
22341    });
22342    wait_until("CurrentThread shutdown to publish generation stop", || {
22343      executor.stop.is_stopping()
22344    });
22345    release_admission_tx.send(()).unwrap();
22346
22347    let blocking_error = blocking_result_rx
22348      .recv_timeout(Duration::from_secs(2))
22349      .expect("the admitted blocking handle must resolve")
22350      .expect_err("stop publication must reject the blocking lane claim");
22351    assert_eq!(
22352      blocking_error.to_string(),
22353      "the async runtime stopped before the blocking task could start"
22354    );
22355    assert!(
22356      !ran.load(Ordering::SeqCst),
22357      "blocking work must not start after stop publication"
22358    );
22359    shutdown_result_rx
22360      .recv_timeout(Duration::from_secs(2))
22361      .expect("shutdown must finish after the rejected admission retires")
22362      .unwrap();
22363    submitter.join().unwrap();
22364    shutdown.join().unwrap();
22365  }
22366
22367  #[cfg(not(target_family = "wasm"))]
22368  #[test]
22369  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
22370    use std::sync::mpsc;
22371
22372    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22373      RuntimeMetrics::default(),
22374    )));
22375    let ran = Arc::new(AtomicBool::new(false));
22376    let job_ran = Arc::clone(&ran);
22377    {
22378      let mut state = executor
22379        .blocking_admission
22380        .state
22381        .lock()
22382        .unwrap_or_else(std::sync::PoisonError::into_inner);
22383      state.queue.push(QueuedBlocking {
22384        id: BlockingJobId(u64::MAX - 71),
22385        run: Box::new(move || {
22386          job_ran.store(true, Ordering::SeqCst);
22387        }),
22388      });
22389    }
22390
22391    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22392    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22393    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22394    let claim_executor = Arc::clone(&executor);
22395    let claimant = std::thread::spawn(move || {
22396      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22397        *slot.borrow_mut() = Some(Box::new(move || {
22398          claim_locked_tx.send(()).unwrap();
22399          release_claim_rx.recv().unwrap();
22400        }));
22401      });
22402      claim_result_tx
22403        .send(claim_executor.run_one_blocking())
22404        .unwrap();
22405    });
22406    claim_locked_rx
22407      .recv_timeout(Duration::from_secs(2))
22408      .expect("the FIFO claimant must pause while holding the admission lock");
22409
22410    executor.stop.begin_shutdown();
22411    release_claim_tx.send(()).unwrap();
22412    assert!(
22413      !claim_result_rx
22414        .recv_timeout(Duration::from_secs(2))
22415        .expect("the stopped FIFO claim must finish"),
22416      "stop publication must reject the queued FIFO claim"
22417    );
22418    claimant.join().unwrap();
22419    assert!(
22420      !ran.load(Ordering::SeqCst),
22421      "queued FIFO work must not start after stop publication"
22422    );
22423    assert!(
22424      !executor
22425        .blocking_admission
22426        .state
22427        .lock()
22428        .unwrap_or_else(std::sync::PoisonError::into_inner)
22429        .queue
22430        .is_empty(),
22431      "the rejected FIFO job must remain queued for shutdown cancellation"
22432    );
22433
22434    executor.close_blocking_admission();
22435    assert!(
22436      executor
22437        .blocking_admission
22438        .state
22439        .lock()
22440        .unwrap_or_else(std::sync::PoisonError::into_inner)
22441        .queue
22442        .is_empty()
22443    );
22444  }
22445
22446  #[cfg(not(target_family = "wasm"))]
22447  #[test]
22448  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22449    use std::sync::mpsc;
22450
22451    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22452      RuntimeMetrics::default(),
22453    )));
22454    let ran = Arc::new(AtomicBool::new(false));
22455    let job_ran = Arc::clone(&ran);
22456    {
22457      let mut state = executor
22458        .blocking_admission
22459        .state
22460        .lock()
22461        .unwrap_or_else(std::sync::PoisonError::into_inner);
22462      state.queue.push(QueuedBlocking {
22463        id: BlockingJobId(u64::MAX - 73),
22464        run: Box::new(move || {
22465          job_ran.store(true, Ordering::SeqCst);
22466        }),
22467      });
22468    }
22469
22470    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22471    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22472    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22473    let claim_executor = Arc::clone(&executor);
22474    let claimant = std::thread::spawn(move || {
22475      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22476        *slot.borrow_mut() = Some(Box::new(move || {
22477          claim_locked_tx.send(()).unwrap();
22478          release_claim_rx.recv().unwrap();
22479        }));
22480      });
22481      claim_result_tx
22482        .send(claim_executor.run_one_blocking())
22483        .unwrap();
22484    });
22485    claim_locked_rx
22486      .recv_timeout(Duration::from_secs(2))
22487      .expect("the FIFO claimant must pause while holding the admission lock");
22488
22489    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22490    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22491    let cancelling_executor = Arc::clone(&executor);
22492    let cancellation = std::thread::spawn(move || {
22493      let cancelling = cancelling_executor
22494        .begin_failed_dispatch_cancellation()
22495        .expect("the test must publish terminal dispatch cancellation");
22496      cancellation_published_tx.send(()).unwrap();
22497      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22498      cancellation_done_tx.send(()).unwrap();
22499    });
22500    cancellation_published_rx
22501      .recv_timeout(Duration::from_secs(2))
22502      .expect("terminal dispatch cancellation must publish before queue cancellation");
22503    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22504    assert!(
22505      matches!(
22506        cancellation_done_rx.try_recv(),
22507        Err(mpsc::TryRecvError::Empty)
22508      ),
22509      "terminal cancellation must wait for the blocking-admission lock"
22510    );
22511
22512    release_claim_tx.send(()).unwrap();
22513    assert!(
22514      !claim_result_rx
22515        .recv_timeout(Duration::from_secs(2))
22516        .expect("the cancelled FIFO claim must finish"),
22517      "terminal cancellation publication must reject the queued FIFO claim"
22518    );
22519    cancellation_done_rx
22520      .recv_timeout(Duration::from_secs(2))
22521      .expect("terminal cancellation must drain the rejected FIFO job");
22522    claimant.join().unwrap();
22523    cancellation.join().unwrap();
22524
22525    assert!(
22526      !ran.load(Ordering::SeqCst),
22527      "terminally cancelled FIFO work must not start"
22528    );
22529    assert!(
22530      executor
22531        .blocking_admission
22532        .state
22533        .lock()
22534        .unwrap_or_else(std::sync::PoisonError::into_inner)
22535        .queue
22536        .is_empty(),
22537      "terminal cancellation must drain the rejected FIFO job"
22538    );
22539    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22540  }
22541
22542  #[cfg(not(target_family = "wasm"))]
22543  #[test]
22544  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
22545    use std::sync::mpsc;
22546
22547    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22548      RuntimeMetrics::default(),
22549    )));
22550    let owner = executor.fresh_owner_token();
22551    let job = BlockingJobId(u64::MAX - 72);
22552    let ran = Arc::new(AtomicBool::new(false));
22553    let job_ran = Arc::clone(&ran);
22554    {
22555      let mut state = executor
22556        .blocking_admission
22557        .state
22558        .lock()
22559        .unwrap_or_else(std::sync::PoisonError::into_inner);
22560      state.owner = Some(owner);
22561      state.queue.push(QueuedBlocking {
22562        id: job,
22563        run: Box::new(move || {
22564          job_ran.store(true, Ordering::SeqCst);
22565        }),
22566      });
22567    }
22568    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22569    dependency.set_owned(BlockingDependency {
22570      job,
22571      owner: Some(owner),
22572    });
22573
22574    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22575    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22576    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22577    let claim_executor = Arc::clone(&executor);
22578    let claim_dependency = Arc::clone(&dependency);
22579    let claimant = std::thread::spawn(move || {
22580      let _owner = claim_executor.enter_blocking_owner(owner, false);
22581      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22582        *slot.borrow_mut() = Some(Box::new(move || {
22583          claim_locked_tx.send(()).unwrap();
22584          release_claim_rx.recv().unwrap();
22585        }));
22586      });
22587      claim_result_tx
22588        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22589        .unwrap();
22590    });
22591    claim_locked_rx
22592      .recv_timeout(Duration::from_secs(2))
22593      .expect("the owner claimant must pause while holding the admission lock");
22594
22595    executor.stop.begin_shutdown();
22596    release_claim_tx.send(()).unwrap();
22597    assert!(
22598      !claim_result_rx
22599        .recv_timeout(Duration::from_secs(2))
22600        .expect("the stopped owner claim must finish"),
22601      "stop publication must reject the exact-owner claim"
22602    );
22603    claimant.join().unwrap();
22604    assert!(
22605      !ran.load(Ordering::SeqCst),
22606      "queued exact-owner work must not start after stop publication"
22607    );
22608    assert!(
22609      !executor
22610        .blocking_admission
22611        .state
22612        .lock()
22613        .unwrap_or_else(std::sync::PoisonError::into_inner)
22614        .queue
22615        .is_empty(),
22616      "the rejected exact-owner job must remain queued for shutdown cancellation"
22617    );
22618
22619    executor
22620      .blocking_admission
22621      .state
22622      .lock()
22623      .unwrap_or_else(std::sync::PoisonError::into_inner)
22624      .owner = None;
22625    executor.close_blocking_admission();
22626    assert!(
22627      executor
22628        .blocking_admission
22629        .state
22630        .lock()
22631        .unwrap_or_else(std::sync::PoisonError::into_inner)
22632        .queue
22633        .is_empty()
22634    );
22635  }
22636
22637  #[cfg(not(target_family = "wasm"))]
22638  #[test]
22639  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22640    use std::sync::mpsc;
22641
22642    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22643      RuntimeMetrics::default(),
22644    )));
22645    let owner = executor.fresh_owner_token();
22646    let job = BlockingJobId(u64::MAX - 74);
22647    let ran = Arc::new(AtomicBool::new(false));
22648    let job_ran = Arc::clone(&ran);
22649    {
22650      let mut state = executor
22651        .blocking_admission
22652        .state
22653        .lock()
22654        .unwrap_or_else(std::sync::PoisonError::into_inner);
22655      state.owner = Some(owner);
22656      state.queue.push(QueuedBlocking {
22657        id: job,
22658        run: Box::new(move || {
22659          job_ran.store(true, Ordering::SeqCst);
22660        }),
22661      });
22662    }
22663    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22664    dependency.set_owned(BlockingDependency {
22665      job,
22666      owner: Some(owner),
22667    });
22668
22669    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22670    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22671    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22672    let claim_executor = Arc::clone(&executor);
22673    let claim_dependency = Arc::clone(&dependency);
22674    let claimant = std::thread::spawn(move || {
22675      let _owner = claim_executor.enter_blocking_owner(owner, false);
22676      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22677        *slot.borrow_mut() = Some(Box::new(move || {
22678          claim_locked_tx.send(()).unwrap();
22679          release_claim_rx.recv().unwrap();
22680        }));
22681      });
22682      claim_result_tx
22683        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22684        .unwrap();
22685    });
22686    claim_locked_rx
22687      .recv_timeout(Duration::from_secs(2))
22688      .expect("the owner claimant must pause while holding the admission lock");
22689
22690    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22691    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22692    let cancelling_executor = Arc::clone(&executor);
22693    let cancellation = std::thread::spawn(move || {
22694      let cancelling = cancelling_executor
22695        .begin_failed_dispatch_cancellation()
22696        .expect("the test must publish terminal dispatch cancellation");
22697      cancellation_published_tx.send(()).unwrap();
22698      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22699      cancellation_done_tx.send(()).unwrap();
22700    });
22701    cancellation_published_rx
22702      .recv_timeout(Duration::from_secs(2))
22703      .expect("terminal dispatch cancellation must publish before queue cancellation");
22704    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22705    assert!(
22706      matches!(
22707        cancellation_done_rx.try_recv(),
22708        Err(mpsc::TryRecvError::Empty)
22709      ),
22710      "terminal cancellation must wait for the blocking-admission lock"
22711    );
22712
22713    release_claim_tx.send(()).unwrap();
22714    assert!(
22715      !claim_result_rx
22716        .recv_timeout(Duration::from_secs(2))
22717        .expect("the cancelled owner claim must finish"),
22718      "terminal cancellation publication must reject the exact-owner claim"
22719    );
22720    cancellation_done_rx
22721      .recv_timeout(Duration::from_secs(2))
22722      .expect("terminal cancellation must drain the rejected exact-owner job");
22723    claimant.join().unwrap();
22724    cancellation.join().unwrap();
22725
22726    assert!(
22727      !ran.load(Ordering::SeqCst),
22728      "terminally cancelled exact-owner work must not start"
22729    );
22730    assert!(
22731      executor
22732        .blocking_admission
22733        .state
22734        .lock()
22735        .unwrap_or_else(std::sync::PoisonError::into_inner)
22736        .queue
22737        .is_empty(),
22738      "terminal cancellation must drain the rejected exact-owner job"
22739    );
22740    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22741  }
22742
22743  #[cfg(not(target_family = "wasm"))]
22744  #[test]
22745  fn current_thread_shutdown_cancels_waiting_blocking_work() {
22746    use std::sync::mpsc;
22747
22748    struct DropSignal(mpsc::Sender<()>);
22749
22750    impl Drop for DropSignal {
22751      fn drop(&mut self) {
22752        let _ = self.0.send(());
22753      }
22754    }
22755
22756    let controller = Arc::new(current_thread_controller(
22757      "current-thread-blocking-shutdown",
22758    ));
22759    let backend = controller.backend();
22760    let (first_started_tx, first_started_rx) = mpsc::channel();
22761    let (release_first_tx, release_first_rx) = mpsc::channel();
22762    let first_controller = Arc::clone(&controller);
22763    let first = std::thread::spawn(move || {
22764      let handle = first_controller
22765        .try_spawn_blocking(move || {
22766          first_started_tx.send(()).unwrap();
22767          release_first_rx.recv().unwrap();
22768        })
22769        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
22770      futures::executor::block_on(handle)
22771    });
22772    first_started_rx
22773      .recv_timeout(Duration::from_secs(1))
22774      .unwrap();
22775
22776    let waiting_ran = Arc::new(AtomicBool::new(false));
22777    let waiting_ran_task = Arc::clone(&waiting_ran);
22778    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
22779    let waiting_drop = DropSignal(waiting_dropped_tx);
22780    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
22781    let waiting_controller = Arc::clone(&controller);
22782    let waiting = std::thread::spawn(move || {
22783      let handle = waiting_controller
22784        .try_spawn_blocking(move || {
22785          let _waiting_drop = waiting_drop;
22786          waiting_ran_task.store(true, Ordering::SeqCst);
22787        })
22788        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
22789      waiting_result_tx
22790        .send(futures::executor::block_on(handle))
22791        .unwrap();
22792    });
22793    wait_until("both CurrentThread blocking calls to be accepted", || {
22794      backend
22795        .work
22796        .state
22797        .lock()
22798        .unwrap_or_else(std::sync::PoisonError::into_inner)
22799        .active
22800        == 2
22801    });
22802
22803    let (shutdown_tx, shutdown_rx) = mpsc::channel();
22804    let shutdown_controller = Arc::clone(&controller);
22805    let shutdown = std::thread::spawn(move || {
22806      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22807    });
22808    waiting_dropped_rx
22809      .recv_timeout(Duration::from_secs(1))
22810      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
22811    assert!(
22812      !waiting_ran.load(Ordering::SeqCst),
22813      "waiting blocking work must not start"
22814    );
22815    assert!(
22816      waiting_result_rx
22817        .recv_timeout(Duration::from_secs(1))
22818        .unwrap()
22819        .is_err(),
22820      "the waiting blocking handle must resolve as cancelled"
22821    );
22822    assert!(
22823      shutdown_rx
22824        .recv_timeout(Duration::from_millis(100))
22825        .is_err(),
22826      "shutdown must still wait for the blocking closure that already owns the lane"
22827    );
22828
22829    release_first_tx.send(()).unwrap();
22830    first.join().unwrap().unwrap();
22831    waiting.join().unwrap();
22832    shutdown_rx
22833      .recv_timeout(Duration::from_secs(1))
22834      .unwrap()
22835      .unwrap();
22836    shutdown.join().unwrap();
22837    assert_eq!(
22838      controller
22839        .metrics
22840        .blocking_tasks_started
22841        .load(Ordering::Relaxed),
22842      1
22843    );
22844    assert_eq!(
22845      controller
22846        .metrics
22847        .blocking_tasks_completed
22848        .load(Ordering::Relaxed),
22849      1
22850    );
22851    assert_eq!(
22852      controller
22853        .metrics
22854        .active_blocking_tasks
22855        .load(Ordering::Relaxed),
22856      0
22857    );
22858  }
22859
22860  #[cfg(not(target_family = "wasm"))]
22861  #[test]
22862  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
22863    use std::{panic::panic_any, sync::mpsc};
22864
22865    struct ReentrantPayload {
22866      controller: Arc<RuntimeController>,
22867      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22868    }
22869
22870    impl Drop for ReentrantPayload {
22871      fn drop(&mut self) {
22872        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22873        let shutdown = self.controller.shutdown();
22874        self.result.send((generation, shutdown)).unwrap();
22875      }
22876    }
22877
22878    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
22879
22880    impl Drop for PanicPayloadOnDrop {
22881      fn drop(&mut self) {
22882        panic_any(self.0.take().expect("the result must panic only once"));
22883      }
22884    }
22885
22886    let controller = Arc::new(current_thread_controller(
22887      "current-thread-panic-payload-generation",
22888    ));
22889    let backend = controller.backend();
22890    let generation = backend.generation();
22891    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22892      panic!("configured CurrentThread backend must create a CurrentThread executor");
22893    };
22894    let executor = Arc::clone(executor);
22895    drop(backend);
22896
22897    // Hold the physical lane long enough to force scheduler-owned delivery.
22898    // Dropping the receiver then makes the queued sender destroy the result.
22899    let owner = executor.fresh_owner_token();
22900    executor
22901      .blocking_admission
22902      .state
22903      .lock()
22904      .unwrap_or_else(std::sync::PoisonError::into_inner)
22905      .owner = Some(owner);
22906    let (result_tx, result_rx) = mpsc::channel();
22907    let payload_controller = Arc::clone(&controller);
22908    let handle = controller
22909      .try_spawn_blocking(move || {
22910        PanicPayloadOnDrop(Some(ReentrantPayload {
22911          controller: payload_controller,
22912          result: result_tx,
22913        }))
22914      })
22915      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22916    drop(handle);
22917    executor
22918      .blocking_admission
22919      .state
22920      .lock()
22921      .unwrap_or_else(std::sync::PoisonError::into_inner)
22922      .owner = None;
22923    let dispatch = current_thread_dispatch(&executor);
22924    controller.drive_current_thread_dispatch(dispatch, false);
22925
22926    let (observed_generation, shutdown) = result_rx
22927      .recv_timeout(Duration::from_secs(1))
22928      .expect("the caught result panic payload must be destroyed in the scheduler turn");
22929    assert_eq!(observed_generation, Some(generation));
22930    let error = shutdown.expect_err("payload destruction is still work in the active generation");
22931    assert!(error.to_string().contains("work running on that runtime"));
22932    controller.shutdown().unwrap();
22933  }
22934
22935  #[test]
22936  fn current_thread_detached_blocking_result_drop_is_contained() {
22937    use std::sync::mpsc;
22938
22939    struct PanicOnDrop(mpsc::Sender<()>);
22940
22941    impl Drop for PanicOnDrop {
22942      fn drop(&mut self) {
22943        self.0.send(()).unwrap();
22944        panic!("intentional CurrentThread blocking result destructor panic");
22945      }
22946    }
22947
22948    let controller = current_thread_controller("current-thread-blocking-result-drop");
22949    let (dropped_tx, dropped_rx) = mpsc::channel();
22950    let handle = controller
22951      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
22952      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22953
22954    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
22955    dropped_rx
22956      .recv_timeout(Duration::from_secs(1))
22957      .expect("dropping the detached handle must reclaim its blocking result");
22958    assert!(
22959      result.is_ok(),
22960      "the blocking result destructor panic must be contained"
22961    );
22962    controller.shutdown().unwrap();
22963  }
22964
22965  // ---- Runtime option validation and configuration immutability -------------
22966  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
22967  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
22968
22969  #[test]
22970  fn validate_rejects_zero_worker_threads() {
22971    let error = RuntimeOptions {
22972      flavor: RuntimeFlavor::MultiThread,
22973      worker_threads: 0,
22974      max_blocking_tasks: 1,
22975      thread_name_prefix: "rd8".to_string(),
22976      park_deadline: None,
22977      drain_linger: DEFAULT_DRAIN_LINGER,
22978    }
22979    .validate()
22980    .expect_err("worker_threads == 0 must be rejected");
22981    assert_eq!(
22982      error.to_string(),
22983      "worker_threads must be greater than zero"
22984    );
22985  }
22986
22987  #[test]
22988  fn validate_rejects_zero_max_blocking_tasks() {
22989    let error = RuntimeOptions {
22990      flavor: RuntimeFlavor::MultiThread,
22991      worker_threads: 2,
22992      max_blocking_tasks: 0,
22993      thread_name_prefix: "rd8".to_string(),
22994      park_deadline: None,
22995      drain_linger: DEFAULT_DRAIN_LINGER,
22996    }
22997    .validate()
22998    .expect_err("max_blocking_tasks == 0 must be rejected");
22999    assert_eq!(
23000      error.to_string(),
23001      "max_blocking_tasks must be greater than zero"
23002    );
23003  }
23004
23005  #[test]
23006  fn validate_current_thread_coerces_worker_threads_to_one() {
23007    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
23008    // clamped to `.min(worker_threads)` == 1.
23009    let validated = RuntimeOptions {
23010      flavor: RuntimeFlavor::CurrentThread,
23011      worker_threads: 8,
23012      max_blocking_tasks: 8,
23013      thread_name_prefix: "rd8".to_string(),
23014      park_deadline: None,
23015      drain_linger: DEFAULT_DRAIN_LINGER,
23016    }
23017    .validate()
23018    .expect("CurrentThread options must validate");
23019    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
23020    assert_eq!(validated.worker_threads, 1);
23021    assert_eq!(validated.max_blocking_tasks, 1);
23022  }
23023
23024  // Native-only: `validate()` rejects `MultiThread` under `cfg(target_family = "wasm")`
23025  // (after applying the clamp), so this success assertion is valid off-wasm only.
23026  #[cfg(not(target_family = "wasm"))]
23027  #[test]
23028  fn validate_reserves_one_worker_from_blocking_admission() {
23029    let validated = RuntimeOptions {
23030      flavor: RuntimeFlavor::MultiThread,
23031      worker_threads: 2,
23032      max_blocking_tasks: 8,
23033      thread_name_prefix: "rd8".to_string(),
23034      park_deadline: None,
23035      drain_linger: DEFAULT_DRAIN_LINGER,
23036    }
23037    .validate()
23038    .expect("MultiThread options must validate");
23039    assert_eq!(validated.worker_threads, 2);
23040    assert_eq!(validated.max_blocking_tasks, 1);
23041  }
23042
23043  #[cfg(not(target_family = "wasm"))]
23044  #[test]
23045  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
23046    let validated = RuntimeOptions {
23047      flavor: RuntimeFlavor::MultiThread,
23048      worker_threads: 1,
23049      max_blocking_tasks: 8,
23050      thread_name_prefix: "rd8-minimum".to_string(),
23051      park_deadline: None,
23052      drain_linger: DEFAULT_DRAIN_LINGER,
23053    }
23054    .validate()
23055    .expect("MultiThread options must validate");
23056    assert_eq!(validated.worker_threads, 2);
23057    assert_eq!(validated.max_blocking_tasks, 1);
23058  }
23059
23060  #[cfg(not(target_family = "wasm"))]
23061  #[test]
23062  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
23063    let simulated_32_bit = RuntimeOptions {
23064      flavor: RuntimeFlavor::MultiThread,
23065      worker_threads: 256,
23066      max_blocking_tasks: 256,
23067      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
23068      park_deadline: None,
23069      drain_linger: DEFAULT_DRAIN_LINGER,
23070    }
23071    .validate_with_rayon_max_threads(Some(255))
23072    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
23073    assert_eq!(simulated_32_bit.worker_threads, 255);
23074    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
23075
23076    let rayon_max_threads = max_num_threads();
23077    let requested_threads = rayon_max_threads
23078      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
23079      .checked_add(1)
23080      .expect("supported worker counts must leave room for an oversized request");
23081    let controller = RuntimeController::new();
23082    controller
23083      .configure(RuntimeOptions {
23084        flavor: RuntimeFlavor::MultiThread,
23085        worker_threads: requested_threads,
23086        max_blocking_tasks: requested_threads,
23087        thread_name_prefix: "rd8-rayon-cap".to_string(),
23088        park_deadline: None,
23089        drain_linger: DEFAULT_DRAIN_LINGER,
23090      })
23091      .expect("an oversized request must clamp to the production worker ceiling");
23092
23093    let reported = controller.options();
23094    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
23095    assert_eq!(reported.worker_threads, expected_workers);
23096    assert_eq!(
23097      reported.max_blocking_tasks,
23098      expected_workers - 1,
23099      "the blocking cap must reserve a lane from the physically realizable worker count"
23100    );
23101  }
23102
23103  #[cfg(not(target_family = "wasm"))]
23104  #[test]
23105  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
23106    // u64::MAX microseconds (the exact value an unvalidated
23107    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
23108    // via the removed OnceLock) must clamp to the ceiling everywhere a
23109    // budget enters the system: full options validation, a partial patch
23110    // through the controller, and the executor's defensive constructor
23111    // clamp for direct unvalidated test construction.
23112    let extreme = Duration::from_micros(u64::MAX);
23113    let validated = RuntimeOptions {
23114      flavor: RuntimeFlavor::MultiThread,
23115      worker_threads: 2,
23116      max_blocking_tasks: 1,
23117      thread_name_prefix: "linger-clamp".to_string(),
23118      park_deadline: None,
23119      drain_linger: extreme,
23120    }
23121    .validate_with_rayon_max_threads(Some(2))
23122    .expect("an extreme linger budget must clamp, not fail validation");
23123    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
23124
23125    let controller = RuntimeController::new();
23126    controller
23127      .configure_partial(RuntimeOptionsPatch {
23128        drain_linger: Some(extreme),
23129        ..RuntimeOptionsPatch::default()
23130      })
23131      .expect("an extreme linger patch must clamp, not fail validation");
23132    assert_eq!(
23133      controller.options().drain_linger,
23134      MAX_DRAIN_LINGER,
23135      "introspection must report the clamped budget, not the requested one"
23136    );
23137    controller
23138      .configure_partial(RuntimeOptionsPatch {
23139        drain_linger: Some(Duration::ZERO),
23140        ..RuntimeOptionsPatch::default()
23141      })
23142      .expect("ZERO (lingering disabled) must remain a valid budget");
23143    assert_eq!(controller.options().drain_linger, Duration::ZERO);
23144
23145    let metrics = Arc::new(RuntimeMetrics::default());
23146    let unvalidated = RuntimeOptions {
23147      flavor: RuntimeFlavor::MultiThread,
23148      worker_threads: 2,
23149      max_blocking_tasks: 2,
23150      thread_name_prefix: "linger-clamp-exec".to_string(),
23151      park_deadline: None,
23152      drain_linger: extreme,
23153    };
23154    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
23155      .expect("the executor must construct with an extreme unvalidated budget");
23156    assert_eq!(
23157      executor.drain_linger,
23158      Some(MAX_DRAIN_LINGER),
23159      "the constructor's defensive clamp must bound unvalidated budgets"
23160    );
23161
23162    let metrics = Arc::new(RuntimeMetrics::default());
23163    let disabled = RuntimeOptions {
23164      flavor: RuntimeFlavor::MultiThread,
23165      worker_threads: 2,
23166      max_blocking_tasks: 2,
23167      thread_name_prefix: "linger-clamp-off".to_string(),
23168      park_deadline: None,
23169      drain_linger: Duration::ZERO,
23170    };
23171    let executor = MultiThreadExecutor::new(&disabled, metrics)
23172      .expect("the executor must construct with lingering disabled");
23173    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
23174  }
23175
23176  #[cfg(not(target_family = "wasm"))]
23177  #[test]
23178  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
23179    let controller = RuntimeController::new();
23180    let options = controller.options();
23181    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
23182    assert_eq!(
23183      options.max_blocking_tasks,
23184      options.worker_threads.saturating_sub(1).max(1),
23185      "the unconfigured Rust API must use the same validated reserve as the binding"
23186    );
23187  }
23188
23189  #[cfg(not(target_family = "wasm"))]
23190  #[test]
23191  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
23192    use std::{sync::mpsc, time::Duration};
23193
23194    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
23195    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
23196    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
23197    let first_controller = Arc::clone(&controller);
23198    let first = std::thread::spawn(move || {
23199      first_controller
23200        .configure_partial_inner(
23201          RuntimeOptionsPatch {
23202            max_blocking_tasks: Some(3),
23203            ..RuntimeOptionsPatch::default()
23204          },
23205          |candidate| {
23206            assert_eq!(
23207              (candidate.worker_threads, candidate.max_blocking_tasks),
23208              (8, 3)
23209            );
23210            first_merged_tx
23211              .send(())
23212              .expect("the race coordinator must still be listening");
23213            release_first_rx
23214              .recv_timeout(Duration::from_secs(5))
23215              .expect("the first partial configuration must be released");
23216          },
23217        )
23218        .expect("the first partial configuration must commit");
23219    });
23220    first_merged_rx
23221      .recv_timeout(Duration::from_secs(5))
23222      .expect("the first partial configuration must hold the controller lock after merging");
23223
23224    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
23225    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
23226    let second_controller = Arc::clone(&controller);
23227    let second = std::thread::spawn(move || {
23228      second_started_tx
23229        .send(())
23230        .expect("the race coordinator must still be listening");
23231      second_controller
23232        .configure_partial_inner(
23233          RuntimeOptionsPatch {
23234            worker_threads: Some(6),
23235            ..RuntimeOptionsPatch::default()
23236          },
23237          |candidate| {
23238            second_candidate_tx
23239              .send((candidate.worker_threads, candidate.max_blocking_tasks))
23240              .expect("the race coordinator must still be listening");
23241          },
23242        )
23243        .expect("the second partial configuration must commit");
23244    });
23245    second_started_rx
23246      .recv_timeout(Duration::from_secs(5))
23247      .expect("the second partial configuration thread must start");
23248    release_first_tx
23249      .send(())
23250      .expect("the first partial configuration must still be waiting");
23251
23252    first
23253      .join()
23254      .expect("the first configuration thread must not panic");
23255    second
23256      .join()
23257      .expect("the second configuration thread must not panic");
23258    assert_eq!(
23259      second_candidate_rx
23260        .recv_timeout(Duration::from_secs(5))
23261        .expect("the second partial configuration must expose its merged candidate"),
23262      (6, 3),
23263      "the second caller must merge against the first caller's committed options"
23264    );
23265
23266    let options = controller.options();
23267    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
23268    assert_eq!(options.thread_name_prefix, "partial-config-race");
23269  }
23270
23271  #[cfg(not(target_family = "wasm"))]
23272  #[test]
23273  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
23274    use std::{sync::mpsc, time::Duration};
23275
23276    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
23277    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
23278    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
23279    let invalid_controller = Arc::clone(&controller);
23280    let invalid = std::thread::spawn(move || {
23281      invalid_controller
23282        .configure_partial_inner(
23283          RuntimeOptionsPatch {
23284            worker_threads: Some(0),
23285            ..RuntimeOptionsPatch::default()
23286          },
23287          |candidate| {
23288            assert_eq!(
23289              (candidate.worker_threads, candidate.max_blocking_tasks),
23290              (0, 2)
23291            );
23292            invalid_merged_tx
23293              .send(())
23294              .expect("the race coordinator must still be listening");
23295            release_invalid_rx
23296              .recv_timeout(Duration::from_secs(5))
23297              .expect("the invalid partial configuration must be released");
23298          },
23299        )
23300        .expect_err("the invalid partial configuration must be rejected")
23301        .to_string()
23302    });
23303    invalid_merged_rx
23304      .recv_timeout(Duration::from_secs(5))
23305      .expect("the invalid partial configuration must hold the lock after merging");
23306
23307    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
23308    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
23309    let valid_controller = Arc::clone(&controller);
23310    let valid = std::thread::spawn(move || {
23311      valid_started_tx
23312        .send(())
23313        .expect("the race coordinator must still be listening");
23314      valid_controller
23315        .configure_partial_inner(
23316          RuntimeOptionsPatch {
23317            max_blocking_tasks: Some(4),
23318            ..RuntimeOptionsPatch::default()
23319          },
23320          |candidate| {
23321            valid_candidate_tx
23322              .send((candidate.worker_threads, candidate.max_blocking_tasks))
23323              .expect("the race coordinator must still be listening");
23324          },
23325        )
23326        .expect("the valid partial configuration must commit");
23327    });
23328    valid_started_rx
23329      .recv_timeout(Duration::from_secs(5))
23330      .expect("the valid partial configuration thread must start");
23331    release_invalid_tx
23332      .send(())
23333      .expect("the invalid partial configuration must still be waiting");
23334
23335    assert_eq!(
23336      invalid
23337        .join()
23338        .expect("the invalid configuration thread must not panic"),
23339      "worker_threads must be greater than zero"
23340    );
23341    valid
23342      .join()
23343      .expect("the valid configuration thread must not panic");
23344    assert_eq!(
23345      valid_candidate_rx
23346        .recv_timeout(Duration::from_secs(5))
23347        .expect("the valid partial configuration must expose its merged candidate"),
23348      (6, 4),
23349      "the valid caller must merge against the last committed options, not the rejected candidate"
23350    );
23351
23352    let options = controller.options();
23353    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
23354    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
23355  }
23356
23357  #[test]
23358  fn configure_after_backend_started_is_rejected() {
23359    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
23360    // so starting it is cheap and spawns no OS threads.
23361    let controller = RuntimeController::new();
23362    controller
23363      .configure(RuntimeOptions {
23364        flavor: RuntimeFlavor::CurrentThread,
23365        worker_threads: 1,
23366        max_blocking_tasks: 1,
23367        thread_name_prefix: "rd8".to_string(),
23368        park_deadline: None,
23369        drain_linger: DEFAULT_DRAIN_LINGER,
23370      })
23371      .expect("first configure before the backend exists must succeed");
23372
23373    // Materialize the backend; further configuration must now be refused.
23374    let _backend = controller.backend();
23375
23376    let error = controller
23377      .configure(RuntimeOptions::default())
23378      .expect_err("configure after the backend started must be rejected");
23379    assert_eq!(
23380      error.to_string(),
23381      "the async runtime configuration is frozen; configure it before the first async call"
23382    );
23383  }
23384
23385  #[test]
23386  fn partial_configuration_after_backend_started_is_rejected() {
23387    let controller = current_thread_controller("partial-config-frozen");
23388    let _backend = controller.backend();
23389
23390    let error = controller
23391      .configure_partial(RuntimeOptionsPatch {
23392        worker_threads: Some(2),
23393        ..RuntimeOptionsPatch::default()
23394      })
23395      .expect_err("partial configuration after the backend started must be rejected");
23396    assert_eq!(
23397      error.to_string(),
23398      "the async runtime configuration is frozen; configure it before the first async call"
23399    );
23400  }
23401
23402  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
23403    let controller = RuntimeController::new();
23404    controller
23405      .configure(RuntimeOptions {
23406        flavor: RuntimeFlavor::CurrentThread,
23407        worker_threads: 1,
23408        max_blocking_tasks: 1,
23409        thread_name_prefix: thread_name_prefix.to_string(),
23410        park_deadline: None,
23411        drain_linger: DEFAULT_DRAIN_LINGER,
23412      })
23413      .expect("CurrentThread test configuration must be accepted");
23414    controller
23415  }
23416
23417  #[cfg(not(target_family = "wasm"))]
23418  fn multi_thread_controller(
23419    thread_name_prefix: &str,
23420    worker_threads: usize,
23421    max_blocking_tasks: usize,
23422  ) -> RuntimeController {
23423    let controller = RuntimeController::new();
23424    controller
23425      .configure(RuntimeOptions {
23426        flavor: RuntimeFlavor::MultiThread,
23427        worker_threads,
23428        max_blocking_tasks,
23429        thread_name_prefix: thread_name_prefix.to_string(),
23430        park_deadline: None,
23431        drain_linger: DEFAULT_DRAIN_LINGER,
23432      })
23433      .expect("MultiThread test configuration must be accepted");
23434    controller
23435  }
23436
23437  #[test]
23438  fn blocking_backend_creation_failure_preserves_diagnostic() {
23439    let controller = current_thread_controller("blocking-backend-creation-failure");
23440    let ran = Arc::new(AtomicBool::new(false));
23441    let work_ran = Arc::clone(&ran);
23442
23443    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23444    let Err((error, work)) = controller.try_spawn_blocking(move || {
23445      work_ran.store(true, Ordering::SeqCst);
23446      17usize
23447    }) else {
23448      panic!("the injected backend creation failure must reject blocking work");
23449    };
23450    assert_eq!(
23451      error.to_string(),
23452      "injected async runtime backend creation failure"
23453    );
23454    assert!(
23455      !ran.load(Ordering::SeqCst),
23456      "rejected blocking work must not run"
23457    );
23458    assert_eq!(
23459      work(),
23460      17,
23461      "the rejected blocking closure must remain usable"
23462    );
23463    assert!(ran.load(Ordering::SeqCst));
23464
23465    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23466    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
23467      .expect_err("infallible blocking submission must return a failed join handle");
23468    assert_eq!(
23469      error.to_string(),
23470      "injected async runtime backend creation failure"
23471    );
23472
23473    let handle = controller
23474      .try_spawn_blocking(|| 31usize)
23475      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
23476    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
23477    controller.shutdown().unwrap();
23478  }
23479
23480  #[cfg(not(target_family = "wasm"))]
23481  #[test]
23482  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
23483    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
23484
23485    if std::env::var_os(CHILD_ENV).is_some() {
23486      struct PollProbe {
23487        polls: Arc<AtomicUsize>,
23488      }
23489
23490      impl Future for PollProbe {
23491        type Output = ();
23492
23493        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23494          self.polls.fetch_add(1, Ordering::SeqCst);
23495          Poll::Ready(())
23496        }
23497      }
23498
23499      shutdown().expect("the isolated runtime must stop before block_on admission");
23500      let polls = Arc::new(AtomicUsize::new(0));
23501      let mut future = std::pin::pin!(PollProbe {
23502        polls: Arc::clone(&polls)
23503      });
23504      let error = try_block_on_dyn(future.as_mut())
23505        .expect_err("the stopped runtime must reject the borrowed future");
23506      assert_eq!(
23507        error.to_string(),
23508        "the async runtime is stopped; call start before submitting work"
23509      );
23510      assert_eq!(
23511        polls.load(Ordering::SeqCst),
23512        0,
23513        "rejected block_on must not poll its future"
23514      );
23515
23516      start().expect("the isolated runtime must restart");
23517      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
23518      assert_eq!(polls.load(Ordering::SeqCst), 1);
23519      shutdown().expect("the restarted isolated runtime must stop");
23520      return;
23521    }
23522
23523    let output = std::process::Command::new(std::env::current_exe().unwrap())
23524      .arg("--exact")
23525      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
23526      .arg("--nocapture")
23527      .env(CHILD_ENV, "1")
23528      .output()
23529      .expect("the stopped block_on subprocess must start");
23530    assert!(
23531      output.status.success(),
23532      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
23533      output.status.code(),
23534      String::from_utf8_lossy(&output.stdout),
23535      String::from_utf8_lossy(&output.stderr)
23536    );
23537  }
23538
23539  #[cfg(not(target_family = "wasm"))]
23540  #[test]
23541  fn try_block_on_dyn_reports_concurrent_shutdown() {
23542    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
23543
23544    if std::env::var_os(CHILD_ENV).is_some() {
23545      use std::sync::mpsc;
23546
23547      struct PendingProbe {
23548        entered: Option<mpsc::Sender<()>>,
23549        complete: Arc<AtomicBool>,
23550      }
23551
23552      impl Future for PendingProbe {
23553        type Output = ();
23554
23555        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23556          if let Some(entered) = self.entered.take() {
23557            entered.send(()).unwrap();
23558          }
23559          if self.complete.load(Ordering::SeqCst) {
23560            Poll::Ready(())
23561          } else {
23562            Poll::Pending
23563          }
23564        }
23565      }
23566
23567      configure(RuntimeOptions {
23568        flavor: RuntimeFlavor::CurrentThread,
23569        worker_threads: 1,
23570        max_blocking_tasks: 1,
23571        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
23572        park_deadline: None,
23573        drain_linger: DEFAULT_DRAIN_LINGER,
23574      })
23575      .expect("the isolated runtime must accept CurrentThread configuration");
23576      start().expect("the isolated runtime must start");
23577
23578      let (entered_tx, entered_rx) = mpsc::channel();
23579      let (error_tx, error_rx) = mpsc::channel();
23580      let (retry_tx, retry_rx) = mpsc::channel();
23581      let complete = Arc::new(AtomicBool::new(false));
23582      let future_complete = Arc::clone(&complete);
23583      let runner = std::thread::spawn(move || {
23584        let mut future = std::pin::pin!(PendingProbe {
23585          entered: Some(entered_tx),
23586          complete: future_complete
23587        });
23588        let error = try_block_on_dyn(future.as_mut())
23589          .expect_err("concurrent shutdown must interrupt the borrowed future")
23590          .to_string();
23591        error_tx.send(error).unwrap();
23592        retry_rx.recv().unwrap();
23593        try_block_on_dyn(future.as_mut())
23594          .expect("restart must accept the same interrupted borrowed future");
23595      });
23596      entered_rx
23597        .recv_timeout(Duration::from_secs(2))
23598        .expect("the borrowed future must be polled before shutdown");
23599      shutdown().expect("shutdown must wait for the active block_on scope to retire");
23600      assert_eq!(
23601        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
23602        "the async runtime stopped before block_on completed its future"
23603      );
23604      start().expect("the isolated runtime must restart after the interrupted drive");
23605      complete.store(true, Ordering::SeqCst);
23606      retry_tx.send(()).unwrap();
23607      runner.join().unwrap();
23608      shutdown().expect("the retried isolated runtime must stop");
23609      return;
23610    }
23611
23612    let output = std::process::Command::new(std::env::current_exe().unwrap())
23613      .arg("--exact")
23614      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
23615      .arg("--nocapture")
23616      .env(CHILD_ENV, "1")
23617      .output()
23618      .expect("the concurrent block_on shutdown subprocess must start");
23619    assert!(
23620      output.status.success(),
23621      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
23622      output.status.code(),
23623      String::from_utf8_lossy(&output.stdout),
23624      String::from_utf8_lossy(&output.stderr)
23625    );
23626  }
23627
23628  #[test]
23629  fn initial_lifecycle_start_preserves_lazy_configuration() {
23630    let controller = RuntimeController::new();
23631    controller
23632      .start()
23633      .expect("initial lifecycle start must succeed");
23634    controller
23635      .configure(RuntimeOptions {
23636        flavor: RuntimeFlavor::CurrentThread,
23637        worker_threads: 1,
23638        max_blocking_tasks: 1,
23639        thread_name_prefix: "lifecycle-initial-start".to_string(),
23640        park_deadline: None,
23641        drain_linger: DEFAULT_DRAIN_LINGER,
23642      })
23643      .expect("module-registration start must not consume the configuration window");
23644
23645    let state = controller
23646      .state
23647      .lock()
23648      .unwrap_or_else(std::sync::PoisonError::into_inner);
23649    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23650  }
23651
23652  #[test]
23653  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
23654    let controller = RuntimeController::new();
23655
23656    for _ in 0..3 {
23657      controller
23658        .start()
23659        .expect("zero-work lifecycle start must succeed");
23660      assert!(matches!(
23661        &controller
23662          .state
23663          .lock()
23664          .unwrap_or_else(std::sync::PoisonError::into_inner)
23665          .lifecycle,
23666        RuntimeLifecycle::Initial
23667      ));
23668
23669      controller
23670        .shutdown()
23671        .expect("zero-work lifecycle shutdown must succeed");
23672      assert!(matches!(
23673        &controller
23674          .state
23675          .lock()
23676          .unwrap_or_else(std::sync::PoisonError::into_inner)
23677          .lifecycle,
23678        RuntimeLifecycle::StoppedBeforeFirstUse
23679      ));
23680    }
23681
23682    controller
23683      .start()
23684      .expect("a later zero-work restart must remain lazy");
23685    controller
23686      .configure(RuntimeOptions {
23687        flavor: RuntimeFlavor::CurrentThread,
23688        worker_threads: 1,
23689        max_blocking_tasks: 1,
23690        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
23691        park_deadline: None,
23692        drain_linger: DEFAULT_DRAIN_LINGER,
23693      })
23694      .expect("zero-work lifecycle cycles must not consume the configuration window");
23695
23696    let state = controller
23697      .state
23698      .lock()
23699      .unwrap_or_else(std::sync::PoisonError::into_inner);
23700    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23701  }
23702
23703  #[cfg(not(target_family = "wasm"))]
23704  #[test]
23705  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
23706    use std::sync::mpsc;
23707
23708    struct BlockingDropFuture {
23709      entered: mpsc::Sender<()>,
23710      release: mpsc::Receiver<()>,
23711    }
23712
23713    impl Future for BlockingDropFuture {
23714      type Output = ();
23715
23716      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23717        Poll::Pending
23718      }
23719    }
23720
23721    impl Drop for BlockingDropFuture {
23722      fn drop(&mut self) {
23723        self.entered.send(()).unwrap();
23724        self.release.recv().unwrap();
23725      }
23726    }
23727
23728    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
23729    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23730    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23731    let submit_controller = Arc::clone(&controller);
23732    let submitter = std::thread::spawn(move || {
23733      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23734      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23735        entered: drop_entered_tx,
23736        release: release_drop_rx,
23737      }));
23738      assert!(
23739        result.is_err(),
23740        "the injected backend creation failure must reject the submission"
23741      );
23742    });
23743    drop_entered_rx
23744      .recv_timeout(Duration::from_secs(2))
23745      .expect("the rejected future destructor must start");
23746
23747    let retry_ran = Arc::new(AtomicBool::new(false));
23748    let retry_ran_task = Arc::clone(&retry_ran);
23749    let Err(retry) = controller.try_spawn_detached(async move {
23750      retry_ran_task.store(true, Ordering::SeqCst);
23751    }) else {
23752      panic!("backend creation must remain closed during rejected destruction");
23753    };
23754    drop(retry);
23755    assert!(!retry_ran.load(Ordering::SeqCst));
23756
23757    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23758    let shutdown_controller = Arc::clone(&controller);
23759    let shutdown = std::thread::spawn(move || {
23760      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23761    });
23762    wait_until("initial shutdown to close runtime admission", || {
23763      matches!(
23764        controller
23765          .state
23766          .lock()
23767          .unwrap_or_else(std::sync::PoisonError::into_inner)
23768          .lifecycle,
23769        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
23770      )
23771    });
23772    assert!(
23773      shutdown_rx
23774        .recv_timeout(Duration::from_millis(200))
23775        .is_err(),
23776      "initial shutdown must wait for rejected user state destruction"
23777    );
23778    assert!(
23779      controller.try_spawn_detached(async {}).is_err(),
23780      "shutdown must keep first-generation admission closed while it waits"
23781    );
23782
23783    let (start_tx, start_rx) = mpsc::channel();
23784    let start_controller = Arc::clone(&controller);
23785    let starter = std::thread::spawn(move || {
23786      start_tx.send(start_controller.start()).unwrap();
23787    });
23788    assert!(
23789      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23790      "start must wait for the zero-backend shutdown transition to finish"
23791    );
23792
23793    release_drop_tx.send(()).unwrap();
23794    submitter.join().unwrap();
23795    shutdown_rx
23796      .recv_timeout(Duration::from_secs(2))
23797      .unwrap()
23798      .unwrap();
23799    shutdown.join().unwrap();
23800    start_rx
23801      .recv_timeout(Duration::from_secs(2))
23802      .unwrap()
23803      .unwrap();
23804    starter.join().unwrap();
23805
23806    let ran = Arc::new(AtomicBool::new(false));
23807    let ran_task = Arc::clone(&ran);
23808    controller
23809      .try_spawn_detached(async move {
23810        ran_task.store(true, Ordering::SeqCst);
23811      })
23812      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
23813    let dispatch = controller_current_thread_dispatch(&controller);
23814    controller.drive_current_thread_dispatch(dispatch, false);
23815    assert!(ran.load(Ordering::SeqCst));
23816    controller.shutdown().unwrap();
23817  }
23818
23819  #[cfg(not(target_family = "wasm"))]
23820  #[test]
23821  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
23822    use std::sync::mpsc;
23823
23824    struct BlockingDropFuture {
23825      entered: mpsc::Sender<()>,
23826      release: mpsc::Receiver<()>,
23827    }
23828
23829    impl Future for BlockingDropFuture {
23830      type Output = ();
23831
23832      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23833        Poll::Pending
23834      }
23835    }
23836
23837    impl Drop for BlockingDropFuture {
23838      fn drop(&mut self) {
23839        self.entered.send(()).unwrap();
23840        self.release.recv().unwrap();
23841      }
23842    }
23843
23844    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
23845    let old_generation = controller.backend().generation();
23846    controller.shutdown().unwrap();
23847
23848    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23849    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23850    let submit_controller = Arc::clone(&controller);
23851    let submitter = std::thread::spawn(move || {
23852      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23853        entered: drop_entered_tx,
23854        release: release_drop_rx,
23855      }));
23856      assert!(
23857        result.is_err(),
23858        "the stopped runtime must reject the submission"
23859      );
23860    });
23861    drop_entered_rx
23862      .recv_timeout(Duration::from_secs(2))
23863      .unwrap();
23864
23865    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23866    let shutdown_controller = Arc::clone(&controller);
23867    let shutdown = std::thread::spawn(move || {
23868      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23869    });
23870    wait_until(
23871      "stopped shutdown to enter its zero-backend transition",
23872      || {
23873        matches!(
23874          controller
23875            .state
23876            .lock()
23877            .unwrap_or_else(std::sync::PoisonError::into_inner)
23878            .lifecycle,
23879          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
23880        )
23881      },
23882    );
23883
23884    let (start_tx, start_rx) = mpsc::channel();
23885    let start_controller = Arc::clone(&controller);
23886    let starter = std::thread::spawn(move || {
23887      start_tx.send(start_controller.start()).unwrap();
23888    });
23889    assert!(
23890      shutdown_rx
23891        .recv_timeout(Duration::from_millis(200))
23892        .is_err(),
23893      "stopped shutdown must wait for rejected destruction"
23894    );
23895    assert!(
23896      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23897      "restart must wait for the stopped zero-backend transition"
23898    );
23899
23900    release_drop_tx.send(()).unwrap();
23901    submitter.join().unwrap();
23902    shutdown_rx
23903      .recv_timeout(Duration::from_secs(2))
23904      .unwrap()
23905      .unwrap();
23906    shutdown.join().unwrap();
23907    start_rx
23908      .recv_timeout(Duration::from_secs(2))
23909      .unwrap()
23910      .unwrap();
23911    starter.join().unwrap();
23912
23913    let new_generation = controller.backend().generation();
23914    assert_ne!(new_generation, old_generation);
23915    controller.shutdown().unwrap();
23916  }
23917
23918  #[test]
23919  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
23920    use std::sync::mpsc;
23921
23922    struct ShutdownOnDrop {
23923      controller: Arc<RuntimeController>,
23924      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23925    }
23926
23927    impl Future for ShutdownOnDrop {
23928      type Output = ();
23929
23930      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23931        Poll::Pending
23932      }
23933    }
23934
23935    impl Drop for ShutdownOnDrop {
23936      fn drop(&mut self) {
23937        self.result.send(self.controller.shutdown()).unwrap();
23938      }
23939    }
23940
23941    let controller = Arc::new(current_thread_controller(
23942      "failed-initial-reentrant-shutdown",
23943    ));
23944    let (result_tx, result_rx) = mpsc::channel();
23945    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23946    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
23947      controller: Arc::clone(&controller),
23948      result: result_tx,
23949    }));
23950    assert!(rejected.is_err());
23951
23952    let error = result_rx
23953      .recv_timeout(Duration::from_secs(1))
23954      .expect("the rejected destructor must not self-deadlock")
23955      .expect_err("rejected destruction cannot wait for its own shutdown");
23956    assert!(error.to_string().contains("rejected submission"));
23957    {
23958      let state = controller
23959        .state
23960        .lock()
23961        .unwrap_or_else(std::sync::PoisonError::into_inner);
23962      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23963      assert_eq!(state.rejected_drops, 0);
23964    }
23965
23966    let handle = controller
23967      .try_spawn(async { 23usize })
23968      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
23969    let dispatch = controller_current_thread_dispatch(&controller);
23970    controller.drive_current_thread_dispatch(dispatch, false);
23971    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
23972    controller.shutdown().unwrap();
23973  }
23974
23975  #[test]
23976  fn failed_initial_submission_destructor_cannot_reenter_start() {
23977    use std::sync::mpsc;
23978
23979    struct StartOnDrop {
23980      controller: Arc<RuntimeController>,
23981      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23982      release: mpsc::Receiver<()>,
23983    }
23984
23985    impl Future for StartOnDrop {
23986      type Output = ();
23987
23988      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23989        Poll::Pending
23990      }
23991    }
23992
23993    impl Drop for StartOnDrop {
23994      fn drop(&mut self) {
23995        self.result.send(self.controller.start()).unwrap();
23996        self.release.recv().unwrap();
23997      }
23998    }
23999
24000    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
24001    let (result_tx, result_rx) = mpsc::channel();
24002    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24003    let (submission_tx, submission_rx) = mpsc::channel();
24004    let submit_controller = Arc::clone(&controller);
24005    let submitter = std::thread::spawn(move || {
24006      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24007      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
24008        controller: Arc::clone(&submit_controller),
24009        result: result_tx,
24010        release: release_drop_rx,
24011      }));
24012      submission_tx.send(rejected).unwrap();
24013    });
24014
24015    let error = result_rx
24016      .recv_timeout(Duration::from_secs(1))
24017      .expect("the rejected destructor must not self-deadlock")
24018      .expect_err("rejected destruction must not report a successful runtime start");
24019    assert!(error.to_string().contains("rejected submission"));
24020    {
24021      let state = controller
24022        .state
24023        .lock()
24024        .unwrap_or_else(std::sync::PoisonError::into_inner);
24025      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24026      assert_eq!(state.rejected_drops, 1);
24027    }
24028
24029    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
24030    let (external_start_tx, external_start_rx) = mpsc::channel();
24031    let start_controller = Arc::clone(&controller);
24032    let starter = std::thread::spawn(move || {
24033      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
24034        *slot.borrow_mut() = Some(Box::new(move || {
24035          wait_entered_tx.send(()).unwrap();
24036        }));
24037      });
24038      external_start_tx.send(start_controller.start()).unwrap();
24039    });
24040    wait_entered_rx
24041      .recv_timeout(Duration::from_secs(1))
24042      .expect("external start must reach the Initial rejected-destruction wait");
24043    assert!(
24044      external_start_rx.try_recv().is_err(),
24045      "external start must not return while rejected destruction remains active"
24046    );
24047
24048    release_drop_tx.send(()).unwrap();
24049    assert!(
24050      submission_rx
24051        .recv_timeout(Duration::from_secs(2))
24052        .unwrap()
24053        .is_err()
24054    );
24055    submitter.join().unwrap();
24056    external_start_rx
24057      .recv_timeout(Duration::from_secs(2))
24058      .unwrap()
24059      .unwrap();
24060    starter.join().unwrap();
24061
24062    {
24063      let state = controller
24064        .state
24065        .lock()
24066        .unwrap_or_else(std::sync::PoisonError::into_inner);
24067      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24068      assert_eq!(state.rejected_drops, 0);
24069    }
24070
24071    let handle = controller
24072      .try_spawn(async { 31usize })
24073      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
24074    let dispatch = controller_current_thread_dispatch(&controller);
24075    controller.drive_current_thread_dispatch(dispatch, false);
24076    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
24077    controller.shutdown().unwrap();
24078  }
24079
24080  #[test]
24081  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
24082    use std::sync::mpsc;
24083
24084    struct BlockingDropFuture {
24085      controller: Arc<RuntimeController>,
24086      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24087      release: mpsc::Receiver<()>,
24088    }
24089
24090    impl Future for BlockingDropFuture {
24091      type Output = ();
24092
24093      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24094        Poll::Pending
24095      }
24096    }
24097
24098    impl Drop for BlockingDropFuture {
24099      fn drop(&mut self) {
24100        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
24101        self
24102          .entered
24103          .send((generation, self.controller.shutdown()))
24104          .unwrap();
24105        self.release.recv().unwrap();
24106      }
24107    }
24108
24109    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
24110    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24111    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24112    let (runner_done_tx, runner_done_rx) = mpsc::channel();
24113    let runner_controller = Arc::clone(&controller);
24114    let runner = std::thread::spawn(move || {
24115      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24116      let result = catch_unwind(AssertUnwindSafe(|| {
24117        block_on_with_controller(
24118          &runner_controller,
24119          BlockingDropFuture {
24120            controller: Arc::clone(&runner_controller),
24121            entered: drop_entered_tx,
24122            release: release_drop_rx,
24123          },
24124        );
24125      }));
24126      runner_done_tx.send(result.is_err()).unwrap();
24127    });
24128
24129    let (generation, reentrant_shutdown) = drop_entered_rx
24130      .recv_timeout(Duration::from_secs(2))
24131      .expect("failed block_on acquisition must destroy its owned future");
24132    assert_eq!(generation, None, "no runtime generation was created");
24133    let error = reentrant_shutdown
24134      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
24135    assert!(error.to_string().contains("rejected submission"));
24136
24137    let Err(retry_error) = controller.try_backend() else {
24138      panic!("backend retry must remain closed during rejected block_on destruction");
24139    };
24140    assert!(retry_error.to_string().contains("rejected submission"));
24141
24142    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24143    let shutdown_controller = Arc::clone(&controller);
24144    let shutdown = std::thread::spawn(move || {
24145      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24146    });
24147    assert!(
24148      shutdown_rx
24149        .recv_timeout(Duration::from_millis(200))
24150        .is_err(),
24151      "initial shutdown must wait for rejected block_on destruction"
24152    );
24153
24154    release_drop_tx.send(()).unwrap();
24155    assert!(
24156      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24157      "block_on must preserve its infallible panic contract after contained cleanup"
24158    );
24159    runner.join().unwrap();
24160    shutdown_rx
24161      .recv_timeout(Duration::from_secs(2))
24162      .unwrap()
24163      .unwrap();
24164    shutdown.join().unwrap();
24165
24166    controller
24167      .start()
24168      .expect("zero-generation shutdown must remain restartable");
24169    let ran = Arc::new(AtomicBool::new(false));
24170    let ran_task = Arc::clone(&ran);
24171    controller
24172      .try_spawn_detached(async move {
24173        ran_task.store(true, Ordering::SeqCst);
24174      })
24175      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
24176    let dispatch = controller_current_thread_dispatch(&controller);
24177    controller.drive_current_thread_dispatch(dispatch, false);
24178    assert!(ran.load(Ordering::SeqCst));
24179    controller.shutdown().unwrap();
24180  }
24181
24182  #[test]
24183  fn public_block_on_contains_poll_and_drop_double_panic() {
24184    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
24185
24186    if std::env::var_os(CHILD_ENV).is_some() {
24187      struct PanicInPollAndDrop;
24188
24189      impl Future for PanicInPollAndDrop {
24190        type Output = ();
24191
24192        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24193          panic!("intentional block_on poll panic");
24194        }
24195      }
24196
24197      impl Drop for PanicInPollAndDrop {
24198        fn drop(&mut self) {
24199          panic!("intentional block_on future destructor panic");
24200        }
24201      }
24202
24203      let controller = current_thread_controller("block-on-double-panic");
24204      assert!(
24205        catch_unwind(AssertUnwindSafe(|| {
24206          block_on_with_controller(&controller, PanicInPollAndDrop);
24207        }))
24208        .is_err(),
24209        "the original poll panic must still reach the caller"
24210      );
24211      controller
24212        .shutdown()
24213        .expect("contained future destruction must not strand the runtime");
24214      return;
24215    }
24216
24217    let output = std::process::Command::new(std::env::current_exe().unwrap())
24218      .arg("--exact")
24219      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
24220      .arg("--nocapture")
24221      .env(CHILD_ENV, "1")
24222      .output()
24223      .expect("the subprocess test must start");
24224    assert!(
24225      output.status.success(),
24226      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
24227      output.status.code(),
24228      String::from_utf8_lossy(&output.stdout),
24229      String::from_utf8_lossy(&output.stderr)
24230    );
24231  }
24232
24233  #[test]
24234  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
24235    let controller = RuntimeController::new();
24236    controller
24237      .start()
24238      .expect("initial lifecycle start must succeed");
24239    controller
24240      .shutdown()
24241      .expect("zero-work lifecycle shutdown must succeed");
24242    controller
24243      .start()
24244      .expect("zero-work lifecycle restart must remain lazy");
24245    controller
24246      .configure(RuntimeOptions {
24247        flavor: RuntimeFlavor::CurrentThread,
24248        worker_threads: 1,
24249        max_blocking_tasks: 1,
24250        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
24251        park_deadline: None,
24252        drain_linger: DEFAULT_DRAIN_LINGER,
24253      })
24254      .expect("configuration before first real async use must succeed");
24255
24256    let first_generation = controller.backend().generation();
24257    controller
24258      .shutdown()
24259      .expect("the first real generation must stop");
24260    assert!(matches!(
24261      &controller
24262        .state
24263        .lock()
24264        .unwrap_or_else(std::sync::PoisonError::into_inner)
24265        .lifecycle,
24266      RuntimeLifecycle::Stopped
24267    ));
24268    let error = controller
24269      .configure(RuntimeOptions::default())
24270      .expect_err("a real generation must permanently freeze configuration");
24271    assert_eq!(
24272      error.to_string(),
24273      "the async runtime configuration is frozen; configure it before the first async call"
24274    );
24275
24276    controller
24277      .start()
24278      .expect("the stopped real generation must restart");
24279    let restarted_generation = {
24280      let state = controller
24281        .state
24282        .lock()
24283        .unwrap_or_else(std::sync::PoisonError::into_inner);
24284      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
24285        panic!("a real generation restart must eagerly create its replacement backend");
24286      };
24287      backend.generation()
24288    };
24289    assert_ne!(
24290      restarted_generation, first_generation,
24291      "a real generation restart must not reuse the retired backend"
24292    );
24293    controller
24294      .shutdown()
24295      .expect("the replacement generation must stop");
24296  }
24297
24298  #[test]
24299  fn runtime_controller_rejects_submissions_after_shutdown() {
24300    let controller = current_thread_controller("lifecycle-reject");
24301    controller.start().expect("runtime must start");
24302    drop(controller.backend());
24303    controller.shutdown().expect("runtime must stop");
24304
24305    let async_ran = Arc::new(AtomicBool::new(false));
24306    let async_ran_task = Arc::clone(&async_ran);
24307    let Err(rejected_future) = controller.try_spawn_detached(async move {
24308      async_ran_task.store(true, Ordering::SeqCst);
24309    }) else {
24310      panic!("shutdown must reject async work");
24311    };
24312    assert!(
24313      !async_ran.load(Ordering::SeqCst),
24314      "a rejected future must not be polled"
24315    );
24316    futures::executor::block_on(rejected_future);
24317    assert!(
24318      async_ran.load(Ordering::SeqCst),
24319      "the original future must remain usable"
24320    );
24321
24322    let blocking_ran = Arc::new(AtomicBool::new(false));
24323    let blocking_ran_task = Arc::clone(&blocking_ran);
24324    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
24325      blocking_ran_task.store(true, Ordering::SeqCst);
24326      7
24327    }) else {
24328      panic!("shutdown must reject blocking work");
24329    };
24330    assert_eq!(
24331      blocking_error.to_string(),
24332      "the async runtime is stopped; call start before submitting work"
24333    );
24334    assert!(
24335      !blocking_ran.load(Ordering::SeqCst),
24336      "rejected blocking work must not run"
24337    );
24338    assert_eq!(
24339      rejected_work(),
24340      7,
24341      "the original closure must remain usable"
24342    );
24343
24344    let config_error = controller
24345      .configure(RuntimeOptions::default())
24346      .expect_err("shutdown must not make runtime configuration mutable again");
24347    assert_eq!(
24348      config_error.to_string(),
24349      "the async runtime configuration is frozen; configure it before the first async call"
24350    );
24351
24352    let state = controller
24353      .state
24354      .lock()
24355      .unwrap_or_else(std::sync::PoisonError::into_inner);
24356    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
24357  }
24358
24359  #[test]
24360  fn rejected_future_can_be_resubmitted_after_restart() {
24361    let controller = current_thread_controller("lifecycle-retry-future");
24362    controller.start().expect("runtime must start");
24363    drop(controller.backend());
24364    controller.shutdown().expect("runtime must stop");
24365
24366    let runs = Arc::new(AtomicUsize::new(0));
24367    let runs_task = Arc::clone(&runs);
24368    let future = async move {
24369      runs_task.fetch_add(1, Ordering::SeqCst);
24370      23
24371    };
24372    let Err((error, future)) = controller.try_spawn(future) else {
24373      panic!("stopped runtime must reject the future");
24374    };
24375    assert!(error.to_string().contains("runtime is stopped"));
24376    assert_eq!(runs.load(Ordering::SeqCst), 0);
24377
24378    controller.start().expect("runtime must restart");
24379    let handle = controller
24380      .try_spawn(future)
24381      .unwrap_or_else(|_| panic!("restart must accept retained future"));
24382    let dispatch = controller_current_thread_dispatch(&controller);
24383    controller.drive_current_thread_dispatch(dispatch, false);
24384    assert_eq!(
24385      futures::executor::block_on(handle).expect("retained future must complete"),
24386      23
24387    );
24388    assert_eq!(runs.load(Ordering::SeqCst), 1);
24389    controller
24390      .shutdown()
24391      .expect("replacement runtime must stop");
24392  }
24393
24394  #[test]
24395  fn runtime_controller_start_after_shutdown_accepts_work() {
24396    let controller = current_thread_controller("lifecycle-restart");
24397    controller.start().expect("runtime must start");
24398    drop(controller.backend());
24399    controller.shutdown().expect("runtime must stop");
24400    controller.start().expect("runtime must restart");
24401
24402    let async_ran = Arc::new(AtomicBool::new(false));
24403    let async_ran_task = Arc::clone(&async_ran);
24404    controller
24405      .try_spawn_detached(async move {
24406        async_ran_task.store(true, Ordering::SeqCst);
24407      })
24408      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
24409    assert!(
24410      !async_ran.load(Ordering::SeqCst),
24411      "hostless CurrentThread submission must remain enqueue-only"
24412    );
24413    let dispatch = controller_current_thread_dispatch(&controller);
24414    controller.drive_current_thread_dispatch(dispatch, false);
24415    assert!(
24416      async_ran.load(Ordering::SeqCst),
24417      "restarted runtime must poll submitted work"
24418    );
24419
24420    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
24421      panic!("restarted runtime must accept blocking work");
24422    };
24423    assert_eq!(
24424      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
24425      11
24426    );
24427  }
24428
24429  #[test]
24430  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
24431    let controller = current_thread_controller("stale-host-generation");
24432    let old_generation = controller.backend().generation();
24433    let old_dispatch = controller_current_thread_dispatch(&controller);
24434    controller.shutdown().unwrap();
24435    controller.start().unwrap();
24436    let new_generation = controller.backend().generation();
24437    assert_ne!(new_generation, old_generation);
24438
24439    let ran = Arc::new(AtomicBool::new(false));
24440    let ran_task = Arc::clone(&ran);
24441    controller
24442      .try_spawn_detached(async move {
24443        ran_task.store(true, Ordering::SeqCst);
24444      })
24445      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
24446    let new_dispatch = controller_current_thread_dispatch(&controller);
24447    assert_ne!(new_dispatch, old_dispatch);
24448
24449    controller.drive_current_thread_dispatch(old_dispatch, false);
24450    assert!(
24451      !ran.load(Ordering::SeqCst),
24452      "a callback from the retired generation must not touch the restarted executor"
24453    );
24454    controller.drive_current_thread_dispatch(new_dispatch, false);
24455    assert!(ran.load(Ordering::SeqCst));
24456    controller.shutdown().unwrap();
24457  }
24458
24459  #[test]
24460  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
24461    let controller = current_thread_controller("stale-host-cancellation");
24462    let old_generation = controller.backend().generation();
24463    let old_dispatch = controller_current_thread_dispatch(&controller);
24464    controller.shutdown().unwrap();
24465    controller.start().unwrap();
24466    let backend = controller.backend();
24467    let new_generation = backend.generation();
24468    assert_ne!(new_generation, old_generation);
24469    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24470      panic!("configured CurrentThread backend must create a CurrentThread executor");
24471    };
24472    let new_dispatch = current_thread_dispatch(executor);
24473    assert_ne!(new_dispatch, old_dispatch);
24474
24475    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
24476    assert_eq!(
24477      executor.dispatch_pending.load(Ordering::Acquire),
24478      new_dispatch,
24479      "a delayed failure from the retired generation must not release the replacement latch"
24480    );
24481
24482    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
24483    assert_eq!(
24484      executor.dispatch_pending.load(Ordering::Acquire),
24485      0,
24486      "the live generation must be able to cancel its accepted dispatch"
24487    );
24488    drop(backend);
24489    controller.shutdown().unwrap();
24490  }
24491
24492  #[cfg(not(target_family = "wasm"))]
24493  #[test]
24494  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
24495    use std::sync::mpsc;
24496
24497    type ProbeResult = (
24498      &'static str,
24499      Option<u64>,
24500      Result<(), String>,
24501      Result<(), String>,
24502      Result<(), String>,
24503    );
24504
24505    struct StaleGenerationProbe {
24506      label: &'static str,
24507      controller: Arc<RuntimeController>,
24508      result: mpsc::Sender<ProbeResult>,
24509    }
24510
24511    impl Drop for StaleGenerationProbe {
24512      fn drop(&mut self) {
24513        let generation = active_runtime_generation();
24514        let submission = match self.controller.try_spawn(async {}) {
24515          Ok(handle) => {
24516            drop(handle);
24517            Ok(())
24518          }
24519          Err((error, future)) => {
24520            drop(future);
24521            Err(error.to_string())
24522          }
24523        };
24524        let start = self.controller.start().map_err(|error| error.to_string());
24525        let shutdown = self
24526          .controller
24527          .shutdown()
24528          .map_err(|error| error.to_string());
24529        self
24530          .result
24531          .send((self.label, generation, submission, start, shutdown))
24532          .unwrap();
24533      }
24534    }
24535
24536    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
24537    let old_generation = controller.backend().generation();
24538    let (result_tx, result_rx) = mpsc::channel();
24539
24540    let async_controller = Arc::clone(&controller);
24541    let async_result = result_tx.clone();
24542    let async_handle = controller
24543      .try_spawn(async move {
24544        StaleGenerationProbe {
24545          label: "async",
24546          controller: async_controller,
24547          result: async_result,
24548        }
24549      })
24550      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
24551    let old_dispatch = controller_current_thread_dispatch(&controller);
24552    controller.drive_current_thread_dispatch(old_dispatch, false);
24553
24554    let ready_controller = Arc::clone(&controller);
24555    let ready_handle = controller
24556      .try_spawn_blocking(move || StaleGenerationProbe {
24557        label: "ready",
24558        controller: ready_controller,
24559        result: result_tx,
24560      })
24561      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
24562
24563    controller.shutdown().unwrap();
24564    controller.start().unwrap();
24565    let new_generation = controller.backend().generation();
24566    assert_ne!(new_generation, old_generation);
24567
24568    drop(async_handle);
24569    let async_result = result_rx
24570      .recv_timeout(Duration::from_secs(2))
24571      .expect("the retained async output must be destroyed without waiting on the replacement");
24572    drop(ready_handle);
24573    let ready_result = result_rx
24574      .recv_timeout(Duration::from_secs(2))
24575      .expect("the retained immediate output must be destroyed without touching the replacement");
24576
24577    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
24578      let (label, generation, submission, start, shutdown) = result;
24579      assert_eq!(label, expected_label);
24580      assert_eq!(generation, Some(old_generation));
24581      for error in [submission, start, shutdown] {
24582        assert!(
24583          error
24584            .expect_err("retired-generation output destruction must be rejected")
24585            .contains("retired generation")
24586        );
24587      }
24588    }
24589
24590    let survivor = controller
24591      .try_spawn(async { 29usize })
24592      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
24593    let new_dispatch = controller_current_thread_dispatch(&controller);
24594    controller.drive_current_thread_dispatch(new_dispatch, false);
24595    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
24596    controller.shutdown().unwrap();
24597  }
24598
24599  #[cfg(not(target_family = "wasm"))]
24600  #[test]
24601  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
24602    use std::sync::mpsc;
24603
24604    struct ShutdownOnDrop {
24605      controller: Arc<RuntimeController>,
24606      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24607    }
24608
24609    impl Drop for ShutdownOnDrop {
24610      fn drop(&mut self) {
24611        self
24612          .result
24613          .send((active_runtime_generation(), self.controller.shutdown()))
24614          .unwrap();
24615      }
24616    }
24617
24618    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
24619    let old_generation = controller.backend().generation();
24620    let (result_tx, result_rx) = mpsc::channel();
24621    let old_handle = controller
24622      .try_spawn_blocking({
24623        let controller = Arc::clone(&controller);
24624        move || ShutdownOnDrop {
24625          controller,
24626          result: result_tx,
24627        }
24628      })
24629      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
24630    wait_until(
24631      "the first-generation blocking result to be buffered",
24632      || {
24633        controller
24634          .metrics
24635          .blocking_tasks_completed
24636          .load(Ordering::Acquire)
24637          == 1
24638      },
24639    );
24640    controller.shutdown().unwrap();
24641    controller.start().unwrap();
24642    let new_generation = controller.backend().generation();
24643    assert_ne!(new_generation, old_generation);
24644
24645    let (holder_started_tx, holder_started_rx) = mpsc::channel();
24646    let (release_holder_tx, release_holder_rx) = mpsc::channel();
24647    let holder = controller
24648      .try_spawn_blocking(move || {
24649        holder_started_tx.send(()).unwrap();
24650        release_holder_rx.recv().unwrap();
24651      })
24652      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
24653    holder_started_rx
24654      .recv_timeout(Duration::from_secs(2))
24655      .unwrap();
24656
24657    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24658    let shutdown_controller = Arc::clone(&controller);
24659    let shutdown = std::thread::spawn(move || {
24660      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24661    });
24662    wait_until("the replacement generation to enter Stopping", || {
24663      matches!(
24664        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
24665        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
24666      )
24667    });
24668
24669    let dropper = std::thread::spawn(move || drop(old_handle));
24670    let (observed_generation, reentrant_shutdown) =
24671      match result_rx.recv_timeout(Duration::from_secs(2)) {
24672        Ok(result) => result,
24673        Err(error) => {
24674          release_holder_tx.send(()).unwrap();
24675          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
24676          let _ = dropper.join();
24677          let _ = shutdown.join();
24678          panic!("retained first-generation output waited on replacement shutdown: {error}");
24679        }
24680      };
24681    assert_eq!(observed_generation, Some(old_generation));
24682    let error = reentrant_shutdown
24683      .expect_err("a retained first-generation output must not control replacement shutdown");
24684    assert!(error.to_string().contains("retired generation"));
24685
24686    release_holder_tx.send(()).unwrap();
24687    shutdown_rx
24688      .recv_timeout(Duration::from_secs(2))
24689      .unwrap()
24690      .unwrap();
24691    dropper.join().unwrap();
24692    shutdown.join().unwrap();
24693    futures::executor::block_on(holder).unwrap();
24694  }
24695
24696  #[cfg(not(target_family = "wasm"))]
24697  #[test]
24698  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
24699    use std::sync::mpsc;
24700
24701    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
24702    let generation = controller.backend().generation();
24703    let dispatch = controller_current_thread_dispatch(&controller);
24704    let (admitted_tx, admitted_rx) = mpsc::channel();
24705    let (release_tx, release_rx) = mpsc::channel();
24706    let driver_controller = Arc::clone(&controller);
24707    let driver = std::thread::spawn(move || {
24708      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
24709        *slot.borrow_mut() = Some(Box::new(move || {
24710          admitted_tx.send(()).unwrap();
24711          release_rx.recv().unwrap();
24712        }));
24713      });
24714      driver_controller.drive_current_thread_dispatch(dispatch, false);
24715    });
24716    admitted_rx
24717      .recv_timeout(Duration::from_secs(2))
24718      .expect("the host turn must claim its scheduler role before execution");
24719
24720    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24721    let shutdown_controller = Arc::clone(&controller);
24722    let shutdown = std::thread::spawn(move || {
24723      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24724    });
24725    wait_until(
24726      "shutdown to close the admitted CurrentThread generation",
24727      || {
24728        let state = controller
24729          .state
24730          .lock()
24731          .unwrap_or_else(std::sync::PoisonError::into_inner);
24732        matches!(
24733          &state.lifecycle,
24734          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
24735        )
24736      },
24737    );
24738    assert!(
24739      shutdown_rx
24740        .recv_timeout(Duration::from_millis(200))
24741        .is_err(),
24742      "shutdown must wait for an admitted host callback even before it starts draining"
24743    );
24744
24745    release_tx.send(()).unwrap();
24746    driver.join().unwrap();
24747    shutdown_rx
24748      .recv_timeout(Duration::from_secs(2))
24749      .unwrap()
24750      .unwrap();
24751    shutdown.join().unwrap();
24752  }
24753
24754  #[test]
24755  fn spawn_racing_shutdown_cannot_recreate_backend() {
24756    use std::sync::mpsc;
24757
24758    let controller = Arc::new(current_thread_controller("lifecycle-race"));
24759    controller.start().expect("runtime must start");
24760    drop(controller.backend());
24761
24762    let (at_hook_tx, at_hook_rx) = mpsc::channel();
24763    let (release_tx, release_rx) = mpsc::channel();
24764    let ran = Arc::new(AtomicBool::new(false));
24765    let ran_task = Arc::clone(&ran);
24766    let worker_controller = Arc::clone(&controller);
24767    let worker = std::thread::spawn(move || {
24768      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
24769        *slot.borrow_mut() = Some(Box::new(move || {
24770          at_hook_tx
24771            .send(())
24772            .expect("test coordinator must still be listening");
24773          release_rx
24774            .recv()
24775            .expect("test coordinator must release the submission");
24776        }));
24777      });
24778
24779      let future = async move {
24780        ran_task.store(true, Ordering::SeqCst);
24781      };
24782      match worker_controller.try_spawn_detached(future) {
24783        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
24784        Err(future) => drop(future),
24785      }
24786    });
24787
24788    at_hook_rx
24789      .recv_timeout(Duration::from_secs(5))
24790      .expect("submitting thread must stop before taking the controller lock");
24791    controller.shutdown().expect("runtime must stop");
24792    release_tx
24793      .send(())
24794      .expect("submitting thread must still be waiting");
24795    worker.join().expect("submitting thread must not panic");
24796
24797    assert!(
24798      !ran.load(Ordering::SeqCst),
24799      "the rejected task must never run"
24800    );
24801    let state = controller
24802      .state
24803      .lock()
24804      .unwrap_or_else(std::sync::PoisonError::into_inner);
24805    assert!(
24806      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
24807      "the rejected submission must not lazily recreate a backend"
24808    );
24809  }
24810
24811  #[cfg(not(target_family = "wasm"))]
24812  #[test]
24813  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
24814    use std::sync::mpsc;
24815
24816    struct BlockingDropFuture {
24817      work: Arc<GenerationWork>,
24818      entered: mpsc::Sender<(Option<u64>, usize)>,
24819      release: mpsc::Receiver<()>,
24820    }
24821
24822    impl Future for BlockingDropFuture {
24823      type Output = ();
24824
24825      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24826        Poll::Pending
24827      }
24828    }
24829
24830    impl Drop for BlockingDropFuture {
24831      fn drop(&mut self) {
24832        let active = self
24833          .work
24834          .state
24835          .lock()
24836          .unwrap_or_else(std::sync::PoisonError::into_inner)
24837          .active;
24838        self
24839          .entered
24840          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24841          .unwrap();
24842        self.release.recv().unwrap();
24843      }
24844    }
24845
24846    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
24847    let backend = controller.backend();
24848    let old_generation = backend.generation();
24849    let work = Arc::clone(&backend.work);
24850    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24851      panic!("configured CurrentThread backend must create a CurrentThread executor");
24852    };
24853    let executor = Arc::clone(executor);
24854    drop(backend);
24855
24856    let (registered_tx, registered_rx) = mpsc::channel();
24857    let (release_submission_tx, release_submission_rx) = mpsc::channel();
24858    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24859    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24860    let submit_controller = Arc::clone(&controller);
24861    let submit_work = Arc::clone(&work);
24862    let submitter = std::thread::spawn(move || {
24863      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24864        *slot.borrow_mut() = Some(Box::new(move || {
24865          registered_tx.send(()).unwrap();
24866          release_submission_rx.recv().unwrap();
24867        }));
24868      });
24869      submit_controller
24870        .try_spawn_detached(BlockingDropFuture {
24871          work: submit_work,
24872          entered: drop_entered_tx,
24873          release: release_drop_rx,
24874        })
24875        .unwrap_or_else(|_| {
24876          panic!("the submission linearized before shutdown and must be accepted")
24877        });
24878    });
24879    registered_rx
24880      .recv_timeout(Duration::from_secs(2))
24881      .expect("the submitting thread must register generation work before shutdown");
24882
24883    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24884    let shutdown_controller = Arc::clone(&controller);
24885    let shutdown = std::thread::spawn(move || {
24886      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24887    });
24888    wait_until("CurrentThread shutdown to close the runnable queue", || {
24889      executor
24890        .queue
24891        .lock()
24892        .unwrap_or_else(std::sync::PoisonError::into_inner)
24893        .closed
24894    });
24895    release_submission_tx.send(()).unwrap();
24896
24897    let (drop_generation, active_during_drop) = drop_entered_rx
24898      .recv_timeout(Duration::from_secs(2))
24899      .expect("the closed queue must destroy the accepted unpolled future");
24900    assert_eq!(drop_generation, Some(old_generation));
24901    assert_eq!(
24902      active_during_drop, 1,
24903      "accepted generation work must remain registered through user future destruction"
24904    );
24905
24906    let (start_tx, start_rx) = mpsc::channel();
24907    let start_controller = Arc::clone(&controller);
24908    let starter = std::thread::spawn(move || {
24909      start_tx.send(start_controller.start()).unwrap();
24910    });
24911    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24912    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
24913    let shutdown_overlapped = early_shutdown.is_some();
24914    let restart_overlapped = early_start.is_some();
24915
24916    release_drop_tx.send(()).unwrap();
24917    submitter.join().unwrap();
24918    let shutdown_result = early_shutdown.unwrap_or_else(|| {
24919      shutdown_rx
24920        .recv_timeout(Duration::from_secs(2))
24921        .expect("shutdown must finish after drop")
24922    });
24923    shutdown_result.unwrap();
24924    shutdown.join().unwrap();
24925    let start_result = early_start.unwrap_or_else(|| {
24926      start_rx
24927        .recv_timeout(Duration::from_secs(2))
24928        .expect("restart must follow shutdown")
24929    });
24930    start_result.unwrap();
24931    starter.join().unwrap();
24932
24933    assert!(
24934      !shutdown_overlapped,
24935      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
24936    );
24937    assert!(
24938      !restart_overlapped,
24939      "restart must not create a new generation while an accepted old-generation future is being destroyed"
24940    );
24941    assert_ne!(controller.backend().generation(), old_generation);
24942    controller.shutdown().unwrap();
24943  }
24944
24945  #[cfg(not(target_family = "wasm"))]
24946  #[test]
24947  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
24948    use std::sync::mpsc;
24949
24950    struct BlockingDrop {
24951      work: Arc<GenerationWork>,
24952      entered: mpsc::Sender<(Option<u64>, usize)>,
24953      release: mpsc::Receiver<()>,
24954    }
24955
24956    impl Drop for BlockingDrop {
24957      fn drop(&mut self) {
24958        let active = self
24959          .work
24960          .state
24961          .lock()
24962          .unwrap_or_else(std::sync::PoisonError::into_inner)
24963          .active;
24964        self
24965          .entered
24966          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24967          .unwrap();
24968        self.release.recv().unwrap();
24969      }
24970    }
24971
24972    let controller = Arc::new(multi_thread_controller(
24973      "accepted-blocking-submission-drop",
24974      2,
24975      1,
24976    ));
24977    let backend = controller.backend();
24978    let old_generation = backend.generation();
24979    let work = Arc::clone(&backend.work);
24980    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
24981      panic!("configured MultiThread backend must create a MultiThread executor");
24982    };
24983    let executor = Arc::downgrade(executor);
24984    drop(backend);
24985
24986    let (registered_tx, registered_rx) = mpsc::channel();
24987    let (release_submission_tx, release_submission_rx) = mpsc::channel();
24988    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24989    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24990    let submit_controller = Arc::clone(&controller);
24991    let submitter = std::thread::spawn(move || {
24992      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24993        *slot.borrow_mut() = Some(Box::new(move || {
24994          registered_tx.send(()).unwrap();
24995          release_submission_rx.recv().unwrap();
24996        }));
24997      });
24998      let drop_signal = BlockingDrop {
24999        work,
25000        entered: drop_entered_tx,
25001        release: release_drop_rx,
25002      };
25003      let handle = submit_controller
25004        .try_spawn_blocking(move || {
25005          let _drop_signal = drop_signal;
25006        })
25007        .unwrap_or_else(|_| {
25008          panic!("the submission linearized before shutdown and must be accepted")
25009        });
25010      assert!(
25011        futures::executor::block_on(handle).is_err(),
25012        "the executor must cancel work that reaches its queue after shutdown closed it"
25013      );
25014    });
25015    registered_rx
25016      .recv_timeout(Duration::from_secs(2))
25017      .expect("the submitting thread must register generation work before shutdown");
25018
25019    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25020    let shutdown_controller = Arc::clone(&controller);
25021    let shutdown = std::thread::spawn(move || {
25022      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25023    });
25024    wait_until("MultiThread shutdown to close the blocking queue", || {
25025      executor
25026        .upgrade()
25027        .expect("the retiring executor must remain alive until shutdown completes")
25028        .blocking_queue
25029        .lock()
25030        .unwrap_or_else(std::sync::PoisonError::into_inner)
25031        .closed
25032    });
25033    release_submission_tx.send(()).unwrap();
25034
25035    let (drop_generation, active_during_drop) = drop_entered_rx
25036      .recv_timeout(Duration::from_secs(2))
25037      .expect("the closed queue must destroy the accepted blocking closure");
25038    assert_eq!(drop_generation, Some(old_generation));
25039    assert_eq!(
25040      active_during_drop, 1,
25041      "accepted generation work must remain registered through blocking closure destruction"
25042    );
25043
25044    let (start_tx, start_rx) = mpsc::channel();
25045    let start_controller = Arc::clone(&controller);
25046    let starter = std::thread::spawn(move || {
25047      start_tx.send(start_controller.start()).unwrap();
25048    });
25049    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
25050    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
25051    let shutdown_overlapped = early_shutdown.is_some();
25052    let restart_overlapped = early_start.is_some();
25053
25054    release_drop_tx.send(()).unwrap();
25055    submitter.join().unwrap();
25056    let shutdown_result = early_shutdown.unwrap_or_else(|| {
25057      shutdown_rx
25058        .recv_timeout(Duration::from_secs(2))
25059        .expect("shutdown must finish after drop")
25060    });
25061    shutdown_result.unwrap();
25062    shutdown.join().unwrap();
25063    let start_result = early_start.unwrap_or_else(|| {
25064      start_rx
25065        .recv_timeout(Duration::from_secs(2))
25066        .expect("restart must follow shutdown")
25067    });
25068    start_result.unwrap();
25069    starter.join().unwrap();
25070
25071    assert!(
25072      !shutdown_overlapped,
25073      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
25074    );
25075    assert!(
25076      !restart_overlapped,
25077      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
25078    );
25079    assert_ne!(controller.backend().generation(), old_generation);
25080    controller.shutdown().unwrap();
25081  }
25082
25083  #[cfg(not(target_family = "wasm"))]
25084  #[test]
25085  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
25086    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
25087    let options = controller.options();
25088    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
25089
25090    let backend = controller.backend();
25091    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25092      panic!("configured MultiThread backend must create a Rayon executor");
25093    };
25094    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
25095    let lifecycle = Arc::clone(&executor.worker_lifecycle);
25096    drop(backend);
25097
25098    controller.shutdown().expect("runtime must stop");
25099    assert_eq!(
25100      lifecycle.remaining(),
25101      0,
25102      "shutdown must observe every physical worker exit"
25103    );
25104  }
25105
25106  #[cfg(not(target_family = "wasm"))]
25107  #[test]
25108  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
25109    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
25110    // includes worker TLS-destructor completion -- `wait_for_all_workers`
25111    // joins the OS threads, and an OS thread only becomes joinable after its
25112    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
25113    // worker's destructor and sampling the counter on the shutdown thread at
25114    // the very instant `shutdown()` returns: a barrier that excluded the
25115    // destructors would race that sample and fail loudly.
25116    //
25117    // The destructor must never do more than record. The previous shape
25118    // parked it on a condvar until the harness released it -- on Windows TLS
25119    // destructors run under the process loader lock, so the first parked
25120    // worker blocked the second from even ENTERING its destructor, the
25121    // release condition (both entered) became unsatisfiable, and the parked
25122    // destructor froze every thread spawn/exit in the process: the
25123    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
25124    // `WorkerTlsDropAction`).
25125    use std::sync::mpsc;
25126
25127    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
25128    let backend = controller.backend();
25129    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25130      panic!("configured MultiThread backend must create a Rayon executor");
25131    };
25132    let worker_count = executor.pool.current_num_threads();
25133    let completed = Arc::new(AtomicUsize::new(0));
25134    let probe_completed = Arc::clone(&completed);
25135    executor.pool.broadcast(move |_| {
25136      WORKER_TLS_DROP_PROBE.with(|slot| {
25137        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25138          action: Some(WorkerTlsDropAction::Record {
25139            completed: Arc::clone(&probe_completed),
25140          }),
25141        });
25142        assert!(
25143          previous.is_none(),
25144          "each worker must install exactly one TLS drop probe"
25145        );
25146      });
25147    });
25148    drop(backend);
25149
25150    assert_eq!(
25151      completed.load(Ordering::SeqCst),
25152      0,
25153      "no worker TLS destructor may run before shutdown retires the workers"
25154    );
25155
25156    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25157    let shutdown_controller = Arc::clone(&controller);
25158    let sample_completed = Arc::clone(&completed);
25159    let shutdown = std::thread::spawn(move || {
25160      let result = shutdown_controller.shutdown();
25161      // Sample on the SAME thread, before any further synchronization could
25162      // mask a barrier gap: at this instant every destructor must already
25163      // have recorded itself.
25164      let seen_at_return = sample_completed.load(Ordering::SeqCst);
25165      shutdown_tx.send((result, seen_at_return)).unwrap();
25166    });
25167    let (shutdown_result, seen_at_return) = shutdown_rx
25168      .recv_timeout(Duration::from_secs(10))
25169      .expect("shutdown must finish once worker TLS destructors have run");
25170    shutdown_result.unwrap();
25171    assert_eq!(
25172      seen_at_return, worker_count,
25173      "every worker TLS destructor must have completed before shutdown() returned: \
25174       the worker-join barrier must include TLS-destructor completion"
25175    );
25176    join_within(
25177      "the shutdown thread exits after teardown",
25178      shutdown,
25179      Duration::from_secs(30),
25180    );
25181  }
25182
25183  #[cfg(not(target_family = "wasm"))]
25184  #[test]
25185  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
25186    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
25187
25188    if std::env::var_os(CHILD_ENV).is_some() {
25189      use std::sync::mpsc;
25190
25191      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
25192      let backend = controller.backend();
25193      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25194        panic!("configured MultiThread backend must create a Rayon executor");
25195      };
25196      let installed = Arc::new(AtomicBool::new(false));
25197      let (completed_tx, completed_rx) = mpsc::channel();
25198      let probe_controller = Arc::clone(&controller);
25199      let probe_installed = Arc::clone(&installed);
25200      executor.pool.broadcast(move |_| {
25201        if probe_installed.swap(true, Ordering::SeqCst) {
25202          return;
25203        }
25204        WORKER_TLS_DROP_PROBE.with(|slot| {
25205          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25206            action: Some(WorkerTlsDropAction::ReenterLifecycle {
25207              controller: Arc::clone(&probe_controller),
25208              completed: completed_tx.clone(),
25209            }),
25210          });
25211          assert!(
25212            previous.is_none(),
25213            "the selected worker must have no prior TLS probe"
25214          );
25215        });
25216      });
25217      assert!(
25218        installed.load(Ordering::SeqCst),
25219        "one worker must install the lifecycle probe"
25220      );
25221      drop(backend);
25222
25223      controller
25224        .shutdown()
25225        .expect("external shutdown must join the retiring workers");
25226      let (start, shutdown) = completed_rx
25227        .recv_timeout(Duration::from_secs(2))
25228        .expect("the TLS destructor must complete both lifecycle reentry attempts");
25229      assert_eq!(
25230        start.unwrap_err(),
25231        "cannot restart the async runtime from work in the generation being stopped"
25232      );
25233      assert_eq!(
25234        shutdown.unwrap_err(),
25235        "cannot wait for async runtime shutdown from work in the generation being stopped"
25236      );
25237      return;
25238    }
25239
25240    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
25241      .arg("--exact")
25242      .arg(
25243        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
25244      )
25245      .arg("--nocapture")
25246      .env(CHILD_ENV, "1")
25247      .stdout(std::process::Stdio::piped())
25248      .stderr(std::process::Stdio::piped())
25249      .spawn()
25250      .expect("the TLS lifecycle-reentry subprocess must start");
25251    let deadline = Instant::now() + Duration::from_secs(5);
25252    let status = loop {
25253      if let Some(status) = child
25254        .try_wait()
25255        .expect("the subprocess status must be readable")
25256      {
25257        break status;
25258      }
25259      if Instant::now() >= deadline {
25260        let _ = child.kill();
25261        let output = child
25262          .wait_with_output()
25263          .expect("the timed-out subprocess must be reaped");
25264        panic!(
25265          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
25266          String::from_utf8_lossy(&output.stdout),
25267          String::from_utf8_lossy(&output.stderr)
25268        );
25269      }
25270      std::thread::sleep(Duration::from_millis(10));
25271    };
25272    let output = child
25273      .wait_with_output()
25274      .expect("the completed subprocess output must be read");
25275    assert!(
25276      status.success(),
25277      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
25278      status.code(),
25279      String::from_utf8_lossy(&output.stdout),
25280      String::from_utf8_lossy(&output.stderr)
25281    );
25282  }
25283
25284  #[cfg(not(target_family = "wasm"))]
25285  #[test]
25286  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
25287    use std::sync::mpsc;
25288
25289    struct DropSignal(mpsc::Sender<()>);
25290    impl Drop for DropSignal {
25291      fn drop(&mut self) {
25292        let _ = self.0.send(());
25293      }
25294    }
25295
25296    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
25297    let (polled_tx, polled_rx) = mpsc::channel();
25298    let (dropped_tx, dropped_rx) = mpsc::channel();
25299    controller
25300      .try_spawn_detached(async move {
25301        let _drop_signal = DropSignal(dropped_tx);
25302        polled_tx.send(()).unwrap();
25303        std::future::pending::<()>().await;
25304      })
25305      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
25306    polled_rx
25307      .recv_timeout(Duration::from_secs(5))
25308      .expect("the pending task must be polled before shutdown");
25309
25310    controller
25311      .shutdown()
25312      .expect("shutdown must cancel accepted async work");
25313    dropped_rx
25314      .recv_timeout(Duration::from_secs(5))
25315      .expect("shutdown must drop the accepted task future before returning");
25316  }
25317
25318  #[cfg(not(target_family = "wasm"))]
25319  #[test]
25320  fn current_thread_shutdown_outranks_perpetual_self_wake() {
25321    use std::sync::mpsc;
25322
25323    struct SelfWakingPending {
25324      entered: Option<mpsc::Sender<()>>,
25325    }
25326
25327    impl Future for SelfWakingPending {
25328      type Output = ();
25329
25330      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25331        if let Some(entered) = self.entered.take() {
25332          entered.send(()).unwrap();
25333        }
25334        cx.waker().wake_by_ref();
25335        Poll::Pending
25336      }
25337    }
25338
25339    let controller = Arc::new(current_thread_controller(
25340      "current-thread-self-wake-shutdown",
25341    ));
25342    let (entered_tx, entered_rx) = mpsc::channel();
25343    let (outcome_tx, outcome_rx) = mpsc::channel();
25344    let block_on_controller = Arc::clone(&controller);
25345    let runner = std::thread::spawn(move || {
25346      let mut future = std::pin::pin!(SelfWakingPending {
25347        entered: Some(entered_tx)
25348      });
25349      outcome_tx
25350        .send(block_on_controller.block_on(future.as_mut()))
25351        .unwrap();
25352    });
25353    entered_rx
25354      .recv_timeout(Duration::from_secs(2))
25355      .expect("the self-waking future must be polled before shutdown");
25356
25357    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25358    let shutdown_controller = Arc::clone(&controller);
25359    let shutdown = std::thread::spawn(move || {
25360      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25361    });
25362    shutdown_rx
25363      .recv_timeout(Duration::from_secs(2))
25364      .expect("shutdown must outrank a stored self-wake permit")
25365      .unwrap();
25366    assert_eq!(
25367      outcome_rx
25368        .recv_timeout(Duration::from_secs(2))
25369        .expect("the raw block_on driver must report shutdown"),
25370      BlockOnOutcome::Stopped
25371    );
25372    runner.join().unwrap();
25373    shutdown.join().unwrap();
25374  }
25375
25376  #[cfg(not(target_family = "wasm"))]
25377  #[test]
25378  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
25379    use std::sync::mpsc;
25380
25381    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
25382    let backend = controller.backend();
25383    let executor = match &backend.executor {
25384      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25385      RuntimeExecutor::CurrentThread(_) => unreachable!(),
25386    };
25387    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
25388    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
25389    for _ in 0..2 {
25390      let worker_ready_tx = worker_ready_tx.clone();
25391      let release_workers = Arc::clone(&release_workers);
25392      executor.pool.spawn(move || {
25393        worker_ready_tx.send(()).unwrap();
25394        let (released, changed) = &*release_workers;
25395        let mut released = released
25396          .lock()
25397          .unwrap_or_else(std::sync::PoisonError::into_inner);
25398        while !*released {
25399          released = changed
25400            .wait(released)
25401            .unwrap_or_else(std::sync::PoisonError::into_inner);
25402        }
25403      });
25404    }
25405    drop(worker_ready_tx);
25406    for _ in 0..2 {
25407      worker_ready_rx
25408        .recv_timeout(Duration::from_secs(5))
25409        .expect("both workers must be occupied before the FIFO runnable is submitted");
25410    }
25411    drop(executor);
25412    drop(backend);
25413
25414    let polls = Arc::new(AtomicUsize::new(0));
25415    let handle = controller.spawn(StopPollProbe {
25416      polls: Arc::clone(&polls),
25417      first_poll: None,
25418    });
25419    wait_until("the FIFO runnable to be queued", || {
25420      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
25421    });
25422
25423    let observed_queue = Arc::new(Mutex::new(None));
25424    let hook_observed_queue = Arc::clone(&observed_queue);
25425    let hook_metrics = Arc::clone(&controller.metrics);
25426    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25427      *slot.borrow_mut() = Some(Box::new(move || {
25428        let (released, changed) = &*release_workers;
25429        *released
25430          .lock()
25431          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
25432        changed.notify_all();
25433        wait_until("the stopped FIFO runnable claim", || {
25434          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
25435        });
25436        *hook_observed_queue
25437          .lock()
25438          .unwrap_or_else(std::sync::PoisonError::into_inner) =
25439          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
25440      }));
25441    });
25442
25443    controller
25444      .shutdown()
25445      .expect("shutdown must cancel the stopped FIFO claim");
25446    assert_eq!(polls.load(Ordering::SeqCst), 0);
25447    assert_eq!(
25448      *observed_queue
25449        .lock()
25450        .unwrap_or_else(std::sync::PoisonError::into_inner),
25451      Some(0)
25452    );
25453    assert_eq!(
25454      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25455      0
25456    );
25457    drop(handle);
25458  }
25459
25460  #[cfg(not(target_family = "wasm"))]
25461  #[test]
25462  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
25463    use std::sync::mpsc;
25464
25465    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
25466    let backend = controller.backend();
25467    let executor = match &backend.executor {
25468      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25469      RuntimeExecutor::CurrentThread(_) => unreachable!(),
25470    };
25471    let polls = Arc::new(AtomicUsize::new(0));
25472    let scheduler = Arc::downgrade(&executor);
25473    let (runnable, task) = async_task::spawn(
25474      StopPollProbe {
25475        polls: Arc::clone(&polls),
25476        first_poll: None,
25477      },
25478      move |runnable| {
25479        if let Some(executor) = scheduler.upgrade() {
25480          executor.schedule(runnable);
25481        }
25482      },
25483    );
25484    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
25485    let (release_slot_tx, release_slot_rx) = mpsc::channel();
25486    let (claim_done_tx, claim_done_rx) = mpsc::channel();
25487    let worker_executor = Arc::clone(&executor);
25488    let worker_metrics = Arc::clone(&controller.metrics);
25489    executor.pool.spawn(move || {
25490      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
25491      worker_executor.schedule(runnable);
25492      slot_ready_tx.send(()).unwrap();
25493      release_slot_rx.recv().unwrap();
25494      let claimed = worker_executor.run_one_runnable();
25495      claim_done_tx
25496        .send((
25497          claimed,
25498          worker_metrics.queued_runnables.load(Ordering::SeqCst),
25499        ))
25500        .unwrap();
25501    });
25502    slot_ready_rx
25503      .recv_timeout(Duration::from_secs(5))
25504      .expect("the worker must publish the LIFO runnable before shutdown");
25505    assert_eq!(
25506      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25507      1
25508    );
25509    drop(executor);
25510    drop(backend);
25511
25512    let observed_claim = Arc::new(Mutex::new(None));
25513    let hook_observed_claim = Arc::clone(&observed_claim);
25514    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25515      *slot.borrow_mut() = Some(Box::new(move || {
25516        release_slot_tx.send(()).unwrap();
25517        let result = claim_done_rx
25518          .recv_timeout(Duration::from_secs(5))
25519          .expect("the stopped LIFO runnable claim must complete before abort");
25520        *hook_observed_claim
25521          .lock()
25522          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
25523      }));
25524    });
25525
25526    controller
25527      .shutdown()
25528      .expect("shutdown must cancel the stopped LIFO claim");
25529    assert_eq!(polls.load(Ordering::SeqCst), 0);
25530    assert_eq!(
25531      *observed_claim
25532        .lock()
25533        .unwrap_or_else(std::sync::PoisonError::into_inner),
25534      Some((true, 0))
25535    );
25536    assert_eq!(
25537      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25538      0
25539    );
25540    drop(task);
25541  }
25542
25543  #[cfg(not(target_family = "wasm"))]
25544  #[test]
25545  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
25546    use std::sync::mpsc;
25547
25548    struct BlockingDrop {
25549      entered: mpsc::Sender<()>,
25550      release: mpsc::Receiver<()>,
25551    }
25552
25553    impl Drop for BlockingDrop {
25554      fn drop(&mut self) {
25555        self.entered.send(()).unwrap();
25556        self.release.recv().unwrap();
25557      }
25558    }
25559
25560    struct TwoPollProbe {
25561      first_poll: Option<mpsc::Sender<()>>,
25562      release_first_poll: mpsc::Receiver<()>,
25563      second_poll: mpsc::Sender<()>,
25564    }
25565
25566    impl Future for TwoPollProbe {
25567      type Output = ();
25568
25569      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25570        if let Some(first_poll) = self.first_poll.take() {
25571          first_poll.send(()).unwrap();
25572          self.release_first_poll.recv().unwrap();
25573        } else {
25574          self.second_poll.send(()).unwrap();
25575        }
25576        cx.waker().wake_by_ref();
25577        Poll::Pending
25578      }
25579    }
25580
25581    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
25582    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25583    let (release_drop_tx, release_drop_rx) = mpsc::channel();
25584    let blocking_drop = BlockingDrop {
25585      entered: drop_entered_tx,
25586      release: release_drop_rx,
25587    };
25588    controller
25589      .try_spawn_detached(async move {
25590        let _drop = blocking_drop;
25591        std::future::pending::<()>().await;
25592      })
25593      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
25594
25595    let (first_poll_tx, first_poll_rx) = mpsc::channel();
25596    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
25597    let (second_poll_tx, second_poll_rx) = mpsc::channel();
25598    let (outcome_tx, outcome_rx) = mpsc::channel();
25599    let runner_controller = Arc::clone(&controller);
25600    let runner = std::thread::spawn(move || {
25601      let mut future = std::pin::pin!(TwoPollProbe {
25602        first_poll: Some(first_poll_tx),
25603        release_first_poll: release_first_poll_rx,
25604        second_poll: second_poll_tx,
25605      });
25606      outcome_tx
25607        .send(runner_controller.block_on(future.as_mut()))
25608        .unwrap();
25609    });
25610    first_poll_rx
25611      .recv_timeout(Duration::from_secs(2))
25612      .expect("the active block_on poll must enter before shutdown");
25613
25614    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25615    let shutdown_controller = Arc::clone(&controller);
25616    let shutdown = std::thread::spawn(move || {
25617      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25618    });
25619    drop_entered_rx
25620      .recv_timeout(Duration::from_secs(2))
25621      .expect("shutdown must begin destroying queued work");
25622
25623    release_first_poll_tx.send(()).unwrap();
25624    let polled_again = second_poll_rx
25625      .recv_timeout(Duration::from_millis(200))
25626      .is_ok();
25627    release_drop_tx.send(()).unwrap();
25628
25629    assert!(
25630      !polled_again,
25631      "an active block_on driver must observe generation stop before queued user cleanup blocks"
25632    );
25633    assert_eq!(
25634      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25635      BlockOnOutcome::Stopped
25636    );
25637    shutdown_rx
25638      .recv_timeout(Duration::from_secs(2))
25639      .unwrap()
25640      .unwrap();
25641    runner.join().unwrap();
25642    shutdown.join().unwrap();
25643  }
25644
25645  #[cfg(not(target_family = "wasm"))]
25646  #[test]
25647  fn shutdown_cancels_nested_block_on_within_deadline() {
25648    use std::sync::mpsc;
25649
25650    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
25651    let task_controller = Arc::clone(&controller);
25652    let (entered_tx, entered_rx) = mpsc::channel();
25653    let task = controller
25654      .try_spawn(async move {
25655        entered_tx.send(()).unwrap();
25656        block_on_with_controller(&task_controller, std::future::pending::<()>());
25657      })
25658      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
25659    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25660
25661    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25662    let shutdown_controller = Arc::clone(&controller);
25663    let shutdown = std::thread::spawn(move || {
25664      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25665    });
25666    shutdown_rx
25667      .recv_timeout(Duration::from_secs(2))
25668      .expect("shutdown must wake and cancel an active nested block_on")
25669      .unwrap();
25670    shutdown.join().unwrap();
25671    assert!(
25672      futures::executor::block_on(task).is_err(),
25673      "the cancelled nested block_on task must resolve as stopped"
25674    );
25675  }
25676
25677  #[cfg(not(target_family = "wasm"))]
25678  #[test]
25679  fn stopped_public_block_on_drops_future_before_generation_retirement() {
25680    use std::sync::mpsc;
25681
25682    struct PendingDrop {
25683      entered: Option<mpsc::Sender<()>>,
25684      dropped: mpsc::Sender<Option<u64>>,
25685      release: mpsc::Receiver<()>,
25686    }
25687
25688    impl Future for PendingDrop {
25689      type Output = ();
25690
25691      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25692        if let Some(entered) = self.entered.take() {
25693          entered.send(()).unwrap();
25694        }
25695        Poll::Pending
25696      }
25697    }
25698
25699    impl Drop for PendingDrop {
25700      fn drop(&mut self) {
25701        self
25702          .dropped
25703          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25704          .unwrap();
25705        self.release.recv().unwrap();
25706      }
25707    }
25708
25709    let controller = Arc::new(current_thread_controller(
25710      "public-block-on-generation-destruction",
25711    ));
25712    let generation = controller.backend().generation();
25713    let (entered_tx, entered_rx) = mpsc::channel();
25714    let (dropped_tx, dropped_rx) = mpsc::channel();
25715    let (release_tx, release_rx) = mpsc::channel();
25716    let (runner_done_tx, runner_done_rx) = mpsc::channel();
25717    let block_controller = Arc::clone(&controller);
25718    let runner = std::thread::spawn(move || {
25719      let result = catch_unwind(AssertUnwindSafe(|| {
25720        block_on_with_controller(
25721          &block_controller,
25722          PendingDrop {
25723            entered: Some(entered_tx),
25724            dropped: dropped_tx,
25725            release: release_rx,
25726          },
25727        );
25728      }));
25729      runner_done_tx.send(result.is_err()).unwrap();
25730    });
25731    entered_rx
25732      .recv_timeout(Duration::from_secs(2))
25733      .expect("the public block_on future must be polled before shutdown");
25734
25735    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25736    let shutdown_controller = Arc::clone(&controller);
25737    let shutdown = std::thread::spawn(move || {
25738      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25739    });
25740
25741    assert_eq!(
25742      dropped_rx
25743        .recv_timeout(Duration::from_secs(2))
25744        .expect("shutdown must wake block_on and begin destroying its future"),
25745      Some(generation),
25746      "the stopped future must be destroyed under its retiring generation identity"
25747    );
25748    assert!(
25749      shutdown_rx
25750        .recv_timeout(Duration::from_millis(200))
25751        .is_err(),
25752      "shutdown must not publish Stopped while the public block_on future destructor is running"
25753    );
25754
25755    release_tx.send(()).unwrap();
25756    assert!(
25757      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25758      "the public helper must retain its existing early-stop panic behavior"
25759    );
25760    shutdown_rx
25761      .recv_timeout(Duration::from_secs(2))
25762      .unwrap()
25763      .unwrap();
25764    runner.join().unwrap();
25765    shutdown.join().unwrap();
25766  }
25767
25768  #[cfg(not(target_family = "wasm"))]
25769  #[test]
25770  fn shutdown_contains_panicking_nested_block_on_future_drop() {
25771    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
25772
25773    if std::env::var_os(CHILD_ENV).is_some() {
25774      use std::sync::mpsc;
25775
25776      struct PendingPanicOnDrop {
25777        entered: Option<mpsc::Sender<()>>,
25778      }
25779
25780      impl Future for PendingPanicOnDrop {
25781        type Output = ();
25782
25783        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25784          if let Some(entered) = self.entered.take() {
25785            entered.send(()).unwrap();
25786          }
25787          Poll::Pending
25788        }
25789      }
25790
25791      impl Drop for PendingPanicOnDrop {
25792        fn drop(&mut self) {
25793          panic!("intentional nested block_on future destructor panic");
25794        }
25795      }
25796
25797      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
25798      let task_controller = Arc::clone(&controller);
25799      let (entered_tx, entered_rx) = mpsc::channel();
25800      let task = controller
25801        .try_spawn(async move {
25802          block_on_with_controller(
25803            &task_controller,
25804            PendingPanicOnDrop {
25805              entered: Some(entered_tx),
25806            },
25807          );
25808        })
25809        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
25810      entered_rx
25811        .recv_timeout(Duration::from_secs(2))
25812        .expect("the nested block_on future must be polled before shutdown");
25813
25814      controller
25815        .shutdown()
25816        .expect("shutdown must contain nested future destruction");
25817      assert!(
25818        futures::executor::block_on(task).is_err(),
25819        "shutdown cancellation must still surface as a task error"
25820      );
25821      return;
25822    }
25823
25824    let output = std::process::Command::new(std::env::current_exe().unwrap())
25825      .arg("--exact")
25826      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
25827      .arg("--nocapture")
25828      .env(CHILD_ENV, "1")
25829      .output()
25830      .expect("the subprocess test must start");
25831    assert!(
25832      output.status.success(),
25833      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
25834      output.status.code(),
25835      String::from_utf8_lossy(&output.stdout),
25836      String::from_utf8_lossy(&output.stderr)
25837    );
25838  }
25839
25840  #[test]
25841  fn panicking_future_drop_is_contained_before_async_task_drop() {
25842    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
25843
25844    if std::env::var_os(CHILD_ENV).is_some() {
25845      struct PanicOnDrop;
25846      impl Drop for PanicOnDrop {
25847        fn drop(&mut self) {
25848          panic!("intentional future destructor panic");
25849        }
25850      }
25851
25852      let controller = current_thread_controller("future-drop-subprocess");
25853      let bomb = PanicOnDrop;
25854      controller
25855        .try_spawn_detached(async move {
25856          let _bomb = bomb;
25857          std::future::pending::<()>().await;
25858        })
25859        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
25860      controller
25861        .shutdown()
25862        .expect("shutdown must contain the future destructor panic");
25863      return;
25864    }
25865
25866    let output = std::process::Command::new(std::env::current_exe().unwrap())
25867      .arg("--exact")
25868      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
25869      .arg("--nocapture")
25870      .env(CHILD_ENV, "1")
25871      .output()
25872      .expect("the subprocess test must start");
25873    assert!(
25874      output.status.success(),
25875      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25876      output.status.code(),
25877      String::from_utf8_lossy(&output.stdout),
25878      String::from_utf8_lossy(&output.stderr)
25879    );
25880  }
25881
25882  #[test]
25883  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
25884    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
25885
25886    if std::env::var_os(CHILD_ENV).is_some() {
25887      struct PanicOnDrop;
25888      impl Drop for PanicOnDrop {
25889        fn drop(&mut self) {
25890          panic!("intentional detached output destructor panic");
25891        }
25892      }
25893
25894      let controller = current_thread_controller("output-drop-subprocess");
25895      let handle = controller
25896        .try_spawn(async { PanicOnDrop })
25897        .unwrap_or_else(|_| panic!("runtime must accept the task"));
25898      drop(handle);
25899      let dispatch = controller_current_thread_dispatch(&controller);
25900      controller.drive_current_thread_dispatch(dispatch, false);
25901      controller.shutdown().unwrap();
25902      return;
25903    }
25904
25905    let output = std::process::Command::new(std::env::current_exe().unwrap())
25906      .arg("--exact")
25907      .arg(
25908        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
25909      )
25910      .arg("--nocapture")
25911      .env(CHILD_ENV, "1")
25912      .output()
25913      .expect("the subprocess test must start");
25914    assert!(
25915      output.status.success(),
25916      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25917      output.status.code(),
25918      String::from_utf8_lossy(&output.stdout),
25919      String::from_utf8_lossy(&output.stderr)
25920    );
25921  }
25922
25923  #[cfg(not(target_family = "wasm"))]
25924  #[test]
25925  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
25926    use std::sync::mpsc;
25927
25928    struct BlockingDrop {
25929      controller: Arc<RuntimeController>,
25930      entered: mpsc::Sender<Option<u64>>,
25931      release: mpsc::Receiver<()>,
25932      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
25933    }
25934
25935    impl Drop for BlockingDrop {
25936      fn drop(&mut self) {
25937        self
25938          .entered
25939          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25940          .unwrap();
25941        self.release.recv().unwrap();
25942        self
25943          .reentrant_shutdown
25944          .send(self.controller.shutdown())
25945          .unwrap();
25946      }
25947    }
25948
25949    let controller = Arc::new(current_thread_controller(
25950      "current-thread-host-drain-output-retirement",
25951    ));
25952    let old_generation = controller.backend().generation();
25953    let (entered_tx, entered_rx) = mpsc::channel();
25954    let (release_tx, release_rx) = mpsc::channel();
25955    let (reentrant_tx, reentrant_rx) = mpsc::channel();
25956    let output_controller = Arc::clone(&controller);
25957    let handle = controller
25958      .try_spawn(async move {
25959        BlockingDrop {
25960          controller: output_controller,
25961          entered: entered_tx,
25962          release: release_rx,
25963          reentrant_shutdown: reentrant_tx,
25964        }
25965      })
25966      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
25967    drop(handle);
25968    let dispatch = controller_current_thread_dispatch(&controller);
25969
25970    let driver_controller = Arc::clone(&controller);
25971    let driver =
25972      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
25973    assert_eq!(
25974      entered_rx
25975        .recv_timeout(Duration::from_secs(2))
25976        .expect("the host turn must begin destroying the detached output"),
25977      Some(old_generation),
25978      "detached output destruction must retain the generation that produced it"
25979    );
25980
25981    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25982    let shutdown_controller = Arc::clone(&controller);
25983    let shutdown = std::thread::spawn(move || {
25984      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25985    });
25986    wait_until(
25987      "CurrentThread shutdown to enter the old generation's stopping state",
25988      || {
25989        let state = controller
25990          .state
25991          .lock()
25992          .unwrap_or_else(std::sync::PoisonError::into_inner);
25993        matches!(
25994          &state.lifecycle,
25995          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
25996        )
25997      },
25998    );
25999
26000    let (restart_tx, restart_rx) = mpsc::channel();
26001    let restart_controller = Arc::clone(&controller);
26002    let restart = std::thread::spawn(move || {
26003      restart_controller.start().unwrap();
26004      restart_tx
26005        .send(restart_controller.backend().generation())
26006        .unwrap();
26007    });
26008
26009    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
26010    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
26011    let shutdown_overlapped = early_shutdown.is_some();
26012    let restart_overlapped = early_restart.is_some();
26013    release_tx.send(()).unwrap();
26014
26015    let reentrant_error = reentrant_rx
26016      .recv_timeout(Duration::from_secs(2))
26017      .expect("the retiring output destructor must finish its lifecycle check")
26018      .expect_err("old-generation output destruction must not shut down a replacement runtime");
26019    assert!(
26020      reentrant_error
26021        .to_string()
26022        .contains("generation being stopped"),
26023      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
26024    );
26025
26026    let shutdown_result = early_shutdown.unwrap_or_else(|| {
26027      shutdown_rx
26028        .recv_timeout(Duration::from_secs(2))
26029        .expect("shutdown must finish after detached output destruction retires")
26030    });
26031    shutdown_result.unwrap();
26032    let new_generation = early_restart.unwrap_or_else(|| {
26033      restart_rx
26034        .recv_timeout(Duration::from_secs(2))
26035        .expect("restart must finish after the old host turn retires")
26036    });
26037
26038    driver.join().unwrap();
26039    shutdown.join().unwrap();
26040    restart.join().unwrap();
26041    assert!(
26042      !shutdown_overlapped,
26043      "shutdown must not publish Stopped while a host turn is destroying detached output"
26044    );
26045    assert!(
26046      !restart_overlapped,
26047      "restart must not overlap a host turn from the retiring generation"
26048    );
26049    assert_ne!(new_generation, old_generation);
26050    controller.shutdown().unwrap();
26051  }
26052
26053  #[cfg(not(target_family = "wasm"))]
26054  #[test]
26055  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
26056    use std::sync::mpsc;
26057
26058    struct ShutdownOnDrop {
26059      controller: Arc<RuntimeController>,
26060      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26061    }
26062
26063    impl Drop for ShutdownOnDrop {
26064      fn drop(&mut self) {
26065        let _ = self.result.send(self.controller.shutdown());
26066      }
26067    }
26068
26069    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
26070    let (running_tx, running_rx) = mpsc::channel();
26071    let (release_tx, release_rx) = mpsc::channel();
26072    let running = controller
26073      .try_spawn_blocking(move || {
26074        running_tx.send(()).unwrap();
26075        release_rx.recv().unwrap();
26076      })
26077      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26078    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26079
26080    let (reentrant_tx, reentrant_rx) = mpsc::channel();
26081    let bomb = ShutdownOnDrop {
26082      controller: Arc::clone(&controller),
26083      result: reentrant_tx,
26084    };
26085    let queued = controller
26086      .try_spawn_blocking(move || {
26087        let _bomb = &bomb;
26088      })
26089      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26090
26091    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26092    let shutdown_controller = Arc::clone(&controller);
26093    let shutdown = std::thread::spawn(move || {
26094      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26095    });
26096    let error = reentrant_rx
26097      .recv_timeout(Duration::from_secs(2))
26098      .expect("queued destruction must not self-deadlock")
26099      .expect_err("the retiring generation must reject reentrant shutdown");
26100    assert!(error.to_string().contains("generation being stopped"));
26101    release_tx.send(()).unwrap();
26102    shutdown_rx
26103      .recv_timeout(Duration::from_secs(2))
26104      .unwrap()
26105      .unwrap();
26106    shutdown.join().unwrap();
26107    futures::executor::block_on(running).unwrap();
26108    assert!(futures::executor::block_on(queued).is_err());
26109  }
26110
26111  #[cfg(not(target_family = "wasm"))]
26112  #[test]
26113  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
26114    use std::sync::mpsc;
26115
26116    struct ShutdownAfterRelease {
26117      controller: Arc<RuntimeController>,
26118      entered: mpsc::Sender<()>,
26119      release: mpsc::Receiver<()>,
26120      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26121    }
26122
26123    impl Drop for ShutdownAfterRelease {
26124      fn drop(&mut self) {
26125        self.entered.send(()).unwrap();
26126        self.release.recv().unwrap();
26127        self.result.send(self.controller.shutdown()).unwrap();
26128      }
26129    }
26130
26131    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
26132    let (holder_started_tx, holder_started_rx) = mpsc::channel();
26133    let (release_holder_tx, release_holder_rx) = mpsc::channel();
26134    let holder = controller
26135      .try_spawn_blocking(move || {
26136        holder_started_tx.send(()).unwrap();
26137        release_holder_rx.recv().unwrap();
26138      })
26139      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26140    holder_started_rx
26141      .recv_timeout(Duration::from_secs(2))
26142      .unwrap();
26143
26144    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26145    let shutdown_controller = Arc::clone(&controller);
26146    let shutdown = std::thread::spawn(move || {
26147      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26148    });
26149    wait_until("the runtime entered Stopping", || {
26150      matches!(
26151        controller
26152          .state
26153          .lock()
26154          .unwrap_or_else(std::sync::PoisonError::into_inner)
26155          .lifecycle,
26156        RuntimeLifecycle::Stopping(_)
26157      )
26158    });
26159
26160    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26161    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26162    let (reentrant_tx, reentrant_rx) = mpsc::channel();
26163    let rejected_controller = Arc::clone(&controller);
26164    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
26165    let rejected = std::thread::spawn(move || {
26166      let bomb = ShutdownAfterRelease {
26167        controller: Arc::clone(&rejected_controller),
26168        entered: drop_entered_tx,
26169        release: release_drop_rx,
26170        result: reentrant_tx,
26171      };
26172      let handle = rejected_controller.spawn(async move {
26173        let _bomb = bomb;
26174        std::future::pending::<()>().await;
26175      });
26176      rejected_done_tx
26177        .send(futures::executor::block_on(handle))
26178        .unwrap();
26179    });
26180    drop_entered_rx
26181      .recv_timeout(Duration::from_secs(2))
26182      .unwrap();
26183
26184    release_holder_tx.send(()).unwrap();
26185    assert!(
26186      shutdown_rx
26187        .recv_timeout(Duration::from_millis(200))
26188        .is_err(),
26189      "shutdown crossed into Stopped before rejected destruction finished"
26190    );
26191
26192    let (start_tx, start_rx) = mpsc::channel();
26193    let start_controller = Arc::clone(&controller);
26194    let starter = std::thread::spawn(move || {
26195      start_tx.send(start_controller.start()).unwrap();
26196    });
26197    assert!(
26198      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26199      "restart crossed into a new generation before rejected destruction finished"
26200    );
26201
26202    release_drop_tx.send(()).unwrap();
26203    let error = reentrant_rx
26204      .recv_timeout(Duration::from_secs(2))
26205      .expect("the rejected destructor must complete")
26206      .expect_err("the retiring generation must reject the destructor's shutdown");
26207    assert!(error.to_string().contains("generation being stopped"));
26208    assert!(
26209      rejected_done_rx
26210        .recv_timeout(Duration::from_secs(2))
26211        .unwrap()
26212        .is_err()
26213    );
26214    rejected.join().unwrap();
26215
26216    shutdown_rx
26217      .recv_timeout(Duration::from_secs(2))
26218      .unwrap()
26219      .unwrap();
26220    shutdown.join().unwrap();
26221    start_rx
26222      .recv_timeout(Duration::from_secs(2))
26223      .unwrap()
26224      .unwrap();
26225    starter.join().unwrap();
26226    futures::executor::block_on(holder).unwrap();
26227    controller.shutdown().unwrap();
26228  }
26229
26230  #[cfg(not(target_family = "wasm"))]
26231  #[test]
26232  fn shutdown_contains_hostile_caught_panic_payload() {
26233    use std::{panic::panic_any, sync::mpsc};
26234
26235    struct PanicAgain;
26236    impl Drop for PanicAgain {
26237      fn drop(&mut self) {
26238        panic!("caught panic payload destructor escaped");
26239      }
26240    }
26241
26242    struct PanicPayloadOnDrop(mpsc::Sender<()>);
26243    impl Drop for PanicPayloadOnDrop {
26244      fn drop(&mut self) {
26245        let _ = self.0.send(());
26246        panic_any(PanicAgain);
26247      }
26248    }
26249
26250    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
26251    let (running_tx, running_rx) = mpsc::channel();
26252    let (release_tx, release_rx) = mpsc::channel();
26253    let running = controller
26254      .try_spawn_blocking(move || {
26255        running_tx.send(()).unwrap();
26256        release_rx.recv().unwrap();
26257      })
26258      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26259    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26260
26261    let (drop_tx, drop_rx) = mpsc::channel();
26262    let payload = PanicPayloadOnDrop(drop_tx);
26263    let queued = controller
26264      .try_spawn_blocking(move || {
26265        let _payload = &payload;
26266      })
26267      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26268
26269    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26270    let shutdown_controller = Arc::clone(&controller);
26271    let shutdown = std::thread::spawn(move || {
26272      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26273    });
26274    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
26275    release_tx.send(()).unwrap();
26276    shutdown_rx
26277      .recv_timeout(Duration::from_secs(2))
26278      .expect("hostile panic payload destruction must remain contained")
26279      .unwrap();
26280    shutdown
26281      .join()
26282      .expect("the caught payload must not panic during destruction");
26283    futures::executor::block_on(running).unwrap();
26284    assert!(futures::executor::block_on(queued).is_err());
26285  }
26286
26287  #[cfg(not(target_family = "wasm"))]
26288  #[test]
26289  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
26290    use std::sync::mpsc;
26291
26292    struct DropSignal(mpsc::Sender<()>);
26293    impl Drop for DropSignal {
26294      fn drop(&mut self) {
26295        let _ = self.0.send(());
26296      }
26297    }
26298
26299    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
26300    let (running_tx, running_rx) = mpsc::channel();
26301    let (release_tx, release_rx) = mpsc::channel();
26302    let running = controller
26303      .try_spawn_blocking(move || {
26304        running_tx.send(()).unwrap();
26305        release_rx.recv().unwrap();
26306        7usize
26307      })
26308      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26309    running_rx
26310      .recv_timeout(Duration::from_secs(5))
26311      .expect("the first blocking job must hold the only blocking slot");
26312
26313    let queued_ran = Arc::new(AtomicBool::new(false));
26314    let queued_ran_work = Arc::clone(&queued_ran);
26315    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
26316    let queued_drop_signal = DropSignal(queued_dropped_tx);
26317    let queued = controller
26318      .try_spawn_blocking(move || {
26319        let _drop_signal = queued_drop_signal;
26320        queued_ran_work.store(true, Ordering::SeqCst);
26321        11usize
26322      })
26323      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26324
26325    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26326    let shutdown_controller = Arc::clone(&controller);
26327    let shutdown = std::thread::spawn(move || {
26328      shutdown_controller.shutdown().unwrap();
26329      shutdown_done_tx.send(()).unwrap();
26330    });
26331
26332    queued_dropped_rx
26333      .recv_timeout(Duration::from_secs(5))
26334      .expect("shutdown must drop queued blocking work");
26335    assert!(
26336      !queued_ran.load(Ordering::SeqCst),
26337      "queued blocking work must not execute"
26338    );
26339    assert!(
26340      shutdown_done_rx.try_recv().is_err(),
26341      "shutdown must wait while an accepted blocking closure is still running"
26342    );
26343
26344    release_tx.send(()).unwrap();
26345    shutdown_done_rx
26346      .recv_timeout(Duration::from_secs(5))
26347      .expect("shutdown must finish after running blocking work retires");
26348    shutdown.join().unwrap();
26349    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
26350    assert!(
26351      futures::executor::block_on(queued).is_err(),
26352      "the queued blocking handle must resolve as cancelled"
26353    );
26354  }
26355
26356  #[cfg(not(target_family = "wasm"))]
26357  #[test]
26358  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
26359    use std::sync::mpsc;
26360
26361    struct PanicOnDrop(mpsc::Sender<()>);
26362    impl Drop for PanicOnDrop {
26363      fn drop(&mut self) {
26364        let _ = self.0.send(());
26365        panic!("intentional queued blocking destructor panic");
26366      }
26367    }
26368
26369    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
26370    let (running_tx, running_rx) = mpsc::channel();
26371    let (release_tx, release_rx) = mpsc::channel();
26372    let running = controller
26373      .try_spawn_blocking(move || {
26374        running_tx.send(()).unwrap();
26375        release_rx.recv().unwrap();
26376      })
26377      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26378    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26379
26380    let (drop_tx, drop_rx) = mpsc::channel();
26381    let panic_on_drop = PanicOnDrop(drop_tx);
26382    let queued = controller
26383      .try_spawn_blocking(move || {
26384        let _keep_captured_until_run = &panic_on_drop;
26385      })
26386      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26387
26388    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26389    let shutdown_controller = Arc::clone(&controller);
26390    let shutdown = std::thread::spawn(move || {
26391      shutdown_controller.shutdown().unwrap();
26392      shutdown_done_tx.send(()).unwrap();
26393    });
26394
26395    drop_rx
26396      .recv_timeout(Duration::from_secs(5))
26397      .expect("shutdown must attempt to drop the queued closure");
26398    release_tx.send(()).unwrap();
26399    shutdown_done_rx
26400      .recv_timeout(Duration::from_secs(5))
26401      .expect("a panicking queued destructor must not strand shutdown");
26402    shutdown
26403      .join()
26404      .expect("shutdown must contain the destructor panic");
26405    futures::executor::block_on(running).unwrap();
26406    assert!(futures::executor::block_on(queued).is_err());
26407
26408    controller
26409      .start()
26410      .expect("the stopped controller must restart");
26411    let restarted = controller
26412      .try_spawn_blocking(|| 17usize)
26413      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26414    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
26415    controller.shutdown().unwrap();
26416  }
26417
26418  #[cfg(not(target_family = "wasm"))]
26419  #[test]
26420  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
26421    use std::{panic::panic_any, sync::mpsc};
26422
26423    struct ReentrantPayload {
26424      controller: Arc<RuntimeController>,
26425      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26426    }
26427
26428    impl Drop for ReentrantPayload {
26429      fn drop(&mut self) {
26430        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26431        let shutdown = self.controller.shutdown();
26432        self.result.send((generation, shutdown)).unwrap();
26433      }
26434    }
26435
26436    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26437
26438    impl Drop for PanicPayloadOnDrop {
26439      fn drop(&mut self) {
26440        panic_any(self.0.take().expect("the result must panic only once"));
26441      }
26442    }
26443
26444    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
26445    let generation = controller.backend().generation();
26446    let (started_tx, started_rx) = mpsc::channel();
26447    let (release_tx, release_rx) = mpsc::channel();
26448    let (result_tx, result_rx) = mpsc::channel();
26449    let payload_controller = Arc::clone(&controller);
26450    let handle = controller
26451      .try_spawn_blocking(move || {
26452        started_tx.send(()).unwrap();
26453        release_rx.recv().unwrap();
26454        PanicPayloadOnDrop(Some(ReentrantPayload {
26455          controller: payload_controller,
26456          result: result_tx,
26457        }))
26458      })
26459      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26460    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26461
26462    // Detach the receiver before the result is delivered. Sending then drops
26463    // the result on the worker, whose destructor intentionally panics.
26464    drop(handle);
26465    release_tx.send(()).unwrap();
26466    let (observed_generation, reentrant_shutdown) = result_rx
26467      .recv_timeout(Duration::from_secs(5))
26468      .expect("the dropped result panic payload must not self-deadlock");
26469    assert_eq!(observed_generation, Some(generation));
26470    let error =
26471      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
26472    assert!(error.to_string().contains("work running on that runtime"));
26473
26474    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26475    let shutdown_controller = Arc::clone(&controller);
26476    let shutdown = std::thread::spawn(move || {
26477      shutdown_controller.shutdown().unwrap();
26478      shutdown_done_tx.send(()).unwrap();
26479    });
26480    shutdown_done_rx
26481      .recv_timeout(Duration::from_secs(5))
26482      .expect("a dropped-result destructor panic must not strand shutdown");
26483    shutdown
26484      .join()
26485      .expect("shutdown must contain the destructor panic");
26486
26487    controller
26488      .start()
26489      .expect("the stopped controller must restart");
26490    let restarted = controller
26491      .try_spawn_blocking(|| 19usize)
26492      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26493    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
26494    controller.shutdown().unwrap();
26495  }
26496
26497  #[cfg(not(target_family = "wasm"))]
26498  #[test]
26499  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
26500    use std::sync::mpsc;
26501
26502    struct PanicOnDrop(mpsc::Sender<()>);
26503
26504    impl Drop for PanicOnDrop {
26505      fn drop(&mut self) {
26506        self.0.send(()).unwrap();
26507        panic!("intentional buffered blocking result destructor panic");
26508      }
26509    }
26510
26511    let options = RuntimeOptions {
26512      flavor: RuntimeFlavor::MultiThread,
26513      worker_threads: 2,
26514      max_blocking_tasks: 1,
26515      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
26516      park_deadline: None,
26517      drain_linger: DEFAULT_DRAIN_LINGER,
26518    };
26519    let executor =
26520      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26521    let (started_tx, started_rx) = mpsc::channel();
26522    let (dropped_tx, dropped_rx) = mpsc::channel();
26523    let handle = executor.schedule_blocking(move || {
26524      started_tx.send(()).unwrap();
26525      PanicOnDrop(dropped_tx)
26526    });
26527    started_rx
26528      .recv_timeout(Duration::from_secs(2))
26529      .expect("the blocking closure must run");
26530    wait_until("the completed blocking result to be buffered", || {
26531      executor.active_blocking.load(Ordering::Acquire) == 0
26532    });
26533    assert!(
26534      dropped_rx.try_recv().is_err(),
26535      "the buffered result must remain owned by the live receiver"
26536    );
26537
26538    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26539    assert!(
26540      dropped.is_ok(),
26541      "dropping the buffered result must contain its destructor panic"
26542    );
26543    dropped_rx
26544      .recv_timeout(Duration::from_secs(2))
26545      .expect("dropping the receiver must reclaim the buffered result");
26546  }
26547
26548  #[cfg(not(target_family = "wasm"))]
26549  #[test]
26550  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
26551    use std::sync::mpsc;
26552
26553    struct PanicOnDrop(mpsc::Sender<()>);
26554    impl Drop for PanicOnDrop {
26555      fn drop(&mut self) {
26556        let _ = self.0.send(());
26557        panic!("intentional late blocking destructor panic");
26558      }
26559    }
26560
26561    let executor = multi_thread_executor(2, None, "closed-queue-drop");
26562    executor.begin_shutdown();
26563
26564    let (drop_tx, drop_rx) = mpsc::channel();
26565    let panic_on_drop = PanicOnDrop(drop_tx);
26566    let handle = executor.schedule_blocking(move || {
26567      let _keep_captured_until_run = &panic_on_drop;
26568    });
26569
26570    drop_rx
26571      .recv_timeout(Duration::from_secs(5))
26572      .expect("the closed queue must retire the rejected closure");
26573    assert!(
26574      futures::executor::block_on(handle).is_err(),
26575      "the rejected blocking handle must resolve as cancelled"
26576    );
26577  }
26578
26579  #[cfg(not(target_family = "wasm"))]
26580  #[test]
26581  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
26582    struct PanicWake;
26583    impl std::task::Wake for PanicWake {
26584      fn wake(self: Arc<Self>) {
26585        panic!("intentional timer waker panic");
26586      }
26587    }
26588
26589    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
26590    let backend = controller.backend();
26591    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26592      panic!("configured MultiThread backend must create a Rayon executor");
26593    };
26594    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
26595    let waker = Waker::from(Arc::new(PanicWake));
26596    let mut cx = Context::from_waker(&waker);
26597    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
26598    drop(backend);
26599
26600    controller
26601      .shutdown()
26602      .expect("shutdown must contain the timer waker panic");
26603    controller
26604      .start()
26605      .expect("the stopped controller must restart");
26606    let restarted = controller
26607      .try_spawn_blocking(|| 23usize)
26608      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26609    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
26610    controller.shutdown().unwrap();
26611  }
26612
26613  #[cfg(not(target_family = "wasm"))]
26614  #[test]
26615  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
26616    use std::sync::mpsc;
26617
26618    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
26619    let backend = controller.backend();
26620    let old_generation = backend.generation();
26621    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26622      panic!("configured MultiThread backend must create a Rayon executor");
26623    };
26624    let old_workers = Arc::clone(&executor.worker_lifecycle);
26625    drop(backend);
26626
26627    let (running_tx, running_rx) = mpsc::channel();
26628    let (release_tx, release_rx) = mpsc::channel();
26629    let running = controller
26630      .try_spawn_blocking(move || {
26631        running_tx.send(()).unwrap();
26632        release_rx.recv().unwrap();
26633      })
26634      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26635    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26636
26637    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26638    let shutdown_controller = Arc::clone(&controller);
26639    let shutdown = std::thread::spawn(move || {
26640      shutdown_controller.shutdown().unwrap();
26641      shutdown_done_tx.send(()).unwrap();
26642    });
26643    wait_until("controller entered Stopping", || {
26644      let state = controller
26645        .state
26646        .lock()
26647        .unwrap_or_else(std::sync::PoisonError::into_inner);
26648      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
26649    });
26650
26651    let (start_done_tx, start_done_rx) = mpsc::channel();
26652    let start_controller = Arc::clone(&controller);
26653    let starter = std::thread::spawn(move || {
26654      start_controller.start().unwrap();
26655      start_done_tx.send(()).unwrap();
26656    });
26657    assert!(
26658      start_done_rx.try_recv().is_err(),
26659      "start must wait while shutdown is quiescing"
26660    );
26661    assert!(
26662      old_workers.remaining() > 0,
26663      "the held old generation must still own workers"
26664    );
26665
26666    release_tx.send(()).unwrap();
26667    futures::executor::block_on(running).unwrap();
26668    shutdown_done_rx
26669      .recv_timeout(Duration::from_secs(5))
26670      .unwrap();
26671    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26672    shutdown.join().unwrap();
26673    starter.join().unwrap();
26674    assert_eq!(
26675      old_workers.remaining(),
26676      0,
26677      "restart may begin only after every old worker exits"
26678    );
26679
26680    let backend = controller.backend();
26681    assert_ne!(backend.generation(), old_generation);
26682    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26683      panic!("restarted backend must remain MultiThread");
26684    };
26685    assert_eq!(
26686      executor.pool.current_num_threads(),
26687      controller.options().worker_threads
26688    );
26689    drop(backend);
26690    controller.shutdown().unwrap();
26691  }
26692
26693  #[cfg(not(target_family = "wasm"))]
26694  #[test]
26695  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
26696    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
26697    let task_controller = Arc::clone(&controller);
26698    let handle = controller
26699      .try_spawn(async move {
26700        task_controller
26701          .shutdown()
26702          .expect_err("runtime work must not wait for its own generation")
26703          .to_string()
26704      })
26705      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
26706    let message = futures::executor::block_on(handle).unwrap();
26707    assert!(message.contains("cannot shut down"));
26708    controller.shutdown().unwrap();
26709  }
26710
26711  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
26712  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
26713  // global `RUNTIME`) and run the workload on a child thread guarded by a
26714  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
26715  // wedging the whole suite.
26716
26717  #[cfg(not(target_family = "wasm"))]
26718  #[test]
26719  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
26720    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
26721    // blocking job while `active_blocking >= max_blocking`. The guard is
26722    // exercised deterministically (no sleeps, no incidental worker overlap):
26723    //
26724    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
26725    //      occupies a counted slot (`take_blocking` increments `active_blocking`
26726    //      BEFORE running the closure), signals `held`, then parks on a release
26727    //      barrier -- so all slots are provably occupied before we proceed.
26728    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
26729    //      With the cap intact `take_blocking` returns `None` for them, so the
26730    //      idle workers cannot pop them: no extra may signal `extra_started`.
26731    //   3. We release the holders, then assert EVERY job (holders + extras)
26732    //      completes with the right result and the recorded peak never exceeded
26733    //      the cap.
26734    //
26735    // Decisive property: if the `active >= self.max_blocking` guard were removed,
26736    // the two idle workers would pop the extras while both holders are still
26737    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
26738    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
26739    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
26740    // can never self-deadlock on the over-cap extras.
26741    use std::sync::{Barrier, mpsc};
26742    use std::time::Duration;
26743
26744    let (done_tx, done_rx) = mpsc::channel();
26745    let peak_out = Arc::new(Mutex::new(None));
26746    let peak_slot = Arc::clone(&peak_out);
26747    let runner = std::thread::spawn(move || {
26748      let metrics = Arc::new(RuntimeMetrics::default());
26749      let options = RuntimeOptions {
26750        flavor: RuntimeFlavor::MultiThread,
26751        worker_threads: 4,
26752        max_blocking_tasks: 2,
26753        thread_name_prefix: "rd9-blk-cap".to_string(),
26754        park_deadline: None,
26755        drain_linger: DEFAULT_DRAIN_LINGER,
26756      };
26757      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26758
26759      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
26760      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
26761
26762      // Holders: signal once they hold a counted slot, then park until released.
26763      let (held_tx, held_rx) = mpsc::channel();
26764      let release = Arc::new(Barrier::new(holder_count + 1));
26765      let mut holder_handles = Vec::new();
26766      for _ in 0..holder_count {
26767        let held_tx = held_tx.clone();
26768        let release = Arc::clone(&release);
26769        holder_handles.push(executor.schedule_blocking(move || {
26770          held_tx.send(()).unwrap();
26771          release.wait();
26772          0usize
26773        }));
26774      }
26775      // Wait until every slot is provably occupied (cap fully saturated).
26776      for _ in 0..holder_count {
26777        held_rx.recv().unwrap();
26778      }
26779
26780      // Queue the beyond-cap extras. Each signals the instant it begins running.
26781      let (extra_started_tx, extra_started_rx) = mpsc::channel();
26782      let mut extra_handles = Vec::new();
26783      for i in 0..extra_count {
26784        let extra_started_tx = extra_started_tx.clone();
26785        extra_handles.push(executor.schedule_blocking(move || {
26786          extra_started_tx.send(i).unwrap();
26787          i * 2
26788        }));
26789      }
26790
26791      // With the cap intact, `take_blocking` returns `None` while both slots are
26792      // held, so no extra can have started running yet.
26793      assert!(
26794        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
26795        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
26796      );
26797
26798      // Release the holders so the queued extras can run within the cap.
26799      release.wait();
26800
26801      for handle in holder_handles {
26802        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
26803      }
26804      // Each handle owns its own oneshot receiver, so awaiting in order returns
26805      // each extra's own result regardless of the order the pool ran them.
26806      for (i, handle) in extra_handles.into_iter().enumerate() {
26807        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
26808      }
26809
26810      // `blocking_tasks_started` is incremented at job entry, so once every result
26811      // is in, all holder + extra starts are recorded.
26812      assert_eq!(
26813        metrics.blocking_tasks_started.load(Ordering::Relaxed),
26814        (holder_count + extra_count) as u64
26815      );
26816
26817      *peak_slot
26818        .lock()
26819        .unwrap_or_else(std::sync::PoisonError::into_inner) =
26820        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
26821      let _ = done_tx.send(());
26822    });
26823
26824    match done_rx.recv_timeout(Duration::from_secs(10)) {
26825      Ok(()) => runner.join().unwrap(),
26826      Err(error) => panic!("blocking-cap test did not complete ({error})"),
26827    }
26828    let peak = peak_out
26829      .lock()
26830      .unwrap_or_else(std::sync::PoisonError::into_inner)
26831      .expect("peak captured");
26832    assert!(
26833      peak >= 1,
26834      "at least one blocking job must have run (peak {peak})"
26835    );
26836    assert!(
26837      peak <= 2,
26838      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
26839    );
26840  }
26841
26842  #[cfg(not(target_family = "wasm"))]
26843  #[test]
26844  fn multi_thread_drain_budget_completes_large_runnable_batch() {
26845    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
26846    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
26847    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
26848    // still complete every task, with the runnable counters settling back to 0.
26849    use std::sync::mpsc;
26850    use std::time::{Duration, Instant};
26851
26852    let (done_tx, done_rx) = mpsc::channel();
26853    let runner = std::thread::spawn(move || {
26854      let metrics = Arc::new(RuntimeMetrics::default());
26855      let options = RuntimeOptions {
26856        flavor: RuntimeFlavor::MultiThread,
26857        worker_threads: 2,
26858        max_blocking_tasks: 2,
26859        thread_name_prefix: "rd9-budget".to_string(),
26860        park_deadline: None,
26861        drain_linger: DEFAULT_DRAIN_LINGER,
26862      };
26863      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26864
26865      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
26866      let completed = Arc::new(AtomicU64::new(0));
26867      let mut tasks = Vec::new();
26868      for _ in 0..task_count {
26869        let completed = Arc::clone(&completed);
26870        let scheduler = Arc::clone(&executor);
26871        let (runnable, task) = async_task::spawn(
26872          async move {
26873            completed.fetch_add(1, Ordering::Relaxed);
26874          },
26875          move |r| scheduler.schedule(r),
26876        );
26877        executor.schedule(runnable);
26878        tasks.push(task);
26879      }
26880      for task in tasks {
26881        futures::executor::block_on(task);
26882      }
26883
26884      // Every immediately-ready future is polled exactly once; `runnable_polls`
26885      // (incremented at poll entry, before completion) is therefore exact here.
26886      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
26887      assert_eq!(
26888        metrics.runnable_polls.load(Ordering::Relaxed),
26889        task_count as u64
26890      );
26891
26892      // The active-runnable guard for the LAST task drops just after its Task
26893      // resolves (i.e. just after `block_on` returns), so give the counters a
26894      // brief, bounded moment to settle to 0 rather than asserting them racily.
26895      let deadline = Instant::now() + Duration::from_secs(5);
26896      loop {
26897        let active = metrics.active_runnables.load(Ordering::Relaxed);
26898        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
26899        if active == 0 && queued == 0 {
26900          break;
26901        }
26902        assert!(
26903          Instant::now() < deadline,
26904          "runnable counters did not settle to 0: active={active} queued={queued}"
26905        );
26906        std::thread::yield_now();
26907      }
26908      let _ = done_tx.send(());
26909    });
26910
26911    match done_rx.recv_timeout(Duration::from_secs(15)) {
26912      Ok(()) => runner.join().unwrap(),
26913      Err(error) => panic!("drain-budget test did not complete ({error})"),
26914    }
26915  }
26916
26917  #[cfg(not(target_family = "wasm"))]
26918  #[test]
26919  fn multi_thread_spawned_panic_surfaces_as_join_error() {
26920    // A panicking spawned future must surface as `Err(JoinError)` carrying the
26921    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
26922    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
26923    // drive the resulting `JoinHandle` on this executor -- without touching the
26924    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
26925    // `panic!("x")` message round-trips as `"x"`.
26926    use std::sync::mpsc;
26927    use std::time::Duration;
26928
26929    let (done_tx, done_rx) = mpsc::channel();
26930    let runner = std::thread::spawn(move || {
26931      let metrics = Arc::new(RuntimeMetrics::default());
26932      let options = RuntimeOptions {
26933        flavor: RuntimeFlavor::MultiThread,
26934        worker_threads: 2,
26935        max_blocking_tasks: 2,
26936        thread_name_prefix: "rd9-panic".to_string(),
26937        park_deadline: None,
26938        drain_linger: DEFAULT_DRAIN_LINGER,
26939      };
26940      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
26941
26942      let scheduler = Arc::clone(&executor);
26943      let wrapped = async {
26944        AssertUnwindSafe(async { panic!("x") })
26945          .catch_unwind()
26946          .await
26947          .map(|()| ContainedTaskOutput::new((), u64::MAX))
26948          .map_err(panic_payload_to_join_error)
26949      };
26950      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
26951      executor.schedule(runnable);
26952
26953      let result: Result<(), JoinError> =
26954        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
26955          task: task.fallible(),
26956          awaiter: None,
26957          dependency: Arc::new(TaskDependency::default()),
26958        }));
26959      let _ = done_tx.send(result);
26960    });
26961
26962    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
26963      Ok(result) => {
26964        runner.join().unwrap();
26965        result
26966      }
26967      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
26968    };
26969    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
26970    assert_eq!(error.to_string(), "x");
26971  }
26972
26973  #[test]
26974  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
26975    use std::sync::mpsc;
26976
26977    let metrics = Arc::new(RuntimeMetrics::default());
26978    metrics.tasks_spawned.store(1, Ordering::Relaxed);
26979    metrics.tasks_completed.store(2, Ordering::Relaxed);
26980    metrics.tasks_panicked.store(3, Ordering::Relaxed);
26981    metrics.runnable_schedules.store(4, Ordering::Relaxed);
26982    metrics.runnable_polls.store(5, Ordering::Relaxed);
26983    metrics
26984      .blocking_tasks_scheduled
26985      .store(14, Ordering::Relaxed);
26986    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
26987    metrics
26988      .blocking_tasks_completed
26989      .store(11, Ordering::Relaxed);
26990    metrics.runnable_scheduled();
26991    let (guards_live_tx, guards_live_rx) = mpsc::channel();
26992    let (release_tx, release_rx) = mpsc::channel();
26993    let guard_metrics = Arc::clone(&metrics);
26994    let guard_thread = std::thread::spawn(move || {
26995      let runnable_guard = guard_metrics.runnable_started();
26996      let blocking_guard = guard_metrics.blocking_started(true);
26997      guards_live_tx.send(()).unwrap();
26998      release_rx.recv().unwrap();
26999      drop(runnable_guard);
27000      drop(blocking_guard);
27001    });
27002    guards_live_rx.recv().unwrap();
27003    let fingerprint_before_reset = metrics.progress_fingerprint();
27004
27005    metrics.reset();
27006
27007    assert!(
27008      metrics.progress_fingerprint() != fingerprint_before_reset,
27009      "a reset must advance the deadlock detector generation even if counters later repeat"
27010    );
27011    assert_eq!(
27012      metrics.tasks_spawned.load(Ordering::Relaxed),
27013      0,
27014      "tasks_spawned"
27015    );
27016    assert_eq!(
27017      metrics.tasks_completed.load(Ordering::Relaxed),
27018      0,
27019      "tasks_completed"
27020    );
27021    assert_eq!(
27022      metrics.tasks_panicked.load(Ordering::Relaxed),
27023      0,
27024      "tasks_panicked"
27025    );
27026    assert_eq!(
27027      metrics.runnable_schedules.load(Ordering::Relaxed),
27028      0,
27029      "runnable_schedules"
27030    );
27031    assert_eq!(
27032      metrics.runnable_polls.load(Ordering::Relaxed),
27033      0,
27034      "runnable_polls"
27035    );
27036    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
27037    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
27038    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
27039    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
27040    assert_eq!(
27041      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
27042      0,
27043      "blocking_tasks_scheduled"
27044    );
27045    assert_eq!(
27046      metrics.blocking_tasks_started.load(Ordering::Relaxed),
27047      0,
27048      "blocking_tasks_started"
27049    );
27050    assert_eq!(
27051      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
27052      0,
27053      "blocking_tasks_completed"
27054    );
27055    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
27056    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
27057
27058    release_tx.send(()).unwrap();
27059    guard_thread.join().unwrap();
27060    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
27061    assert_eq!(
27062      metrics.active_blocking_tasks.load(Ordering::Relaxed),
27063      0,
27064      "a live blocking guard completed after reset without unsigned underflow"
27065    );
27066    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
27067  }
27068
27069  #[test]
27070  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
27071    use std::sync::mpsc;
27072
27073    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
27074      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
27075        *slot.borrow_mut() = Some(Box::new(move || {
27076          entered.send(()).unwrap();
27077          release.recv().unwrap();
27078        }));
27079      });
27080    }
27081
27082    let options = RuntimeOptions {
27083      flavor: RuntimeFlavor::CurrentThread,
27084      worker_threads: 1,
27085      max_blocking_tasks: 1,
27086      thread_name_prefix: "metrics-snapshot".to_string(),
27087      park_deadline: None,
27088      drain_linger: DEFAULT_DRAIN_LINGER,
27089    };
27090
27091    let queued_metrics = Arc::new(RuntimeMetrics::default());
27092    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
27093    let (queued_release_tx, queued_release_rx) = mpsc::channel();
27094    let queued_writer_metrics = Arc::clone(&queued_metrics);
27095    let queued_writer = std::thread::spawn(move || {
27096      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
27097      queued_writer_metrics.runnable_scheduled();
27098    });
27099    queued_entered_rx.recv().unwrap();
27100    assert_eq!(
27101      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
27102      0
27103    );
27104    let snapshot = queued_metrics.snapshot(&options);
27105    assert_eq!(snapshot.queued_runnables, 1);
27106    assert_eq!(snapshot.max_queued_runnables, 1);
27107    queued_release_tx.send(()).unwrap();
27108    queued_writer.join().unwrap();
27109    queued_metrics.runnable_cancelled();
27110
27111    let active_metrics = Arc::new(RuntimeMetrics::default());
27112    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
27113    let (active_entered_tx, active_entered_rx) = mpsc::channel();
27114    let (active_release_tx, active_release_rx) = mpsc::channel();
27115    let active_writer_metrics = Arc::clone(&active_metrics);
27116    let active_writer = std::thread::spawn(move || {
27117      pause_next_high_water_update(active_entered_tx, active_release_rx);
27118      let _active = active_writer_metrics.runnable_started();
27119    });
27120    active_entered_rx.recv().unwrap();
27121    assert_eq!(
27122      active_metrics.max_active_runnables.load(Ordering::Relaxed),
27123      0
27124    );
27125    let snapshot = active_metrics.snapshot(&options);
27126    assert_eq!(snapshot.active_runnables, 1);
27127    assert_eq!(snapshot.max_active_runnables, 1);
27128    active_release_tx.send(()).unwrap();
27129    active_writer.join().unwrap();
27130
27131    let blocking_metrics = Arc::new(RuntimeMetrics::default());
27132    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
27133    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
27134    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
27135    let blocking_writer = std::thread::spawn(move || {
27136      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
27137      let _active = blocking_writer_metrics.blocking_started(true);
27138    });
27139    blocking_entered_rx.recv().unwrap();
27140    assert_eq!(
27141      blocking_metrics
27142        .max_active_blocking_tasks
27143        .load(Ordering::Relaxed),
27144      0
27145    );
27146    let snapshot = blocking_metrics.snapshot(&options);
27147    assert_eq!(snapshot.active_blocking_tasks, 1);
27148    assert_eq!(snapshot.max_active_blocking_tasks, 1);
27149    blocking_release_tx.send(()).unwrap();
27150    blocking_writer.join().unwrap();
27151  }
27152
27153  #[test]
27154  fn runtime_metrics_reset_fails_before_generation_wraparound() {
27155    let metrics = RuntimeMetrics::default();
27156    metrics
27157      .reset_generation
27158      .store(u64::MAX - 1, Ordering::SeqCst);
27159    metrics.tasks_spawned.store(7, Ordering::Relaxed);
27160
27161    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
27162    assert!(exhausted.is_err());
27163    assert!(!metrics.reset_lock.is_poisoned());
27164    assert_eq!(
27165      metrics.reset_generation.load(Ordering::SeqCst),
27166      u64::MAX - 1
27167    );
27168    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
27169  }
27170
27171  // ---- Self-detecting block_on deadlocks -------------------------------------
27172  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
27173  // park decision with no pending wake panics with the typed diagnostic; (2) a
27174  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
27175  // lives at the PARK DECISION, not at Pending-return; (3) an
27176  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
27177  // progress RESETS the deadline instead of firing; (5) the
27178  // foreign/napi whole-build park is excluded from the deadline entirely.
27179
27180  /// A future that never completes and never wakes anyone: once its driver
27181  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
27182  struct NeverReady;
27183  impl Future for NeverReady {
27184    type Output = ();
27185    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27186      Poll::Pending
27187    }
27188  }
27189
27190  #[cfg(not(target_family = "wasm"))]
27191  struct ReadyAfterOwnerLanePublication {
27192    dependency: BlockingDependency,
27193    ready: Arc<AtomicBool>,
27194  }
27195
27196  #[cfg(not(target_family = "wasm"))]
27197  impl Future for ReadyAfterOwnerLanePublication {
27198    type Output = ();
27199
27200    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27201      if self.ready.load(Ordering::SeqCst) {
27202        Poll::Ready(())
27203      } else {
27204        // This shape publishes on every pending poll and never retires; the
27205        // owner-lane assertions read the state, not the token.
27206        let mut published = PublishedBlockingDependencies::default();
27207        record_blocking_dependency(&mut published, self.dependency);
27208        Poll::Pending
27209      }
27210    }
27211  }
27212
27213  #[test]
27214  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
27215    // Shape (1): on a threadless build a CT park decision with an empty queue
27216    // and no wake token pending is a PROVABLE deadlock (no other thread exists
27217    // to deliver the wake) -- `block_on` must panic immediately with the typed
27218    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
27219    // runs on a child thread bounded by recv_timeout so a missing detection
27220    // (a hang) fails the test rather than wedging the suite.
27221    use std::sync::mpsc;
27222
27223    let (done_tx, done_rx) = mpsc::channel();
27224    let runner = std::thread::spawn(move || {
27225      let metrics = Arc::new(RuntimeMetrics::default());
27226      // Native stand-in for the threadless wasm build; no deadline because the
27227      // certain case is not timing-based.
27228      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27229
27230      let payload = catch_unwind(AssertUnwindSafe(|| {
27231        let mut future = std::pin::pin!(NeverReady);
27232        executor.block_on(future.as_mut());
27233      }))
27234      .expect_err("a threadless park with an empty queue and no pending wake must panic");
27235      let diagnostic = payload
27236        .downcast_ref::<BlockOnDeadlock>()
27237        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
27238      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
27239      assert_eq!(
27240        diagnostic.park_deadline, None,
27241        "the certain case carries no deadline"
27242      );
27243      let message = diagnostic.to_string();
27244      assert!(
27245        message.contains("block_on") && message.contains("JS"),
27246        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
27247      );
27248      let _ = done_tx.send(());
27249    });
27250
27251    match done_rx.recv_timeout(Duration::from_secs(10)) {
27252      Ok(()) => runner.join().unwrap(),
27253      Err(error) => panic!(
27254        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
27255      ),
27256    }
27257  }
27258
27259  #[test]
27260  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
27261    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
27262    // `Pending` but wakes itself synchronously during the poll leaves
27263    // a wake token pending, so the loop must consume the token and re-poll --
27264    // NOT panic -- even on a threadless build. "Pending returned" alone is
27265    // never a deadlock.
27266    use std::sync::mpsc;
27267
27268    struct PendThenReady {
27269      polls: usize,
27270    }
27271    impl Future for PendThenReady {
27272      type Output = ();
27273      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
27274        self.polls += 1;
27275        if self.polls >= 2 {
27276          Poll::Ready(())
27277        } else {
27278          cx.waker().wake_by_ref();
27279          Poll::Pending
27280        }
27281      }
27282    }
27283
27284    let (done_tx, done_rx) = mpsc::channel();
27285    let runner = std::thread::spawn(move || {
27286      let metrics = Arc::new(RuntimeMetrics::default());
27287      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27288      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
27289      executor.block_on(future.as_mut());
27290      assert_eq!(
27291        future.polls, 2,
27292        "the self-woken future must have been re-polled to completion"
27293      );
27294      let _ = done_tx.send(());
27295    });
27296
27297    match done_rx.recv_timeout(Duration::from_secs(10)) {
27298      Ok(()) => runner.join().unwrap(),
27299      Err(error) => panic!(
27300        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
27301      ),
27302    }
27303  }
27304
27305  #[cfg(not(target_family = "wasm"))]
27306  #[test]
27307  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
27308    // Threaded CT is NOT provably dead at the park decision (another OS thread
27309    // could deliver a wake later), so detection there is deadline-based and
27310    // opt-in. A park that outlives the armed deadline with ZERO runtime
27311    // progress must panic with the typed diagnostic.
27312    use std::sync::mpsc;
27313
27314    let (done_tx, done_rx) = mpsc::channel();
27315    let runner = std::thread::spawn(move || {
27316      let metrics = Arc::new(RuntimeMetrics::default());
27317      let executor = Arc::new(CurrentThreadExecutor::with_detection(
27318        metrics,
27319        false,
27320        Some(Duration::from_millis(50)),
27321      ));
27322
27323      let payload = catch_unwind(AssertUnwindSafe(|| {
27324        let mut future = std::pin::pin!(NeverReady);
27325        executor.block_on(future.as_mut());
27326      }))
27327      .expect_err("a dead native CT park must fire the armed deadline");
27328      let diagnostic = payload
27329        .downcast_ref::<BlockOnDeadlock>()
27330        .expect("typed payload");
27331      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
27332      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27333      // Teardown inside the bounded window: a wedged executor drop must fail
27334      // the recv below loudly, not hang the final join silently.
27335      drop(executor);
27336      let _ = done_tx.send(());
27337    });
27338
27339    match done_rx.recv_timeout(Duration::from_secs(10)) {
27340      Ok(()) => join_within(
27341        "the CT deadline-fire runner exits after teardown",
27342        runner,
27343        Duration::from_secs(30),
27344      ),
27345      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
27346    }
27347  }
27348
27349  #[cfg(not(target_family = "wasm"))]
27350  #[test]
27351  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
27352    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
27353    // runtime is alive (e.g. a slow JS plugin with other
27354    // work proceeding), so the deadline must re-arm instead of firing.
27355    // Progress is injected by advancing the very counter the reset reads --
27356    // the exact seam, isolated from wake delivery (production advances it via
27357    // real runnable polls).
27358    //
27359    // Deadline/cadence ratio: the reset is only exercised when at least one
27360    // tick lands inside EVERY armed window, so the window must dwarf the
27361    // injector thread's worst-case scheduling stall. On starved CI hosts
27362    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
27363    // can overshoot past a 60ms window, leaving one window with zero
27364    // progress -- which IS a deadline fire by design. Ticks every ~25ms
27365    // against a 1s window tolerate a ~975ms stall while the park is still
27366    // held across multiple full windows, so the repeated RE-ARM (not merely
27367    // the first arm) is what keeps the park alive.
27368    use std::sync::mpsc;
27369
27370    let deadline = Duration::from_millis(1000);
27371    let metrics = Arc::new(RuntimeMetrics::default());
27372    let executor = Arc::new(CurrentThreadExecutor::with_detection(
27373      Arc::clone(&metrics),
27374      false,
27375      Some(deadline),
27376    ));
27377
27378    let (value_tx, value_rx) = oneshot::channel::<usize>();
27379    let (done_tx, done_rx) = mpsc::channel();
27380    let runner = std::thread::spawn(move || {
27381      let mut output = None;
27382      {
27383        let mut future = std::pin::pin!(async {
27384          output = Some(value_rx.await.unwrap());
27385        });
27386        executor.block_on(future.as_mut());
27387      }
27388      assert_eq!(output, Some(9));
27389      // Teardown inside the bounded window (see `join_within`).
27390      drop(executor);
27391      let _ = done_tx.send(());
27392    });
27393
27394    // Hold the park across several full deadline windows, every one
27395    // containing many injected progress ticks, then deliver the (legitimately
27396    // late) wake. The loop is elapsed-based so an oversleeping iteration
27397    // extends the hold (more windows) instead of thinning the tick supply,
27398    // and the final tick lands at most one iteration before the wake.
27399    let hold_until = Instant::now() + deadline * 5 / 2;
27400    while Instant::now() < hold_until {
27401      std::thread::sleep(Duration::from_millis(25));
27402      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27403    }
27404    value_tx.send(9).unwrap();
27405
27406    match done_rx.recv_timeout(Duration::from_secs(30)) {
27407      Ok(()) => join_within(
27408        "the CT deadline-reset runner exits after teardown",
27409        runner,
27410        Duration::from_secs(30),
27411      ),
27412      Err(error) => panic!(
27413        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
27414      ),
27415    }
27416  }
27417
27418  #[cfg(not(target_family = "wasm"))]
27419  #[test]
27420  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
27421    // Shape (3): the MT deadline applies to the DriverParker inside
27422    // `cooperative_block_on`. Run the cooperative branch directly on this
27423    // thread with the test-only on-pool marker so the panic is observable as
27424    // a typed payload without rayon in between.
27425    use std::sync::mpsc;
27426
27427    let (done_tx, done_rx) = mpsc::channel();
27428    let runner = std::thread::spawn(move || {
27429      let options = RuntimeOptions {
27430        flavor: RuntimeFlavor::MultiThread,
27431        worker_threads: 1,
27432        max_blocking_tasks: 1,
27433        thread_name_prefix: "deadline-fire".to_string(),
27434        park_deadline: Some(Duration::from_millis(50)),
27435        drain_linger: DEFAULT_DRAIN_LINGER,
27436      };
27437      let executor =
27438        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27439
27440      let payload = {
27441        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27442        catch_unwind(AssertUnwindSafe(|| {
27443          let mut future = std::pin::pin!(NeverReady);
27444          executor.block_on(future.as_mut());
27445        }))
27446        .expect_err("a dead cooperative park must fire the armed deadline")
27447      };
27448      let diagnostic = payload
27449        .downcast_ref::<BlockOnDeadlock>()
27450        .expect("typed payload");
27451      assert_eq!(
27452        diagnostic.kind,
27453        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
27454      );
27455      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27456      let message = diagnostic.to_string();
27457      assert!(
27458        message.contains("block_on"),
27459        "diagnostic must name the hazard, got: {message}"
27460      );
27461      assert_eq!(
27462        executor.parked_drivers.count.load(Ordering::SeqCst),
27463        0,
27464        "the firing driver must deregister itself before panicking"
27465      );
27466      // Teardown inside the bounded window (see `join_within`).
27467      drop(executor);
27468      let _ = done_tx.send(());
27469    });
27470
27471    match done_rx.recv_timeout(Duration::from_secs(10)) {
27472      Ok(()) => join_within(
27473        "the MT deadline-fire runner exits after executor teardown",
27474        runner,
27475        Duration::from_secs(30),
27476      ),
27477      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
27478    }
27479  }
27480
27481  #[cfg(not(target_family = "wasm"))]
27482  #[test]
27483  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
27484    // Shape (4), MT side of the progress reset: a legitimately slow wake under
27485    // an armed deadline must NOT fire while the executor's
27486    // metrics keep advancing. Topology: the sole worker is parked as a
27487    // cooperative driver (real pool thread, real DriverParker park); the test
27488    // injects progress ticks across several full deadline windows, then
27489    // delivers the late wake.
27490    //
27491    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
27492    // so the window must dwarf the injector thread's worst-case scheduling
27493    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
27494    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
27495    // and a full window with zero progress IS a deadline fire by design.
27496    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
27497    // still holding the park across multiple full windows, proving the
27498    // repeated RE-ARM rather than a single lucky first window.
27499    use std::sync::mpsc;
27500
27501    let (done_tx, done_rx) = mpsc::channel();
27502    let runner = std::thread::spawn(move || {
27503      let deadline = Duration::from_millis(1000);
27504      let metrics = Arc::new(RuntimeMetrics::default());
27505      let options = RuntimeOptions {
27506        flavor: RuntimeFlavor::MultiThread,
27507        worker_threads: 1,
27508        max_blocking_tasks: 1,
27509        thread_name_prefix: "deadline-reset".to_string(),
27510        park_deadline: Some(deadline),
27511        drain_linger: DEFAULT_DRAIN_LINGER,
27512      };
27513      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
27514
27515      // Park the sole worker as a cooperative driver awaiting a gate (same
27516      // topology as the miss-compensation test).
27517      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27518      let td_exec = Arc::clone(&executor);
27519      let completed = Arc::new(AtomicBool::new(false));
27520      let td_flag = Arc::clone(&completed);
27521      let td_body = async move {
27522        let mut inner = std::pin::pin!(async move {
27523          assert_eq!(gate_rx.await.unwrap(), 9);
27524        });
27525        td_exec.block_on(inner.as_mut());
27526        td_flag.store(true, Ordering::SeqCst);
27527      };
27528      let sched = Arc::clone(&executor);
27529      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
27530      executor.schedule(td_runnable);
27531      wait_until("the sole driver parked", || {
27532        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
27533      });
27534
27535      // Outlive the armed deadline several times over, with every window
27536      // containing many injected progress ticks (the counter the reset
27537      // reads). Elapsed-based so an oversleeping iteration extends the hold
27538      // instead of thinning the tick supply, and the final tick lands at
27539      // most one iteration before the late wake below.
27540      let hold_until = Instant::now() + deadline * 5 / 2;
27541      while Instant::now() < hold_until {
27542        std::thread::sleep(Duration::from_millis(25));
27543        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27544      }
27545      assert!(
27546        !completed.load(Ordering::SeqCst),
27547        "the driver must still be parked awaiting its late wake"
27548      );
27549      gate_tx.send(9).unwrap();
27550      futures::executor::block_on(td_task);
27551      assert!(
27552        completed.load(Ordering::SeqCst),
27553        "the late wake must complete the parked driver"
27554      );
27555      // Teardown inside the bounded window (see `join_within`).
27556      drop(executor);
27557      let _ = done_tx.send(());
27558    });
27559
27560    match done_rx.recv_timeout(Duration::from_secs(30)) {
27561      Ok(()) => join_within(
27562        "the MT deadline-reset runner exits after executor teardown",
27563        runner,
27564        Duration::from_secs(30),
27565      ),
27566      Err(error) => panic!(
27567        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
27568      ),
27569    }
27570  }
27571
27572  #[cfg(not(target_family = "wasm"))]
27573  #[test]
27574  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
27575    // Shape (5): the foreign/napi caller thread parks in
27576    // `futures::executor::block_on` for the ENTIRE build -- the normal
27577    // production shape. Even with an aggressively short deadline armed and
27578    // ZERO executor progress for many windows, that park must never fire.
27579    use std::sync::mpsc;
27580
27581    let (done_tx, done_rx) = mpsc::channel();
27582    let runner = std::thread::spawn(move || {
27583      let options = RuntimeOptions {
27584        flavor: RuntimeFlavor::MultiThread,
27585        worker_threads: 2,
27586        max_blocking_tasks: 2,
27587        thread_name_prefix: "foreign-exempt".to_string(),
27588        park_deadline: Some(Duration::from_millis(40)),
27589        drain_linger: DEFAULT_DRAIN_LINGER,
27590      };
27591      let executor =
27592        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27593
27594      let (value_tx, value_rx) = oneshot::channel::<usize>();
27595      let waker_thread = std::thread::spawn(move || {
27596        // Well past several deadline windows, with zero executor progress.
27597        std::thread::sleep(Duration::from_millis(300));
27598        let _ = value_tx.send(7);
27599      });
27600
27601      let started = Instant::now();
27602      let mut output = None;
27603      {
27604        let mut future = std::pin::pin!(async {
27605          output = Some(value_rx.await.unwrap());
27606        });
27607        // This (non-pool) thread takes the parking branch: exempt by design.
27608        executor.block_on(future.as_mut());
27609      }
27610      assert_eq!(output, Some(7));
27611      assert!(
27612        started.elapsed() >= Duration::from_millis(250),
27613        "the park must genuinely have outlived the 40ms deadline many times over"
27614      );
27615      waker_thread.join().unwrap();
27616      // Teardown inside the bounded window: this test was one of the silent
27617      // wedges on the Windows CI runners -- every assertion above had
27618      // passed, so the hang lived in the unbounded residue (executor drop /
27619      // thread teardown). Keep that phase inside the loud bounds.
27620      drop(executor);
27621      let _ = done_tx.send(());
27622    });
27623
27624    match done_rx.recv_timeout(Duration::from_secs(10)) {
27625      Ok(()) => join_within(
27626        "the exempt foreign-park runner exits after executor teardown",
27627        runner,
27628        Duration::from_secs(30),
27629      ),
27630      Err(error) => {
27631        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
27632      }
27633    }
27634  }
27635
27636  #[cfg(not(target_family = "wasm"))]
27637  #[test]
27638  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
27639    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
27640    // hook instead of a wall-clock lottery. Interleaving:
27641    //   (1) the cooperative park's deadline expires with genuinely zero
27642    //       progress (nothing else touches the executor before the park);
27643    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
27644    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
27645    //       pops this parker, stores the permit and reports the wake as
27646    //       DELIVERED (so no drainer is spawned). The wake was counted as
27647    //       delivered TO US, so the post-deregister permit re-check is the
27648    //       first-line catch; queued work and the enqueue counters in the
27649    //       fingerprint are further backstops;
27650    //   (3) the expiry decision runs. It must treat the permit as a wake, run
27651    //       the job (which sends the gate), and complete the awaited future
27652    //       instead of reporting `BlockOnDeadlock`.
27653    use std::sync::mpsc;
27654
27655    let (done_tx, done_rx) = mpsc::channel();
27656    let runner = std::thread::spawn(move || {
27657      let options = RuntimeOptions {
27658        flavor: RuntimeFlavor::MultiThread,
27659        worker_threads: 1,
27660        max_blocking_tasks: 1,
27661        thread_name_prefix: "edge-wake".to_string(),
27662        park_deadline: Some(Duration::from_millis(40)),
27663        drain_linger: DEFAULT_DRAIN_LINGER,
27664      };
27665      let executor =
27666        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27667
27668      // Until the racing job sends this gate, the awaited future is Pending.
27669      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27670      // (registered_before_wake, registered_after_wake) observed by the hook.
27671      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
27672
27673      let hook_exec = Arc::clone(&executor);
27674      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
27675        *slot.borrow_mut() = Some(Box::new(move || {
27676          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27677          // The racing schedule: pushes the job, then `wake_one` pops the
27678          // still-registered parker (permit stored, wake counted as
27679          // delivered, no drainer).
27680          let handle = hook_exec.schedule_blocking(move || {
27681            let _ = gate_tx.send(5usize);
27682            0usize
27683          });
27684          drop(handle); // result unobserved; the gate send is the signal
27685          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27686          hook_saw_tx.send((before, after)).unwrap();
27687        }));
27688      });
27689
27690      // The test-only on-pool marker runs the cooperative branch and its
27691      // thread-local hook on THIS thread.
27692      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27693      let mut output = None;
27694      {
27695        let mut future = std::pin::pin!(async {
27696          output = Some(gate_rx.await.unwrap());
27697        });
27698        executor.block_on(future.as_mut());
27699      }
27700      // Clear the hook slot BEFORE any assert can panic: a hook left
27701      // installed captures an executor Arc, and dropping the last executor
27702      // Arc from a TLS destructor at thread exit is loader-lock hostile on
27703      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
27704      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
27705      assert_eq!(
27706        output,
27707        Some(5),
27708        "the edge-delivered wake's job must have run and completed the future"
27709      );
27710
27711      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
27712      assert_eq!(
27713        before, 1,
27714        "the hook must observe the parker STILL registered (race window)"
27715      );
27716      assert_eq!(
27717        after, 0,
27718        "the racing wake_one must have popped the parker (wake DELIVERED)"
27719      );
27720      // Teardown inside the bounded window (see `join_within`).
27721      drop(executor);
27722      let _ = done_tx.send(());
27723    });
27724
27725    match done_rx.recv_timeout(Duration::from_secs(10)) {
27726      Ok(()) => join_within(
27727        "the expiry-edge-wake runner exits after executor teardown",
27728        runner,
27729        Duration::from_secs(30),
27730      ),
27731      Err(error) => panic!(
27732        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
27733      ),
27734    }
27735  }
27736
27737  #[cfg(not(target_family = "wasm"))]
27738  #[test]
27739  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
27740    // Enqueue/fingerprint invariant: both runnable and blocking submissions
27741    // must advance the progress fingerprint at ENQUEUE. A blocking submission
27742    // landing after `has_queued_work()` but before the final fingerprint read
27743    // has already pushed deliverable work, while its wake targets an EMPTY
27744    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
27745    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
27746    // fires in this window on the driver thread:
27747    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
27748    //     job on the real worker -- `blocking_tasks_started` provably cannot
27749    //     advance behind our back;
27750    //   * the enqueue counter must therefore save the healthy park.
27751    use std::sync::mpsc;
27752
27753    let (done_tx, done_rx) = mpsc::channel();
27754    let runner = std::thread::spawn(move || {
27755      let options = RuntimeOptions {
27756        flavor: RuntimeFlavor::MultiThread,
27757        worker_threads: 1,
27758        max_blocking_tasks: 1,
27759        thread_name_prefix: "verdict-blocking".to_string(),
27760        park_deadline: Some(Duration::from_millis(40)),
27761        drain_linger: DEFAULT_DRAIN_LINGER,
27762      };
27763      let executor =
27764        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27765      executor
27766        .active_drainers
27767        .store(executor.max_drainers, Ordering::SeqCst);
27768
27769      // Until the racing job sends this gate, the awaited future is Pending.
27770      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27771      // Registry size observed by the hook (must be 0: already deregistered,
27772      // so no permit can possibly be delivered for this enqueue).
27773      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
27774
27775      let hook_exec = Arc::clone(&executor);
27776      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
27777        *slot.borrow_mut() = Some(Box::new(move || {
27778          let handle = hook_exec.schedule_blocking(move || {
27779            let _ = gate_tx.send(11usize);
27780            0usize
27781          });
27782          drop(handle); // result unobserved; the gate send is the signal
27783          hook_saw_tx
27784            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
27785            .unwrap();
27786        }));
27787      });
27788
27789      // The test-only on-pool marker runs the cooperative branch and its
27790      // thread-local hook on THIS thread.
27791      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27792      let mut output = None;
27793      {
27794        let mut future = std::pin::pin!(async {
27795          output = Some(gate_rx.await.unwrap());
27796        });
27797        executor.block_on(future.as_mut());
27798      }
27799      assert_eq!(
27800        output,
27801        Some(11),
27802        "the enqueued job must have been run by the surviving driver via run_one"
27803      );
27804
27805      let registered_at_hook = hook_saw_rx
27806        .recv()
27807        .expect("the verdict hook must have fired");
27808      assert_eq!(
27809        registered_at_hook, 0,
27810        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
27811      );
27812      executor.active_drainers.store(0, Ordering::SeqCst);
27813      let _ = done_tx.send(());
27814    });
27815
27816    match done_rx.recv_timeout(Duration::from_secs(10)) {
27817      Ok(()) => runner.join().unwrap(),
27818      Err(error) => panic!(
27819        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
27820      ),
27821    }
27822  }
27823
27824  #[cfg(not(target_family = "wasm"))]
27825  #[test]
27826  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
27827    // Publish only through the deadlock-admission protocol after every legacy
27828    // final check. No runnable, blocking job, permit, timer, or metrics
27829    // fingerprint changes, so only the synchronized publication epoch can
27830    // prevent the false verdict. The future observes a separate test flag on
27831    // its next poll; that flag is deliberately not a scheduler wake source.
27832    use std::sync::mpsc;
27833
27834    struct ReadyAfterPublication(Arc<AtomicBool>);
27835
27836    impl Future for ReadyAfterPublication {
27837      type Output = ();
27838
27839      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27840        if self.0.load(Ordering::SeqCst) {
27841          Poll::Ready(())
27842        } else {
27843          Poll::Pending
27844        }
27845      }
27846    }
27847
27848    let (done_tx, done_rx) = mpsc::channel();
27849    let runner = std::thread::spawn(move || {
27850      let options = RuntimeOptions {
27851        flavor: RuntimeFlavor::MultiThread,
27852        worker_threads: 1,
27853        max_blocking_tasks: 1,
27854        thread_name_prefix: "final-verdict-publication".to_string(),
27855        park_deadline: Some(Duration::from_millis(40)),
27856        drain_linger: DEFAULT_DRAIN_LINGER,
27857      };
27858      let executor =
27859        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27860
27861      let ready = Arc::new(AtomicBool::new(false));
27862      let baseline_fingerprint = executor.metrics.progress_fingerprint();
27863      let baseline_publications = executor.deadlock_state.publications();
27864      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
27865      let hook_exec = Arc::clone(&executor);
27866      let hook_ready = Arc::clone(&ready);
27867      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
27868        *slot.borrow_mut() = Some(Box::new(move || {
27869          let publication_executor = Arc::clone(&hook_exec);
27870          std::thread::spawn(move || {
27871            let mut admission = publication_executor
27872              .begin_deadlock_admission()
27873              .expect("the deadline detector must enable admission accounting");
27874            hook_ready.store(true, Ordering::SeqCst);
27875            admission.mark_published();
27876          })
27877          .join()
27878          .unwrap();
27879          hook_saw_tx
27880            .send((
27881              hook_exec.deadlock_state.publications(),
27882              !hook_exec.has_queued_runnable(),
27883              hook_exec
27884                .blocking_queue
27885                .lock()
27886                .unwrap_or_else(std::sync::PoisonError::into_inner)
27887                .is_empty(),
27888              hook_exec.next_timer_deadline().is_none(),
27889              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
27890              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
27891            ))
27892            .unwrap();
27893        }));
27894      });
27895
27896      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27897      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
27898      assert_eq!(
27899        executor.block_on(future.as_mut()),
27900        BlockOnOutcome::Completed,
27901        "the publication epoch must veto the first verdict and permit the next poll"
27902      );
27903      let (
27904        publications,
27905        runnable_queue_empty,
27906        blocking_queue_empty,
27907        timer_heap_empty,
27908        fingerprint_unchanged,
27909        parked_drivers,
27910      ) = hook_saw_rx
27911        .recv()
27912        .expect("the final-verdict hook must have fired");
27913      assert_eq!(
27914        publications,
27915        baseline_publications
27916          .checked_add(1)
27917          .expect("test publication epoch must not overflow"),
27918        "the cross-thread admission must publish exactly one epoch"
27919      );
27920      assert!(
27921        runnable_queue_empty,
27922        "the publication test must not queue a runnable"
27923      );
27924      assert!(
27925        blocking_queue_empty,
27926        "the publication test must not queue blocking work"
27927      );
27928      assert!(
27929        timer_heap_empty,
27930        "the publication test must not register a timer"
27931      );
27932      assert!(
27933        fingerprint_unchanged,
27934        "ordinary metrics progress must not veto the publication-only verdict"
27935      );
27936      assert_eq!(
27937        parked_drivers, 0,
27938        "the publication must land after the cooperative parker was deregistered"
27939      );
27940      let _ = done_tx.send(());
27941    });
27942
27943    match done_rx.recv_timeout(Duration::from_secs(10)) {
27944      Ok(()) => runner.join().unwrap(),
27945      Err(error) => panic!(
27946        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
27947      ),
27948    }
27949  }
27950
27951  #[cfg(not(target_family = "wasm"))]
27952  #[test]
27953  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
27954    // The exact owner lane is saturated and reserved while the cooperative
27955    // driver parks. At expiry, release selects that driver and publishes its
27956    // handoff, then pauses before unpark. The driver reaches the gated verdict
27957    // with no queue, timer, metric, or ordinary publication change. The
27958    // exact-owner publication boundary must serialize the final check. The
27959    // hook then consumes the ordinary permit and renews the lane reservation,
27960    // leaving only the pending-handoff predicate to veto the false deadlock.
27961    use std::sync::mpsc;
27962
27963    let executor = multi_thread_executor(
27964      1,
27965      Some(Duration::from_millis(40)),
27966      "deadline-selected-handoff",
27967    );
27968    let owner = executor.fresh_owner_token();
27969    executor.active_blocking_owners.register(owner);
27970    executor
27971      .active_blocking
27972      .store(executor.max_blocking, Ordering::SeqCst);
27973
27974    let ready = Arc::new(AtomicBool::new(false));
27975    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
27976    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
27977    let (selected_tx, selected_rx) = mpsc::channel();
27978    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
27979    let (released_tx, released_rx) = mpsc::channel();
27980    let (renewed_tx, renewed_rx) = mpsc::channel();
27981    let release_executor = Arc::clone(&executor);
27982    let renewal_executor = Arc::clone(&executor);
27983    let release_ready = Arc::clone(&ready);
27984    let releaser = std::thread::spawn(move || {
27985      let reservation =
27986        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
27987          .expect("the synthetic exact-owner lane must start reserved");
27988      reservation_ready_tx.send(()).unwrap();
27989      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
27990        *slot.borrow_mut() = Some(Box::new(move || {
27991          selected_tx.send(()).unwrap();
27992          allow_unpark_rx.recv().unwrap();
27993          let renewed = renewal_executor
27994            .active_blocking_owners
27995            .reserve(owner)
27996            .expect("the released exact-owner lane must be reservable before unpark");
27997          release_ready.store(true, Ordering::SeqCst);
27998          renewed_tx.send(renewed).unwrap();
27999        }));
28000      });
28001      release_reservation_rx.recv().unwrap();
28002      released_tx
28003        .send(release_executor.release_owner_lane_and_wake(reservation))
28004        .unwrap();
28005    });
28006    reservation_ready_rx
28007      .recv_timeout(Duration::from_secs(2))
28008      .expect("the exact-owner reservation must be established before the park");
28009
28010    let baseline_fingerprint = executor.metrics.progress_fingerprint();
28011    let baseline_publications = executor.deadlock_state.publications();
28012    let expiry_executor = Arc::clone(&executor);
28013    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
28014      *slot.borrow_mut() = Some(Box::new(move || {
28015        release_reservation_tx.send(()).unwrap();
28016        selected_rx
28017          .recv_timeout(Duration::from_secs(2))
28018          .expect("reservation release must select the still-registered exact-owner driver");
28019        assert_eq!(
28020          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
28021          0,
28022          "handoff selection must remove the driver before pausing ahead of unpark"
28023        );
28024      }));
28025    });
28026
28027    let verdict_executor = Arc::clone(&executor);
28028    let verdict_ready = Arc::clone(&ready);
28029    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28030      *slot.borrow_mut() = Some(Box::new(move || {
28031        assert!(
28032          verdict_executor.active_blocking_owners.is_available(owner),
28033          "reservation release must publish lane availability before handoff selection"
28034        );
28035        assert!(!verdict_executor.has_queued_runnable());
28036        assert!(
28037          verdict_executor
28038            .blocking_queue
28039            .lock()
28040            .unwrap_or_else(std::sync::PoisonError::into_inner)
28041            .is_empty()
28042        );
28043        assert!(verdict_executor.next_timer_deadline().is_none());
28044        assert!(
28045          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28046          "ordinary metrics progress must not save the selected-handoff verdict"
28047        );
28048        assert_eq!(
28049          verdict_executor.deadlock_state.publications(),
28050          baseline_publications
28051        );
28052        assert!(
28053          !verdict_ready.load(Ordering::SeqCst),
28054          "the selected handoff must still be paused before unpark at the gated verdict"
28055        );
28056        allow_unpark_tx.send(()).unwrap();
28057      }));
28058    });
28059    let exact_owner_executor = Arc::clone(&executor);
28060    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
28061      *slot.borrow_mut() = Some(Box::new(move |parker| {
28062        assert!(
28063          parker.consume_permit(),
28064          "the selected handoff must unpark before exact-owner final predicates run"
28065        );
28066        assert!(
28067          parker.has_owner_handoff(),
28068          "consuming the ordinary permit must leave the exact-owner handoff pending"
28069        );
28070        assert!(
28071          !exact_owner_executor
28072            .active_blocking_owners
28073            .is_available(owner),
28074          "the renewed reservation must prevent lane availability from saving the verdict"
28075        );
28076      }));
28077    });
28078
28079    {
28080      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28081      let _owner = BlockingOwnerGuard::enter(Some(owner));
28082      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28083        dependency: BlockingDependency {
28084          job: BlockingJobId(u64::MAX - 80),
28085          owner: Some(owner)
28086        },
28087        ready: Arc::clone(&ready),
28088      });
28089      assert_eq!(
28090        executor.block_on(future.as_mut()),
28091        BlockOnOutcome::Completed
28092      );
28093    }
28094    assert!(
28095      released_rx
28096        .recv_timeout(Duration::from_secs(2))
28097        .expect("the selected handoff publication must finish"),
28098      "reservation release must have selected the cooperative driver"
28099    );
28100    let renewed = renewed_rx
28101      .recv_timeout(Duration::from_secs(2))
28102      .expect("the selected handoff must renew the owner reservation before unpark");
28103    releaser.join().unwrap();
28104
28105    executor.active_blocking_owners.release(owner, renewed);
28106    executor.active_blocking.store(0, Ordering::SeqCst);
28107    executor.active_blocking_owners.unregister(owner);
28108    executor.begin_shutdown();
28109    executor.wait_until_scheduler_idle();
28110  }
28111
28112  #[cfg(not(target_family = "wasm"))]
28113  #[test]
28114  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
28115    // Release begins only after the deadline path has deregistered its parker
28116    // and completed every legacy final check. No targeted wake or ordinary
28117    // scheduler publication can save the verdict; the synchronized exact-owner
28118    // availability predicate must observe the released lane.
28119    use std::sync::mpsc;
28120
28121    let executor = multi_thread_executor(
28122      1,
28123      Some(Duration::from_millis(40)),
28124      "deadline-owner-no-parker",
28125    );
28126    let owner = executor.fresh_owner_token();
28127    executor.active_blocking_owners.register(owner);
28128    executor
28129      .active_blocking
28130      .store(executor.max_blocking, Ordering::SeqCst);
28131
28132    let ready = Arc::new(AtomicBool::new(false));
28133    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
28134    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28135    let (released_tx, released_rx) = mpsc::channel();
28136    let release_executor = Arc::clone(&executor);
28137    let release_ready = Arc::clone(&ready);
28138    let releaser = std::thread::spawn(move || {
28139      let reservation =
28140        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28141          .expect("the synthetic exact-owner lane must start reserved");
28142      reservation_ready_tx.send(()).unwrap();
28143      release_reservation_rx.recv().unwrap();
28144      let woke = release_executor.release_owner_lane_and_wake(reservation);
28145      release_ready.store(true, Ordering::SeqCst);
28146      released_tx.send(woke).unwrap();
28147    });
28148    reservation_ready_rx
28149      .recv_timeout(Duration::from_secs(2))
28150      .expect("the exact-owner reservation must be established before the park");
28151
28152    let baseline_fingerprint = executor.metrics.progress_fingerprint();
28153    let baseline_publications = executor.deadlock_state.publications();
28154    let verdict_executor = Arc::clone(&executor);
28155    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
28156      *slot.borrow_mut() = Some(Box::new(move || {
28157        assert_eq!(
28158          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
28159          0,
28160          "the release must begin only after deadline deregistration"
28161        );
28162        release_reservation_tx.send(()).unwrap();
28163        assert!(
28164          !released_rx
28165            .recv_timeout(Duration::from_secs(2))
28166            .expect("the no-parker owner release must finish"),
28167          "no registered parker may receive the released owner lane"
28168        );
28169        assert!(verdict_executor.active_blocking_owners.is_available(owner));
28170        assert!(!verdict_executor.has_queued_runnable());
28171        assert!(
28172          verdict_executor
28173            .blocking_queue
28174            .lock()
28175            .unwrap_or_else(std::sync::PoisonError::into_inner)
28176            .is_empty()
28177        );
28178        assert!(verdict_executor.next_timer_deadline().is_none());
28179        assert!(
28180          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28181          "ordinary metrics progress must not save the no-parker release verdict"
28182        );
28183        assert_eq!(
28184          verdict_executor.deadlock_state.publications(),
28185          baseline_publications
28186        );
28187      }));
28188    });
28189
28190    {
28191      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28192      let _owner = BlockingOwnerGuard::enter(Some(owner));
28193      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28194        dependency: BlockingDependency {
28195          job: BlockingJobId(u64::MAX - 81),
28196          owner: Some(owner)
28197        },
28198        ready: Arc::clone(&ready),
28199      });
28200      assert_eq!(
28201        executor.block_on(future.as_mut()),
28202        BlockOnOutcome::Completed
28203      );
28204    }
28205    releaser.join().unwrap();
28206
28207    executor.active_blocking.store(0, Ordering::SeqCst);
28208    executor.active_blocking_owners.unregister(owner);
28209    executor.begin_shutdown();
28210    executor.wait_until_scheduler_idle();
28211  }
28212
28213  #[cfg(not(target_family = "wasm"))]
28214  #[test]
28215  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
28216    // Pin the shutdown race after the final verdict gate has closed:
28217    //   1. an accepted task is pending, so shutdown abort must wake it;
28218    //   2. a cooperative block_on reaches its deadline and pauses while
28219    //      holding the closed verdict gate;
28220    //   3. shutdown publishes generation stop, transitions
28221    //      Running -> Stopping, then its abort wake blocks entering scheduler
28222    //      admission behind that gate;
28223    //   4. the gated final verdict must observe stop and return Stopped,
28224    //      reopening the gate so the abort wake and shutdown can finish.
28225    //
28226    // Before stop was published by the controller transition, step 3 blocked
28227    // inside abort before executor shutdown could set it. The driver then saw
28228    // empty queues and emitted a false BlockOnDeadlock.
28229    use std::sync::mpsc;
28230
28231    let controller = Arc::new(RuntimeController::new());
28232    controller
28233      .configure(RuntimeOptions {
28234        flavor: RuntimeFlavor::MultiThread,
28235        worker_threads: 2,
28236        max_blocking_tasks: 1,
28237        thread_name_prefix: "shutdown-gated-verdict".to_string(),
28238        park_deadline: Some(Duration::from_millis(40)),
28239        drain_linger: DEFAULT_DRAIN_LINGER,
28240      })
28241      .expect("the shutdown race test configuration must be accepted");
28242
28243    let backend = controller.backend();
28244    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28245      panic!("the configured backend must be MultiThread");
28246    };
28247    let executor_id = executor.id;
28248    let weak_executor = Arc::downgrade(executor);
28249    drop(backend);
28250
28251    let (task_polled_tx, task_polled_rx) = mpsc::channel();
28252    let task = controller
28253      .try_spawn(async move {
28254        task_polled_tx.send(()).unwrap();
28255        std::future::pending::<()>().await;
28256      })
28257      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
28258    task_polled_rx
28259      .recv_timeout(Duration::from_secs(2))
28260      .expect("the accepted task must publish an abortable pending waker");
28261
28262    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
28263    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28264    let (driver_result_tx, driver_result_rx) = mpsc::channel();
28265    let driver_controller = Arc::clone(&controller);
28266    let driver = std::thread::spawn(move || {
28267      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
28268        *slot.borrow_mut() = Some(Box::new(move || {
28269          gate_closed_tx.send(()).unwrap();
28270          release_verdict_rx.recv().unwrap();
28271        }));
28272      });
28273
28274      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28275      let mut pending = std::pin::pin!(NeverReady);
28276      let result = catch_unwind(AssertUnwindSafe(|| {
28277        driver_controller.block_on(pending.as_mut())
28278      }));
28279      let result = match result {
28280        Ok(outcome) => Ok(outcome),
28281        Err(payload) => {
28282          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28283          discard_panic_payload(payload);
28284          Err(was_deadlock)
28285        }
28286      };
28287      driver_result_tx.send(result).unwrap();
28288    });
28289
28290    gate_closed_rx
28291      .recv_timeout(Duration::from_secs(2))
28292      .expect("the cooperative driver must pause with the verdict gate closed");
28293
28294    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
28295    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28296    let shutdown_controller = Arc::clone(&controller);
28297    let shutdown = std::thread::spawn(move || {
28298      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
28299        *slot.borrow_mut() = Some(Box::new(move || {
28300          abort_blocked_tx.send(()).unwrap();
28301        }));
28302      });
28303      shutdown_result_tx
28304        .send(shutdown_controller.shutdown())
28305        .unwrap();
28306    });
28307
28308    abort_blocked_rx
28309      .recv_timeout(Duration::from_secs(2))
28310      .expect("the accepted task's abort wake must reach the closed verdict gate");
28311    let lifecycle_is_stopping = {
28312      let state = controller
28313        .state
28314        .lock()
28315        .unwrap_or_else(std::sync::PoisonError::into_inner);
28316      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
28317    };
28318    let stop_was_published = weak_executor
28319      .upgrade()
28320      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
28321
28322    release_verdict_tx.send(()).unwrap();
28323
28324    let driver_result = driver_result_rx
28325      .recv_timeout(Duration::from_secs(2))
28326      .expect("the gated driver must resolve after observing shutdown");
28327    let shutdown_result = shutdown_result_rx
28328      .recv_timeout(Duration::from_secs(5))
28329      .expect("shutdown must finish after the verdict gate reopens");
28330    // Both threads have sent their results; what remains is teardown (and on
28331    // the shutdown thread, the controller's `wait_for_all_workers` already
28332    // completed inside `shutdown()`). This test was one of the silent wedges
28333    // on the Windows CI runners -- bound the residue loudly.
28334    join_within(
28335      "the gated cooperative driver exits after teardown",
28336      driver,
28337      Duration::from_secs(30),
28338    );
28339    join_within(
28340      "the shutdown thread exits after teardown",
28341      shutdown,
28342      Duration::from_secs(30),
28343    );
28344
28345    assert!(
28346      lifecycle_is_stopping,
28347      "the abort wake must occur after Running -> Stopping"
28348    );
28349    assert!(
28350      stop_was_published,
28351      "generation stop must be visible before shutdown invokes accepted-task aborts"
28352    );
28353    assert_eq!(
28354      driver_result,
28355      Ok(BlockOnOutcome::Stopped),
28356      "shutdown must not be reported as a gated BlockOnDeadlock"
28357    );
28358    shutdown_result.unwrap();
28359    assert!(
28360      futures::executor::block_on(task).is_err(),
28361      "the accepted pending task must resolve as cancelled"
28362    );
28363  }
28364
28365  #[cfg(not(target_family = "wasm"))]
28366  #[test]
28367  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
28368    // Complement the publication-wins race above. The verdict owns both its
28369    // admission gate and the stop-publication mutex before shutdown starts.
28370    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
28371    // Running -> Stopping until the verdict finishes its synchronized checks.
28372    use std::sync::mpsc;
28373
28374    let controller = Arc::new(RuntimeController::new());
28375    controller
28376      .configure(RuntimeOptions {
28377        flavor: RuntimeFlavor::MultiThread,
28378        worker_threads: 2,
28379        max_blocking_tasks: 1,
28380        thread_name_prefix: "verdict-before-shutdown".to_string(),
28381        park_deadline: Some(Duration::from_millis(40)),
28382        drain_linger: DEFAULT_DRAIN_LINGER,
28383      })
28384      .expect("the verdict-first test configuration must be accepted");
28385
28386    let backend = controller.backend();
28387    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28388      panic!("the configured backend must be MultiThread");
28389    };
28390    let executor_id = executor.id;
28391    drop(backend);
28392
28393    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
28394    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28395    let (driver_result_tx, driver_result_rx) = mpsc::channel();
28396    let driver_controller = Arc::clone(&controller);
28397    let driver = std::thread::spawn(move || {
28398      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28399        *slot.borrow_mut() = Some(Box::new(move || {
28400          verdict_locked_tx.send(()).unwrap();
28401          release_verdict_rx.recv().unwrap();
28402        }));
28403      });
28404
28405      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28406      let mut pending = std::pin::pin!(NeverReady);
28407      let result = catch_unwind(AssertUnwindSafe(|| {
28408        driver_controller.block_on(pending.as_mut())
28409      }));
28410      let result = match result {
28411        Ok(outcome) => Ok(outcome),
28412        Err(payload) => {
28413          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28414          discard_panic_payload(payload);
28415          Err(was_deadlock)
28416        }
28417      };
28418      driver_result_tx.send(result).unwrap();
28419    });
28420
28421    verdict_locked_rx
28422      .recv_timeout(Duration::from_secs(2))
28423      .expect("the verdict must hold the synchronized stop-publication guard");
28424
28425    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
28426    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28427    let shutdown_controller = Arc::clone(&controller);
28428    let shutdown = std::thread::spawn(move || {
28429      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
28430        *slot.borrow_mut() = Some(Box::new(move || {
28431          shutdown_waiting_tx.send(()).unwrap();
28432        }));
28433      });
28434      shutdown_result_tx
28435        .send(shutdown_controller.shutdown())
28436        .unwrap();
28437    });
28438
28439    shutdown_waiting_rx
28440      .recv_timeout(Duration::from_secs(2))
28441      .expect("shutdown must reach the lifecycle-to-publication lock edge");
28442    let early_shutdown = shutdown_result_rx
28443      .recv_timeout(Duration::from_millis(100))
28444      .ok();
28445    let shutdown_was_blocked = early_shutdown.is_none();
28446    release_verdict_tx.send(()).unwrap();
28447
28448    let driver_result = driver_result_rx
28449      .recv_timeout(Duration::from_secs(2))
28450      .expect("the verdict-first driver must finish after release");
28451    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28452      shutdown_result_rx
28453        .recv_timeout(Duration::from_secs(5))
28454        .expect("shutdown must finish after the verdict releases publication")
28455    });
28456    driver.join().unwrap();
28457    shutdown.join().unwrap();
28458
28459    assert!(
28460      shutdown_was_blocked,
28461      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
28462    );
28463    assert_eq!(
28464      driver_result,
28465      Err(true),
28466      "a verdict that linearizes before shutdown must retain the typed deadlock"
28467    );
28468    shutdown_result.unwrap();
28469  }
28470
28471  #[cfg(not(target_family = "wasm"))]
28472  #[test]
28473  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
28474    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
28475
28476    if std::env::var_os(CHILD_ENV).is_some() {
28477      let executor =
28478        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
28479      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
28480      // hook only needs to prove that scheduler admission returns.
28481      executor
28482        .active_drainers
28483        .store(executor.max_drainers, Ordering::SeqCst);
28484
28485      let hook_returned = Arc::new(AtomicBool::new(false));
28486      let hook_flag = Arc::clone(&hook_returned);
28487      let hook_executor = Arc::clone(&executor);
28488      let previous_hook = std::panic::take_hook();
28489      std::panic::set_hook(Box::new(move |_| {
28490        let handle = hook_executor.schedule_blocking(|| ());
28491        drop(handle);
28492        hook_flag.store(true, Ordering::SeqCst);
28493      }));
28494
28495      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28496      let result = catch_unwind(AssertUnwindSafe(|| {
28497        let mut pending = std::pin::pin!(std::future::pending::<()>());
28498        executor.block_on(pending.as_mut());
28499      }));
28500      std::panic::set_hook(previous_hook);
28501
28502      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
28503      assert!(
28504        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
28505        "the deadline must retain its structured diagnostic"
28506      );
28507      assert!(
28508        hook_returned.load(Ordering::SeqCst),
28509        "the panic hook's scheduler submission must return before unwind"
28510      );
28511
28512      executor.active_drainers.store(0, Ordering::SeqCst);
28513      executor.begin_shutdown();
28514      executor.wait_until_scheduler_idle();
28515      return;
28516    }
28517
28518    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28519      .arg("--exact")
28520      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
28521      .arg("--nocapture")
28522      .env(CHILD_ENV, "1")
28523      .stdout(std::process::Stdio::piped())
28524      .stderr(std::process::Stdio::piped())
28525      .spawn()
28526      .expect("the panic-hook subprocess must start");
28527    let deadline = Instant::now() + Duration::from_secs(5);
28528    let status = loop {
28529      if let Some(status) = child
28530        .try_wait()
28531        .expect("the subprocess status must be readable")
28532      {
28533        break status;
28534      }
28535      if Instant::now() >= deadline {
28536        let _ = child.kill();
28537        let output = child
28538          .wait_with_output()
28539          .expect("the timed-out subprocess must be reaped");
28540        panic!(
28541          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
28542          String::from_utf8_lossy(&output.stdout),
28543          String::from_utf8_lossy(&output.stderr)
28544        );
28545      }
28546      std::thread::sleep(Duration::from_millis(10));
28547    };
28548    let output = child
28549      .wait_with_output()
28550      .expect("the completed subprocess output must be read");
28551    assert!(
28552      status.success(),
28553      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
28554      status.code(),
28555      String::from_utf8_lossy(&output.stdout),
28556      String::from_utf8_lossy(&output.stderr)
28557    );
28558  }
28559
28560  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
28561  // the embedding host's env-resolution pipeline together with the read
28562  // itself; its tests live there.
28563
28564  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
28565  // The tests cover both deadlock-detection interactions. All tests build
28566  // executors LOCALLY (never the global RUNTIME or the process-global host
28567  // driver) and run hang-prone workloads on a child thread bounded by
28568  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
28569
28570  #[test]
28571  fn contained_waker_separates_wake_and_drop_panics() {
28572    struct PanicWakeAndDrop;
28573
28574    impl std::task::Wake for PanicWakeAndDrop {
28575      fn wake(self: Arc<Self>) {
28576        panic!("intentional consuming wake panic");
28577      }
28578
28579      fn wake_by_ref(self: &Arc<Self>) {
28580        panic!("intentional borrowed wake panic");
28581      }
28582    }
28583
28584    impl Drop for PanicWakeAndDrop {
28585      fn drop(&mut self) {
28586        panic!("intentional RawWaker destructor panic");
28587      }
28588    }
28589
28590    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
28591  }
28592
28593  struct TestHostileRawWakerState {
28594    clones: AtomicUsize,
28595    drop_panics: AtomicUsize,
28596    panic_on_clone: Option<usize>,
28597  }
28598
28599  struct TestHostileRawWakerToken {
28600    state: Arc<TestHostileRawWakerState>,
28601    panic_on_drop: bool,
28602  }
28603
28604  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
28605    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
28606    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
28607    assert_ne!(
28608      token.state.panic_on_clone,
28609      Some(clone),
28610      "intentional RawWaker clone panic"
28611    );
28612    let clone = Box::new(TestHostileRawWakerToken {
28613      state: Arc::clone(&token.state),
28614      panic_on_drop: true,
28615    });
28616    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
28617  }
28618
28619  unsafe fn wake_test_hostile_waker(data: *const ()) {
28620    unsafe { drop_test_hostile_waker(data) };
28621  }
28622
28623  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
28624
28625  unsafe fn drop_test_hostile_waker(data: *const ()) {
28626    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
28627    if token.panic_on_drop {
28628      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
28629      panic!("intentional cloned RawWaker destructor panic");
28630    }
28631  }
28632
28633  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
28634    clone_test_hostile_waker,
28635    wake_test_hostile_waker,
28636    wake_test_hostile_waker_by_ref,
28637    drop_test_hostile_waker,
28638  );
28639
28640  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
28641    let token = Box::new(TestHostileRawWakerToken {
28642      state,
28643      panic_on_drop: false,
28644    });
28645    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
28646    unsafe { Waker::from_raw(raw) }
28647  }
28648
28649  /// Waker that only counts. For polling a raw `Sleep` without an executor.
28650  struct CountingWake(AtomicUsize);
28651  impl std::task::Wake for CountingWake {
28652    fn wake(self: Arc<Self>) {
28653      self.0.fetch_add(1, Ordering::SeqCst);
28654    }
28655  }
28656
28657  /// One armed stub timer: the latest waker plus a cancellation flag.
28658  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
28659
28660  /// Test-local host driver: fires each registered timer from a helper OS
28661  /// thread at its deadline (re-registration under the same id refreshes the
28662  /// waker without arming a second thread, per the `TimerDriver` contract).
28663  struct StubHostTimerDriver {
28664    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
28665  }
28666
28667  impl StubHostTimerDriver {
28668    fn new() -> Self {
28669      Self {
28670        timers: Mutex::new(FxHashMap::default()),
28671      }
28672    }
28673  }
28674
28675  impl TimerDriver for StubHostTimerDriver {
28676    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28677      let mut timers = self.timers.lock().unwrap();
28678      if let Some(cell) = timers.get(&id) {
28679        *cell.0.lock().unwrap() = waker;
28680        return;
28681      }
28682      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
28683      timers.insert(id, Arc::clone(&cell));
28684      std::thread::spawn(move || {
28685        let now = Instant::now();
28686        if deadline > now {
28687          std::thread::sleep(deadline - now);
28688        }
28689        if !cell.1.load(Ordering::SeqCst) {
28690          cell.0.lock().unwrap().wake_by_ref();
28691        }
28692      });
28693    }
28694
28695    fn cancel(&self, id: TimerId) {
28696      let removed = self.timers.lock().unwrap().remove(&id);
28697      if let Some(cell) = removed {
28698        cell.1.store(true, Ordering::SeqCst);
28699      }
28700    }
28701  }
28702
28703  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
28704  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
28705  /// per timer id), never fires on its own, and reports the controllable
28706  /// `live` flag (a killed stub plays a driver whose owning host context --
28707  /// napi env in the binding -- was torn down).
28708  struct ManualStubDriver {
28709    live: AtomicBool,
28710    registers: Mutex<Vec<(TimerId, Instant)>>,
28711    cancels: Mutex<Vec<TimerId>>,
28712    pending: Mutex<FxHashMap<TimerId, Waker>>,
28713    swept: AtomicBool,
28714  }
28715
28716  impl ManualStubDriver {
28717    fn new() -> Arc<Self> {
28718      Arc::new(Self {
28719        live: AtomicBool::new(true),
28720        registers: Mutex::new(Vec::new()),
28721        cancels: Mutex::new(Vec::new()),
28722        pending: Mutex::new(FxHashMap::default()),
28723        swept: AtomicBool::new(false),
28724      })
28725    }
28726
28727    fn kill(&self) {
28728      self.live.store(false, Ordering::SeqCst);
28729    }
28730  }
28731
28732  impl TimerDriver for ManualStubDriver {
28733    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28734      self.registers.lock().unwrap().push((id, deadline));
28735      self.pending.lock().unwrap().insert(id, waker);
28736    }
28737
28738    fn cancel(&self, id: TimerId) {
28739      self.cancels.lock().unwrap().push(id);
28740      self.pending.lock().unwrap().remove(&id);
28741    }
28742
28743    fn is_live(&self) -> bool {
28744      self.live.load(Ordering::SeqCst)
28745    }
28746
28747    fn on_swept(&self) {
28748      // The binding shape: drain the pending map and wake everything armed
28749      // here so those sleeps re-poll onto the next live registrant.
28750      self.swept.store(true, Ordering::SeqCst);
28751      let wakers: Vec<Waker> = self
28752        .pending
28753        .lock()
28754        .unwrap()
28755        .drain()
28756        .map(|(_, w)| w)
28757        .collect();
28758      for waker in wakers {
28759        waker.wake();
28760      }
28761    }
28762  }
28763
28764  struct PanickingTimerDriver {
28765    swept: AtomicBool,
28766  }
28767
28768  impl TimerDriver for PanickingTimerDriver {
28769    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28770
28771    fn cancel(&self, _id: TimerId) {}
28772
28773    fn is_live(&self) -> bool {
28774      panic!("intentional timer-driver liveness panic");
28775    }
28776
28777    fn on_swept(&self) {
28778      self.swept.store(true, Ordering::SeqCst);
28779      panic!("intentional timer-driver sweep panic");
28780    }
28781  }
28782
28783  struct PanickingRegisterTimerDriver {
28784    registers: AtomicUsize,
28785    cancels: Mutex<Vec<TimerId>>,
28786    pending: Mutex<FxHashMap<TimerId, Waker>>,
28787  }
28788
28789  impl PanickingRegisterTimerDriver {
28790    fn new() -> Arc<Self> {
28791      Arc::new(Self {
28792        registers: AtomicUsize::new(0),
28793        cancels: Mutex::new(Vec::new()),
28794        pending: Mutex::new(FxHashMap::default()),
28795      })
28796    }
28797  }
28798
28799  impl TimerDriver for PanickingRegisterTimerDriver {
28800    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
28801      self.registers.fetch_add(1, Ordering::SeqCst);
28802      self.pending.lock().unwrap().insert(id, waker);
28803      panic!("intentional timer-driver register panic after arming");
28804    }
28805
28806    fn cancel(&self, id: TimerId) {
28807      self.cancels.lock().unwrap().push(id);
28808      self.pending.lock().unwrap().remove(&id);
28809    }
28810  }
28811
28812  struct PanicBeforeRetainingTimerDriver {
28813    registers: AtomicUsize,
28814  }
28815
28816  impl TimerDriver for PanicBeforeRetainingTimerDriver {
28817    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28818      self.registers.fetch_add(1, Ordering::SeqCst);
28819      panic!("intentional timer-driver register panic before retaining its waker");
28820    }
28821
28822    fn cancel(&self, _id: TimerId) {}
28823  }
28824
28825  struct PanickingCancelDropTimerDriver {
28826    cancels: Arc<AtomicUsize>,
28827    drops: Arc<AtomicUsize>,
28828  }
28829
28830  impl TimerDriver for PanickingCancelDropTimerDriver {
28831    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28832
28833    fn cancel(&self, _id: TimerId) {
28834      self.cancels.fetch_add(1, Ordering::SeqCst);
28835      panic!("intentional timer-driver cancel panic");
28836    }
28837  }
28838
28839  impl Drop for PanickingCancelDropTimerDriver {
28840    fn drop(&mut self) {
28841      self.drops.fetch_add(1, Ordering::SeqCst);
28842      panic!("intentional timer-driver destructor panic");
28843    }
28844  }
28845
28846  struct UnregisterLaterThenPanicTimerDriver {
28847    registry: Weak<TimerDriverRegistry>,
28848    later: Mutex<Option<TimerDriverId>>,
28849    registers: AtomicUsize,
28850  }
28851
28852  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
28853    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28854      self.registers.fetch_add(1, Ordering::SeqCst);
28855      let later = *self.later.lock().unwrap();
28856      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
28857        registry.unregister(later);
28858      }
28859      panic!("intentional earlier timer-driver panic after unregistering a later driver");
28860    }
28861
28862    fn cancel(&self, _id: TimerId) {}
28863  }
28864
28865  struct ReentrantLivenessDriver {
28866    registry: std::sync::Weak<TimerDriverRegistry>,
28867    registration: Mutex<Option<TimerDriverId>>,
28868  }
28869
28870  impl TimerDriver for ReentrantLivenessDriver {
28871    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28872
28873    fn cancel(&self, _id: TimerId) {}
28874
28875    fn is_live(&self) -> bool {
28876      let registration = *self.registration.lock().unwrap();
28877      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
28878        registry.unregister(id);
28879      }
28880      false
28881    }
28882  }
28883
28884  struct ReentrantMultiUnregisterTimerDriver {
28885    registry: std::sync::Weak<TimerDriverRegistry>,
28886    registrations: Mutex<Vec<TimerDriverId>>,
28887  }
28888
28889  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
28890    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28891
28892    fn cancel(&self, _id: TimerId) {}
28893
28894    fn is_live(&self) -> bool {
28895      if let Some(registry) = self.registry.upgrade() {
28896        for registration in self.registrations.lock().unwrap().iter().copied() {
28897          registry.unregister(registration);
28898        }
28899      }
28900      false
28901    }
28902  }
28903
28904  struct ReentrantSweepTimerDriver {
28905    registry: std::sync::Weak<TimerDriverRegistry>,
28906    registration: Mutex<Option<TimerDriverId>>,
28907    sweeps: AtomicUsize,
28908    removed_before_sweep: AtomicBool,
28909  }
28910
28911  impl TimerDriver for ReentrantSweepTimerDriver {
28912    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28913
28914    fn cancel(&self, _id: TimerId) {}
28915
28916    fn is_live(&self) -> bool {
28917      false
28918    }
28919
28920    fn on_swept(&self) {
28921      self.sweeps.fetch_add(1, Ordering::SeqCst);
28922      let registration = self.registration.lock().unwrap().take();
28923      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
28924        let state = registry
28925          .state
28926          .try_lock()
28927          .expect("the sweep callback must run outside the timer-driver registry lock");
28928        assert!(
28929          !state.entries.contains_key(&registration),
28930          "the dead driver must be unlinked before its sweep callback runs"
28931        );
28932        self.removed_before_sweep.store(true, Ordering::SeqCst);
28933        drop(state);
28934        // Mirrors JsTimerHostInner::evict: the sweep already removed this
28935        // registration, so explicit host cleanup must remain a cheap no-op.
28936        registry.unregister(registration);
28937      }
28938    }
28939  }
28940
28941  struct ReentrantDropDriver {
28942    registry: std::sync::Weak<TimerDriverRegistry>,
28943    fallback: TimerDriverId,
28944  }
28945
28946  impl TimerDriver for ReentrantDropDriver {
28947    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28948
28949    fn cancel(&self, _id: TimerId) {}
28950  }
28951
28952  impl Drop for ReentrantDropDriver {
28953    fn drop(&mut self) {
28954      if let Some(registry) = self.registry.upgrade() {
28955        registry.unregister(self.fallback);
28956      }
28957    }
28958  }
28959
28960  /// Waker whose `wake` just latches a flag -- observable evidence that a
28961  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
28962  #[derive(Default)]
28963  struct WakeFlag(AtomicBool);
28964
28965  impl std::task::Wake for WakeFlag {
28966    fn wake(self: Arc<Self>) {
28967      self.0.store(true, Ordering::SeqCst);
28968    }
28969  }
28970
28971  #[cfg(not(target_family = "wasm"))]
28972  fn multi_thread_executor(
28973    workers: usize,
28974    park_deadline: Option<Duration>,
28975    prefix: &str,
28976  ) -> Arc<MultiThreadExecutor> {
28977    let options = RuntimeOptions {
28978      flavor: RuntimeFlavor::MultiThread,
28979      worker_threads: workers,
28980      max_blocking_tasks: workers,
28981      thread_name_prefix: prefix.to_string(),
28982      park_deadline,
28983      drain_linger: DEFAULT_DRAIN_LINGER,
28984    };
28985    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
28986  }
28987
28988  /// Local driver registry holding exactly `driver` (the common single-host
28989  /// shape of the ported pre-registry tests).
28990  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
28991    let registry = Arc::new(TimerDriverRegistry::default());
28992    registry.register(driver);
28993    registry
28994  }
28995
28996  #[cfg(not(target_family = "wasm"))]
28997  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
28998    // The MultiThread arm never touches the driver registry; empty is fine.
28999    make_sleep(
29000      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
29001      &Arc::new(TimerDriverRegistry::default()),
29002      deadline,
29003    )
29004  }
29005
29006  #[cfg(not(target_family = "wasm"))]
29007  #[test]
29008  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
29009    use std::sync::mpsc;
29010
29011    let executor = multi_thread_executor(
29012      1,
29013      Some(Duration::from_secs(1)),
29014      "timer-registration-admission",
29015    );
29016    let (entered_tx, entered_rx) = mpsc::channel();
29017    let (release_tx, release_rx) = mpsc::channel();
29018    let registration_executor = Arc::clone(&executor);
29019    let registration = std::thread::spawn(move || {
29020      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
29021        *slot.borrow_mut() = Some(Box::new(move || {
29022          entered_tx.send(()).unwrap();
29023          release_rx.recv().unwrap();
29024        }));
29025      });
29026      registration_executor.register_timer(
29027        1,
29028        Instant::now() + Duration::from_secs(60),
29029        futures::task::noop_waker(),
29030        &Arc::new(AtomicBool::new(false)),
29031      );
29032    });
29033
29034    entered_rx
29035      .recv_timeout(Duration::from_secs(2))
29036      .expect("timer registration must pause after entering verdict admission");
29037    let verdict = executor.try_begin_deadlock_verdict();
29038    let admission_blocked_verdict = verdict.is_none();
29039    drop(verdict);
29040    release_tx.send(()).unwrap();
29041    registration.join().unwrap();
29042
29043    assert!(
29044      admission_blocked_verdict,
29045      "a timer registration must remain announced until its heap entry is visible"
29046    );
29047    assert!(
29048      executor.next_timer_deadline().is_some(),
29049      "the admitted registration must publish its timer after release"
29050    );
29051    executor.begin_shutdown();
29052    executor.wait_until_scheduler_idle();
29053  }
29054
29055  #[cfg(not(target_family = "wasm"))]
29056  #[derive(Clone, Copy, Debug)]
29057  enum TimerFireAdmissionPath {
29058    Timekeeper,
29059    Cooperative,
29060    Shutdown,
29061  }
29062
29063  #[cfg(not(target_family = "wasm"))]
29064  struct ScheduleAfterTimerWakeRelease {
29065    executor: Arc<MultiThreadExecutor>,
29066    runnable: Mutex<Option<Runnable>>,
29067    entered: std::sync::mpsc::Sender<()>,
29068    release: Mutex<std::sync::mpsc::Receiver<()>>,
29069    scheduled: std::sync::mpsc::Sender<()>,
29070  }
29071
29072  #[cfg(not(target_family = "wasm"))]
29073  impl ScheduleAfterTimerWakeRelease {
29074    fn publish(&self) {
29075      self.entered.send(()).unwrap();
29076      self
29077        .release
29078        .lock()
29079        .unwrap_or_else(std::sync::PoisonError::into_inner)
29080        .recv()
29081        .unwrap();
29082      let runnable = self
29083        .runnable
29084        .lock()
29085        .unwrap_or_else(std::sync::PoisonError::into_inner)
29086        .take();
29087      if let Some(runnable) = runnable {
29088        self.executor.schedule(runnable);
29089      }
29090      self.scheduled.send(()).unwrap();
29091    }
29092  }
29093
29094  #[cfg(not(target_family = "wasm"))]
29095  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
29096    fn wake(self: Arc<Self>) {
29097      self.publish();
29098    }
29099
29100    fn wake_by_ref(self: &Arc<Self>) {
29101      self.publish();
29102    }
29103  }
29104
29105  #[cfg(not(target_family = "wasm"))]
29106  fn insert_timer_for_fire_admission_test(
29107    executor: &MultiThreadExecutor,
29108    id: TimerId,
29109    deadline: Instant,
29110    waker: Waker,
29111    fired: Arc<AtomicBool>,
29112  ) {
29113    let mut inner = executor
29114      .timers
29115      .inner
29116      .lock()
29117      .unwrap_or_else(std::sync::PoisonError::into_inner);
29118    inner.entries.insert(id, HeapTimerEntry { waker, fired });
29119    inner.queue.push(deadline, id);
29120  }
29121
29122  #[cfg(not(target_family = "wasm"))]
29123  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
29124    use std::sync::mpsc;
29125
29126    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
29127    let (runnable, task) = async_task::spawn(async {}, |_| {});
29128    task.detach();
29129    let (entered_tx, entered_rx) = mpsc::channel();
29130    let (release_tx, release_rx) = mpsc::channel();
29131    let (scheduled_tx, scheduled_rx) = mpsc::channel();
29132    let fired = Arc::new(AtomicBool::new(false));
29133    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
29134      executor: Arc::clone(&executor),
29135      runnable: Mutex::new(Some(runnable)),
29136      entered: entered_tx,
29137      release: Mutex::new(release_rx),
29138      scheduled: scheduled_tx,
29139    }));
29140
29141    let fire_thread = match path {
29142      TimerFireAdmissionPath::Timekeeper => {
29143        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
29144        None
29145      }
29146      TimerFireAdmissionPath::Cooperative => {
29147        insert_timer_for_fire_admission_test(
29148          &executor,
29149          1,
29150          Instant::now(),
29151          waker,
29152          Arc::clone(&fired),
29153        );
29154        let fire_executor = Arc::clone(&executor);
29155        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
29156      }
29157      TimerFireAdmissionPath::Shutdown => {
29158        insert_timer_for_fire_admission_test(
29159          &executor,
29160          1,
29161          Instant::now() + Duration::from_secs(60),
29162          waker,
29163          Arc::clone(&fired),
29164        );
29165        let fire_executor = Arc::clone(&executor);
29166        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
29167      }
29168    };
29169
29170    // Hang detector on a guaranteed event that needs a freshly spawned
29171    // thread (timekeeper / fire thread) to get CPU -- generous for starved
29172    // CI runners.
29173    entered_rx
29174      .recv_timeout(Duration::from_secs(10))
29175      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
29176    assert!(
29177      executor.next_timer_deadline().is_none(),
29178      "{path:?} must exercise the remove-before-wake window"
29179    );
29180    assert!(
29181      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
29182      "{path:?} must retain admission while its waker is publishing"
29183    );
29184    let verdict = executor.try_begin_deadlock_verdict();
29185    let admission_blocked_verdict = verdict.is_none();
29186    drop(verdict);
29187
29188    release_tx.send(()).unwrap();
29189    scheduled_rx
29190      .recv_timeout(Duration::from_secs(10))
29191      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
29192    if let Some(fire_thread) = fire_thread {
29193      fire_thread.join().unwrap();
29194    }
29195    assert!(
29196      admission_blocked_verdict,
29197      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
29198    );
29199
29200    executor.begin_shutdown();
29201    executor.wait_until_scheduler_idle();
29202  }
29203
29204  #[cfg(not(target_family = "wasm"))]
29205  #[test]
29206  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
29207    for path in [
29208      TimerFireAdmissionPath::Timekeeper,
29209      TimerFireAdmissionPath::Cooperative,
29210      TimerFireAdmissionPath::Shutdown,
29211    ] {
29212      assert_timer_fire_path_holds_deadlock_admission(path);
29213    }
29214  }
29215
29216  #[cfg(not(target_family = "wasm"))]
29217  #[test]
29218  fn multi_thread_sleep_fires_from_foreign_block_on() {
29219    // The napi-caller shape: a NON-pool thread parks in
29220    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
29221    // the executor, so ONLY the dedicated timekeeper can fire the timer --
29222    // this is the timekeeper's existence test.
29223    use std::sync::mpsc;
29224
29225    let (done_tx, done_rx) = mpsc::channel();
29226    let runner = std::thread::spawn(move || {
29227      let executor = multi_thread_executor(2, None, "timer-foreign");
29228      let started = Instant::now();
29229      let mut future = std::pin::pin!(async {
29230        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
29231      });
29232      executor.block_on(future.as_mut());
29233      assert!(
29234        started.elapsed() >= Duration::from_millis(80),
29235        "the sleep must not resolve before its deadline"
29236      );
29237      let _ = done_tx.send(());
29238    });
29239
29240    match done_rx.recv_timeout(Duration::from_secs(10)) {
29241      Ok(()) => runner.join().unwrap(),
29242      Err(error) => {
29243        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
29244      }
29245    }
29246  }
29247
29248  #[cfg(not(target_family = "wasm"))]
29249  #[test]
29250  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
29251    // A long timer waits on the dedicated timer thread, leaving the sole
29252    // low-level test worker available for ordinary scheduler work.
29253    use std::sync::mpsc;
29254
29255    let (done_tx, done_rx) = mpsc::channel();
29256    let runner = std::thread::spawn(move || {
29257      let executor = multi_thread_executor(1, None, "timer-drainer");
29258
29259      // Arm a long timer through a real spawned task so the sole worker
29260      // returns to rayon afterwards (the watch-idle Debouncing shape).
29261      let sleep_exec = Arc::clone(&executor);
29262      let sleep_done = Arc::new(AtomicBool::new(false));
29263      let sleep_flag = Arc::clone(&sleep_done);
29264      let sched = Arc::clone(&executor);
29265      let (sleep_runnable, sleep_task) = async_task::spawn(
29266        async move {
29267          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
29268          sleep_flag.store(true, Ordering::SeqCst);
29269        },
29270        move |r| sched.schedule(r),
29271      );
29272      executor.schedule(sleep_runnable);
29273      wait_until("the timekeeper parked on the 60s deadline", || {
29274        executor
29275          .timers
29276          .inner
29277          .lock()
29278          .unwrap()
29279          .timekeeper_parker
29280          .is_some()
29281      });
29282
29283      // Queued work must run NOW, not in 60s.
29284      let counter = Arc::new(AtomicUsize::new(0));
29285      let mut tasks = Vec::new();
29286      for _ in 0..32 {
29287        let counter = Arc::clone(&counter);
29288        let sched = Arc::clone(&executor);
29289        let (runnable, task) = async_task::spawn(
29290          async move {
29291            counter.fetch_add(1, Ordering::SeqCst);
29292          },
29293          move |r| sched.schedule(r),
29294        );
29295        executor.schedule(runnable);
29296        tasks.push(task);
29297      }
29298      for task in tasks {
29299        futures::executor::block_on(task);
29300      }
29301      assert_eq!(counter.load(Ordering::SeqCst), 32);
29302      assert!(
29303        !sleep_done.load(Ordering::SeqCst),
29304        "the 60s timer must still be pending: the work ran DURING the timer wait"
29305      );
29306      // Cancel the long sleep so the executor tears down promptly.
29307      executor.shutdown_timers();
29308      futures::executor::block_on(sleep_task);
29309      let _ = done_tx.send(());
29310    });
29311
29312    match done_rx.recv_timeout(Duration::from_secs(15)) {
29313      Ok(()) => runner.join().unwrap(),
29314      Err(error) => {
29315        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
29316      }
29317    }
29318  }
29319
29320  #[cfg(not(target_family = "wasm"))]
29321  #[test]
29322  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
29323    let executor = multi_thread_executor(2, None, "timer-lazy-start");
29324    assert!(
29325      executor.timers.thread.lock().unwrap().is_none(),
29326      "constructing a runtime without timers must not create an extra OS thread"
29327    );
29328
29329    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29330    assert!(
29331      executor.timers.thread.lock().unwrap().is_none(),
29332      "an unpolled sleep has not registered a deadline"
29333    );
29334    let waker = futures::task::noop_waker();
29335    let mut cx = Context::from_waker(&waker);
29336    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29337    assert!(
29338      executor.timers.thread.lock().unwrap().is_some(),
29339      "the first timer registration must start the dedicated service"
29340    );
29341  }
29342
29343  #[cfg(not(target_family = "wasm"))]
29344  #[test]
29345  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
29346    use std::sync::mpsc;
29347
29348    let executor = multi_thread_executor(1, None, "timer-closed-start");
29349    executor.shutdown_timers();
29350    let handle_guard = executor.timers.thread.lock().unwrap();
29351    let ensure_executor = Arc::clone(&executor);
29352    let (completed_tx, completed_rx) = mpsc::channel();
29353    let ensure = std::thread::spawn(move || {
29354      ensure_executor.timers.ensure_started().unwrap();
29355      completed_tx.send(()).unwrap();
29356    });
29357
29358    // Hang detector on a freshly spawned thread getting CPU -- generous for
29359    // starved CI runners (a real regression blocks forever on the held lock).
29360    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
29361      "ensure_started consulted or joined the timer handle before observing the closed heap",
29362    );
29363    drop(handle_guard);
29364    ensure.join().unwrap();
29365  }
29366
29367  #[cfg(not(target_family = "wasm"))]
29368  #[test]
29369  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
29370    const TIMERS: u64 = 4096;
29371
29372    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
29373    let base = Instant::now() + Duration::from_secs(3600);
29374    for id in 0..TIMERS {
29375      executor.register_timer(
29376        id,
29377        base + Duration::from_secs(id),
29378        futures::task::noop_waker(),
29379        &Arc::new(AtomicBool::new(false)),
29380      );
29381    }
29382    {
29383      let inner = executor.timers.inner.lock().unwrap();
29384      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
29385      assert_eq!(inner.queue.len(), inner.entries.len());
29386    }
29387
29388    // Preserve the earliest node while removing every later node. A lazy
29389    // BinaryHeap would retain all 4095 tombstones until that first deadline.
29390    for id in 1..TIMERS {
29391      executor.cancel_timer(id);
29392    }
29393    {
29394      let inner = executor.timers.inner.lock().unwrap();
29395      assert_eq!(inner.entries.len(), 1);
29396      assert_eq!(inner.queue.len(), 1);
29397      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
29398    }
29399
29400    executor.cancel_timer(0);
29401    {
29402      let inner = executor.timers.inner.lock().unwrap();
29403      assert!(inner.entries.is_empty());
29404      assert!(inner.queue.is_empty());
29405    }
29406    executor.shutdown_timers();
29407    executor.timers.join();
29408  }
29409
29410  #[cfg(not(target_family = "wasm"))]
29411  #[test]
29412  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
29413    let executor = multi_thread_executor(2, None, "timer-start-failure");
29414    executor
29415      .timers
29416      .fail_next_start
29417      .store(true, Ordering::SeqCst);
29418
29419    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29420    let waker = Waker::from(Arc::clone(&wake_count));
29421    let baseline_waker_refs = Arc::strong_count(&wake_count);
29422    let mut cx = Context::from_waker(&waker);
29423    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29424
29425    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
29426    assert!(
29427      failure.is_err(),
29428      "the injected timer-thread start failure must panic the poll"
29429    );
29430    {
29431      let inner = executor.timers.inner.lock().unwrap();
29432      assert!(
29433        inner.entries.is_empty(),
29434        "a failed start must not retain a timer entry"
29435      );
29436      assert!(
29437        inner.queue.is_empty(),
29438        "a failed start must not retain a stale heap node"
29439      );
29440      assert!(
29441        inner.timekeeper_parker.is_none(),
29442        "a failed start must not publish a timekeeper parker"
29443      );
29444    }
29445    assert!(
29446      executor.timers.thread.lock().unwrap().is_none(),
29447      "a failed start must not retain a thread handle"
29448    );
29449    assert_eq!(
29450      Arc::strong_count(&wake_count),
29451      baseline_waker_refs,
29452      "a failed start must release the cloned timer waker"
29453    );
29454
29455    assert!(
29456      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29457      "the same Sleep must be safely retryable after a transient start failure"
29458    );
29459    {
29460      let inner = executor.timers.inner.lock().unwrap();
29461      assert_eq!(
29462        inner.entries.len(),
29463        1,
29464        "the retry must publish exactly one timer entry"
29465      );
29466      assert_eq!(
29467        inner.queue.len(),
29468        1,
29469        "the retry must publish exactly one heap node"
29470      );
29471    }
29472    assert!(
29473      executor.timers.thread.lock().unwrap().is_some(),
29474      "the retry must start the dedicated timekeeper"
29475    );
29476
29477    drop(sleep);
29478    assert!(
29479      executor.timers.inner.lock().unwrap().entries.is_empty(),
29480      "dropping the retried Sleep must cancel its registration"
29481    );
29482    executor.shutdown_timers();
29483    executor.wait_until_scheduler_idle();
29484    assert!(
29485      executor.timers.thread.lock().unwrap().is_none(),
29486      "shutdown after a recovered start failure must join the timekeeper"
29487    );
29488  }
29489
29490  #[cfg(not(target_family = "wasm"))]
29491  #[test]
29492  fn pending_timer_preserves_every_configured_rayon_worker() {
29493    use std::sync::mpsc;
29494
29495    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
29496    let waker = futures::task::noop_waker();
29497    let mut cx = Context::from_waker(&waker);
29498    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29499    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29500    wait_until("the dedicated timer thread to park", || {
29501      executor
29502        .timers
29503        .inner
29504        .lock()
29505        .unwrap()
29506        .timekeeper_parker
29507        .is_some()
29508    });
29509
29510    let released = Arc::new((Mutex::new(false), Condvar::new()));
29511    let (started_tx, started_rx) = mpsc::channel();
29512    let (completed_tx, completed_rx) = mpsc::channel();
29513    for _ in 0..2 {
29514      let released = Arc::clone(&released);
29515      let started_tx = started_tx.clone();
29516      let completed_tx = completed_tx.clone();
29517      executor.pool.spawn(move || {
29518        started_tx.send(()).unwrap();
29519        let (lock, changed) = &*released;
29520        let mut released = lock
29521          .lock()
29522          .unwrap_or_else(std::sync::PoisonError::into_inner);
29523        while !*released {
29524          released = changed
29525            .wait(released)
29526            .unwrap_or_else(std::sync::PoisonError::into_inner);
29527        }
29528        completed_tx.send(()).unwrap();
29529      });
29530    }
29531    drop(started_tx);
29532    drop(completed_tx);
29533
29534    let first = started_rx.recv_timeout(Duration::from_secs(2));
29535    let second = started_rx.recv_timeout(Duration::from_secs(2));
29536    {
29537      let (lock, changed) = &*released;
29538      *lock
29539        .lock()
29540        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29541      changed.notify_all();
29542    }
29543    drop(sleep);
29544    for _ in 0..2 {
29545      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29546    }
29547
29548    assert!(
29549      first.is_ok(),
29550      "the first configured Rayon worker did not start"
29551    );
29552    assert!(
29553      second.is_ok(),
29554      "a pending timer consumed one of the two configured Rayon workers"
29555    );
29556  }
29557
29558  #[cfg(not(target_family = "wasm"))]
29559  #[test]
29560  fn timekeeper_never_enters_a_blocking_closure() {
29561    use std::{
29562      sync::{Barrier, mpsc},
29563      time::Duration,
29564    };
29565
29566    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
29567    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29568    let waker = Waker::from(Arc::clone(&wake_count));
29569    let mut cx = Context::from_waker(&waker);
29570    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
29571    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29572    wait_until(
29573      "the timekeeper parked before blocking work was submitted",
29574      || {
29575        executor
29576          .timers
29577          .inner
29578          .lock()
29579          .unwrap()
29580          .timekeeper_parker
29581          .is_some()
29582      },
29583    );
29584
29585    let release = Arc::new(Barrier::new(2));
29586    let blocker_release = Arc::clone(&release);
29587    let (started_tx, started_rx) = mpsc::sync_channel(1);
29588    let blocking = executor.schedule_blocking(move || {
29589      started_tx.send(()).unwrap();
29590      blocker_release.wait();
29591    });
29592    // Hang detector (the closure start needs a worker to get CPU) --
29593    // generous for starved CI runners.
29594    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
29595
29596    wait_until(
29597      "the timer waker to fire while the Rayon worker is blocked",
29598      || wake_count.0.load(Ordering::SeqCst) != 0,
29599    );
29600    release.wait();
29601    futures::executor::block_on(blocking).unwrap();
29602    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29603  }
29604
29605  #[cfg(not(target_family = "wasm"))]
29606  #[test]
29607  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
29608    use std::sync::mpsc;
29609
29610    struct PendingEntered(Option<mpsc::Sender<()>>);
29611
29612    impl Future for PendingEntered {
29613      type Output = ();
29614
29615      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29616        if let Some(entered) = self.0.take() {
29617          entered.send(()).unwrap();
29618        }
29619        Poll::Pending
29620      }
29621    }
29622
29623    let options = RuntimeOptions {
29624      flavor: RuntimeFlavor::MultiThread,
29625      worker_threads: 2,
29626      max_blocking_tasks: 1,
29627      thread_name_prefix: "nested-timer-wake".to_string(),
29628      park_deadline: None,
29629      drain_linger: DEFAULT_DRAIN_LINGER,
29630    };
29631    let executor =
29632      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29633
29634    let timer_exec = Arc::clone(&executor);
29635    let (timer_tx, timer_rx) = mpsc::channel();
29636    let timer_scheduler = Arc::clone(&executor);
29637    let (timer_runnable, timer_task) = async_task::spawn(
29638      async move {
29639        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
29640        timer_tx.send(()).unwrap();
29641      },
29642      move |runnable| timer_scheduler.schedule(runnable),
29643    );
29644    executor.schedule(timer_runnable);
29645    wait_until(
29646      "the dedicated timekeeper parked before nested block_on",
29647      || {
29648        executor
29649          .timers
29650          .inner
29651          .lock()
29652          .unwrap()
29653          .timekeeper_parker
29654          .is_some()
29655      },
29656    );
29657
29658    let (entered_tx, entered_rx) = mpsc::channel();
29659    let nested_exec = Arc::clone(&executor);
29660    let nested_scheduler = Arc::clone(&executor);
29661    let (nested_runnable, nested_task) = async_task::spawn(
29662      async move {
29663        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
29664        let outcome = nested_exec.block_on(pending.as_mut());
29665        drop(pending);
29666        assert_eq!(
29667          outcome,
29668          BlockOnOutcome::Stopped,
29669          "shutdown must stop the raw nested block_on driver"
29670        );
29671      },
29672      move |runnable| nested_scheduler.schedule(runnable),
29673    );
29674    executor.schedule(nested_runnable);
29675    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29676
29677    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
29678    let (release_tx, release_rx) = mpsc::channel();
29679    let blocking = executor.schedule_blocking(move || {
29680      blocking_started_tx.send(()).unwrap();
29681      release_rx.recv().unwrap();
29682    });
29683    blocking_started_rx
29684      .recv_timeout(Duration::from_secs(2))
29685      .expect("a general worker must service blocking work");
29686    timer_rx
29687      .recv_timeout(Duration::from_secs(2))
29688      .expect("the timer wake must reach the nested cooperative driver");
29689
29690    release_tx.send(()).unwrap();
29691    futures::executor::block_on(blocking).unwrap();
29692    executor.begin_shutdown();
29693    assert_eq!(
29694      futures::executor::block_on(nested_task.fallible()),
29695      Some(())
29696    );
29697    futures::executor::block_on(timer_task);
29698  }
29699
29700  #[cfg(not(target_family = "wasm"))]
29701  #[test]
29702  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
29703    // Task requirement: a NEW earliest deadline (heap peek changes) must
29704    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
29705    // timekeeper far in the future; the 60ms timer must still fire on time.
29706    use std::sync::mpsc;
29707
29708    let (done_tx, done_rx) = mpsc::channel();
29709    let runner = std::thread::spawn(move || {
29710      let executor = multi_thread_executor(2, None, "timer-rearm");
29711
29712      let long_exec = Arc::clone(&executor);
29713      let long_done = Arc::new(AtomicBool::new(false));
29714      let long_flag = Arc::clone(&long_done);
29715      let sched = Arc::clone(&executor);
29716      let (long_runnable, long_task) = async_task::spawn(
29717        async move {
29718          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
29719          long_flag.store(true, Ordering::SeqCst);
29720        },
29721        move |r| sched.schedule(r),
29722      );
29723      executor.schedule(long_runnable);
29724      wait_until("the timekeeper parked on the 60s sentinel", || {
29725        executor
29726          .timers
29727          .inner
29728          .lock()
29729          .unwrap()
29730          .timekeeper_parker
29731          .is_some()
29732      });
29733
29734      let started = Instant::now();
29735      let mut future = std::pin::pin!(async {
29736        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
29737      });
29738      executor.block_on(future.as_mut());
29739      let elapsed = started.elapsed();
29740      assert!(
29741        elapsed >= Duration::from_millis(60),
29742        "fired before its deadline: {elapsed:?}"
29743      );
29744      assert!(
29745        !long_done.load(Ordering::SeqCst),
29746        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
29747         timekeeper, not by the sentinel expiring"
29748      );
29749      executor.shutdown_timers();
29750      futures::executor::block_on(long_task);
29751      let _ = done_tx.send(());
29752    });
29753
29754    match done_rx.recv_timeout(Duration::from_secs(15)) {
29755      Ok(()) => runner.join().unwrap(),
29756      Err(error) => panic!(
29757        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
29758      ),
29759    }
29760  }
29761
29762  #[cfg(not(target_family = "wasm"))]
29763  #[test]
29764  fn multi_thread_sleep_cancels_registration_on_drop() {
29765    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
29766    // its heap registration (no waker fired later, heap drains empty).
29767    let executor = multi_thread_executor(1, None, "timer-cancel");
29768    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29769    let waker = Waker::from(Arc::clone(&wake_count));
29770    let mut cx = Context::from_waker(&waker);
29771
29772    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
29773    // starved runner (a past-due Sleep would resolve inline instead of
29774    // registering, failing the assert below for the wrong reason).
29775    let deadline = Instant::now() + Duration::from_millis(250);
29776    let mut sleep = heap_sleep(&executor, deadline);
29777    assert!(
29778      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29779      "must register, not resolve"
29780    );
29781    assert!(
29782      executor.next_timer_deadline().is_some(),
29783      "the registration must be in the heap"
29784    );
29785    drop(sleep);
29786    assert!(
29787      executor.next_timer_deadline().is_none(),
29788      "cancel-on-drop must retire the heap registration"
29789    );
29790    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
29791    // cancelled timer must not wake. The negative assert is structural once
29792    // the heap is empty; waiting until the wall clock is genuinely past the
29793    // deadline keeps the ghost-fire probe honest.
29794    while Instant::now() < deadline + Duration::from_millis(25) {
29795      std::thread::sleep(Duration::from_millis(10));
29796    }
29797    executor.fire_due_timers();
29798    assert_eq!(
29799      wake_count.0.load(Ordering::SeqCst),
29800      0,
29801      "a dropped Sleep's waker must never fire"
29802    );
29803  }
29804
29805  #[cfg(not(target_family = "wasm"))]
29806  #[test]
29807  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
29808    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
29809    let waker = futures::task::noop_waker();
29810    let mut cx = Context::from_waker(&waker);
29811    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29812
29813    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29814    wait_until(
29815      "the timekeeper parked on the timer that will be cancelled",
29816      || {
29817        executor
29818          .timers
29819          .inner
29820          .lock()
29821          .unwrap()
29822          .timekeeper_parker
29823          .is_some()
29824      },
29825    );
29826
29827    drop(sleep);
29828    wait_until(
29829      "the cancelled timer's timekeeper returned to its idle wait",
29830      || {
29831        executor.next_timer_deadline().is_none()
29832          && executor
29833            .timers
29834            .inner
29835            .lock()
29836            .unwrap()
29837            .timekeeper_parker
29838            .is_none()
29839      },
29840    );
29841  }
29842
29843  #[cfg(not(target_family = "wasm"))]
29844  #[test]
29845  fn timer_waker_replacement_drops_outside_heap_lock() {
29846    use std::sync::mpsc;
29847
29848    struct DropOtherTimer(Mutex<Option<Sleep>>);
29849
29850    #[expect(
29851      clippy::manual_noop_waker,
29852      reason = "the no-op wake type has a reentrant Drop used by this regression"
29853    )]
29854    impl std::task::Wake for DropOtherTimer {
29855      fn wake(self: Arc<Self>) {}
29856    }
29857
29858    impl Drop for DropOtherTimer {
29859      fn drop(&mut self) {
29860        drop(self.0.lock().unwrap().take());
29861      }
29862    }
29863
29864    let (done_tx, done_rx) = mpsc::channel();
29865    let runner = std::thread::spawn(move || {
29866      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
29867      let deadline = Instant::now() + Duration::from_secs(120);
29868      let mut other = heap_sleep(&executor, deadline);
29869      let noop = futures::task::noop_waker();
29870      let mut noop_cx = Context::from_waker(&noop);
29871      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
29872
29873      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
29874      let mut primary = heap_sleep(&executor, deadline);
29875      let mut old_cx = Context::from_waker(&old);
29876      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
29877      drop(old);
29878
29879      let replacement = futures::task::noop_waker();
29880      let mut replacement_cx = Context::from_waker(&replacement);
29881      assert!(
29882        Pin::new(&mut primary)
29883          .poll(&mut replacement_cx)
29884          .is_pending()
29885      );
29886      drop(primary);
29887      executor.shutdown_timers();
29888      done_tx.send(()).unwrap();
29889    });
29890    // Hang detector on a freshly spawned runner thread -- generous for
29891    // starved CI runners (a real regression deadlocks forever).
29892    done_rx
29893      .recv_timeout(Duration::from_secs(10))
29894      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
29895    runner.join().unwrap();
29896  }
29897
29898  #[cfg(not(target_family = "wasm"))]
29899  #[test]
29900  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
29901    use std::sync::mpsc;
29902
29903    struct DropFinalExecutorOnWake {
29904      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
29905      completed: mpsc::Sender<()>,
29906    }
29907
29908    impl std::task::Wake for DropFinalExecutorOnWake {
29909      fn wake(self: Arc<Self>) {
29910        let executor = self
29911          .executor
29912          .lock()
29913          .unwrap_or_else(std::sync::PoisonError::into_inner)
29914          .take()
29915          .expect("the timer waker must own the final executor reference");
29916        assert_eq!(
29917          ON_TIMER_THREAD.with(std::cell::Cell::get),
29918          Some(executor.id),
29919          "the dedicated timer thread must deliver the timer wake"
29920        );
29921        drop(executor);
29922        self
29923          .completed
29924          .send(())
29925          .expect("the test receiver must outlive the timer-thread callback");
29926      }
29927    }
29928
29929    let executor = multi_thread_executor(1, None, "timer-final-arc");
29930    let (completed_tx, completed_rx) = mpsc::channel();
29931    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
29932    {
29933      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
29934        executor: Mutex::new(Some(Arc::clone(&executor))),
29935        completed: completed_tx,
29936      }));
29937      let mut cx = Context::from_waker(&waker);
29938      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29939    }
29940    wait_until(
29941      "the timer thread parked before owning the final executor Arc",
29942      || {
29943        executor
29944          .timers
29945          .inner
29946          .lock()
29947          .unwrap()
29948          .timekeeper_parker
29949          .is_some()
29950      },
29951    );
29952
29953    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
29954    // heap waker owns the final direct executor Arc and releases it from the
29955    // timer thread while `TimerHeap::drop` still owns that thread's handle.
29956    drop(executor);
29957    // Hang detector: the fire needs the (already spawned) timer thread to
29958    // reach its 80ms deadline -- generous for starved CI runners.
29959    completed_rx
29960      .recv_timeout(Duration::from_secs(10))
29961      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
29962  }
29963
29964  #[cfg(not(target_family = "wasm"))]
29965  #[test]
29966  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
29967    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
29968    // pending wakers -- a `watcher.close()` awaiting a
29969    // debounce sleep must complete, not hang. Also pins fire-on-register for
29970    // a Sleep polled after shutdown.
29971    use std::sync::mpsc;
29972
29973    let (done_tx, done_rx) = mpsc::channel();
29974    let runner = std::thread::spawn(move || {
29975      let controller = Arc::new(RuntimeController::new());
29976      // Default options: MultiThread on native.
29977      let backend = controller.backend();
29978      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29979        panic!("default native backend must be MultiThread");
29980      };
29981      let executor = Arc::downgrade(executor);
29982      let mut late = heap_sleep(
29983        &executor
29984          .upgrade()
29985          .expect("the initial executor must be live"),
29986        Instant::now() + Duration::from_secs(120),
29987      );
29988      drop(backend);
29989
29990      let (parked_tx, parked_rx) = mpsc::channel();
29991      let block_controller = Arc::clone(&controller);
29992      let block_exec = Weak::clone(&executor);
29993      let blocker = std::thread::spawn(move || {
29994        let mut future = std::pin::pin!(async {
29995          let executor = block_exec
29996            .upgrade()
29997            .expect("the runtime must remain live while blocking");
29998          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
29999          drop(executor);
30000          parked_tx.send(()).unwrap();
30001          sleep.await;
30002        });
30003        block_controller.block_on(future.as_mut());
30004      });
30005      parked_rx.recv().unwrap();
30006      wait_until("the 120s sleep registered in the heap", || {
30007        executor
30008          .upgrade()
30009          .is_some_and(|executor| executor.next_timer_deadline().is_some())
30010      });
30011
30012      // The acceptance surface: controller shutdown while a sleep is armed.
30013      controller
30014        .shutdown()
30015        .expect("runtime shutdown must succeed");
30016      blocker
30017        .join()
30018        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
30019
30020      // A Sleep polled AFTER shutdown resolves immediately instead of
30021      // registering with a dead heap.
30022      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
30023      let mut cx = Context::from_waker(&waker);
30024      assert!(
30025        Pin::new(&mut late).poll(&mut cx).is_ready(),
30026        "a Sleep polled after shutdown must fire early, never park a closing runtime"
30027      );
30028      let _ = done_tx.send(());
30029    });
30030
30031    match done_rx.recv_timeout(Duration::from_secs(10)) {
30032      Ok(()) => runner.join().unwrap(),
30033      Err(error) => {
30034        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
30035      }
30036    }
30037  }
30038
30039  #[cfg(not(target_family = "wasm"))]
30040  #[test]
30041  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
30042    // Timer-wait/deadline invariant: the park deadline is armed
30043    // SHORTER than the next timer deadline. The cooperative driver awaiting
30044    // the sleep sits with ZERO
30045    // executor progress for several full deadline windows -- a timer wait is
30046    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
30047    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
30048    // also allows the parked driver to race the dedicated timekeeper and fire
30049    // the timer itself, pinning the cooperative timer-bounded park.
30050    use std::sync::mpsc;
30051
30052    let (done_tx, done_rx) = mpsc::channel();
30053    let runner = std::thread::spawn(move || {
30054      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
30055
30056      let task_exec = Arc::clone(&executor);
30057      let body = async move {
30058        let started = Instant::now();
30059        let mut inner = std::pin::pin!(async {
30060          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
30061        });
30062        // Re-entrant block_on from the pool worker: the cooperative park.
30063        task_exec.block_on(inner.as_mut());
30064        assert!(started.elapsed() >= Duration::from_millis(250));
30065      };
30066      let sched = Arc::clone(&executor);
30067      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
30068      executor.schedule(runnable);
30069
30070      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
30071      // by run_runnable's catch_unwind), so the task would never complete and
30072      // the bounded harness below would report the failure.
30073      futures::executor::block_on(task);
30074      // Teardown inside the bounded window: this test was one of the silent
30075      // wedges on the Windows CI runners with every assertion already
30076      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
30077      // (a timer was polled, so the timekeeper thread is live) -- exactly
30078      // the kind of phase that must fail loudly rather than hang.
30079      drop(executor);
30080      let _ = done_tx.send(());
30081    });
30082
30083    match done_rx.recv_timeout(Duration::from_secs(10)) {
30084      Ok(()) => join_within(
30085        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
30086        runner,
30087        Duration::from_secs(30),
30088      ),
30089      Err(error) => {
30090        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
30091      }
30092    }
30093  }
30094
30095  #[cfg(not(target_family = "wasm"))]
30096  #[test]
30097  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
30098    // Timer-registration/verdict race, deterministic via the verdict hook: a
30099    // timer registered AFTER a deadline park was committed bumps no progress
30100    // counter by design, lands after the permit and queued-work re-checks, and
30101    // is invisible to the fingerprint. The heap re-check must recognize that
30102    // guaranteed future wake, veto the panic, and let the driver re-park
30103    // timer-bounded until the fire's task-wake completes the future.
30104    use std::sync::mpsc;
30105
30106    // A raw Sleep the hook registers is woken by this waker, which releases
30107    // the awaited future's gate.
30108    struct GateWake {
30109      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
30110    }
30111    impl std::task::Wake for GateWake {
30112      fn wake(self: Arc<Self>) {
30113        let gate = self.gate.lock().unwrap().take();
30114        if let Some(gate) = gate {
30115          let _ = gate.send(23);
30116        }
30117      }
30118    }
30119
30120    let (done_tx, done_rx) = mpsc::channel();
30121    let runner = std::thread::spawn(move || {
30122      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
30123      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
30124      // replacement drainer behind our back; only the verdict's heap re-check
30125      // can save this park.
30126      executor
30127        .active_drainers
30128        .store(executor.max_drainers, Ordering::SeqCst);
30129
30130      // Completion condition: the timer registered inside the hook fires and
30131      // its waker (the gate) releases the awaited future.
30132      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30133      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
30134
30135      let hook_exec = Arc::clone(&executor);
30136      let hook_gate = Arc::clone(&gate_tx);
30137      // The Sleep must outlive the hook (drop cancels): park it in a slot the
30138      // runner thread later cleans up.
30139      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
30140      let hook_sleep_slot = Arc::clone(&parked_sleep);
30141      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30142        *slot.borrow_mut() = Some(Box::new(move || {
30143          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
30144          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
30145          let mut cx = Context::from_waker(&waker);
30146          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30147          *hook_sleep_slot.lock().unwrap() = Some(sleep);
30148        }));
30149      });
30150
30151      // The test-only on-pool marker runs the cooperative branch and its
30152      // thread-local hook on THIS thread.
30153      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30154      let mut output = None;
30155      {
30156        let mut future = std::pin::pin!(async {
30157          output = Some(gate_rx.await.unwrap());
30158        });
30159        executor.block_on(future.as_mut());
30160      }
30161      // Clear the hook slot BEFORE any assert can panic: this hook captures
30162      // an executor Arc and THIS executor's timekeeper is live (a timer was
30163      // polled), so a leftover hook dropped by a TLS destructor would join
30164      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
30165      // RULE at `WorkerTlsDropAction`).
30166      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30167      assert_eq!(
30168        output,
30169        Some(23),
30170        "the edge-registered timer must have fired and released the gate"
30171      );
30172      executor.active_drainers.store(0, Ordering::SeqCst);
30173      // Teardown inside the bounded window; includes the timekeeper join
30174      // (a timer was polled). See `join_within`.
30175      drop(executor);
30176      let _ = done_tx.send(());
30177    });
30178
30179    match done_rx.recv_timeout(Duration::from_secs(10)) {
30180      Ok(()) => join_within(
30181        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
30182        runner,
30183        Duration::from_secs(30),
30184      ),
30185      Err(error) => panic!(
30186        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
30187      ),
30188    }
30189  }
30190
30191  #[test]
30192  fn current_thread_sleep_without_host_driver_fails_loud() {
30193    // Missing-driver invariant: a CT runtime without a registered host driver
30194    // must fail LOUD with this exact diagnostic -- never a silent never-firing
30195    // debounce or a misleading certain-deadlock panic later.
30196    let metrics = Arc::new(RuntimeMetrics::default());
30197    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30198    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
30199
30200    let payload = catch_unwind(AssertUnwindSafe(|| {
30201      let _sleep = make_sleep(
30202        &backend,
30203        &Arc::new(TimerDriverRegistry::default()),
30204        Instant::now() + Duration::from_millis(10),
30205      );
30206    }))
30207    .expect_err("sleep_until on CurrentThread without a driver must panic");
30208    let message = payload
30209      .downcast_ref::<String>()
30210      .cloned()
30211      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30212      .expect("panic payload must be a message");
30213    assert!(
30214      message.contains("CurrentThread runtime has no live timer driver registered"),
30215      "the diagnostic must name the missing driver, got: {message}"
30216    );
30217  }
30218
30219  #[test]
30220  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
30221    // Registry semantics: all-live snapshots with newest-live compatibility
30222    // selection, dead entries swept on contact, explicit unregister, and a
30223    // dead-only registry reading as NO driver.
30224    let registry = TimerDriverRegistry::default();
30225    let a = ManualStubDriver::new();
30226    let b = ManualStubDriver::new();
30227    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
30228    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30229    assert_ne!(a_id, b_id, "registration handles must be distinct");
30230    assert_eq!(
30231      registry.current().map(|(id, _)| id),
30232      Some(b_id),
30233      "newest live registrant serves"
30234    );
30235    assert!(registry.has_live_driver());
30236
30237    // A dead newest (stub for: its owning env torn down) is skipped AND
30238    // swept; selection falls back to the older live registrant.
30239    b.kill();
30240    assert_eq!(
30241      registry.current().map(|(id, _)| id),
30242      Some(a_id),
30243      "dead newest must be skipped"
30244    );
30245    // The sweep is permanent: resurrecting the flag must not bring the
30246    // evicted registration back.
30247    b.live.store(true, Ordering::SeqCst);
30248    assert_eq!(
30249      registry.current().map(|(id, _)| id),
30250      Some(a_id),
30251      "swept entries stay gone"
30252    );
30253
30254    // Explicit unregister (the binding's env-cleanup path) empties the
30255    // registry; empty and dead-only both read as NO live driver.
30256    registry.unregister(a_id);
30257    assert!(registry.current().is_none());
30258    assert!(
30259      !registry.has_live_driver(),
30260      "an emptied registry must not report a live driver"
30261    );
30262  }
30263
30264  #[test]
30265  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
30266    let registry = TimerDriverRegistry::default();
30267    let fallback = ManualStubDriver::new();
30268    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30269    let panicking = Arc::new(PanickingTimerDriver {
30270      swept: AtomicBool::new(false),
30271    });
30272    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30273
30274    assert_eq!(
30275      registry.current().map(|(id, _)| id),
30276      Some(fallback_id),
30277      "a panicking liveness callback must be treated as a dead driver"
30278    );
30279    assert!(
30280      panicking.swept.load(Ordering::SeqCst),
30281      "a panicking liveness callback must still run its contained sweep hook"
30282    );
30283    assert!(
30284      registry.has_live_driver(),
30285      "the healthy fallback must remain selectable"
30286    );
30287  }
30288
30289  #[test]
30290  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
30291    use std::{sync::mpsc, time::Duration};
30292
30293    let registry = Arc::new(TimerDriverRegistry::default());
30294    let fallback = ManualStubDriver::new();
30295    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30296    let reentrant = Arc::new(ReentrantLivenessDriver {
30297      registry: Arc::downgrade(&registry),
30298      registration: Mutex::new(None),
30299    });
30300    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30301    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
30302
30303    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
30304    let selection_registry = Arc::clone(&registry);
30305    let selection_thread = std::thread::spawn(move || {
30306      selection_tx
30307        .send(selection_registry.current().map(|(id, _)| id))
30308        .unwrap();
30309    });
30310    assert_eq!(
30311      selection_rx
30312        .recv_timeout(Duration::from_secs(10))
30313        .expect("is_live re-entry deadlocked"),
30314      Some(fallback_id)
30315    );
30316    selection_thread.join().unwrap();
30317
30318    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
30319      registry: Arc::downgrade(&registry),
30320      fallback: fallback_id,
30321    });
30322    let drop_id = registry.register(Arc::clone(&drop_driver));
30323    drop(drop_driver);
30324
30325    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
30326    let drop_registry = Arc::clone(&registry);
30327    let drop_thread = std::thread::spawn(move || {
30328      drop_registry.unregister(drop_id);
30329      drop_tx.send(()).unwrap();
30330    });
30331    drop_rx
30332      .recv_timeout(Duration::from_secs(10))
30333      .expect("driver destructor re-entry deadlocked");
30334    drop_thread.join().unwrap();
30335    assert!(
30336      registry.current().is_none(),
30337      "the destructor must be able to unregister the fallback"
30338    );
30339  }
30340
30341  #[test]
30342  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
30343    const DRIVER_PAIRS: usize = 2_048;
30344
30345    let registry = Arc::new(TimerDriverRegistry::default());
30346    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
30347    let mut live = Vec::with_capacity(DRIVER_PAIRS);
30348    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
30349    for _ in 0..DRIVER_PAIRS {
30350      let driver = Arc::new(ReentrantSweepTimerDriver {
30351        registry: Arc::downgrade(&registry),
30352        registration: Mutex::new(None),
30353        sweeps: AtomicUsize::new(0),
30354        removed_before_sweep: AtomicBool::new(false),
30355      });
30356      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30357      *driver.registration.lock().unwrap() = Some(id);
30358      dead.push(driver);
30359
30360      let driver = ManualStubDriver::new();
30361      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
30362      live.push(driver);
30363    }
30364    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
30365      registry: Arc::downgrade(&registry),
30366      registration: Mutex::new(None),
30367      sweeps: AtomicUsize::new(0),
30368      removed_before_sweep: AtomicBool::new(false),
30369    });
30370    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
30371    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
30372    dead.push(dead_tail);
30373
30374    let selection = registry.live();
30375    assert_eq!(
30376      selection
30377        .as_slice()
30378        .iter()
30379        .map(|(id, _)| *id)
30380        .collect::<Vec<_>>(),
30381      live_ids,
30382      "sweeping every interleaved dead host must retain live registration order"
30383    );
30384    drop(selection);
30385    assert!(
30386      dead.iter().all(|driver| {
30387        driver.sweeps.load(Ordering::SeqCst) == 1
30388          && driver.removed_before_sweep.load(Ordering::SeqCst)
30389          && driver.registration.lock().unwrap().is_none()
30390      }),
30391      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
30392    );
30393
30394    {
30395      let state = registry
30396        .state
30397        .lock()
30398        .unwrap_or_else(std::sync::PoisonError::into_inner);
30399      assert_eq!(state.entries.len(), DRIVER_PAIRS);
30400      assert_eq!(state.oldest, live_ids.first().copied());
30401      assert_eq!(state.newest, live_ids.last().copied());
30402      assert_eq!(
30403        state
30404          .ordered_snapshot()
30405          .iter()
30406          .map(|(id, _)| *id)
30407          .collect::<Vec<_>>(),
30408        live_ids,
30409        "keyed removals must preserve the linked order of every live host"
30410      );
30411    }
30412    let second_selection = registry.live();
30413    assert_eq!(
30414      second_selection
30415        .as_slice()
30416        .iter()
30417        .map(|(id, _)| *id)
30418        .collect::<Vec<_>>(),
30419      live_ids,
30420      "a second selection must retain the same live order"
30421    );
30422    drop(second_selection);
30423    assert!(
30424      dead
30425        .iter()
30426        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
30427      "permanently swept hosts must not receive repeated eviction callbacks"
30428    );
30429    assert_eq!(
30430      registry.current().map(|(id, _)| id),
30431      live_ids.last().copied(),
30432      "newest-live compatibility selection must survive the mass sweep"
30433    );
30434
30435    drop(live);
30436    drop(dead);
30437  }
30438
30439  #[test]
30440  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
30441    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
30442
30443    if std::env::var_os(CHILD_ENV).is_some() {
30444      let registry = Arc::new(TimerDriverRegistry::default());
30445      let fallback = ManualStubDriver::new();
30446      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30447      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
30448        registry: Arc::downgrade(&registry),
30449        registrations: Mutex::new(Vec::new()),
30450      });
30451      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30452
30453      let first_cancels = Arc::new(AtomicUsize::new(0));
30454      let first_drops = Arc::new(AtomicUsize::new(0));
30455      let first = Arc::new(PanickingCancelDropTimerDriver {
30456        cancels: Arc::clone(&first_cancels),
30457        drops: Arc::clone(&first_drops),
30458      });
30459      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30460      drop(first);
30461
30462      let second_cancels = Arc::new(AtomicUsize::new(0));
30463      let second_drops = Arc::new(AtomicUsize::new(0));
30464      let second = Arc::new(PanickingCancelDropTimerDriver {
30465        cancels: Arc::clone(&second_cancels),
30466        drops: Arc::clone(&second_drops),
30467      });
30468      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30469      drop(second);
30470      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
30471
30472      assert_eq!(
30473        registry.current().map(|(id, _)| id),
30474        Some(fallback_id),
30475        "selection must retry after reentrant removals and preserve the healthy fallback"
30476      );
30477      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30478      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30479      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30480      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30481      assert_eq!(
30482        registry
30483          .state
30484          .lock()
30485          .unwrap_or_else(std::sync::PoisonError::into_inner)
30486          .entries
30487          .len(),
30488        1
30489      );
30490      return;
30491    }
30492
30493    let output = std::process::Command::new(std::env::current_exe().unwrap())
30494      .arg("--exact")
30495      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
30496      .arg("--nocapture")
30497      .env(CHILD_ENV, "1")
30498      .output()
30499      .expect("the hostile timer-driver snapshot subprocess test must start");
30500    assert!(
30501      output.status.success(),
30502      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30503      output.status.code(),
30504      String::from_utf8_lossy(&output.stdout),
30505      String::from_utf8_lossy(&output.stderr)
30506    );
30507  }
30508
30509  #[test]
30510  fn timer_driver_registry_live_result_contains_each_driver_drop() {
30511    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
30512
30513    if std::env::var_os(CHILD_ENV).is_some() {
30514      let registry = TimerDriverRegistry::default();
30515
30516      let first_cancels = Arc::new(AtomicUsize::new(0));
30517      let first_drops = Arc::new(AtomicUsize::new(0));
30518      let first = Arc::new(PanickingCancelDropTimerDriver {
30519        cancels: Arc::clone(&first_cancels),
30520        drops: Arc::clone(&first_drops),
30521      });
30522      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30523      drop(first);
30524
30525      let second_cancels = Arc::new(AtomicUsize::new(0));
30526      let second_drops = Arc::new(AtomicUsize::new(0));
30527      let second = Arc::new(PanickingCancelDropTimerDriver {
30528        cancels: Arc::clone(&second_cancels),
30529        drops: Arc::clone(&second_drops),
30530      });
30531      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30532      drop(second);
30533
30534      let live = registry.live();
30535      assert_eq!(live.len(), 2);
30536      registry.unregister(first_id);
30537      registry.unregister(second_id);
30538      drop(live);
30539
30540      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30541      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30542      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30543      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30544      assert!(
30545        registry
30546          .state
30547          .lock()
30548          .unwrap_or_else(std::sync::PoisonError::into_inner)
30549          .entries
30550          .is_empty()
30551      );
30552      return;
30553    }
30554
30555    let output = std::process::Command::new(std::env::current_exe().unwrap())
30556      .arg("--exact")
30557      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
30558      .arg("--nocapture")
30559      .env(CHILD_ENV, "1")
30560      .output()
30561      .expect("the hostile live timer-driver snapshot subprocess test must start");
30562    assert!(
30563      output.status.success(),
30564      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30565      output.status.code(),
30566      String::from_utf8_lossy(&output.stdout),
30567      String::from_utf8_lossy(&output.stderr)
30568    );
30569  }
30570
30571  #[test]
30572  fn timer_driver_registry_drop_contains_each_driver_destructor() {
30573    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
30574
30575    if std::env::var_os(CHILD_ENV).is_some() {
30576      let first_drops = Arc::new(AtomicUsize::new(0));
30577      let second_drops = Arc::new(AtomicUsize::new(0));
30578      let registry = TimerDriverRegistry::default();
30579      let first = Arc::new(PanickingCancelDropTimerDriver {
30580        cancels: Arc::new(AtomicUsize::new(0)),
30581        drops: Arc::clone(&first_drops),
30582      });
30583      let second = Arc::new(PanickingCancelDropTimerDriver {
30584        cancels: Arc::new(AtomicUsize::new(0)),
30585        drops: Arc::clone(&second_drops),
30586      });
30587      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30588      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30589      drop(first);
30590      drop(second);
30591
30592      drop(registry);
30593
30594      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30595      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30596      return;
30597    }
30598
30599    let output = std::process::Command::new(std::env::current_exe().unwrap())
30600      .arg("--exact")
30601      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
30602      .arg("--nocapture")
30603      .env(CHILD_ENV, "1")
30604      .output()
30605      .expect("the hostile timer-driver registry drop subprocess test must start");
30606    assert!(
30607      output.status.success(),
30608      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
30609      output.status.code(),
30610      String::from_utf8_lossy(&output.stdout),
30611      String::from_utf8_lossy(&output.stderr)
30612    );
30613  }
30614
30615  #[test]
30616  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
30617    const DRIVERS: usize = 512;
30618
30619    let metrics = Arc::new(RuntimeMetrics::default());
30620    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30621    let backend =
30622      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30623    let registry = Arc::new(TimerDriverRegistry::default());
30624    let drivers = (0..DRIVERS)
30625      .map(|_| {
30626        let driver = ManualStubDriver::new();
30627        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30628        driver
30629      })
30630      .collect::<Vec<_>>();
30631    let mut sleep = make_sleep(
30632      &backend,
30633      &registry,
30634      Instant::now() + Duration::from_secs(60),
30635    );
30636    let mut cx = Context::from_waker(Waker::noop());
30637
30638    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30639    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30640    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
30641    match &sleep.inner {
30642      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
30643      #[cfg(not(target_family = "wasm"))]
30644      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30645    }
30646    for driver in &drivers {
30647      assert_eq!(
30648        driver.registers.lock().unwrap().len(),
30649        2,
30650        "a re-poll must refresh each live driver exactly once"
30651      );
30652    }
30653
30654    for driver in drivers.iter().step_by(2) {
30655      driver.kill();
30656    }
30657    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30658    match &sleep.inner {
30659      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
30660      #[cfg(not(target_family = "wasm"))]
30661      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30662    }
30663    for (index, driver) in drivers.iter().enumerate() {
30664      let registers = driver.registers.lock().unwrap().len();
30665      let cancels = driver.cancels.lock().unwrap().clone();
30666      if index % 2 == 0 {
30667        assert_eq!(registers, 2);
30668        assert_eq!(cancels, vec![timer_id]);
30669      } else {
30670        assert_eq!(registers, 3);
30671        assert!(cancels.is_empty());
30672      }
30673    }
30674
30675    drop(sleep);
30676    for driver in drivers {
30677      assert_eq!(
30678        driver.cancels.lock().unwrap().as_slice(),
30679        &[timer_id],
30680        "every physical arm must be cancelled exactly once"
30681      );
30682    }
30683    assert_eq!(registry.pending_len(), 0);
30684    assert!(!executor.host_timers.has_pending());
30685  }
30686
30687  #[test]
30688  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
30689    let metrics = Arc::new(RuntimeMetrics::default());
30690    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30691    let backend =
30692      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30693    let healthy = ManualStubDriver::new();
30694    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30695    let deadline = Instant::now() + Duration::from_secs(60);
30696    let mut sleep = make_sleep(&backend, &registry, deadline);
30697    let mut cx = Context::from_waker(Waker::noop());
30698
30699    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30700    let timer_id = healthy.registers.lock().unwrap()[0].0;
30701
30702    let panicking = PanickingRegisterTimerDriver::new();
30703    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30704    let result = catch_unwind(AssertUnwindSafe(|| {
30705      let _ = Pin::new(&mut sleep).poll(&mut cx);
30706    }));
30707    assert!(
30708      result.is_err(),
30709      "the hostile register panic must retain its normal propagation"
30710    );
30711    assert_eq!(
30712      healthy.registers.lock().unwrap().len(),
30713      2,
30714      "the previously armed driver must have completed its refresh before the later panic"
30715    );
30716    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
30717    assert!(
30718      panicking.pending.lock().unwrap().contains_key(&timer_id),
30719      "the hostile driver must model an arm committed before it panics"
30720    );
30721
30722    drop(sleep);
30723    assert_eq!(
30724      healthy.cancels.lock().unwrap().as_slice(),
30725      &[timer_id],
30726      "unwind must preserve cancellation ownership for the previously armed driver"
30727    );
30728    assert_eq!(
30729      panicking.cancels.lock().unwrap().as_slice(),
30730      &[timer_id],
30731      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
30732    );
30733    assert!(panicking.pending.lock().unwrap().is_empty());
30734    assert_eq!(registry.pending_len(), 0);
30735    assert!(!executor.host_timers.has_pending());
30736  }
30737
30738  #[test]
30739  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
30740    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
30741
30742    if std::env::var_os(CHILD_ENV).is_some() {
30743      let metrics = Arc::new(RuntimeMetrics::default());
30744      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30745      let backend =
30746        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30747      let registry = Arc::new(TimerDriverRegistry::default());
30748      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
30749        registry: Arc::downgrade(&registry),
30750        later: Mutex::new(None),
30751        registers: AtomicUsize::new(0),
30752      });
30753      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
30754
30755      let later_cancels = Arc::new(AtomicUsize::new(0));
30756      let later_drops = Arc::new(AtomicUsize::new(0));
30757      let later = Arc::new(PanickingCancelDropTimerDriver {
30758        cancels: Arc::clone(&later_cancels),
30759        drops: Arc::clone(&later_drops),
30760      });
30761      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
30762      *earlier.later.lock().unwrap() = Some(later_id);
30763      drop(later);
30764
30765      let mut sleep = make_sleep(
30766        &backend,
30767        &registry,
30768        Instant::now() + Duration::from_secs(60),
30769      );
30770      let mut cx = Context::from_waker(Waker::noop());
30771      let result = catch_unwind(AssertUnwindSafe(|| {
30772        let _ = Pin::new(&mut sleep).poll(&mut cx);
30773      }));
30774
30775      assert!(
30776        result.is_err(),
30777        "the earlier driver's register panic must still propagate"
30778      );
30779      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
30780      assert_eq!(
30781        later_drops.load(Ordering::SeqCst),
30782        0,
30783        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
30784      );
30785
30786      drop(sleep);
30787      assert_eq!(
30788        later_cancels.load(Ordering::SeqCst),
30789        1,
30790        "the later driver must remain owned for cancellation despite its registry removal"
30791      );
30792      assert_eq!(
30793        later_drops.load(Ordering::SeqCst),
30794        1,
30795        "the later driver's final destructor must run once behind containment"
30796      );
30797      assert_eq!(registry.pending_len(), 0);
30798      assert!(!executor.host_timers.has_pending());
30799      return;
30800    }
30801
30802    let output = std::process::Command::new(std::env::current_exe().unwrap())
30803      .arg("--exact")
30804      .arg(
30805        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
30806      )
30807      .arg("--nocapture")
30808      .env(CHILD_ENV, "1")
30809      .output()
30810      .expect("the hostile host timer snapshot subprocess test must start");
30811    assert!(
30812      output.status.success(),
30813      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
30814      output.status.code(),
30815      String::from_utf8_lossy(&output.stdout),
30816      String::from_utf8_lossy(&output.stderr)
30817    );
30818  }
30819
30820  #[test]
30821  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
30822    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
30823
30824    if std::env::var_os(CHILD_ENV).is_some() {
30825      let metrics = Arc::new(RuntimeMetrics::default());
30826      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30827      let backend =
30828        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30829      let cancels = Arc::new(AtomicUsize::new(0));
30830      let drops = Arc::new(AtomicUsize::new(0));
30831      let hostile = Arc::new(PanickingCancelDropTimerDriver {
30832        cancels: Arc::clone(&cancels),
30833        drops: Arc::clone(&drops),
30834      });
30835      let healthy = ManualStubDriver::new();
30836      let registry = Arc::new(TimerDriverRegistry::default());
30837      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
30838      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30839      let mut sleep = make_sleep(
30840        &backend,
30841        &registry,
30842        Instant::now() + Duration::from_secs(60),
30843      );
30844      let mut cx = Context::from_waker(Waker::noop());
30845
30846      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30847      let timer_id = healthy.registers.lock().unwrap()[0].0;
30848
30849      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
30850      // independently contain both its cancel panic and its destructor panic.
30851      registry.unregister(hostile_id);
30852      drop(hostile);
30853      drop(sleep);
30854      assert_eq!(cancels.load(Ordering::SeqCst), 1);
30855      assert_eq!(drops.load(Ordering::SeqCst), 1);
30856      assert_eq!(
30857        healthy.cancels.lock().unwrap().as_slice(),
30858        &[timer_id],
30859        "one hostile cancellation must not skip later armed drivers"
30860      );
30861      assert_eq!(registry.pending_len(), 0);
30862      assert!(!executor.host_timers.has_pending());
30863      return;
30864    }
30865
30866    let output = std::process::Command::new(std::env::current_exe().unwrap())
30867      .arg("--exact")
30868      .arg(
30869        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
30870      )
30871      .arg("--nocapture")
30872      .env(CHILD_ENV, "1")
30873      .output()
30874      .expect("the hostile timer-driver cancellation subprocess test must start");
30875    assert!(
30876      output.status.success(),
30877      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
30878      output.status.code(),
30879      String::from_utf8_lossy(&output.stdout),
30880      String::from_utf8_lossy(&output.stderr)
30881    );
30882  }
30883
30884  #[test]
30885  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
30886    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
30887
30888    if std::env::var_os(CHILD_ENV).is_some() {
30889      let metrics = Arc::new(RuntimeMetrics::default());
30890      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30891      let backend =
30892        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30893      let driver = ManualStubDriver::new();
30894      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30895      let state = Arc::new(TestHostileRawWakerState {
30896        clones: AtomicUsize::new(0),
30897        drop_panics: AtomicUsize::new(0),
30898        panic_on_clone: Some(2),
30899      });
30900
30901      let result = catch_unwind(AssertUnwindSafe(|| {
30902        let mut sleep = make_sleep(
30903          &backend,
30904          &registry,
30905          Instant::now() + Duration::from_secs(60),
30906        );
30907        let waker = test_hostile_waker(Arc::clone(&state));
30908        let mut cx = Context::from_waker(&waker);
30909        let _ = Pin::new(&mut sleep).poll(&mut cx);
30910      }));
30911      assert!(
30912        result.is_err(),
30913        "the original RawWaker clone panic must still propagate"
30914      );
30915      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
30916      assert_eq!(
30917        state.drop_panics.load(Ordering::SeqCst),
30918        1,
30919        "the first clone must be destroyed once behind a contained boundary"
30920      );
30921      assert!(driver.registers.lock().unwrap().is_empty());
30922      assert_eq!(registry.pending_len(), 0);
30923      assert!(!executor.host_timers.has_pending());
30924      return;
30925    }
30926
30927    let output = std::process::Command::new(std::env::current_exe().unwrap())
30928      .arg("--exact")
30929      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
30930      .arg("--nocapture")
30931      .env(CHILD_ENV, "1")
30932      .output()
30933      .expect("the hostile RawWaker subprocess test must start");
30934    assert!(
30935      output.status.success(),
30936      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
30937      output.status.code(),
30938      String::from_utf8_lossy(&output.stdout),
30939      String::from_utf8_lossy(&output.stderr)
30940    );
30941  }
30942
30943  #[test]
30944  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
30945    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
30946
30947    if std::env::var_os(CHILD_ENV).is_some() {
30948      let metrics = Arc::new(RuntimeMetrics::default());
30949      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30950      let backend =
30951        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30952      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
30953        registers: AtomicUsize::new(0),
30954      });
30955      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30956      let state = Arc::new(TestHostileRawWakerState {
30957        clones: AtomicUsize::new(0),
30958        drop_panics: AtomicUsize::new(0),
30959        panic_on_clone: None,
30960      });
30961      let mut sleep = make_sleep(
30962        &backend,
30963        &registry,
30964        Instant::now() + Duration::from_secs(60),
30965      );
30966      let waker = test_hostile_waker(Arc::clone(&state));
30967      let mut cx = Context::from_waker(&waker);
30968
30969      let result = catch_unwind(AssertUnwindSafe(|| {
30970        let _ = Pin::new(&mut sleep).poll(&mut cx);
30971      }));
30972      assert!(
30973        result.is_err(),
30974        "the timer-driver register panic must still propagate"
30975      );
30976      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
30977      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
30978      assert_eq!(
30979        state.drop_panics.load(Ordering::SeqCst),
30980        1,
30981        "the unretained register argument must be destroyed once behind the proxy boundary"
30982      );
30983      assert_eq!(registry.pending_len(), 1);
30984      assert!(executor.host_timers.has_pending());
30985
30986      drop(sleep);
30987      assert_eq!(
30988        state.drop_panics.load(Ordering::SeqCst),
30989        3,
30990        "repoll and registry wakers must retain their existing contained cleanup"
30991      );
30992      assert_eq!(registry.pending_len(), 0);
30993      assert!(!executor.host_timers.has_pending());
30994      return;
30995    }
30996
30997    let output = std::process::Command::new(std::env::current_exe().unwrap())
30998      .arg("--exact")
30999      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
31000      .arg("--nocapture")
31001      .env(CHILD_ENV, "1")
31002      .output()
31003      .expect("the hostile register-waker subprocess test must start");
31004    assert!(
31005      output.status.success(),
31006      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
31007      output.status.code(),
31008      String::from_utf8_lossy(&output.stdout),
31009      String::from_utf8_lossy(&output.stderr)
31010    );
31011  }
31012
31013  #[test]
31014  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
31015    // A sleep armed on a driver whose host dies must keep every remaining live
31016    // registrant armed with its remaining time preserved
31017    // (absolute deadline), keep the executor's wake-token accounting at
31018    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
31019    // armed on when dropped.
31020    let metrics = Arc::new(RuntimeMetrics::default());
31021    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31022    let backend =
31023      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31024
31025    let a = ManualStubDriver::new();
31026    let b = ManualStubDriver::new();
31027    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31028    let deadline = Instant::now() + Duration::from_secs(60);
31029
31030    let (timer_id, b_id) = {
31031      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
31032      let mut cx = Context::from_waker(Waker::noop());
31033
31034      // First poll arms on A (the only registrant) and takes the wake-token.
31035      assert!(sleep.as_mut().poll(&mut cx).is_pending());
31036      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
31037      assert_eq!(armed_deadline, deadline);
31038      assert!(
31039        executor.host_timers.has_pending(),
31040        "first poll must arm the wake-token"
31041      );
31042
31043      // A's host dies; a newer live registrant appears (the binding shape:
31044      // eviction wakes every pending sleep, which then re-polls -- here the
31045      // re-poll is driven manually).
31046      a.kill();
31047      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31048      assert!(sleep.as_mut().poll(&mut cx).is_pending());
31049      assert_eq!(
31050        b.registers.lock().unwrap().as_slice(),
31051        &[(timer_id, deadline)],
31052        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
31053      );
31054      assert_eq!(
31055        a.cancels.lock().unwrap().as_slice(),
31056        &[timer_id],
31057        "the dead driver gets a best-effort cancel for the old arm"
31058      );
31059      assert_eq!(
31060        registry.current().map(|(id, _)| id),
31061        Some(b_id),
31062        "the dead driver was evicted"
31063      );
31064      assert!(
31065        executor.host_timers.has_pending(),
31066        "the wake-token survives the re-arm (still exactly this sleep's entry)"
31067      );
31068      (timer_id, b_id)
31069    };
31070
31071    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
31072    // wake-token retires.
31073    let _ = b_id;
31074    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
31075    assert!(
31076      !executor.host_timers.has_pending(),
31077      "drop must retire the wake-token"
31078    );
31079  }
31080
31081  #[test]
31082  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
31083    let metrics = Arc::new(RuntimeMetrics::default());
31084    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31085    let backend =
31086      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31087    let responsive = Arc::new(StubHostTimerDriver::new());
31088    let starved = ManualStubDriver::new();
31089    let registry = Arc::new(TimerDriverRegistry::default());
31090    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
31091    // Newest-live-only selection would arm exclusively on this host and never
31092    // receive a wake. The responsive older host must be raced in parallel.
31093    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31094
31095    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
31096    let waker = Waker::from(Arc::clone(&wake_count));
31097    let mut cx = Context::from_waker(&waker);
31098    // This deadline's pre-fire budget covers only `make_sleep` plus the very
31099    // next poll (a past-due Sleep completes inline and would bypass the
31100    // racing-arm machinery under test); 500ms dwarfs those two statements on
31101    // a starved runner without dragging the fire wait out.
31102    let deadline = Instant::now() + Duration::from_millis(500);
31103    let mut sleep = make_sleep(&backend, &registry, deadline);
31104    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31105
31106    let responsive_timer = *responsive
31107      .timers
31108      .lock()
31109      .unwrap()
31110      .keys()
31111      .next()
31112      .expect("responsive timer must be armed");
31113    assert_eq!(
31114      starved.registers.lock().unwrap().as_slice(),
31115      &[(responsive_timer, deadline)],
31116      "every live host must receive the same timer identity and absolute deadline"
31117    );
31118
31119    // Event-based fire wait (this was a fixed 140ms sleep, which required
31120    // the responsive stub's freshly spawned fire thread to get CPU inside
31121    // ~80ms on a starved CI host). The wait is structurally sound: the
31122    // starved ManualStubDriver has NO fire path (it only records), so the
31123    // one-and-only wake can come from the responsive host -- the bound is a
31124    // pure hang detector.
31125    let fire_bound = Instant::now() + Duration::from_secs(30);
31126    while wake_count.0.load(Ordering::SeqCst) == 0 {
31127      assert!(
31128        Instant::now() < fire_bound,
31129        "the responsive host must wake a sleep whose newest host is live but starved"
31130      );
31131      std::thread::sleep(Duration::from_millis(10));
31132    }
31133    // The stub fires at (or after) the shared absolute deadline, so the wall
31134    // clock is past `deadline` here and the poll completes.
31135    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31136    assert_eq!(
31137      starved.cancels.lock().unwrap().as_slice(),
31138      &[responsive_timer],
31139      "completion through the winning host must cancel the starved losing arm"
31140    );
31141  }
31142
31143  #[test]
31144  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
31145    use std::sync::mpsc;
31146
31147    struct ReentrantTimerRegistrationWake {
31148      registry: Weak<TimerDriverRegistry>,
31149      wakes: AtomicUsize,
31150      reentered: AtomicBool,
31151    }
31152
31153    impl std::task::Wake for ReentrantTimerRegistrationWake {
31154      fn wake(self: Arc<Self>) {
31155        self.wakes.fetch_add(1, Ordering::SeqCst);
31156        if let Some(registry) = self.registry.upgrade() {
31157          let _ = registry.has_live_driver();
31158          self.reentered.store(true, Ordering::SeqCst);
31159        }
31160      }
31161    }
31162
31163    let metrics = Arc::new(RuntimeMetrics::default());
31164    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31165    let backend =
31166      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31167    let registry = Arc::new(TimerDriverRegistry::default());
31168    let starved = ManualStubDriver::new();
31169    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31170
31171    // The whole pre-fire phase (four polls plus a registration-thread round
31172    // trip) must finish while the wall clock is still BEFORE this shared
31173    // deadline: a past-due `Sleep` completes inline at poll
31174    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
31175    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
31176    // a starved GitHub macOS runner) without dragging the fire wait out.
31177    let deadline = Instant::now() + Duration::from_secs(2);
31178    let mut first = make_sleep(&backend, &registry, deadline);
31179    let mut second = make_sleep(&backend, &registry, deadline);
31180    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
31181      registry: Arc::downgrade(&registry),
31182      wakes: AtomicUsize::new(0),
31183      reentered: AtomicBool::new(false),
31184    });
31185    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
31186      registry: Arc::downgrade(&registry),
31187      wakes: AtomicUsize::new(0),
31188      reentered: AtomicBool::new(false),
31189    });
31190    let first_waker = Waker::from(Arc::clone(&first_wake));
31191    let second_waker = Waker::from(Arc::clone(&second_wake));
31192    let mut first_cx = Context::from_waker(&first_waker);
31193    let mut second_cx = Context::from_waker(&second_waker);
31194
31195    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31196    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31197    let initial = starved.registers.lock().unwrap().clone();
31198    let first_id = initial[0].0;
31199    let second_id = initial[1].0;
31200    assert_ne!(first_id, second_id);
31201    assert_eq!(
31202      registry.pending_len(),
31203      2,
31204      "one stable repoll entry is retained per sleep"
31205    );
31206
31207    let responsive = Arc::new(StubHostTimerDriver::new());
31208    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
31209    let registration_registry = Arc::clone(&registry);
31210    let registration_driver = Arc::clone(&responsive);
31211    let registration = std::thread::spawn(move || {
31212      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
31213      registered_tx.send(id).unwrap();
31214    });
31215    // Hang detector only (the regression it pins is a PERMANENT deadlock:
31216    // `register` re-entering the repoll callback), so the bound is generous
31217    // rather than a latency assertion a starved runner could trip.
31218    registered_rx
31219      .recv_timeout(Duration::from_secs(10))
31220      .expect("late driver registration or repoll callback re-entry deadlocked");
31221    registration.join().unwrap();
31222
31223    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
31224    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
31225    assert!(first_wake.reentered.load(Ordering::SeqCst));
31226    assert!(second_wake.reentered.load(Ordering::SeqCst));
31227    assert!(
31228      responsive.timers.lock().unwrap().is_empty(),
31229      "registration requests bounded re-polls instead of invoking driver callbacks inline"
31230    );
31231
31232    // Both sleeps must still be PENDING when they re-arm on the late driver;
31233    // see the deadline-margin note above. Fail with a diagnosis instead of a
31234    // misleading Pending-assert failure if a pathological stall consumed the
31235    // whole margin anyway.
31236    assert!(
31237      Instant::now() < deadline,
31238      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
31239       this runner is starved beyond the margin this test budgets for"
31240    );
31241    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31242    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31243    let responsive_ids = responsive
31244      .timers
31245      .lock()
31246      .unwrap()
31247      .keys()
31248      .copied()
31249      .collect::<FxHashSet<_>>();
31250    assert_eq!(
31251      responsive_ids,
31252      FxHashSet::from_iter([first_id, second_id]),
31253      "every pending sleep must re-arm its existing identity on the new driver"
31254    );
31255    assert_eq!(
31256      starved
31257        .registers
31258        .lock()
31259        .unwrap()
31260        .iter()
31261        .filter(|(id, _)| *id == first_id)
31262        .count(),
31263      2,
31264      "the old host receives only the normal waker refresh on the one re-poll"
31265    );
31266    assert_eq!(
31267      starved
31268        .registers
31269        .lock()
31270        .unwrap()
31271        .iter()
31272        .filter(|(id, _)| *id == second_id)
31273        .count(),
31274      2,
31275      "the old host receives only the normal waker refresh on the one re-poll"
31276    );
31277    assert_eq!(
31278      registry.pending_len(),
31279      2,
31280      "late registration must not duplicate pending state"
31281    );
31282
31283    // Event-based fire wait instead of a fixed sleep: the starved host has
31284    // NO fire path by construction (ManualStubDriver only records), so the
31285    // second wake per sleep can only come from the late responsive host --
31286    // "while the original host remains starved" is structural, not a
31287    // wall-clock race. The bound is purely a hang detector, generous enough
31288    // for a starved runner to schedule the stub's two fire threads.
31289    let fire_bound = Instant::now() + Duration::from_secs(30);
31290    while first_wake.wakes.load(Ordering::SeqCst) < 2
31291      || second_wake.wakes.load(Ordering::SeqCst) < 2
31292    {
31293      assert!(
31294        Instant::now() < fire_bound,
31295        "the late responsive host must fire both timers while the original host remains starved"
31296      );
31297      std::thread::sleep(Duration::from_millis(10));
31298    }
31299    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
31300    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
31301
31302    let mut cancelled = starved.cancels.lock().unwrap().clone();
31303    cancelled.sort_unstable();
31304    let mut expected = vec![first_id, second_id];
31305    expected.sort_unstable();
31306    assert_eq!(
31307      cancelled, expected,
31308      "completion must cancel both losing starved-host arms"
31309    );
31310    assert_eq!(
31311      registry.pending_len(),
31312      0,
31313      "completion retires every repoll entry"
31314    );
31315    assert!(!executor.host_timers.has_pending());
31316  }
31317
31318  #[test]
31319  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
31320    // With NO live registrant left mid-flight there is nothing to re-arm on:
31321    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
31322    // sleep silently.
31323    let metrics = Arc::new(RuntimeMetrics::default());
31324    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31325    let backend =
31326      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31327
31328    let a = ManualStubDriver::new();
31329    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31330    let mut sleep = std::pin::pin!(make_sleep(
31331      &backend,
31332      &registry,
31333      Instant::now() + Duration::from_secs(60)
31334    ));
31335    let mut cx = Context::from_waker(Waker::noop());
31336    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31337
31338    a.kill();
31339    let payload = catch_unwind(AssertUnwindSafe(|| {
31340      let _ = sleep.as_mut().poll(&mut cx);
31341    }))
31342    .expect_err("a sleep with every driver dead must panic, not pend silently");
31343    let message = payload
31344      .downcast_ref::<String>()
31345      .cloned()
31346      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
31347      .expect("panic payload must be a message");
31348    assert!(
31349      message.contains("lost every live timer driver mid-sleep"),
31350      "the diagnostic must name the mid-flight driver loss, got: {message}"
31351    );
31352  }
31353
31354  #[test]
31355  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
31356    // Sweep-eviction invariant: the registry's selection sweep can be the
31357    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
31358    // or any call failure. A retain-only sweep would strand every sleep whose
31359    // armed waker lives in the swept driver's pending map. The sweep must call
31360    // `on_swept` outside the registry lock so the driver wakes those sleeps
31361    // into re-selection.
31362    let metrics = Arc::new(RuntimeMetrics::default());
31363    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31364    let backend =
31365      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31366
31367    let a = ManualStubDriver::new();
31368    let b = ManualStubDriver::new();
31369    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31370    let mut sleep = std::pin::pin!(make_sleep(
31371      &backend,
31372      &registry,
31373      Instant::now() + Duration::from_secs(60)
31374    ));
31375
31376    // Arm on A with an observable waker.
31377    let flag = Arc::new(WakeFlag::default());
31378    let waker = Waker::from(Arc::clone(&flag));
31379    let mut cx = Context::from_waker(&waker);
31380    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31381    assert!(!flag.0.load(Ordering::SeqCst));
31382
31383    // A's host dies; a DIFFERENT selection (any registry touch -- here a
31384    // liveness query, in production another sleep's poll) sweeps it.
31385    a.kill();
31386    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31387    let _ = registry.has_live_driver();
31388
31389    assert!(
31390      a.swept.load(Ordering::SeqCst),
31391      "the sweep must notify the swept driver"
31392    );
31393    assert!(
31394      flag.0.load(Ordering::SeqCst),
31395      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
31396    );
31397
31398    // The woken sleep's re-poll lands on the live registrant.
31399    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31400    assert_eq!(
31401      b.registers.lock().unwrap().len(),
31402      1,
31403      "the re-poll must re-arm on the live driver"
31404    );
31405  }
31406
31407  #[test]
31408  fn current_thread_sleep_fires_through_stub_host_driver() {
31409    // CT delegation: a registered host driver's fire must complete a sleep
31410    // awaited through the CT `block_on` drive loop.
31411    use std::sync::mpsc;
31412
31413    let (done_tx, done_rx) = mpsc::channel();
31414    let runner = std::thread::spawn(move || {
31415      let metrics = Arc::new(RuntimeMetrics::default());
31416      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31417      let backend =
31418        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31419      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31420
31421      let started = Instant::now();
31422      let mut future = std::pin::pin!(async {
31423        make_sleep(
31424          &backend,
31425          &registry_with(driver),
31426          Instant::now() + Duration::from_millis(60),
31427        )
31428        .await;
31429      });
31430      executor.block_on(future.as_mut());
31431      assert!(started.elapsed() >= Duration::from_millis(60));
31432      assert!(
31433        !executor.host_timers.has_pending(),
31434        "a completed sleep must retire its pending-registry entry"
31435      );
31436      let _ = done_tx.send(());
31437    });
31438
31439    match done_rx.recv_timeout(Duration::from_secs(10)) {
31440      Ok(()) => runner.join().unwrap(),
31441      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
31442    }
31443  }
31444
31445  #[test]
31446  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
31447    let metrics = Arc::new(RuntimeMetrics::default());
31448    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31449    let backend =
31450      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31451    let drivers = Arc::new(TimerDriverRegistry::default());
31452    let driver = ManualStubDriver::new();
31453    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31454    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
31455    let mut cx = Context::from_waker(Waker::noop());
31456
31457    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31458    let timer_id = driver.registers.lock().unwrap()[0].0;
31459    assert_eq!(drivers.pending_len(), 1);
31460
31461    executor.host_timers.shutdown();
31462    assert_eq!(
31463      drivers.pending_len(),
31464      0,
31465      "shutdown must retire the registration-repoll index before the Sleep is dropped"
31466    );
31467    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31468    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
31469  }
31470
31471  #[test]
31472  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
31473    use std::sync::mpsc;
31474
31475    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
31476    let backend = controller.backend();
31477    let drivers = Arc::new(TimerDriverRegistry::default());
31478    let driver = ManualStubDriver::new();
31479    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31480    let sleep = make_sleep(
31481      &backend,
31482      &drivers,
31483      Instant::now() + Duration::from_secs(120),
31484    );
31485    drop(backend);
31486
31487    let (polled_tx, polled_rx) = mpsc::channel();
31488    let block_controller = Arc::clone(&controller);
31489    let blocker = std::thread::spawn(move || {
31490      let mut announced = false;
31491      let mut sleep = std::pin::pin!(sleep);
31492      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
31493        let poll = sleep.as_mut().poll(cx);
31494        if !announced {
31495          announced = true;
31496          polled_tx.send(()).unwrap();
31497        }
31498        poll
31499      }));
31500      block_controller.block_on(future.as_mut());
31501    });
31502    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
31503
31504    let (shutdown_tx, shutdown_rx) = mpsc::channel();
31505    let shutdown_controller = Arc::clone(&controller);
31506    let shutdown = std::thread::spawn(move || {
31507      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
31508    });
31509    shutdown_rx
31510      .recv_timeout(Duration::from_secs(2))
31511      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
31512      .unwrap();
31513    shutdown.join().unwrap();
31514    blocker.join().unwrap();
31515    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
31516  }
31517
31518  #[test]
31519  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
31520    // Threadless pending-timer invariant: on a
31521    // THREADLESS build a pending host timer is NOT a wake-token source -- the
31522    // host event loop's `setTimeout` relay can only run on the very thread
31523    // that is about to park, so it can never fire while `block_on` holds it.
31524    // The certain check must panic with the typed diagnostic anyway (the
31525    // timer-backed kind, so the message names the shape), never fall through
31526    // to the park -- on the real wasip1 target that park is an untyped
31527    // "condvar wait not supported" abort. A native helper thread can fire the
31528    // timer in this test harness, but that concurrency cannot exist on the
31529    // threadless target and must not rescue the park.
31530    use std::sync::mpsc;
31531
31532    let (done_tx, done_rx) = mpsc::channel();
31533    let runner = std::thread::spawn(move || {
31534      let metrics = Arc::new(RuntimeMetrics::default());
31535      // Native stand-in for the threadless wasm build; the stub driver would
31536      // play the host loop from a helper thread, but the panic must land at
31537      // the first park decision, long before its 60ms fire.
31538      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31539      let backend =
31540        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31541      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31542
31543      let payload = catch_unwind(AssertUnwindSafe(|| {
31544        let mut future = std::pin::pin!(async {
31545          make_sleep(
31546            &backend,
31547            &registry_with(driver),
31548            Instant::now() + Duration::from_millis(60),
31549          )
31550          .await;
31551        });
31552        executor.block_on(future.as_mut());
31553      }))
31554      .expect_err("a threadless park must self-detect even while a host timer is pending");
31555      let diagnostic = payload
31556        .downcast_ref::<BlockOnDeadlock>()
31557        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31558      assert_eq!(
31559        diagnostic.kind,
31560        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
31561      );
31562      assert_eq!(
31563        diagnostic.park_deadline, None,
31564        "the certain case carries no deadline"
31565      );
31566      let message = diagnostic.to_string();
31567      assert!(
31568        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
31569        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
31570      );
31571      let _ = done_tx.send(());
31572    });
31573
31574    match done_rx.recv_timeout(Duration::from_secs(10)) {
31575      Ok(()) => runner.join().unwrap(),
31576      Err(error) => panic!(
31577        "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})"
31578      ),
31579    }
31580  }
31581
31582  #[test]
31583  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
31584    // Unpolled-sleep invariant: the registry entry only exists once the host
31585    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
31586    // creation. A created-but-never-polled Sleep has no host callback behind
31587    // it, and on a parked single thread nothing can ever poll it into
31588    // arming -- it is provably NOT a future wake source. Counting it would
31589    // mislabel the certain diagnostic as timer-backed. (The armed twin above
31590    // pins the polled side: a POLLED pending timer still certain-panics, just
31591    // with the timer-backed kind.)
31592    use std::sync::mpsc;
31593
31594    let (done_tx, done_rx) = mpsc::channel();
31595    let runner = std::thread::spawn(move || {
31596      let metrics = Arc::new(RuntimeMetrics::default());
31597      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31598      let backend =
31599        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31600      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31601
31602      // Created and held across the park decision, but NEVER polled: no host
31603      // timer armed, no wake source.
31604      let _unpolled = make_sleep(
31605        &backend,
31606        &registry_with(driver),
31607        Instant::now() + Duration::from_secs(60),
31608      );
31609
31610      let payload = catch_unwind(AssertUnwindSafe(|| {
31611        let mut future = std::pin::pin!(NeverReady);
31612        executor.block_on(future.as_mut());
31613      }))
31614      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
31615      let diagnostic = payload
31616        .downcast_ref::<BlockOnDeadlock>()
31617        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31618      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31619      let _ = done_tx.send(());
31620    });
31621
31622    match done_rx.recv_timeout(Duration::from_secs(10)) {
31623      Ok(()) => runner.join().unwrap(),
31624      Err(error) => panic!(
31625        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
31626      ),
31627    }
31628  }
31629
31630  #[cfg(not(target_family = "wasm"))]
31631  #[test]
31632  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
31633    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
31634    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
31635    // legitimate (the wake is scheduled on the host loop); the deadline
31636    // verdict must not fire. Once the timer fires, the sleep completes.
31637    use std::sync::mpsc;
31638
31639    let (done_tx, done_rx) = mpsc::channel();
31640    let runner = std::thread::spawn(move || {
31641      let metrics = Arc::new(RuntimeMetrics::default());
31642      // Deadline/grace ratio: the armed park deadline doubles as the
31643      // `has_live_wait` GRACE -- once the host timer is past due by more
31644      // than one deadline window without firing, the veto stops holding BY
31645      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
31646      // the stub host's freshly spawned fire thread had to get CPU within
31647      // ~230ms of test start or the verdict fired -- the starved-CI failure.
31648      // A 2s window keeps the invariant identical (a LIVE host-timer wait
31649      // with zero progress must not be declared dead) while tolerating
31650      // multi-second scheduling stalls of the fire thread: the veto holds
31651      // until deadline+2s, and the expiry checks land on 2s boundaries, so
31652      // the fire has >4s before a verdict could stick.
31653      let executor = Arc::new(CurrentThreadExecutor::with_detection(
31654        metrics,
31655        false,
31656        Some(Duration::from_secs(2)),
31657      ));
31658      let backend =
31659        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31660      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31661
31662      let started = Instant::now();
31663      let mut future = std::pin::pin!(async {
31664        make_sleep(
31665          &backend,
31666          &registry_with(driver),
31667          Instant::now() + Duration::from_millis(200),
31668        )
31669        .await;
31670      });
31671      executor.block_on(future.as_mut());
31672      assert!(started.elapsed() >= Duration::from_millis(200));
31673      let _ = done_tx.send(());
31674    });
31675
31676    match done_rx.recv_timeout(Duration::from_secs(30)) {
31677      Ok(()) => runner.join().unwrap(),
31678      Err(error) => {
31679        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
31680      }
31681    }
31682  }
31683
31684  #[test]
31685  fn host_timer_unrepresentable_grace_remains_live() {
31686    let registry = HostTimerRegistry::default();
31687    let fired = Arc::new(AtomicBool::new(false));
31688    let drivers = Arc::new(TimerDriverRegistry::default());
31689    let repoll = Arc::new(HostTimerRepoll::default());
31690    assert!(
31691      Instant::now().checked_add(Duration::MAX).is_none(),
31692      "Duration::MAX must exercise the unrepresentable-grace path"
31693    );
31694    assert!(
31695      registry.register(
31696        1,
31697        Instant::now(),
31698        futures::task::noop_waker(),
31699        &fired,
31700        &drivers,
31701        &repoll,
31702      ),
31703      "the open registry must accept the timer"
31704    );
31705
31706    assert!(
31707      registry.has_live_wait(Duration::MAX),
31708      "an unrepresentable grace deadline is effectively unbounded"
31709    );
31710    registry.remove(1);
31711  }
31712
31713  #[test]
31714  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
31715    // A deadline firing inside a spawned task's poll unwinds into the spawn
31716    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
31717    // message must survive that trip (JoinError::from_panic downcasts it), or
31718    // the build error would read "async runtime task panicked" with no clue.
31719    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
31720    let expected = diagnostic.to_string();
31721    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
31722    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
31723  }
31724}