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) && !producer_give_up.load(Ordering::SeqCst) {
15949          let scheduler = Arc::clone(&producer_executor);
15950          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
15951          producer_executor.schedule(runnable);
15952          task.detach();
15953          next_tick += cadence;
15954          while Instant::now() < next_tick {
15955            std::hint::spin_loop();
15956          }
15957        }
15958      });
15959
15960      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
15961      give_up.store(true, Ordering::SeqCst);
15962      producer.join().unwrap();
15963      match outcome {
15964        Ok(elapsed) => {
15965          futures::executor::block_on(handle).expect("blocking closure join");
15966          drop(t1_task);
15967          eprintln!("round {round}: completed in {elapsed:?}");
15968        }
15969        Err(error) => panic!(
15970          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
15971           sub-budget periodic work must not keep a drain frame resident forever"
15972        ),
15973      }
15974    }
15975  }
15976
15977  #[cfg(not(target_family = "wasm"))]
15978  #[test]
15979  #[ignore = "microbenchmark: run explicitly in release mode"]
15980  fn parked_drivers_registry_cycle_microbench() {
15981    // Isolates the registry traffic of one steady-state linger cycle at
15982    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
15983    // drainer deregisters (the entry is already gone, so the position scan
15984    // walks the WHOLE vector and fails), then re-registers (push). Also
15985    // measures the timeout-path deregister (entry present) and a
15986    // wake_one_owner_dependency sweep over dependency-less lingerers.
15987    for &n in &[8usize, 64, 256] {
15988      let registry = ParkedDrivers::default();
15989      let parkers: Vec<Arc<DriverParker>> =
15990        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
15991      for parker in &parkers {
15992        registry.register_with_role(parker, true, None);
15993      }
15994      let hot = &parkers[n - 1];
15995
15996      let iters = 2_000_000u32;
15997      let start = Instant::now();
15998      for _ in 0..iters {
15999        assert!(registry.wake_one());
16000        registry.deregister(hot);
16001        assert!(hot.consume_permit());
16002        registry.register_with_role(hot, true, None);
16003      }
16004      let wake_cycle = start.elapsed() / iters;
16005
16006      let start = Instant::now();
16007      for _ in 0..iters {
16008        registry.deregister(hot);
16009        registry.register_with_role(hot, true, None);
16010      }
16011      let timeout_cycle = start.elapsed() / iters;
16012
16013      let owner = BlockingOwnerToken {
16014        executor_id: u64::MAX,
16015        frame: u64::MAX,
16016      };
16017      let start = Instant::now();
16018      for _ in 0..iters {
16019        assert!(!registry.wake_one_owner_dependency(owner));
16020      }
16021      let owner_sweep = start.elapsed() / iters;
16022
16023      eprintln!(
16024        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
16025         timeout-deregister+re-register {timeout_cycle:?}, \
16026         owner-dependency sweep {owner_sweep:?}"
16027      );
16028    }
16029  }
16030
16031  #[cfg(not(target_family = "wasm"))]
16032  #[test]
16033  fn multi_thread_block_on_from_caller_thread_still_parks() {
16034    // The non-pool (napi caller) path must keep using the plain parking
16035    // `block_on`; here we just assert it drives a ready future to completion.
16036    let metrics = Arc::new(RuntimeMetrics::default());
16037    let options = RuntimeOptions {
16038      flavor: RuntimeFlavor::MultiThread,
16039      worker_threads: 3,
16040      max_blocking_tasks: 2,
16041      thread_name_prefix: "rd1-caller".to_string(),
16042      park_deadline: None,
16043      drain_linger: DEFAULT_DRAIN_LINGER,
16044    };
16045    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16046
16047    let mut output = None;
16048    let mut future = std::pin::pin!(async {
16049      output = Some(41usize + 1);
16050    });
16051    executor.block_on(future.as_mut());
16052    assert_eq!(output, Some(42));
16053  }
16054
16055  #[cfg(not(target_family = "wasm"))]
16056  #[test]
16057  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
16058    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
16059    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
16060    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
16061    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
16062    // This pins ONLY the cross-thread wake path: the pool task completing must wake
16063    // the parked caller's waker so `block_on` returns the pool-computed value.
16064    //
16065    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
16066    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
16067    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
16068    // parking waker is registered on the task), signals "caller parked" to a releaser
16069    // thread. The releaser then -- and only then -- releases the gate, so the pool
16070    // task completes strictly AFTER the caller registered its waker. This forecloses
16071    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
16072    // caller's first poll, because its gate is opened only in response to that first
16073    // `Pending` poll. The completion is thus forced to travel the real cross-thread
16074    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
16075    // if that wake is broken the caller parks forever and the bounded recv_timeout
16076    // below fails loudly instead of hanging the suite.
16077    //
16078    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
16079    // caller-is-pool-worker case, which has dedicated reentrancy tests.
16080    use std::sync::mpsc;
16081    use std::time::Duration;
16082
16083    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
16084    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
16085    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
16086    // is already registered before the gate can open.
16087    struct CallerProbe {
16088      task: Task<usize>,
16089      parked_tx: Option<mpsc::Sender<()>>,
16090    }
16091    impl Future for CallerProbe {
16092      type Output = usize;
16093      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
16094        // `Task` is `Unpin`; poll it through a fresh `Pin`.
16095        let poll = Pin::new(&mut self.task).poll(cx);
16096        if poll.is_pending()
16097          && let Some(tx) = self.parked_tx.take()
16098        {
16099          // Waker is now registered on `task`; release the gate is safe.
16100          let _ = tx.send(());
16101        }
16102        poll
16103      }
16104    }
16105
16106    let (done_tx, done_rx) = mpsc::channel();
16107    let runner = std::thread::spawn(move || {
16108      let metrics = Arc::new(RuntimeMetrics::default());
16109      let options = RuntimeOptions {
16110        flavor: RuntimeFlavor::MultiThread,
16111        worker_threads: 2,
16112        max_blocking_tasks: 2,
16113        thread_name_prefix: "rd10-caller".to_string(),
16114        park_deadline: None,
16115        drain_linger: DEFAULT_DRAIN_LINGER,
16116      };
16117      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16118
16119      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
16120      // worker registers the gate waker and returns `Pending`. When the releaser sends
16121      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
16122      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
16123      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
16124      let scheduler = Arc::clone(&executor);
16125      let (runnable, task) = async_task::spawn(
16126        async move { gate_rx.await.expect("gate sender dropped") },
16127        move |r| {
16128          scheduler.schedule(r);
16129        },
16130      );
16131      executor.schedule(runnable);
16132
16133      // Releaser thread: opens the gate ONLY after the caller has parked (registered
16134      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
16135      // completes, so completion must travel the cross-thread wake path.
16136      let (parked_tx, parked_rx) = mpsc::channel::<()>();
16137      let releaser = std::thread::spawn(move || {
16138        if parked_rx.recv().is_ok() {
16139          let _ = gate_tx.send(42usize);
16140        }
16141      });
16142
16143      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
16144      // `futures::executor::block_on` rather than driving the queue cooperatively.
16145      let mut output = None;
16146      {
16147        let mut future = std::pin::pin!(async {
16148          output = Some(
16149            CallerProbe {
16150              task,
16151              parked_tx: Some(parked_tx),
16152            }
16153            .await,
16154          );
16155        });
16156        executor.block_on(future.as_mut());
16157      }
16158
16159      releaser.join().unwrap();
16160      assert_eq!(
16161        output,
16162        Some(42usize),
16163        "block_on must return the pool-computed value"
16164      );
16165      let _ = done_tx.send(());
16166    });
16167
16168    match done_rx.recv_timeout(Duration::from_secs(10)) {
16169      Ok(()) => runner.join().unwrap(),
16170      Err(error) => panic!(
16171        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
16172      ),
16173    }
16174  }
16175
16176  #[cfg(not(target_family = "wasm"))]
16177  #[test]
16178  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
16179    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
16180    // before the work that will wake it is scheduled. Once every pool worker
16181    // is parked this way, `ensure_drainer` cannot spawn a replacement because
16182    // the parked workers are still counted as active drainers. A later
16183    // `schedule()` must wake a parked driver directly so the runnable can run.
16184    use std::sync::{Barrier, mpsc};
16185    use std::time::Duration;
16186
16187    let (done_tx, done_rx) = mpsc::channel();
16188    let runner = std::thread::spawn(move || {
16189      let metrics = Arc::new(RuntimeMetrics::default());
16190      let options = RuntimeOptions {
16191        flavor: RuntimeFlavor::MultiThread,
16192        worker_threads: 2,
16193        max_blocking_tasks: 2,
16194        thread_name_prefix: "rd1-park".to_string(),
16195        park_deadline: None,
16196        drain_linger: DEFAULT_DRAIN_LINGER,
16197      };
16198      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
16199
16200      let task_count = 2usize;
16201      let barrier = Arc::new(Barrier::new(task_count));
16202
16203      let mut senders = Vec::new();
16204      let mut tasks = Vec::new();
16205      for _ in 0..task_count {
16206        let (tx, rx) = oneshot::channel::<usize>();
16207        senders.push(tx);
16208        let exec = Arc::clone(&executor);
16209        let barrier = Arc::clone(&barrier);
16210        let body = async move {
16211          // Make sure both pool workers are occupied before either parks.
16212          barrier.wait();
16213          // Child task whose wake routes through `schedule()`: the oneshot
16214          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
16215          let child_exec = Arc::clone(&exec);
16216          let (child_runnable, child_task) =
16217            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
16218              child_exec.schedule(r)
16219            });
16220          exec.schedule(child_runnable);
16221          let mut inner = std::pin::pin!(async move {
16222            assert_eq!(child_task.await, 9usize);
16223          });
16224          exec.block_on(inner.as_mut());
16225        };
16226        let scheduler = Arc::clone(&executor);
16227        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
16228        executor.schedule(runnable);
16229        tasks.push(task);
16230      }
16231
16232      // Give both workers time to poll their child Pending and park in block_on.
16233      std::thread::sleep(Duration::from_millis(200));
16234      for tx in senders {
16235        let _ = tx.send(9usize);
16236      }
16237
16238      for task in tasks {
16239        futures::executor::block_on(task);
16240      }
16241      let _ = done_tx.send(());
16242    });
16243
16244    match done_rx.recv_timeout(Duration::from_secs(10)) {
16245      Ok(()) => runner.join().unwrap(),
16246      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
16247    }
16248  }
16249
16250  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
16251  /// Used to observe executor-internal states (e.g. "both drivers are
16252  /// registered as parked") without sleeps. The bound is a HANG detector,
16253  /// not a latency assertion: starved CI runners can stall fresh threads for
16254  /// hundreds of milliseconds, so it is deliberately generous.
16255  #[cfg(not(target_family = "wasm"))]
16256  fn wait_until(what: &str, condition: impl Fn() -> bool) {
16257    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
16258    while !condition() {
16259      assert!(
16260        std::time::Instant::now() < deadline,
16261        "timed out waiting for: {what}"
16262      );
16263      std::thread::yield_now();
16264    }
16265  }
16266
16267  /// Bounded join for harness threads that already signalled completion of
16268  /// their assertions: everything left on the thread is teardown (executor
16269  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
16270  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
16271  /// into a silent libtest-worker hang for the full CI step timeout (observed
16272  /// on Windows in run 29195160953: several park-deadline-family tests never
16273  /// completed while every in-test assertion had already passed). Fail loudly
16274  /// and NAME the awaited phase instead.
16275  #[cfg(not(target_family = "wasm"))]
16276  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
16277    let deadline = std::time::Instant::now() + timeout;
16278    while !handle.is_finished() {
16279      assert!(
16280        std::time::Instant::now() < deadline,
16281        "thread wedged past its {timeout:?} bound while: {what}"
16282      );
16283      std::thread::sleep(Duration::from_millis(10));
16284    }
16285    handle.join().unwrap();
16286  }
16287
16288  #[cfg(not(target_family = "wasm"))]
16289  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
16290    executor: &Arc<MultiThreadExecutor>,
16291    start_counter: &AtomicU64,
16292    claim: impl FnOnce() -> T + Send + 'static,
16293  ) -> T {
16294    use std::sync::mpsc;
16295
16296    let (claimed_tx, claimed_rx) = mpsc::channel();
16297    let (release_claim_tx, release_claim_rx) = mpsc::channel();
16298    let (started_tx, started_rx) = mpsc::channel();
16299    let (release_start_tx, release_start_rx) = mpsc::channel();
16300    let starts_before = start_counter.load(Ordering::SeqCst);
16301    let worker = std::thread::spawn(move || {
16302      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
16303        *slot.borrow_mut() = Some(Box::new(move || {
16304          claimed_tx.send(()).unwrap();
16305          release_claim_rx.recv().unwrap();
16306        }));
16307      });
16308      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
16309        *slot.borrow_mut() = Some(Box::new(move || {
16310          started_tx.send(()).unwrap();
16311          release_start_rx.recv().unwrap();
16312        }));
16313      });
16314      claim()
16315    });
16316    claimed_rx
16317      .recv_timeout(Duration::from_secs(2))
16318      .expect("the scheduler claim must pause after removing work");
16319
16320    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16321    let verdict = executor.try_begin_deadlock_verdict();
16322    let claim_verdict_rejected = verdict.is_none();
16323    drop(verdict);
16324    release_claim_tx.send(()).unwrap();
16325
16326    started_rx
16327      .recv_timeout(Duration::from_secs(2))
16328      .expect("the scheduler claim must pause after publishing its start");
16329    let admission_active_after_start =
16330      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
16331    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
16332    let verdict = executor.try_begin_deadlock_verdict();
16333    let start_verdict_rejected = verdict.is_none();
16334    drop(verdict);
16335    release_start_tx.send(()).unwrap();
16336    let output = worker.join().unwrap();
16337
16338    assert!(
16339      admission_active,
16340      "removing scheduler work must retain a detector admission until start accounting"
16341    );
16342    assert!(
16343      claim_verdict_rejected,
16344      "a deadline verdict must not close while removed work has not published its start"
16345    );
16346    assert!(
16347      start_published,
16348      "work start accounting must publish before admission retires"
16349    );
16350    assert!(
16351      admission_active_after_start,
16352      "the detector admission must remain active until after start accounting"
16353    );
16354    assert!(
16355      start_verdict_rejected,
16356      "a deadline verdict must not close between start accounting and admission retirement"
16357    );
16358    output
16359  }
16360
16361  #[cfg(not(target_family = "wasm"))]
16362  #[test]
16363  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
16364    let executor =
16365      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
16366
16367    let fifo_ran = Arc::new(AtomicBool::new(false));
16368    let fifo_task_ran = Arc::clone(&fifo_ran);
16369    let (fifo, fifo_task) = async_task::spawn(
16370      async move {
16371        fifo_task_ran.store(true, Ordering::SeqCst);
16372      },
16373      |_| {},
16374    );
16375    executor.metrics.runnable_scheduled();
16376    executor.push_fifo_raw(fifo);
16377    let fifo_executor = Arc::clone(&executor);
16378    assert!(run_claim_with_deadlock_gate_assertion(
16379      &executor,
16380      &executor.metrics.runnable_polls,
16381      move || {
16382        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16383        fifo_executor.run_one_fifo_runnable()
16384      }
16385    ));
16386    futures::executor::block_on(fifo_task);
16387    assert!(fifo_ran.load(Ordering::SeqCst));
16388
16389    let fifo_order = Arc::new(Mutex::new(Vec::new()));
16390    let mut fifo_tasks = Vec::new();
16391    for name in ["first", "second"] {
16392      let task_order = Arc::clone(&fifo_order);
16393      let (runnable, task) = async_task::spawn(
16394        async move {
16395          task_order.lock().unwrap().push(name);
16396        },
16397        |_| {},
16398      );
16399      executor.metrics.runnable_scheduled();
16400      executor.push_fifo_raw(runnable);
16401      fifo_tasks.push(task);
16402    }
16403    for _ in 0..2 {
16404      let fifo_executor = Arc::clone(&executor);
16405      assert!(run_claim_with_deadlock_gate_assertion(
16406        &executor,
16407        &executor.metrics.runnable_polls,
16408        move || {
16409          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16410          fifo_executor.run_one_runnable()
16411        }
16412      ));
16413    }
16414    for task in fifo_tasks {
16415      futures::executor::block_on(task);
16416    }
16417    assert_eq!(
16418      *fifo_order.lock().unwrap(),
16419      ["first", "second"],
16420      "the detector-enabled normal fallback must preserve FIFO order"
16421    );
16422
16423    let lifo_ran = Arc::new(AtomicBool::new(false));
16424    let lifo_task_ran = Arc::clone(&lifo_ran);
16425    let (lifo, lifo_task) = async_task::spawn(
16426      async move {
16427        lifo_task_ran.store(true, Ordering::SeqCst);
16428      },
16429      |_| {},
16430    );
16431    executor.metrics.runnable_scheduled();
16432    let lifo_executor = Arc::clone(&executor);
16433    assert!(run_claim_with_deadlock_gate_assertion(
16434      &executor,
16435      &executor.metrics.runnable_polls,
16436      move || {
16437        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
16438        LIFO_SLOT.with(|slot| {
16439          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
16440        });
16441        lifo_executor.run_one_runnable()
16442      }
16443    ));
16444    futures::executor::block_on(lifo_task);
16445    assert!(lifo_ran.load(Ordering::SeqCst));
16446
16447    executor.begin_shutdown();
16448    executor.wait_until_scheduler_idle();
16449  }
16450
16451  #[cfg(not(target_family = "wasm"))]
16452  #[test]
16453  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
16454    let executor =
16455      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
16456
16457    let fifo_ran = Arc::new(AtomicBool::new(false));
16458    let fifo_job_ran = Arc::clone(&fifo_ran);
16459    executor.metrics.blocking_scheduled();
16460    executor
16461      .blocking_queue
16462      .lock()
16463      .unwrap()
16464      .push(QueuedBlocking {
16465        id: BlockingJobId(u64::MAX - 90),
16466        run: Box::new(move || {
16467          fifo_job_ran.store(true, Ordering::SeqCst);
16468        }),
16469      });
16470    let fifo_executor = Arc::clone(&executor);
16471    assert!(run_claim_with_deadlock_gate_assertion(
16472      &executor,
16473      &executor.metrics.blocking_tasks_started,
16474      move || {
16475        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
16476        fifo_executor.run_one_blocking()
16477      }
16478    ));
16479    assert!(fifo_ran.load(Ordering::SeqCst));
16480
16481    let owner = executor.fresh_owner_token();
16482    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
16483    let exact_job = BlockingJobId(u64::MAX - 91);
16484    let exact_ran = Arc::new(AtomicBool::new(false));
16485    let exact_job_ran = Arc::clone(&exact_ran);
16486    executor.metrics.blocking_scheduled();
16487    executor
16488      .blocking_queue
16489      .lock()
16490      .unwrap()
16491      .push(QueuedBlocking {
16492        id: exact_job,
16493        run: Box::new(move || {
16494          exact_job_ran.store(true, Ordering::SeqCst);
16495        }),
16496      });
16497    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
16498    dependency.set_owned(BlockingDependency {
16499      job: exact_job,
16500      owner: Some(owner),
16501    });
16502    let exact_executor = Arc::clone(&executor);
16503    assert!(run_claim_with_deadlock_gate_assertion(
16504      &executor,
16505      &executor.metrics.blocking_tasks_started,
16506      move || {
16507        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
16508        let _owner = BlockingOwnerGuard::enter(Some(owner));
16509        exact_executor.try_owned_blocking_over_cap(&dependency)
16510      }
16511    ));
16512    assert!(exact_ran.load(Ordering::SeqCst));
16513
16514    drop(active_owner);
16515    executor.begin_shutdown();
16516    executor.wait_until_scheduler_idle();
16517  }
16518
16519  #[cfg(not(target_family = "wasm"))]
16520  #[test]
16521  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
16522    // Empty-registry behavior: with nothing registered, `wake_one` must return
16523    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
16524    // the common case, since queue-wakes fire on every schedule while drivers
16525    // park only in rare re-entrant `block_on`). Proxy for "did not take the
16526    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
16527    // anyway, while (control) a wake_one issued with a parker registered must
16528    // take the slow path and block on that held lock until it is released.
16529    use std::sync::mpsc;
16530    use std::time::Duration;
16531
16532    let registry = Arc::new(ParkedDrivers::default());
16533
16534    // Fast path: empty registry -> wake_one completes although the lock is held.
16535    let guard = registry.parked.lock().unwrap();
16536    let (done_tx, done_rx) = mpsc::channel();
16537    let waker_thread = {
16538      let registry = Arc::clone(&registry);
16539      std::thread::spawn(move || {
16540        done_tx.send(registry.wake_one()).unwrap();
16541      })
16542    };
16543    let woke = done_rx
16544      .recv_timeout(Duration::from_secs(5))
16545      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
16546    assert!(!woke, "an empty registry has nobody to wake");
16547    waker_thread.join().unwrap();
16548    drop(guard);
16549
16550    // Control: with a parker registered the SAME held lock must block
16551    // wake_one (slow path). This proves the fast path above genuinely skipped
16552    // the lock rather than wake_one never locking at all.
16553    let parker = Arc::new(DriverParker::default());
16554    registry.register(&parker);
16555    let guard = registry.parked.lock().unwrap();
16556    let (done_tx, done_rx) = mpsc::channel();
16557    let waker_thread = {
16558      let registry = Arc::clone(&registry);
16559      std::thread::spawn(move || {
16560        done_tx.send(registry.wake_one()).unwrap();
16561      })
16562    };
16563    assert!(
16564      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16565      "wake_one with a registered parker must take the slow path (block on the held lock)"
16566    );
16567    drop(guard);
16568    let woke = done_rx
16569      .recv_timeout(Duration::from_secs(5))
16570      .expect("slow-path wake_one must complete once the lock is released");
16571    assert!(
16572      woke,
16573      "the slow path must pop and wake the registered parker"
16574    );
16575    waker_thread.join().unwrap();
16576    // The popped parker received its permit: a park now returns immediately
16577    // instead of sleeping (bounded by the test harness if broken).
16578    parker.park();
16579    assert_eq!(
16580      registry.count.load(Ordering::SeqCst),
16581      0,
16582      "wake_one deregisters what it pops"
16583    );
16584  }
16585
16586  #[cfg(not(target_family = "wasm"))]
16587  #[test]
16588  fn driver_parker_permit_granted_while_running_is_not_lost() {
16589    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
16590    // parked) must be stored as a permit that makes the next `park` return
16591    // immediately; multiple such wakes coalesce into ONE permit.
16592    use std::sync::mpsc;
16593    use std::time::Duration;
16594
16595    let parker = Arc::new(DriverParker::default());
16596    // Two wakes while "running" (nobody parked yet).
16597    parker.unpark();
16598    parker.unpark();
16599
16600    let (tx, rx) = mpsc::channel();
16601    let driver = {
16602      let parker = Arc::clone(&parker);
16603      std::thread::spawn(move || {
16604        parker.park(); // must consume the stored permit without sleeping
16605        tx.send(1).unwrap();
16606        parker.park(); // no permit left: must genuinely sleep
16607        tx.send(2).unwrap();
16608      })
16609    };
16610
16611    assert_eq!(
16612      rx.recv_timeout(Duration::from_secs(5))
16613        .expect("a stored permit must not be lost"),
16614      1
16615    );
16616    assert!(
16617      rx.recv_timeout(Duration::from_millis(300)).is_err(),
16618      "two pre-park wakes must coalesce into one permit (second park must sleep)"
16619    );
16620    parker.unpark();
16621    assert_eq!(
16622      rx.recv_timeout(Duration::from_secs(5))
16623        .expect("unpark must wake a sleeping driver"),
16624      2
16625    );
16626    driver.join().unwrap();
16627  }
16628
16629  #[cfg(not(target_family = "wasm"))]
16630  #[test]
16631  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
16632    use std::sync::mpsc;
16633
16634    let parker = Arc::new(DriverParker::default());
16635    assert!(
16636      Instant::now().checked_add(Duration::MAX).is_none(),
16637      "Duration::MAX must exercise the unrepresentable-deadline path"
16638    );
16639    let (done_tx, done_rx) = mpsc::channel();
16640    let driver = {
16641      let parker = Arc::clone(&parker);
16642      std::thread::spawn(move || {
16643        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
16644      })
16645    };
16646    wait_until("the unbounded timeout driver to sleep", || {
16647      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
16648    });
16649    assert!(
16650      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
16651      "an unrepresentable timeout must not expire immediately"
16652    );
16653
16654    let unparker = {
16655      let parker = Arc::clone(&parker);
16656      std::thread::spawn(move || parker.unpark())
16657    };
16658    assert!(
16659      done_rx
16660        .recv_timeout(Duration::from_secs(1))
16661        .expect("an unrepresentable timeout must remain wakeable"),
16662      "a permit must be reported as a wake"
16663    );
16664    unparker.join().unwrap();
16665    driver.join().unwrap();
16666  }
16667
16668  #[cfg(not(target_family = "wasm"))]
16669  #[test]
16670  fn parked_drivers_wake_one_wakes_exactly_one() {
16671    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
16672    // other stays parked. Targeted wakes must neither wake everyone nor lose
16673    // the wake.
16674    use std::sync::mpsc;
16675    use std::time::Duration;
16676
16677    let registry = Arc::new(ParkedDrivers::default());
16678    let (woke_tx, woke_rx) = mpsc::channel();
16679    let mut drivers = Vec::new();
16680    for index in 0..2 {
16681      let registry = Arc::clone(&registry);
16682      let woke_tx = woke_tx.clone();
16683      drivers.push(std::thread::spawn(move || {
16684        let parker = Arc::new(DriverParker::default());
16685        registry.register(&parker);
16686        parker.park();
16687        registry.deregister(&parker);
16688        woke_tx.send(index).unwrap();
16689      }));
16690    }
16691    wait_until("both drivers registered as parked", || {
16692      registry.count.load(Ordering::SeqCst) == 2
16693    });
16694
16695    assert!(registry.wake_one(), "wake_one must wake a parked driver");
16696    woke_rx
16697      .recv_timeout(Duration::from_secs(5))
16698      .expect("exactly one driver must wake");
16699    assert!(
16700      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
16701      "the second driver must stay parked after a single wake_one"
16702    );
16703    assert_eq!(
16704      registry.count.load(Ordering::SeqCst),
16705      1,
16706      "one driver must remain registered"
16707    );
16708
16709    assert!(
16710      registry.wake_one(),
16711      "a second wake_one must wake the remaining driver"
16712    );
16713    woke_rx
16714      .recv_timeout(Duration::from_secs(5))
16715      .expect("the second driver must wake");
16716    for driver in drivers {
16717      driver.join().unwrap();
16718    }
16719  }
16720
16721  #[cfg(not(target_family = "wasm"))]
16722  #[test]
16723  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
16724    use std::sync::mpsc;
16725
16726    struct ReenterRegistryOnDrop {
16727      registry: Arc<ParkedDrivers>,
16728      dropped: mpsc::Sender<()>,
16729    }
16730
16731    #[expect(
16732      clippy::manual_noop_waker,
16733      reason = "the test observes the waker payload's Drop"
16734    )]
16735    impl std::task::Wake for ReenterRegistryOnDrop {
16736      fn wake(self: Arc<Self>) {}
16737    }
16738
16739    impl Drop for ReenterRegistryOnDrop {
16740      fn drop(&mut self) {
16741        let parker = Arc::new(DriverParker::default());
16742        self.registry.register(&parker);
16743        self.registry.deregister(&parker);
16744        self.dropped.send(()).unwrap();
16745      }
16746    }
16747
16748    let registry = Arc::new(ParkedDrivers::default());
16749    let dependency = Arc::new(TaskDependency::default());
16750    let (dropped_tx, dropped_rx) = mpsc::channel();
16751    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
16752      registry: Arc::clone(&registry),
16753      dropped: dropped_tx,
16754    }));
16755    dependency.register_waiter(&waiter);
16756    drop(waiter);
16757
16758    let parker = Arc::new(DriverParker::default());
16759    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16760    drop(dependency);
16761
16762    let retiring_registry = Arc::clone(&registry);
16763    let retiring_parker = Arc::clone(&parker);
16764    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
16765    dropped_rx
16766      .recv_timeout(Duration::from_secs(1))
16767      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
16768    retiring.join().unwrap();
16769  }
16770
16771  #[cfg(not(target_family = "wasm"))]
16772  #[test]
16773  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
16774    use std::sync::mpsc;
16775
16776    let registry = Arc::new(ParkedDrivers::default());
16777    let owner = BlockingOwnerToken {
16778      executor_id: u64::MAX - 70,
16779      frame: u64::MAX - 71,
16780    };
16781    let dependency = Arc::new(TaskDependency::default());
16782    dependency.set_owned(BlockingDependency {
16783      job: BlockingJobId(u64::MAX - 72),
16784      owner: Some(owner),
16785    });
16786    let parker = Arc::new(DriverParker::default());
16787    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
16788
16789    let (predicate_tx, predicate_rx) = mpsc::channel();
16790    let (release_tx, release_rx) = mpsc::channel();
16791    let selecting_registry = Arc::clone(&registry);
16792    let selector = std::thread::spawn(move || {
16793      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
16794        *slot.borrow_mut() = Some(Box::new(move || {
16795          predicate_tx.send(()).unwrap();
16796          release_rx.recv().unwrap();
16797        }));
16798      });
16799      selecting_registry.wake_one_owner_dependency(owner)
16800    });
16801
16802    predicate_rx
16803      .recv_timeout(Duration::from_secs(2))
16804      .expect("owner selection must reach the dependency predicate");
16805    let registry_was_unlocked = registry.parked.try_lock().is_ok();
16806    release_tx.send(()).unwrap();
16807    assert!(
16808      selector.join().unwrap(),
16809      "the exact-owner parker must still be selected"
16810    );
16811
16812    assert!(
16813      registry_was_unlocked,
16814      "owner dependency predicates must not run while the parked-driver registry is locked"
16815    );
16816    assert!(parker.consume_permit());
16817    assert_eq!(parker.take_owner_handoff(), Some(owner));
16818  }
16819
16820  #[cfg(not(target_family = "wasm"))]
16821  #[test]
16822  fn owner_handoff_skips_newer_untagged_dependency() {
16823    let registry = ParkedDrivers::default();
16824    let owner = BlockingOwnerToken {
16825      executor_id: u64::MAX - 1,
16826      frame: u64::MAX - 2,
16827    };
16828    let owned_dependency = Arc::new(TaskDependency::default());
16829    owned_dependency.set_owned(BlockingDependency {
16830      job: BlockingJobId(u64::MAX - 3),
16831      owner: Some(owner),
16832    });
16833    let untagged_dependency = Arc::new(TaskDependency::default());
16834    untagged_dependency.set_owned(BlockingDependency {
16835      job: BlockingJobId(u64::MAX - 4),
16836      owner: None,
16837    });
16838    let owned = Arc::new(DriverParker::default());
16839    let untagged = Arc::new(DriverParker::default());
16840
16841    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
16842    // Register the unrelated dependency last so an unfiltered LIFO retry
16843    // would consume the exact owner handoff.
16844    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
16845
16846    assert!(registry.wake_one_owner_dependency(owner));
16847    assert!(
16848      owned.consume_permit(),
16849      "the exact owner dependency must receive the retry"
16850    );
16851    assert!(
16852      !untagged.consume_permit(),
16853      "an untagged dependency must not consume an exact owner handoff"
16854    );
16855    registry.deregister(&untagged);
16856  }
16857
16858  #[cfg(not(target_family = "wasm"))]
16859  #[test]
16860  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
16861    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
16862    let owner = executor.fresh_owner_token();
16863    let valid_dependency = Arc::new(TaskDependency::default());
16864    valid_dependency.set_owned(BlockingDependency {
16865      job: BlockingJobId(u64::MAX - 45),
16866      owner: Some(owner),
16867    });
16868    let withdrawn_dependency = Arc::new(TaskDependency::default());
16869    withdrawn_dependency.set_owned(BlockingDependency {
16870      job: BlockingJobId(u64::MAX - 46),
16871      owner: Some(owner),
16872    });
16873    let valid = Arc::new(DriverParker::default());
16874    let withdrawn = Arc::new(DriverParker::default());
16875
16876    executor
16877      .parked_drivers
16878      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
16879    executor.parked_drivers.register_with_role(
16880      &withdrawn,
16881      true,
16882      Some(Arc::clone(&withdrawn_dependency)),
16883    );
16884    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
16885    withdrawn_dependency.clear();
16886
16887    assert_eq!(
16888      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
16889      Some(false),
16890      "a selected dependency withdrawn before retry must forward the owner handoff"
16891    );
16892    assert!(
16893      valid.consume_permit(),
16894      "the next live same-owner dependency must receive the forwarded wake"
16895    );
16896    assert_eq!(
16897      valid.take_owner_handoff(),
16898      Some(owner),
16899      "the forwarded wake must retain the exact owner identity"
16900    );
16901  }
16902
16903  #[cfg(not(target_family = "wasm"))]
16904  #[test]
16905  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
16906    use std::sync::mpsc;
16907
16908    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
16909    let owner = executor.fresh_owner_token();
16910    let fallback_dependency = Arc::new(TaskDependency::default());
16911    fallback_dependency.set_owned(BlockingDependency {
16912      job: BlockingJobId(u64::MAX - 47),
16913      owner: Some(owner),
16914    });
16915    let exiting_dependency = Arc::new(TaskDependency::default());
16916    exiting_dependency.set_owned(BlockingDependency {
16917      job: BlockingJobId(u64::MAX - 48),
16918      owner: Some(owner),
16919    });
16920    let fallback = Arc::new(DriverParker::default());
16921    let exiting = Arc::new(DriverParker::default());
16922
16923    executor.parked_drivers.register_with_role(
16924      &fallback,
16925      true,
16926      Some(Arc::clone(&fallback_dependency)),
16927    );
16928    executor.parked_drivers.register_with_role(
16929      &exiting,
16930      true,
16931      Some(Arc::clone(&exiting_dependency)),
16932    );
16933
16934    let (selected_tx, selected_rx) = mpsc::channel();
16935    let (release_selection_tx, release_selection_rx) = mpsc::channel();
16936    let selector_executor = Arc::clone(&executor);
16937    let selector = std::thread::spawn(move || {
16938      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
16939        *slot.borrow_mut() = Some(Box::new(move || {
16940          selected_tx.send(()).unwrap();
16941          release_selection_rx.recv().unwrap();
16942        }));
16943      });
16944      assert!(
16945        selector_executor
16946          .parked_drivers
16947          .wake_one_owner_dependency(owner)
16948      );
16949    });
16950    selected_rx
16951      .recv_timeout(Duration::from_secs(2))
16952      .expect("selection must pause after removing the exact-owner parker");
16953
16954    let (exited_tx, exited_rx) = mpsc::channel();
16955    let exiting_executor = Arc::clone(&executor);
16956    let exiting_driver = Arc::clone(&exiting);
16957    let driver = std::thread::spawn(move || {
16958      exiting_driver.park();
16959      exiting_executor.parked_drivers.deregister(&exiting_driver);
16960      exiting_executor.forward_owner_handoff(&exiting_driver);
16961      exited_tx.send(()).unwrap();
16962    });
16963    exiting.unpark();
16964    exited_rx
16965      .recv_timeout(Duration::from_secs(2))
16966      .expect("the direct future wake must let the selected driver exit");
16967
16968    release_selection_tx.send(()).unwrap();
16969    selector.join().unwrap();
16970    driver.join().unwrap();
16971
16972    assert!(
16973      fallback.consume_permit(),
16974      "an exiting selected driver must forward the owner handoff to the next dependency"
16975    );
16976    assert_eq!(
16977      fallback.take_owner_handoff(),
16978      Some(owner),
16979      "the forwarded handoff must retain the exact owner identity"
16980    );
16981  }
16982
16983  #[cfg(not(target_family = "wasm"))]
16984  #[test]
16985  fn cooperative_panic_forwards_pending_owner_handoff() {
16986    use std::sync::mpsc;
16987
16988    struct PanicAfterWake {
16989      dependency: BlockingDependency,
16990      first_poll: Option<mpsc::Sender<()>>,
16991    }
16992
16993    impl Future for PanicAfterWake {
16994      type Output = ();
16995
16996      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
16997        if let Some(first_poll) = self.first_poll.take() {
16998          // This shape never retires its publication -- the driver panics
16999          // instead -- so the returned token is dropped straight away.
17000          let mut published = PublishedBlockingDependencies::default();
17001          record_blocking_dependency(&mut published, self.dependency);
17002          first_poll.send(()).unwrap();
17003          return Poll::Pending;
17004        }
17005        panic!("intentional cooperative driver panic after owner handoff");
17006      }
17007    }
17008
17009    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
17010    let owner = executor.fresh_owner_token();
17011    let fallback_dependency = Arc::new(TaskDependency::default());
17012    fallback_dependency.set_owned(BlockingDependency {
17013      job: BlockingJobId(u64::MAX - 49),
17014      owner: Some(owner),
17015    });
17016    let fallback = Arc::new(DriverParker::default());
17017    executor.parked_drivers.register_with_role(
17018      &fallback,
17019      true,
17020      Some(Arc::clone(&fallback_dependency)),
17021    );
17022
17023    let (first_poll_tx, first_poll_rx) = mpsc::channel();
17024    let (panicked_tx, panicked_rx) = mpsc::channel();
17025    let driver_executor = Arc::clone(&executor);
17026    let driver = std::thread::spawn(move || {
17027      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17028      let mut future = std::pin::pin!(PanicAfterWake {
17029        dependency: BlockingDependency {
17030          job: BlockingJobId(u64::MAX - 50),
17031          owner: Some(owner)
17032        },
17033        first_poll: Some(first_poll_tx),
17034      });
17035      let panicked = catch_unwind(AssertUnwindSafe(|| {
17036        driver_executor.block_on(future.as_mut());
17037      }))
17038      .is_err();
17039      panicked_tx.send(panicked).unwrap();
17040    });
17041
17042    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17043    wait_until("the panicking cooperative driver to park", || {
17044      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17045    });
17046    assert!(
17047      executor.parked_drivers.wake_one_owner_dependency(owner),
17048      "the exact owner handoff must select the cooperative driver"
17049    );
17050    assert!(
17051      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17052      "the test future must unwind from its second poll"
17053    );
17054    driver.join().unwrap();
17055
17056    assert!(
17057      fallback.consume_permit(),
17058      "panic cleanup must forward the unconsumed owner handoff"
17059    );
17060    assert_eq!(
17061      fallback.take_owner_handoff(),
17062      Some(owner),
17063      "panic cleanup must preserve the exact owner identity"
17064    );
17065  }
17066
17067  #[cfg(not(target_family = "wasm"))]
17068  #[test]
17069  fn cooperative_panic_flushes_lifo_slot() {
17070    struct ScheduleThenPanic {
17071      executor: Arc<MultiThreadExecutor>,
17072      ran: Arc<AtomicBool>,
17073    }
17074
17075    impl Future for ScheduleThenPanic {
17076      type Output = ();
17077
17078      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17079        let ran = Arc::clone(&self.ran);
17080        let scheduler = Arc::clone(&self.executor);
17081        let (runnable, task) = async_task::spawn(
17082          async move {
17083            ran.store(true, Ordering::SeqCst);
17084          },
17085          move |runnable| scheduler.schedule(runnable),
17086        );
17087        self.executor.schedule(runnable);
17088        task.detach();
17089        panic!("intentional cooperative driver panic with a populated LIFO slot");
17090      }
17091    }
17092
17093    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
17094    executor
17095      .active_drainers
17096      .store(executor.max_drainers, Ordering::SeqCst);
17097    let ran = Arc::new(AtomicBool::new(false));
17098    let panicked = {
17099      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17100      let mut future = std::pin::pin!(ScheduleThenPanic {
17101        executor: Arc::clone(&executor),
17102        ran: Arc::clone(&ran),
17103      });
17104      catch_unwind(AssertUnwindSafe(|| {
17105        executor.block_on(future.as_mut());
17106      }))
17107      .is_err()
17108    };
17109
17110    assert!(panicked, "the test future must unwind from its first poll");
17111    assert!(
17112      executor.pop_lifo_slot().is_none(),
17113      "panic cleanup must empty the LIFO slot"
17114    );
17115    assert_eq!(
17116      executor.injector.len(),
17117      1,
17118      "panic cleanup must move the slot runnable to the shared FIFO"
17119    );
17120    assert!(
17121      executor.run_one(),
17122      "the flushed runnable must remain executable"
17123    );
17124    assert!(ran.load(Ordering::SeqCst));
17125    executor.active_drainers.store(0, Ordering::SeqCst);
17126  }
17127
17128  #[cfg(not(target_family = "wasm"))]
17129  #[test]
17130  fn cooperative_panic_compensates_absorbed_queue_wake() {
17131    use std::sync::mpsc;
17132
17133    struct PanicAfterWake {
17134      first_poll: Option<mpsc::Sender<()>>,
17135    }
17136
17137    impl Future for PanicAfterWake {
17138      type Output = ();
17139
17140      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17141        if let Some(first_poll) = self.first_poll.take() {
17142          first_poll.send(()).unwrap();
17143          return Poll::Pending;
17144        }
17145        panic!("intentional cooperative driver panic after a queue wake");
17146      }
17147    }
17148
17149    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
17150    executor
17151      .active_drainers
17152      .store(executor.max_drainers, Ordering::SeqCst);
17153    let fallback = Arc::new(DriverParker::default());
17154    executor.parked_drivers.register(&fallback);
17155    let (first_poll_tx, first_poll_rx) = mpsc::channel();
17156    let (panicked_tx, panicked_rx) = mpsc::channel();
17157    let driver_executor = Arc::clone(&executor);
17158    let driver = std::thread::spawn(move || {
17159      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
17160      let mut future = std::pin::pin!(PanicAfterWake {
17161        first_poll: Some(first_poll_tx)
17162      });
17163      let panicked = catch_unwind(AssertUnwindSafe(|| {
17164        driver_executor.block_on(future.as_mut());
17165      }))
17166      .is_err();
17167      panicked_tx.send(panicked).unwrap();
17168    });
17169
17170    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
17171    wait_until(
17172      "the panicking cooperative driver to park after the fallback",
17173      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
17174    );
17175
17176    let ran = Arc::new(AtomicBool::new(false));
17177    let ran_task = Arc::clone(&ran);
17178    let scheduler = Arc::clone(&executor);
17179    let (runnable, task) = async_task::spawn(
17180      async move {
17181        ran_task.store(true, Ordering::SeqCst);
17182      },
17183      move |runnable| scheduler.schedule(runnable),
17184    );
17185    task.detach();
17186    executor.metrics.runnable_scheduled();
17187    executor.push_fifo_raw(runnable);
17188    assert!(
17189      executor.parked_drivers.wake_one(),
17190      "the queued runnable's wake must be absorbed by the cooperative driver"
17191    );
17192
17193    assert!(
17194      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
17195      "the test future must unwind after absorbing the queue wake"
17196    );
17197    driver.join().unwrap();
17198    assert!(
17199      fallback.consume_permit(),
17200      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
17201    );
17202    assert!(
17203      executor.run_one(),
17204      "the queued runnable must remain executable"
17205    );
17206    assert!(ran.load(Ordering::SeqCst));
17207    executor.active_drainers.store(0, Ordering::SeqCst);
17208  }
17209
17210  #[cfg(not(target_family = "wasm"))]
17211  #[test]
17212  fn multi_thread_future_wake_targets_its_own_parked_driver() {
17213    // Future-wake targeting invariant: a future-wake must reach the driver that
17214    // AWAITS that future, not "some parked driver". D1 parks first awaiting
17215    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
17216    // though D2 parked more recently -- a future-wake misrouted through the
17217    // registry (which pops most-recently-parked) would wake D2, leave D1
17218    // parked forever, and trip the bounded timeout. The old notify_all
17219    // broadcast never distinguished drivers, so this pins the new obligation.
17220    use std::sync::mpsc;
17221    use std::time::Duration;
17222
17223    let (done_tx, done_rx) = mpsc::channel();
17224    let runner = std::thread::spawn(move || {
17225      let metrics = Arc::new(RuntimeMetrics::default());
17226      let options = RuntimeOptions {
17227        flavor: RuntimeFlavor::MultiThread,
17228        worker_threads: 2,
17229        max_blocking_tasks: 2,
17230        thread_name_prefix: "wake-target".to_string(),
17231        park_deadline: None,
17232        drain_linger: DEFAULT_DRAIN_LINGER,
17233      };
17234      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17235
17236      let (tx1, rx1) = oneshot::channel::<usize>();
17237      let (tx2, rx2) = oneshot::channel::<usize>();
17238      let t1_done = Arc::new(AtomicBool::new(false));
17239      let t2_done = Arc::new(AtomicBool::new(false));
17240
17241      // T1: parks FIRST, awaiting rx1.
17242      let t1_exec = Arc::clone(&executor);
17243      let t1_flag = Arc::clone(&t1_done);
17244      let t1_body = async move {
17245        let mut inner = std::pin::pin!(async move {
17246          assert_eq!(rx1.await.unwrap(), 7usize);
17247        });
17248        t1_exec.block_on(inner.as_mut());
17249        t1_flag.store(true, Ordering::SeqCst);
17250      };
17251      let sched1 = Arc::clone(&executor);
17252      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
17253      executor.schedule(runnable1);
17254      wait_until("driver 1 parked", || {
17255        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17256      });
17257
17258      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
17259      // `schedule`, so no wake_one can pop the already-parked D1) and a
17260      // drainer is spawned for it on the free second worker.
17261      let t2_exec = Arc::clone(&executor);
17262      let t2_flag = Arc::clone(&t2_done);
17263      let t2_body = async move {
17264        let mut inner = std::pin::pin!(async move {
17265          assert_eq!(rx2.await.unwrap(), 9usize);
17266        });
17267        t2_exec.block_on(inner.as_mut());
17268        t2_flag.store(true, Ordering::SeqCst);
17269      };
17270      let sched2 = Arc::clone(&executor);
17271      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
17272      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17273      executor.push_fifo_raw(runnable2);
17274      executor.ensure_drainer();
17275      wait_until("driver 2 parked", || {
17276        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
17277      });
17278
17279      // Complete D1's future. The wake must land on D1 (first-parked), the
17280      // driver that owns rx1's waker -- not on the more recently parked D2.
17281      tx1.send(7usize).unwrap();
17282      wait_until("T1 completed via its own driver's wake", || {
17283        t1_done.load(Ordering::SeqCst)
17284      });
17285      futures::executor::block_on(task1);
17286      assert!(
17287        !t2_done.load(Ordering::SeqCst),
17288        "D2 must still be parked: its future was never completed"
17289      );
17290      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
17291      // linger): its linger parker joins the registry alongside D2 for the
17292      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
17293      // in the window between the cooperative parker's wake-deregistration
17294      // and the drain frame's linger registration, then 2 while lingering,
17295      // then 1 again after expiry -- so a `count == 1` wait can return
17296      // during the transient window and prove nothing about the linger.
17297      // Observe the lifecycle itself instead, via the test counters that
17298      // `drain` maintains: the linger parker must REGISTER, and its idle
17299      // budget must EXPIRE (`drain_linger_expiries` is incremented only
17300      // after the expiry path deregistered the parker, and no further work
17301      // exists to start another episode), after which the registry has
17302      // deterministically settled: EXACTLY the untargeted driver D2 remains
17303      // (a misrouted wake would have left D1 cooperatively parked instead,
17304      // already excluded by the `t1_done` wait above, and a linger parker
17305      // stranded in the registry would read 2 here).
17306      wait_until(
17307        "the retiring drain frame registered its linger parker",
17308        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
17309      );
17310      wait_until("the lingering drainer's idle budget expired", || {
17311        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
17312      });
17313      assert_eq!(
17314        executor.parked_drivers.count.load(Ordering::SeqCst),
17315        1,
17316        "exactly the untargeted driver must remain parked after the linger expired"
17317      );
17318      assert!(
17319        !t2_done.load(Ordering::SeqCst),
17320        "D2 must still be parked after the lingering drainer expired"
17321      );
17322
17323      // Teardown: release D2 as well.
17324      tx2.send(9usize).unwrap();
17325      futures::executor::block_on(task2);
17326      assert!(t2_done.load(Ordering::SeqCst));
17327      let _ = done_tx.send(());
17328    });
17329
17330    match done_rx.recv_timeout(Duration::from_secs(10)) {
17331      Ok(()) => runner.join().unwrap(),
17332      Err(error) => panic!(
17333        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
17334      ),
17335    }
17336  }
17337
17338  #[cfg(not(target_family = "wasm"))]
17339  #[test]
17340  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
17341    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
17342    // with queue work remaining must hand the wake on because it may have
17343    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
17344    // is parked as a cooperative driver, a runnable is pushed without a wake,
17345    // and the test thread forces a ready future through the cooperative branch.
17346    // Exit compensation is the ONLY mechanism left that can wake the parked
17347    // worker to drain the runnable.
17348    use std::sync::mpsc;
17349    use std::time::Duration;
17350
17351    let (done_tx, done_rx) = mpsc::channel();
17352    let runner = std::thread::spawn(move || {
17353      let metrics = Arc::new(RuntimeMetrics::default());
17354      let options = RuntimeOptions {
17355        flavor: RuntimeFlavor::MultiThread,
17356        worker_threads: 1,
17357        max_blocking_tasks: 1,
17358        thread_name_prefix: "wake-compensate".to_string(),
17359        park_deadline: None,
17360        drain_linger: DEFAULT_DRAIN_LINGER,
17361      };
17362      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
17363
17364      // Park the sole worker as a cooperative driver awaiting a gate.
17365      let (gate_tx, gate_rx) = oneshot::channel::<()>();
17366      let td_exec = Arc::clone(&executor);
17367      let td_body = async move {
17368        let mut inner = std::pin::pin!(async move {
17369          gate_rx.await.unwrap();
17370        });
17371        td_exec.block_on(inner.as_mut());
17372      };
17373      let sched = Arc::clone(&executor);
17374      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
17375      executor.schedule(td_runnable);
17376      wait_until("the sole driver parked", || {
17377        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17378      });
17379
17380      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
17381      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
17382      let (ran_tx, ran_rx) = mpsc::channel::<()>();
17383      let sched_stranded = Arc::clone(&executor);
17384      let (stranded_runnable, stranded_task) = async_task::spawn(
17385        async move {
17386          ran_tx.send(()).unwrap();
17387        },
17388        move |r| sched_stranded.schedule(r),
17389      );
17390      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17391      executor.push_fifo_raw(stranded_runnable);
17392      stranded_task.detach();
17393
17394      // Drive a ready future through the cooperative branch on THIS thread
17395      // (fake on-pool marker, as in the executor-scoping test). Its first
17396      // poll is Ready, so the loop performs no work itself -- only the exit
17397      // compensation can hand the queued runnable to the parked driver.
17398      {
17399        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17400        let mut ready = std::pin::pin!(async {});
17401        executor.block_on(ready.as_mut());
17402      }
17403
17404      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
17405        "exit compensation must wake the parked driver to run the stranded queued runnable",
17406      );
17407
17408      // Teardown: release the parked driver.
17409      gate_tx.send(()).unwrap();
17410      futures::executor::block_on(td_task);
17411      let _ = done_tx.send(());
17412    });
17413
17414    match done_rx.recv_timeout(Duration::from_secs(10)) {
17415      Ok(()) => runner.join().unwrap(),
17416      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
17417    }
17418  }
17419
17420  #[cfg(not(target_family = "wasm"))]
17421  #[test]
17422  fn cooperative_exit_compensates_blocking_only_residue() {
17423    use std::sync::mpsc;
17424
17425    struct Controlled {
17426      ready: Arc<AtomicBool>,
17427      entered: Option<mpsc::Sender<()>>,
17428    }
17429
17430    impl Future for Controlled {
17431      type Output = ();
17432
17433      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
17434        if let Some(entered) = self.entered.take() {
17435          entered.send(()).unwrap();
17436        }
17437        if self.ready.load(Ordering::SeqCst) {
17438          Poll::Ready(())
17439        } else {
17440          Poll::Pending
17441        }
17442      }
17443    }
17444
17445    let options = RuntimeOptions {
17446      flavor: RuntimeFlavor::MultiThread,
17447      worker_threads: 2,
17448      max_blocking_tasks: 1,
17449      thread_name_prefix: "blocking-exit-compensation".to_string(),
17450      park_deadline: None,
17451      drain_linger: DEFAULT_DRAIN_LINGER,
17452    };
17453    let executor =
17454      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17455    let ready = Arc::new(AtomicBool::new(false));
17456    let (entered_tx, entered_rx) = mpsc::channel();
17457    let (returned_tx, returned_rx) = mpsc::channel();
17458    let (release_tx, release_rx) = mpsc::channel();
17459    let task_exec = Arc::clone(&executor);
17460    let task_ready = Arc::clone(&ready);
17461    let scheduler = Arc::clone(&executor);
17462    let (runnable, task) = async_task::spawn(
17463      async move {
17464        let mut future = std::pin::pin!(Controlled {
17465          ready: task_ready,
17466          entered: Some(entered_tx)
17467        });
17468        task_exec.block_on(future.as_mut());
17469        returned_tx.send(()).unwrap();
17470        release_rx.recv().unwrap();
17471      },
17472      move |runnable| scheduler.schedule(runnable),
17473    );
17474    executor.schedule(runnable);
17475    entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
17476
17477    // The cooperative frame must be PARKED (registered) before the timer task
17478    // is scheduled, so that schedule's wake targets the frame instead of
17479    // spawning a second drainer. A second drainer would later LINGER as a
17480    // registered blocking-capable parker, and `wake_one_blocking` prefers the
17481    // most recently parked candidate: on a starved runner (observed on the
17482    // Windows CI) the linger parker absorbs the blocking wake below, the
17483    // drainer services the job, and the frame -- whose exit compensation is
17484    // the subject under test -- sleeps in its timer-bounded park until the
17485    // 120s heap timer, far past any sane recv bound.
17486    wait_until("the cooperative frame parked", || {
17487      executor.parked_drivers.count.load(Ordering::SeqCst) == 1
17488    });
17489
17490    let timer_exec = Arc::clone(&executor);
17491    let timer_scheduler = Arc::clone(&executor);
17492    let (timer_runnable, timer_task) = async_task::spawn(
17493      async move {
17494        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
17495      },
17496      move |runnable| timer_scheduler.schedule(runnable),
17497    );
17498    executor.schedule(timer_runnable);
17499    wait_until("the dedicated timekeeper parked", || {
17500      executor
17501        .timers
17502        .inner
17503        .lock()
17504        .unwrap()
17505        .timekeeper_parker
17506        .is_some()
17507    });
17508    // Re-parked after running the timer runnable: the blocking wake below
17509    // must find the frame registered (the sole candidate), so the frame
17510    // absorbs the wake, observes its ready future, and its EXIT COMPENSATION
17511    // services the blocking-only residue.
17512    wait_until(
17513      "the cooperative frame re-parked in its timer-bounded park",
17514      || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
17515    );
17516
17517    ready.store(true, Ordering::SeqCst);
17518    let (blocking_tx, blocking_rx) = mpsc::channel();
17519    let blocking = executor.schedule_blocking(move || {
17520      blocking_tx.send(()).unwrap();
17521    });
17522    // Generous hang-detector bounds, not latency assertions: starved CI
17523    // runners (msys2 CLANG64) can stall a woken thread for whole seconds.
17524    blocking_rx
17525      .recv_timeout(Duration::from_secs(10))
17526      .expect("blocking-only residue must be serviced by a Rayon worker");
17527    returned_rx
17528      .recv_timeout(Duration::from_secs(10))
17529      .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
17530    release_tx.send(()).unwrap();
17531    futures::executor::block_on(task);
17532    futures::executor::block_on(blocking).unwrap();
17533    executor.shutdown_timers();
17534    futures::executor::block_on(timer_task);
17535  }
17536
17537  #[cfg(not(target_family = "wasm"))]
17538  #[test]
17539  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
17540    let options = RuntimeOptions {
17541      flavor: RuntimeFlavor::MultiThread,
17542      worker_threads: 2,
17543      max_blocking_tasks: 1,
17544      thread_name_prefix: "stopped-exit-compensation".to_string(),
17545      park_deadline: None,
17546      drain_linger: DEFAULT_DRAIN_LINGER,
17547    };
17548    let executor =
17549      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17550    let ran = Arc::new(AtomicBool::new(false));
17551    let blocking_ran = Arc::clone(&ran);
17552
17553    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
17554    // become observable while this job remains queued, matching the original
17555    // begin_shutdown ordering window exactly.
17556    executor
17557      .active_drainers
17558      .store(executor.max_drainers, Ordering::SeqCst);
17559    let blocking = executor.schedule_blocking(move || {
17560      blocking_ran.store(true, Ordering::SeqCst);
17561    });
17562    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
17563
17564    executor.stop.begin_shutdown();
17565    executor.compensate_queued_work_wake(true);
17566    let ran_after_stop = ran.load(Ordering::SeqCst);
17567
17568    executor.active_drainers.store(0, Ordering::SeqCst);
17569    executor.begin_shutdown();
17570    let blocking_result = futures::executor::block_on(blocking);
17571
17572    assert!(
17573      !ran_after_stop,
17574      "exit compensation started queued blocking work after stop"
17575    );
17576    assert!(
17577      blocking_result.is_err(),
17578      "shutdown must cancel the queued blocking job"
17579    );
17580  }
17581
17582  #[cfg(not(target_family = "wasm"))]
17583  #[test]
17584  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
17585    // Targeted-wake stress: N rounds of {producer: publish work,
17586    // wake_one()} racing {consumer: check work, register, re-check, park} --
17587    // the exact schedule-vs-park protocol `schedule` and
17588    // `cooperative_block_on` run. This walks the interleaving at every offset
17589    // the OS scheduler produces; a single lost wakeup parks the consumer
17590    // forever with work pending, and the bounded recv_timeout turns that into
17591    // a loud failure instead of a hung suite. (No loom in this repo; std
17592    // threads.)
17593    use std::sync::mpsc;
17594    use std::time::Duration;
17595
17596    const ROUNDS: usize = 20_000;
17597
17598    let (done_tx, done_rx) = mpsc::channel();
17599    let registry = Arc::new(ParkedDrivers::default());
17600    let work = Arc::new(AtomicBool::new(false));
17601
17602    let consumer = {
17603      let registry = Arc::clone(&registry);
17604      let work = Arc::clone(&work);
17605      std::thread::spawn(move || {
17606        let parker = Arc::new(DriverParker::default());
17607        for _ in 0..ROUNDS {
17608          loop {
17609            if work.swap(false, Ordering::SeqCst) {
17610              break;
17611            }
17612            // The cooperative park protocol: register FIRST, then re-check,
17613            // then park (see `ParkedDrivers::wake_one`).
17614            registry.register(&parker);
17615            if work.load(Ordering::SeqCst) {
17616              registry.deregister(&parker);
17617              continue;
17618            }
17619            parker.park();
17620            registry.deregister(&parker);
17621          }
17622        }
17623        done_tx.send(()).unwrap();
17624      })
17625    };
17626
17627    for _ in 0..ROUNDS {
17628      work.store(true, Ordering::SeqCst);
17629      // The schedule side: publish work, then wake one parked driver. When
17630      // wake_one misses (consumer not registered yet), the consumer's
17631      // post-register re-check must see the work instead.
17632      let _ = registry.wake_one();
17633      while work.load(Ordering::SeqCst) {
17634        std::hint::spin_loop();
17635      }
17636    }
17637
17638    done_rx
17639      .recv_timeout(Duration::from_secs(30))
17640      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
17641    consumer.join().unwrap();
17642  }
17643
17644  #[cfg(not(target_family = "wasm"))]
17645  #[test]
17646  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
17647    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
17648    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
17649    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
17650    let options = RuntimeOptions {
17651      flavor: RuntimeFlavor::MultiThread,
17652      worker_threads: 2,
17653      max_blocking_tasks: 2,
17654      thread_name_prefix: "lifo-order".to_string(),
17655      park_deadline: None,
17656      drain_linger: DEFAULT_DRAIN_LINGER,
17657    };
17658    let executor =
17659      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17660    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17661
17662    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
17663    // could race the manual `run_one` calls below).
17664    let old_order = Arc::clone(&order);
17665    let sched = Arc::clone(&executor);
17666    let (old_runnable, old_task) = async_task::spawn(
17667      async move {
17668        old_order.lock().unwrap().push("old");
17669      },
17670      move |r| sched.schedule(r),
17671    );
17672    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
17673    executor.push_fifo_raw(old_runnable);
17674    old_task.detach();
17675
17676    // Simulate being on this executor's pool worker: the schedule below must
17677    // take the slot path (which never touches the FIFO nor spawns a drainer).
17678    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17679    let new_order = Arc::clone(&order);
17680    let sched = Arc::clone(&executor);
17681    let (new_runnable, new_task) = async_task::spawn(
17682      async move {
17683        new_order.lock().unwrap().push("new");
17684      },
17685      move |r| sched.schedule(r),
17686    );
17687    executor.schedule(new_runnable);
17688    new_task.detach();
17689    assert_eq!(
17690      executor.injector.len(),
17691      1,
17692      "the slot-path schedule must not push to the shared FIFO"
17693    );
17694
17695    assert!(executor.run_one(), "run_one must pop the slot first");
17696    assert!(executor.run_one(), "run_one must then pop the FIFO");
17697    assert!(!executor.run_one(), "no work must remain");
17698    assert_eq!(
17699      *order.lock().unwrap(),
17700      vec!["new", "old"],
17701      "the slot runnable (newest) must run before older FIFO work"
17702    );
17703  }
17704
17705  #[cfg(not(target_family = "wasm"))]
17706  #[test]
17707  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
17708    let options = RuntimeOptions {
17709      flavor: RuntimeFlavor::MultiThread,
17710      worker_threads: 2,
17711      max_blocking_tasks: 1,
17712      thread_name_prefix: "fifo-owner".to_string(),
17713      park_deadline: None,
17714      drain_linger: DEFAULT_DRAIN_LINGER,
17715    };
17716    let executor =
17717      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17718    let observed_owner = Arc::new(Mutex::new(None));
17719    let observed = Arc::clone(&observed_owner);
17720    let (runnable, task) = async_task::spawn(
17721      async move {
17722        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
17723      },
17724      |_| {},
17725    );
17726    task.detach();
17727    executor.metrics.runnable_scheduled();
17728    executor.push_fifo_raw(runnable);
17729    executor.active_drainers.store(1, Ordering::Release);
17730
17731    let owner = executor.fresh_owner_token();
17732    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
17733    Arc::clone(&executor).drain();
17734
17735    assert_eq!(
17736      *observed_owner.lock().unwrap(),
17737      Some(None),
17738      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
17739    );
17740    assert_eq!(
17741      BLOCKING_OWNER.with(std::cell::Cell::get),
17742      Some(owner),
17743      "the ambient blocking owner must be restored after the runnable"
17744    );
17745  }
17746
17747  #[cfg(not(target_family = "wasm"))]
17748  #[test]
17749  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
17750    // Drain integration: with one worker, task P scheduling
17751    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
17752    // jumping ahead of the older queued T1/T2.
17753    use std::sync::mpsc;
17754    use std::time::Duration;
17755
17756    let (done_tx, done_rx) = mpsc::channel();
17757    let runner = std::thread::spawn(move || {
17758      let options = RuntimeOptions {
17759        flavor: RuntimeFlavor::MultiThread,
17760        worker_threads: 1,
17761        max_blocking_tasks: 1,
17762        thread_name_prefix: "lifo-drain".to_string(),
17763        park_deadline: None,
17764        drain_linger: DEFAULT_DRAIN_LINGER,
17765      };
17766      let executor =
17767        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17768      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17769
17770      let p_exec = Arc::clone(&executor);
17771      let p_order = Arc::clone(&order);
17772      let p_body = async move {
17773        p_order.lock().unwrap().push("P");
17774        // Scheduled from the pool worker mid-drain: must land in the slot.
17775        let c_order = Arc::clone(&p_order);
17776        let sched = Arc::clone(&p_exec);
17777        let (c_runnable, c_task) = async_task::spawn(
17778          async move {
17779            c_order.lock().unwrap().push("C");
17780          },
17781          move |r| sched.schedule(r),
17782        );
17783        p_exec.schedule(c_runnable);
17784        c_task.detach();
17785      };
17786
17787      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
17788      // mirroring the `runnable_scheduled` accounting a real schedule does so
17789      // the queued-runnables counter stays balanced.
17790      let mut tasks = Vec::new();
17791      let sched = Arc::clone(&executor);
17792      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
17793      executor.metrics.runnable_scheduled();
17794      executor.push_fifo_raw(p_runnable);
17795      tasks.push(p_task);
17796      for name in ["T1", "T2"] {
17797        let t_order = Arc::clone(&order);
17798        let sched = Arc::clone(&executor);
17799        let (t_runnable, t_task) = async_task::spawn(
17800          async move {
17801            t_order.lock().unwrap().push(name);
17802          },
17803          move |r| sched.schedule(r),
17804        );
17805        executor.metrics.runnable_scheduled();
17806        executor.push_fifo_raw(t_runnable);
17807        tasks.push(t_task);
17808      }
17809      executor.ensure_drainer();
17810
17811      for task in tasks {
17812        futures::executor::block_on(task);
17813      }
17814      wait_until("the detached child C ran", || {
17815        order.lock().unwrap().len() == 4
17816      });
17817      assert_eq!(
17818        *order.lock().unwrap(),
17819        vec!["P", "C", "T1", "T2"],
17820        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
17821      );
17822      let _ = done_tx.send(());
17823    });
17824
17825    match done_rx.recv_timeout(Duration::from_secs(10)) {
17826      Ok(()) => runner.join().unwrap(),
17827      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
17828    }
17829  }
17830
17831  #[cfg(not(target_family = "wasm"))]
17832  #[test]
17833  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
17834    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
17835    // NEWEST takes the slot; the displaced occupant must land on the shared
17836    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
17837    // the displacement wake cannot spawn a real drainer racing the manual
17838    // `run_one` calls.
17839    let options = RuntimeOptions {
17840      flavor: RuntimeFlavor::MultiThread,
17841      worker_threads: 2,
17842      max_blocking_tasks: 2,
17843      thread_name_prefix: "lifo-displace".to_string(),
17844      park_deadline: None,
17845      drain_linger: DEFAULT_DRAIN_LINGER,
17846    };
17847    let executor =
17848      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17849    executor
17850      .active_drainers
17851      .store(executor.max_drainers, Ordering::SeqCst);
17852    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17853
17854    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
17855    for name in ["a", "b"] {
17856      let n_order = Arc::clone(&order);
17857      let sched = Arc::clone(&executor);
17858      let (runnable, task) = async_task::spawn(
17859        async move {
17860          n_order.lock().unwrap().push(name);
17861        },
17862        move |r| sched.schedule(r),
17863      );
17864      executor.schedule(runnable);
17865      task.detach();
17866    }
17867    assert_eq!(
17868      executor.injector.len(),
17869      1,
17870      "the displaced occupant (a) must have been pushed to the shared FIFO"
17871    );
17872
17873    assert!(executor.run_one(), "run_one must pop the slot (b) first");
17874    assert!(
17875      executor.run_one(),
17876      "run_one must then pop the displaced (a) from the FIFO"
17877    );
17878    assert!(!executor.run_one(), "no work must remain");
17879    assert_eq!(
17880      *order.lock().unwrap(),
17881      vec!["b", "a"],
17882      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
17883    );
17884    executor.active_drainers.store(0, Ordering::SeqCst);
17885  }
17886
17887  #[cfg(not(target_family = "wasm"))]
17888  #[test]
17889  fn lifo_slot_is_executor_scoped() {
17890    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
17891    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
17892    // scheduling onto executor B must bypass the slot (B's FIFO); and with
17893    // A's runnable IN the slot, B must neither pop nor displace it.
17894    let options = RuntimeOptions {
17895      flavor: RuntimeFlavor::MultiThread,
17896      worker_threads: 2,
17897      max_blocking_tasks: 2,
17898      thread_name_prefix: "lifo-scope".to_string(),
17899      park_deadline: None,
17900      drain_linger: DEFAULT_DRAIN_LINGER,
17901    };
17902    let exec1 =
17903      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17904    let exec2 =
17905      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
17906    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
17907    exec1
17908      .active_drainers
17909      .store(exec1.max_drainers, Ordering::SeqCst);
17910    exec2
17911      .active_drainers
17912      .store(exec2.max_drainers, Ordering::SeqCst);
17913    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
17914
17915    // As exec1's worker: r1 goes to the slot, tagged exec1.
17916    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
17917    let r1_order = Arc::clone(&order);
17918    let sched = Arc::clone(&exec1);
17919    let (r1, t1) = async_task::spawn(
17920      async move {
17921        r1_order.lock().unwrap().push("one");
17922      },
17923      move |r| sched.schedule(r),
17924    );
17925    exec1.schedule(r1);
17926    t1.detach();
17927    assert!(
17928      exec1.injector.is_empty(),
17929      "r1 must sit in the slot, not exec1's FIFO"
17930    );
17931
17932    {
17933      // Nested: now act as exec2's worker while exec1's runnable occupies the
17934      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
17935      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
17936      let r2_order = Arc::clone(&order);
17937      let sched = Arc::clone(&exec2);
17938      let (r2, t2) = async_task::spawn(
17939        async move {
17940          r2_order.lock().unwrap().push("two");
17941        },
17942        move |r| sched.schedule(r),
17943      );
17944      exec2.schedule(r2);
17945      t2.detach();
17946      assert_eq!(
17947        exec2.injector.len(),
17948        1,
17949        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
17950      );
17951      assert!(
17952        exec2.pop_lifo_slot().is_none(),
17953        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
17954      );
17955      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
17956    }
17957
17958    // Back as exec1's worker: its slot entry is intact and runnable.
17959    assert!(
17960      exec1.run_one(),
17961      "exec1's slot entry must have survived the foreign activity"
17962    );
17963    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
17964    exec1.active_drainers.store(0, Ordering::SeqCst);
17965    exec2.active_drainers.store(0, Ordering::SeqCst);
17966  }
17967
17968  #[cfg(not(target_family = "wasm"))]
17969  #[test]
17970  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
17971    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
17972    // next FROM the pool worker (every link lands in the slot) crosses
17973    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
17974    // flushed to the FIFO before `finish_draining` (so its re-check sees the
17975    // runnable and re-arms a drainer); a missing flush strands the chain at
17976    // the first budget boundary (lost task + queued_runnables leak).
17977    use std::sync::mpsc;
17978    use std::time::Duration;
17979
17980    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
17981
17982    fn spawn_link(
17983      executor: &Arc<MultiThreadExecutor>,
17984      remaining: u64,
17985      counter: Arc<AtomicU64>,
17986      done: mpsc::Sender<()>,
17987    ) {
17988      let body_exec = Arc::clone(executor);
17989      let body = async move {
17990        counter.fetch_add(1, Ordering::SeqCst);
17991        if remaining > 0 {
17992          spawn_link(&body_exec, remaining - 1, counter, done);
17993        } else {
17994          done.send(()).unwrap();
17995        }
17996      };
17997      let sched = Arc::clone(executor);
17998      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
17999      executor.schedule(runnable);
18000      task.detach();
18001    }
18002
18003    let (done_tx, done_rx) = mpsc::channel();
18004    let runner = std::thread::spawn(move || {
18005      let metrics = Arc::new(RuntimeMetrics::default());
18006      let options = RuntimeOptions {
18007        flavor: RuntimeFlavor::MultiThread,
18008        worker_threads: 1,
18009        max_blocking_tasks: 1,
18010        thread_name_prefix: "lifo-budget".to_string(),
18011        park_deadline: None,
18012        drain_linger: DEFAULT_DRAIN_LINGER,
18013      };
18014      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
18015
18016      let counter = Arc::new(AtomicU64::new(0));
18017      let (chain_tx, chain_rx) = mpsc::channel();
18018      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
18019
18020      chain_rx
18021        .recv_timeout(Duration::from_secs(10))
18022        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
18023      assert_eq!(
18024        counter.load(Ordering::SeqCst),
18025        CHAIN,
18026        "every chain link must have run"
18027      );
18028      wait_until("runnable counters settle to zero (no slot leak)", || {
18029        metrics.queued_runnables.load(Ordering::Relaxed) == 0
18030          && metrics.active_runnables.load(Ordering::Relaxed) == 0
18031      });
18032      let _ = done_tx.send(());
18033    });
18034
18035    match done_rx.recv_timeout(Duration::from_secs(15)) {
18036      Ok(()) => runner.join().unwrap(),
18037      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
18038    }
18039  }
18040
18041  #[cfg(not(target_family = "wasm"))]
18042  #[test]
18043  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
18044    // Direct flush primitive test: pin the flush behavior used at drain
18045    // exits, before cooperative parks and by the unwind guard -- a
18046    // slot-resident runnable moves to the shared FIFO with its task still
18047    // completable, never dropped. (The pre-park call is defensive: `run_one`
18048    // pops the slot before any park is reachable, so this primitive is its
18049    // only observable seam.)
18050    let options = RuntimeOptions {
18051      flavor: RuntimeFlavor::MultiThread,
18052      worker_threads: 2,
18053      max_blocking_tasks: 2,
18054      thread_name_prefix: "lifo-flush".to_string(),
18055      park_deadline: None,
18056      drain_linger: DEFAULT_DRAIN_LINGER,
18057    };
18058    let executor =
18059      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18060    executor
18061      .active_drainers
18062      .store(executor.max_drainers, Ordering::SeqCst);
18063    let ran = Arc::new(AtomicBool::new(false));
18064
18065    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18066    let ran_flag = Arc::clone(&ran);
18067    let sched = Arc::clone(&executor);
18068    let (runnable, task) = async_task::spawn(
18069      async move {
18070        ran_flag.store(true, Ordering::SeqCst);
18071      },
18072      move |r| sched.schedule(r),
18073    );
18074    executor.schedule(runnable);
18075    task.detach();
18076    assert!(
18077      executor.injector.is_empty(),
18078      "the runnable must start in the slot"
18079    );
18080
18081    executor.flush_lifo_slot();
18082    assert!(
18083      executor.pop_lifo_slot().is_none(),
18084      "the slot must be empty after a flush"
18085    );
18086    assert_eq!(
18087      executor.injector.len(),
18088      1,
18089      "the flushed runnable must be on the shared FIFO"
18090    );
18091    assert!(executor.run_one(), "the flushed runnable must still run");
18092    assert!(
18093      ran.load(Ordering::SeqCst),
18094      "the flushed task must complete, not be dropped"
18095    );
18096    executor.active_drainers.store(0, Ordering::SeqCst);
18097  }
18098
18099  #[cfg(not(target_family = "wasm"))]
18100  #[test]
18101  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
18102    // Blocking-owner isolation must cover the LIFO slot path: a runnable
18103    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
18104    // borrow the driving owner frame's over-cap privilege; the ambient owner
18105    // is restored afterwards.
18106    let options = RuntimeOptions {
18107      flavor: RuntimeFlavor::MultiThread,
18108      worker_threads: 2,
18109      max_blocking_tasks: 2,
18110      thread_name_prefix: "lifo-owner".to_string(),
18111      park_deadline: None,
18112      drain_linger: DEFAULT_DRAIN_LINGER,
18113    };
18114    let executor =
18115      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18116    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
18117
18118    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18119
18120    // Install the slot entry from RUNNABLE context (no ambient owner), the
18121    // only context that may use the slot because schedules issued under an
18122    // owner frame are diverted to the shared FIFO.
18123    let observed_slot = Arc::clone(&observed);
18124    let sched = Arc::clone(&executor);
18125    let (runnable, task) = async_task::spawn(
18126      async move {
18127        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
18128      },
18129      move |r| sched.schedule(r),
18130    );
18131    executor.schedule(runnable);
18132    task.detach();
18133    assert!(
18134      executor.injector.is_empty(),
18135      "the runnable must sit in the slot"
18136    );
18137
18138    // The POP can still happen while an owner frame is ambient (a cooperative
18139    // `block_on` entered from a blocking closure drives `run_one` with the
18140    // closure's token on the stack); the slot branch must clear it.
18141    let token = executor.fresh_owner_token();
18142    let _owner = BlockingOwnerGuard::enter(Some(token));
18143    assert!(executor.run_one(), "the slot runnable must run");
18144    assert_eq!(
18145      *observed.lock().unwrap(),
18146      Some(None),
18147      "a slot runnable must observe a CLEARED blocking owner"
18148    );
18149    assert_eq!(
18150      BLOCKING_OWNER.with(std::cell::Cell::get),
18151      Some(token),
18152      "the ambient owner frame must be restored after the slot runnable"
18153    );
18154  }
18155
18156  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
18157  // each other on every poll. Scheduled from a pool worker, each wake lands in
18158  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
18159  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
18160  // its partner's stored waker so both complete.
18161  #[cfg(not(target_family = "wasm"))]
18162  struct PingPongShared {
18163    count: AtomicU64,
18164    stop: AtomicBool,
18165    stop_after: u64,
18166    wakers: Mutex<[Option<std::task::Waker>; 2]>,
18167  }
18168
18169  #[cfg(not(target_family = "wasm"))]
18170  struct PingPong {
18171    shared: Arc<PingPongShared>,
18172    me: usize,
18173  }
18174
18175  #[cfg(not(target_family = "wasm"))]
18176  impl Future for PingPong {
18177    type Output = ();
18178    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18179      let shared = &self.shared;
18180      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
18181      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
18182        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
18183        if let Some(partner) = partner {
18184          partner.wake();
18185        }
18186        return Poll::Ready(());
18187      }
18188      let mut wakers = shared.wakers.lock().unwrap();
18189      wakers[self.me] = Some(cx.waker().clone());
18190      if let Some(partner) = wakers[1 - self.me].take() {
18191        drop(wakers);
18192        partner.wake();
18193      }
18194      Poll::Pending
18195    }
18196  }
18197
18198  #[cfg(not(target_family = "wasm"))]
18199  fn spawn_ping_pong_pair(
18200    executor: &Arc<MultiThreadExecutor>,
18201    shared: &Arc<PingPongShared>,
18202  ) -> Vec<Task<()>> {
18203    let mut tasks = Vec::new();
18204    for me in 0..2 {
18205      let sched = Arc::clone(executor);
18206      let (runnable, task) = async_task::spawn(
18207        PingPong {
18208          shared: Arc::clone(shared),
18209          me,
18210        },
18211        move |r| sched.schedule(r),
18212      );
18213      executor.schedule(runnable);
18214      tasks.push(task);
18215    }
18216    tasks
18217  }
18218
18219  #[cfg(not(target_family = "wasm"))]
18220  #[test]
18221  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
18222    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
18223    // a hot wake/await pair holds a worker for at most one budget window
18224    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
18225    // a third task queued behind an unbounded ping-pong pair must still run;
18226    // if slot pops bypassed the budget the drain would never exit and T3
18227    // would starve forever (caught by the bounded timeout).
18228    use std::sync::mpsc;
18229    use std::time::Duration;
18230
18231    let (done_tx, done_rx) = mpsc::channel();
18232    let runner = std::thread::spawn(move || {
18233      let options = RuntimeOptions {
18234        flavor: RuntimeFlavor::MultiThread,
18235        worker_threads: 1,
18236        max_blocking_tasks: 0,
18237        thread_name_prefix: "lifo-streak".to_string(),
18238        park_deadline: None,
18239        drain_linger: DEFAULT_DRAIN_LINGER,
18240      };
18241      let executor =
18242        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18243
18244      let shared = Arc::new(PingPongShared {
18245        count: AtomicU64::new(0),
18246        stop: AtomicBool::new(false),
18247        stop_after: u64::MAX,
18248        wakers: Mutex::new([None, None]),
18249      });
18250      let pair = spawn_ping_pong_pair(&executor, &shared);
18251
18252      // T3: queued on the shared FIFO behind the hot pair.
18253      let (t3_tx, t3_rx) = mpsc::channel::<()>();
18254      let sched = Arc::clone(&executor);
18255      let (t3_runnable, t3_task) = async_task::spawn(
18256        async move {
18257          t3_tx.send(()).unwrap();
18258        },
18259        move |r| sched.schedule(r),
18260      );
18261      executor.schedule(t3_runnable);
18262
18263      t3_rx
18264        .recv_timeout(Duration::from_secs(10))
18265        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
18266      assert!(
18267        shared.count.load(Ordering::SeqCst) >= 32,
18268        "the pair must have been bouncing through the slot before T3 got its turn"
18269      );
18270
18271      // Teardown: stop the pair and release whichever side is waiting.
18272      shared.stop.store(true, Ordering::SeqCst);
18273      let stored: Vec<_> = shared
18274        .wakers
18275        .lock()
18276        .unwrap()
18277        .iter_mut()
18278        .filter_map(Option::take)
18279        .collect();
18280      for waker in stored {
18281        waker.wake();
18282      }
18283      for task in pair {
18284        futures::executor::block_on(task);
18285      }
18286      futures::executor::block_on(t3_task);
18287      let _ = done_tx.send(());
18288    });
18289
18290    match done_rx.recv_timeout(Duration::from_secs(15)) {
18291      Ok(()) => runner.join().unwrap(),
18292      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
18293    }
18294  }
18295
18296  #[cfg(not(target_family = "wasm"))]
18297  #[test]
18298  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
18299    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
18300    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
18301    // pair must not turn that priority into a permanent wedge. The budget
18302    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
18303    // blocking queue, and the queued blocking job completes once the pair
18304    // finishes its (long) run on the SOLE worker.
18305    use std::sync::mpsc;
18306    use std::time::Duration;
18307
18308    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
18309
18310    let (done_tx, done_rx) = mpsc::channel();
18311    let runner = std::thread::spawn(move || {
18312      let options = RuntimeOptions {
18313        flavor: RuntimeFlavor::MultiThread,
18314        worker_threads: 1,
18315        max_blocking_tasks: 1,
18316        thread_name_prefix: "lifo-blocking".to_string(),
18317        park_deadline: None,
18318        drain_linger: DEFAULT_DRAIN_LINGER,
18319      };
18320      let executor =
18321        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18322
18323      let shared = Arc::new(PingPongShared {
18324        count: AtomicU64::new(0),
18325        stop: AtomicBool::new(false),
18326        stop_after: BOUNCES,
18327        wakers: Mutex::new([None, None]),
18328      });
18329      let pair = spawn_ping_pong_pair(&executor, &shared);
18330
18331      // Queued while the pair keeps the slot hot on the only worker.
18332      let blocking = executor.schedule_blocking(|| 11usize);
18333
18334      assert_eq!(
18335        futures::executor::block_on(blocking).unwrap(),
18336        11,
18337        "the queued blocking job must complete despite the hot LIFO pair"
18338      );
18339      for task in pair {
18340        futures::executor::block_on(task);
18341      }
18342      assert!(
18343        shared.count.load(Ordering::SeqCst) >= BOUNCES,
18344        "the pair must have actually run its full bounce quota"
18345      );
18346      let _ = done_tx.send(());
18347    });
18348
18349    match done_rx.recv_timeout(Duration::from_secs(15)) {
18350      Ok(()) => runner.join().unwrap(),
18351      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
18352    }
18353  }
18354
18355  #[cfg(not(target_family = "wasm"))]
18356  #[test]
18357  fn infinite_hot_runnable_yields_to_blocking_fifo() {
18358    use std::sync::mpsc;
18359
18360    struct HotFuture {
18361      stop: Arc<AtomicBool>,
18362    }
18363
18364    impl Future for HotFuture {
18365      type Output = ();
18366
18367      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18368        if self.stop.load(Ordering::SeqCst) {
18369          Poll::Ready(())
18370        } else {
18371          cx.waker().wake_by_ref();
18372          Poll::Pending
18373        }
18374      }
18375    }
18376
18377    let options = RuntimeOptions {
18378      flavor: RuntimeFlavor::MultiThread,
18379      worker_threads: 2,
18380      max_blocking_tasks: 1,
18381      thread_name_prefix: "blocking-fairness".to_string(),
18382      park_deadline: None,
18383      drain_linger: DEFAULT_DRAIN_LINGER,
18384    };
18385    let executor =
18386      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18387    let stop = Arc::new(AtomicBool::new(false));
18388    let scheduler = Arc::clone(&executor);
18389    let (runnable, task) = async_task::spawn(
18390      HotFuture {
18391        stop: Arc::clone(&stop),
18392      },
18393      move |r| scheduler.schedule(r),
18394    );
18395    task.detach();
18396    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
18397    executor
18398      .blocking_queue
18399      .lock()
18400      .unwrap()
18401      .push(QueuedBlocking {
18402        id: BlockingJobId(u64::MAX),
18403        run: Box::new(move || blocking_tx.send(()).unwrap()),
18404      });
18405
18406    let _driver = OnPoolWorkerGuard::enter(executor.id);
18407    executor.schedule(runnable);
18408    let mut runnable_streak = 0;
18409    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
18410      assert!(executor.run_one_fair(&mut runnable_streak));
18411    }
18412    blocking_rx
18413      .try_recv()
18414      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
18415
18416    stop.store(true, Ordering::SeqCst);
18417    assert!(executor.run_one_fair(&mut runnable_streak));
18418  }
18419
18420  #[cfg(not(target_family = "wasm"))]
18421  #[test]
18422  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
18423    use std::sync::mpsc;
18424
18425    struct HotFuture {
18426      stop: Arc<AtomicBool>,
18427    }
18428
18429    impl Future for HotFuture {
18430      type Output = ();
18431
18432      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
18433        if self.stop.load(Ordering::SeqCst) {
18434          Poll::Ready(())
18435        } else {
18436          cx.waker().wake_by_ref();
18437          Poll::Pending
18438        }
18439      }
18440    }
18441
18442    let options = RuntimeOptions {
18443      flavor: RuntimeFlavor::MultiThread,
18444      worker_threads: 1,
18445      max_blocking_tasks: 1,
18446      thread_name_prefix: "exact-dependency-fairness".to_string(),
18447      park_deadline: None,
18448      drain_linger: DEFAULT_DRAIN_LINGER,
18449    };
18450    let executor =
18451      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18452    let stop = Arc::new(AtomicBool::new(false));
18453    let (queued_tx, queued_rx) = mpsc::channel();
18454    let (exact_tx, exact_rx) = mpsc::channel();
18455    let owner_executor = Arc::clone(&executor);
18456    let owner_stop = Arc::clone(&stop);
18457    let owner = executor.schedule_blocking(move || {
18458      let scheduler = Arc::clone(&owner_executor);
18459      let hot_stop = Arc::clone(&owner_stop);
18460      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
18461        scheduler.schedule(runnable);
18462      });
18463      owner_executor.schedule(runnable);
18464
18465      let exact_stop = Arc::clone(&owner_stop);
18466      let exact = owner_executor.schedule_blocking(move || {
18467        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
18468        41usize
18469      });
18470      queued_tx.send(()).unwrap();
18471
18472      let mut value = None;
18473      {
18474        let mut future = std::pin::pin!(async {
18475          value = Some(exact.await.unwrap());
18476          hot_task.await;
18477        });
18478        assert_eq!(
18479          owner_executor.block_on(future.as_mut()),
18480          BlockOnOutcome::Completed
18481        );
18482      }
18483      value.unwrap()
18484    });
18485
18486    queued_rx
18487      .recv_timeout(Duration::from_secs(2))
18488      .expect("the exact dependency must be queued behind the saturated owner");
18489    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
18490    stop.store(true, Ordering::SeqCst);
18491    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
18492    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
18493    assert_eq!(
18494      exact_before_stop,
18495      Some(true),
18496      "a hot local runnable must yield to the exact dependency before it is stopped"
18497    );
18498  }
18499
18500  #[cfg(not(target_family = "wasm"))]
18501  #[test]
18502  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
18503    use std::sync::mpsc;
18504
18505    struct BlockingPoll {
18506      started: Option<mpsc::Sender<()>>,
18507      released: Arc<(Mutex<bool>, Condvar)>,
18508    }
18509
18510    impl Future for BlockingPoll {
18511      type Output = ();
18512
18513      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18514        if let Some(started) = self.started.take() {
18515          started.send(()).unwrap();
18516        }
18517        let (released, changed) = &*self.released;
18518        let mut released = released
18519          .lock()
18520          .unwrap_or_else(std::sync::PoisonError::into_inner);
18521        while !*released {
18522          released = changed
18523            .wait(released)
18524            .unwrap_or_else(std::sync::PoisonError::into_inner);
18525        }
18526        Poll::Ready(())
18527      }
18528    }
18529
18530    let options = RuntimeOptions {
18531      flavor: RuntimeFlavor::MultiThread,
18532      worker_threads: 3,
18533      max_blocking_tasks: 2,
18534      thread_name_prefix: "exact-before-last-slot".to_string(),
18535      park_deadline: None,
18536      drain_linger: DEFAULT_DRAIN_LINGER,
18537    };
18538    let executor =
18539      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18540
18541    let released = Arc::new((Mutex::new(false), Condvar::new()));
18542    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
18543    let mut blockers = Vec::new();
18544    for _ in 0..2 {
18545      let scheduler = Arc::clone(&executor);
18546      let blocker_released = Arc::clone(&released);
18547      let started = blocker_started_tx.clone();
18548      let (runnable, task) = async_task::spawn(
18549        BlockingPoll {
18550          started: Some(started),
18551          released: blocker_released,
18552        },
18553        move |runnable| scheduler.schedule(runnable),
18554      );
18555      executor.schedule(runnable);
18556      blockers.push(task);
18557    }
18558    drop(blocker_started_tx);
18559    for _ in 0..2 {
18560      blocker_started_rx
18561        .recv_timeout(Duration::from_secs(2))
18562        .expect("two workers must be gated before the owner starts");
18563    }
18564
18565    let dependency_started = Arc::new(AtomicBool::new(false));
18566    let owner_executor = Arc::clone(&executor);
18567    let owner_dependency_started = Arc::clone(&dependency_started);
18568    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
18569    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
18570    let owner_signal = Arc::clone(&dependency_signal);
18571    let owner = executor.schedule_blocking(move || {
18572      let unrelated_signal = Arc::clone(&owner_signal);
18573      let unrelated_started = Arc::clone(&owner_dependency_started);
18574      let unrelated = owner_executor.schedule_blocking(move || {
18575        let observed = unrelated_started.load(Ordering::SeqCst);
18576        unrelated_observed_tx.send(observed).unwrap();
18577        let (ready, changed) = &*unrelated_signal;
18578        let mut ready = ready
18579          .lock()
18580          .unwrap_or_else(std::sync::PoisonError::into_inner);
18581        while !*ready {
18582          ready = changed
18583            .wait(ready)
18584            .unwrap_or_else(std::sync::PoisonError::into_inner);
18585        }
18586      });
18587
18588      let exact_signal = Arc::clone(&owner_signal);
18589      let exact_started = Arc::clone(&owner_dependency_started);
18590      let exact = owner_executor.schedule_blocking(move || {
18591        exact_started.store(true, Ordering::SeqCst);
18592        let (ready, changed) = &*exact_signal;
18593        *ready
18594          .lock()
18595          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18596        changed.notify_all();
18597        41usize
18598      });
18599
18600      let mut output = None;
18601      {
18602        let mut future = std::pin::pin!(async {
18603          output = Some(exact.await.unwrap());
18604          unrelated.await.unwrap();
18605        });
18606        assert_eq!(
18607          owner_executor.block_on(future.as_mut()),
18608          BlockOnOutcome::Completed
18609        );
18610      }
18611      output.unwrap()
18612    });
18613
18614    let (owner_done_tx, owner_done_rx) = mpsc::channel();
18615    let owner_waiter = std::thread::spawn(move || {
18616      owner_done_tx
18617        .send(futures::executor::block_on(owner))
18618        .unwrap();
18619    });
18620    let (completed_while_gated, owner_result) =
18621      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
18622        Ok(result) => (true, result),
18623        Err(_) => {
18624          // Let a broken implementation recover so the regression fails
18625          // deterministically instead of leaving blocked Rayon workers behind.
18626          let (released, changed) = &*released;
18627          *released
18628            .lock()
18629            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18630          changed.notify_all();
18631          (
18632            false,
18633            owner_done_rx
18634              .recv_timeout(Duration::from_secs(2))
18635              .expect("releasing the other workers must let the broken ordering recover"),
18636          )
18637        }
18638      };
18639    let unrelated_observed = unrelated_observed_rx
18640      .recv_timeout(Duration::from_secs(2))
18641      .expect("the unrelated FIFO job must eventually run");
18642
18643    {
18644      let (released, changed) = &*released;
18645      *released
18646        .lock()
18647        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
18648      changed.notify_all();
18649    }
18650    owner_waiter.join().unwrap();
18651    for blocker in blockers {
18652      futures::executor::block_on(blocker);
18653    }
18654    executor.begin_shutdown();
18655    executor.wait_until_scheduler_idle();
18656
18657    assert!(
18658      completed_while_gated,
18659      "the exact dependency must complete while unrelated workers remain gated"
18660    );
18661    assert_eq!(owner_result.unwrap(), 41);
18662    assert!(
18663      unrelated_observed,
18664      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
18665    );
18666  }
18667
18668  #[cfg(not(target_family = "wasm"))]
18669  #[test]
18670  fn blocking_saturation_preserves_a_runnable_execution_lane() {
18671    use std::{sync::mpsc, time::Duration};
18672
18673    let options = RuntimeOptions {
18674      flavor: RuntimeFlavor::MultiThread,
18675      worker_threads: 2,
18676      max_blocking_tasks: 2,
18677      thread_name_prefix: "blocking-reserve".to_string(),
18678      park_deadline: None,
18679      drain_linger: DEFAULT_DRAIN_LINGER,
18680    }
18681    .validate()
18682    .expect("production options must reserve a runnable lane");
18683    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
18684    let executor =
18685      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18686    let (release_tx, release_rx) = mpsc::channel();
18687    let (started_tx, started_rx) = mpsc::channel();
18688    let first = executor.schedule_blocking(move || {
18689      started_tx.send(()).unwrap();
18690      release_rx.recv().unwrap();
18691    });
18692    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
18693
18694    let (queued_tx, queued_rx) = mpsc::channel();
18695    let second = executor.schedule_blocking(move || {
18696      queued_tx.send(()).unwrap();
18697    });
18698    assert!(
18699      queued_rx.try_recv().is_err(),
18700      "the second blocking job must remain queued while the sole blocking slot is occupied"
18701    );
18702
18703    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
18704    let scheduler = Arc::clone(&executor);
18705    let (runnable, task) = async_task::spawn(
18706      async move {
18707        runnable_tx.send(()).unwrap();
18708      },
18709      move |r| scheduler.schedule(r),
18710    );
18711    task.detach();
18712    executor.schedule(runnable);
18713    runnable_rx
18714      .recv_timeout(Duration::from_secs(1))
18715      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
18716
18717    release_tx.send(()).unwrap();
18718    futures::executor::block_on(first).unwrap();
18719    futures::executor::block_on(second).unwrap();
18720    queued_rx
18721      .recv_timeout(Duration::from_secs(1))
18722      .expect("queued blocking work must run after the occupied slot is released");
18723  }
18724
18725  #[cfg(not(target_family = "wasm"))]
18726  #[test]
18727  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
18728    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
18729    // schedule same-executor work, which lands in THIS worker's LIFO slot --
18730    // no FIFO push, no wake. The Ready exit of
18731    // `cooperative_block_on` must flush the slot: the caller may wait on that
18732    // child synchronously (and the enclosing drain frame can be arbitrarily
18733    // far away -- here it does not exist at all, via the fake on-pool
18734    // marker), so a stranded slot entry is an invisible, dead task.
18735    use std::sync::mpsc;
18736    use std::time::Duration;
18737
18738    struct SpawnOnReady {
18739      executor: Arc<MultiThreadExecutor>,
18740      child_tx: Option<mpsc::Sender<u64>>,
18741    }
18742    impl Future for SpawnOnReady {
18743      type Output = ();
18744      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18745        let child_tx = self.child_tx.take().expect("polled once");
18746        let sched = Arc::clone(&self.executor);
18747        let (runnable, task) = async_task::spawn(
18748          async move {
18749            child_tx.send(21).unwrap();
18750          },
18751          move |r| sched.schedule(r),
18752        );
18753        // On-pool + same executor + no ambient owner: lands in the slot.
18754        self.executor.schedule(runnable);
18755        task.detach();
18756        Poll::Ready(())
18757      }
18758    }
18759
18760    let (done_tx, done_rx) = mpsc::channel();
18761    let runner = std::thread::spawn(move || {
18762      let options = RuntimeOptions {
18763        flavor: RuntimeFlavor::MultiThread,
18764        worker_threads: 2,
18765        max_blocking_tasks: 2,
18766        thread_name_prefix: "exit-flush".to_string(),
18767        park_deadline: None,
18768        drain_linger: DEFAULT_DRAIN_LINGER,
18769      };
18770      let executor =
18771        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18772
18773      let (child_tx, child_rx) = mpsc::channel::<u64>();
18774      {
18775        // The test-only on-pool marker runs the cooperative branch on THIS
18776        // thread, with NO enclosing drain loop to bail the slot out.
18777        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
18778        let mut future = std::pin::pin!(SpawnOnReady {
18779          executor: Arc::clone(&executor),
18780          child_tx: Some(child_tx)
18781        });
18782        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
18783      }
18784
18785      // Synchronous bounded wait, NOT via the executor: only the exit flush
18786      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
18787      // complete the child.
18788      let value = child_rx
18789        .recv_timeout(Duration::from_secs(5))
18790        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
18791      assert_eq!(value, 21);
18792      let _ = done_tx.send(());
18793    });
18794
18795    match done_rx.recv_timeout(Duration::from_secs(10)) {
18796      Ok(()) => runner.join().unwrap(),
18797      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
18798    }
18799  }
18800
18801  /// Perpetual same-executor chain: each link schedules its successor from
18802  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
18803  /// raised. `hops` counts executed links.
18804  #[cfg(not(target_family = "wasm"))]
18805  fn spawn_hot_chain(
18806    executor: &Arc<MultiThreadExecutor>,
18807    stop: Arc<AtomicBool>,
18808    hops: Arc<AtomicU64>,
18809  ) {
18810    if stop.load(Ordering::SeqCst) {
18811      return;
18812    }
18813    let link_exec = Arc::clone(executor);
18814    let body = async move {
18815      hops.fetch_add(1, Ordering::SeqCst);
18816      spawn_hot_chain(&link_exec, stop, hops);
18817    };
18818    let sched = Arc::clone(executor);
18819    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
18820    executor.schedule(runnable);
18821    task.detach();
18822  }
18823
18824  #[cfg(not(target_family = "wasm"))]
18825  #[test]
18826  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
18827    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
18828    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
18829    // the FIFO, so a hot chain (every link schedules its successor into the
18830    // slot) keeps run_one succeeding forever and the FIFO is never reached
18831    // from this thread. On the direct 1-worker test topology a FIFO task that
18832    // completes the awaited future then never runs: deadlock. The budgeted
18833    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
18834    // gets its turn.
18835    use std::sync::mpsc;
18836    use std::time::Duration;
18837
18838    let (done_tx, done_rx) = mpsc::channel();
18839    let runner = std::thread::spawn(move || {
18840      let options = RuntimeOptions {
18841        flavor: RuntimeFlavor::MultiThread,
18842        worker_threads: 1,
18843        // This locality test intentionally bypasses RuntimeOptions::validate
18844        // to isolate one physical worker without admitting blocking work.
18845        max_blocking_tasks: 0,
18846        thread_name_prefix: "coop-budget".to_string(),
18847        park_deadline: None,
18848        drain_linger: DEFAULT_DRAIN_LINGER,
18849      };
18850      let executor =
18851        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18852
18853      let stop = Arc::new(AtomicBool::new(false));
18854      let hops = Arc::new(AtomicU64::new(0));
18855
18856      // M runs on the sole worker: it queues F (the task that completes the
18857      // awaited future) on the FIFO, heats the slot with the chain, then
18858      // block_on's the value F produces.
18859      let m_exec = Arc::clone(&executor);
18860      let m_stop = Arc::clone(&stop);
18861      let m_hops = Arc::clone(&hops);
18862      let m_body = async move {
18863        let (tx, rx) = oneshot::channel::<u64>();
18864        // Schedule F first: it takes the slot momentarily...
18865        let sched_f = Arc::clone(&m_exec);
18866        let (f_runnable, f_task) = async_task::spawn(
18867          async move {
18868            let _ = tx.send(7);
18869          },
18870          move |r| sched_f.schedule(r),
18871        );
18872        m_exec.schedule(f_runnable);
18873        f_task.detach();
18874        // ...then the chain starter displaces it to the FIFO and keeps the
18875        // slot hot from here on.
18876        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
18877
18878        let mut inner = std::pin::pin!(async move {
18879          assert_eq!(
18880            rx.await.unwrap(),
18881            7,
18882            "F must run and complete the awaited future"
18883          );
18884        });
18885        m_exec.block_on(inner.as_mut());
18886        m_stop.store(true, Ordering::SeqCst);
18887      };
18888      let sched_m = Arc::clone(&executor);
18889      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
18890      executor.schedule(m_runnable);
18891
18892      futures::executor::block_on(m_task);
18893      assert!(
18894        hops.load(Ordering::SeqCst) >= 64,
18895        "the chain must have been genuinely hot (>= one budget window) before F ran"
18896      );
18897      let _ = done_tx.send(());
18898    });
18899
18900    match done_rx.recv_timeout(Duration::from_secs(10)) {
18901      Ok(()) => runner.join().unwrap(),
18902      Err(error) => panic!(
18903        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
18904      ),
18905    }
18906  }
18907
18908  #[cfg(not(target_family = "wasm"))]
18909  #[test]
18910  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
18911    use std::sync::mpsc;
18912
18913    struct RefillLifo {
18914      executor: Arc<MultiThreadExecutor>,
18915      fifo_ran: Arc<AtomicBool>,
18916    }
18917
18918    impl Future for RefillLifo {
18919      type Output = ();
18920
18921      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
18922        if self.fifo_ran.load(Ordering::SeqCst) {
18923          return Poll::Ready(());
18924        }
18925        let scheduler = Arc::clone(&self.executor);
18926        let (runnable, task) =
18927          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
18928        self.executor.schedule(runnable);
18929        task.detach();
18930        Poll::Pending
18931      }
18932    }
18933
18934    let (done_tx, done_rx) = mpsc::channel();
18935    let runner = std::thread::spawn(move || {
18936      let options = RuntimeOptions {
18937        flavor: RuntimeFlavor::MultiThread,
18938        worker_threads: 1,
18939        max_blocking_tasks: 0,
18940        thread_name_prefix: "forced-fifo".to_string(),
18941        park_deadline: None,
18942        drain_linger: DEFAULT_DRAIN_LINGER,
18943      };
18944      let executor =
18945        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18946      executor
18947        .active_drainers
18948        .store(executor.max_drainers, Ordering::SeqCst);
18949
18950      let fifo_ran = Arc::new(AtomicBool::new(false));
18951      let fifo_flag = Arc::clone(&fifo_ran);
18952      let scheduler = Arc::clone(&executor);
18953      let (fifo, task) = async_task::spawn(
18954        async move {
18955          fifo_flag.store(true, Ordering::SeqCst);
18956        },
18957        move |runnable| scheduler.schedule(runnable),
18958      );
18959      task.detach();
18960      executor.metrics.runnable_scheduled();
18961      executor.push_fifo_raw(fifo);
18962
18963      {
18964        let _driver = OnPoolWorkerGuard::enter(executor.id);
18965        let mut future = std::pin::pin!(RefillLifo {
18966          executor: Arc::clone(&executor),
18967          fifo_ran: Arc::clone(&fifo_ran)
18968        });
18969        executor.block_on(future.as_mut());
18970        while executor.run_one() {}
18971      }
18972      executor.active_drainers.store(0, Ordering::SeqCst);
18973      assert!(fifo_ran.load(Ordering::SeqCst));
18974      done_tx.send(()).unwrap();
18975    });
18976    done_rx
18977      .recv_timeout(Duration::from_secs(3))
18978      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
18979    runner.join().unwrap();
18980  }
18981
18982  #[cfg(not(target_family = "wasm"))]
18983  #[test]
18984  fn blocking_closure_spawn_is_not_stranded_in_slot() {
18985    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
18986    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
18987    // same-executor task and then waits for it synchronously would deadlock if
18988    // the child were slotted because the slot only drains after the closure
18989    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
18990    // push to the shared FIFO with a wake.
18991    use std::sync::mpsc;
18992    use std::time::Duration;
18993
18994    let (done_tx, done_rx) = mpsc::channel();
18995    let runner = std::thread::spawn(move || {
18996      let options = RuntimeOptions {
18997        flavor: RuntimeFlavor::MultiThread,
18998        worker_threads: 2,
18999        max_blocking_tasks: 2,
19000        thread_name_prefix: "blk-spawn-wait".to_string(),
19001        park_deadline: None,
19002        drain_linger: DEFAULT_DRAIN_LINGER,
19003      };
19004      let executor =
19005        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19006
19007      // Closure spawns a child and BLOCKS on the channel the child must fill.
19008      // A spare worker exists; only a real FIFO push + wake can reach it.
19009      let blk_exec = Arc::clone(&executor);
19010      let handle = executor.schedule_blocking(move || {
19011        let (tx, rx) = mpsc::channel::<u64>();
19012        let sched = Arc::clone(&blk_exec);
19013        let (runnable, task) = async_task::spawn(
19014          async move {
19015            tx.send(21).unwrap();
19016          },
19017          move |r| sched.schedule(r),
19018        );
19019        blk_exec.schedule(runnable);
19020        task.detach();
19021        rx.recv_timeout(Duration::from_secs(5))
19022          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
19023      });
19024      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
19025
19026      // Variant: the closure exits WITHOUT waiting; the child must still
19027      // complete promptly (no stranding on the closure-exit path either).
19028      let (v_tx, v_rx) = mpsc::channel::<u64>();
19029      let v_exec = Arc::clone(&executor);
19030      let v_handle = executor.schedule_blocking(move || {
19031        let sched = Arc::clone(&v_exec);
19032        let (runnable, task) = async_task::spawn(
19033          async move {
19034            v_tx.send(42).unwrap();
19035          },
19036          move |r| sched.schedule(r),
19037        );
19038        v_exec.schedule(runnable);
19039        task.detach();
19040        1u64
19041      });
19042      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
19043      assert_eq!(
19044        v_rx
19045          .recv_timeout(Duration::from_secs(5))
19046          .expect("fire-and-forget child must complete"),
19047        42
19048      );
19049      let _ = done_tx.send(());
19050    });
19051
19052    match done_rx.recv_timeout(Duration::from_secs(15)) {
19053      Ok(()) => runner.join().unwrap(),
19054      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
19055    }
19056  }
19057
19058  #[cfg(not(target_family = "wasm"))]
19059  #[test]
19060  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
19061    // Saturated nested-blocking invariant: a blocking closure re-enters
19062    // `block_on` and awaits another `spawn_blocking` while
19063    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
19064    // The driver must not park while still holding its blocking slot, because
19065    // the inner job would then have no capacity to run.
19066    use std::sync::{Barrier, mpsc};
19067    use std::time::Duration;
19068
19069    let (done_tx, done_rx) = mpsc::channel();
19070    let runner = std::thread::spawn(move || {
19071      let metrics = Arc::new(RuntimeMetrics::default());
19072      let options = RuntimeOptions {
19073        flavor: RuntimeFlavor::MultiThread,
19074        worker_threads: 2,
19075        max_blocking_tasks: 2,
19076        thread_name_prefix: "rd1-nested-blk".to_string(),
19077        park_deadline: None,
19078        drain_linger: DEFAULT_DRAIN_LINGER,
19079      };
19080      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
19081
19082      let outer_count = 2usize; // saturates max_blocking_tasks
19083      let barrier = Arc::new(Barrier::new(outer_count));
19084
19085      let mut handles = Vec::new();
19086      for _ in 0..outer_count {
19087        let exec = Arc::clone(&executor);
19088        let barrier = Arc::clone(&barrier);
19089        let handle = executor.schedule_blocking(move || {
19090          // Both outer blocking jobs hold a slot before either nests.
19091          barrier.wait();
19092          let inner_exec = Arc::clone(&exec);
19093          let mut value = 0usize;
19094          {
19095            let mut inner = std::pin::pin!(async {
19096              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
19097            });
19098            exec.block_on(inner.as_mut());
19099          }
19100          value
19101        });
19102        handles.push(handle);
19103      }
19104
19105      for handle in handles {
19106        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
19107      }
19108      let _ = done_tx.send(());
19109    });
19110
19111    match done_rx.recv_timeout(Duration::from_secs(10)) {
19112      Ok(()) => runner.join().unwrap(),
19113      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
19114    }
19115  }
19116
19117  #[cfg(not(target_family = "wasm"))]
19118  #[test]
19119  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
19120    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
19121    let owner = executor.fresh_owner_token();
19122    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19123    let job = BlockingJobId(u64::MAX - 5);
19124    let dependency = TaskDependency::default();
19125    dependency.set_owned(BlockingDependency { job, owner: None });
19126    let ran = Arc::new(AtomicBool::new(false));
19127    let job_ran = Arc::clone(&ran);
19128    executor
19129      .blocking_queue
19130      .lock()
19131      .unwrap()
19132      .push(QueuedBlocking {
19133        id: job,
19134        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
19135      });
19136
19137    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19138    assert!(
19139      !executor.try_owned_blocking_over_cap(&dependency),
19140      "an untagged worker must not infer ancestry from a sole active owner"
19141    );
19142    assert!(!ran.load(Ordering::SeqCst));
19143    assert!(
19144      executor
19145        .blocking_queue
19146        .lock()
19147        .unwrap()
19148        .jobs
19149        .contains_key(&job),
19150      "rejected owner inference must leave the exact job queued"
19151    );
19152  }
19153
19154  #[cfg(not(target_family = "wasm"))]
19155  #[test]
19156  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
19157    use std::sync::{Barrier, mpsc};
19158
19159    let options = RuntimeOptions {
19160      flavor: RuntimeFlavor::MultiThread,
19161      worker_threads: 2,
19162      max_blocking_tasks: 2,
19163      thread_name_prefix: "exact-owner-frame".to_string(),
19164      park_deadline: None,
19165      drain_linger: DEFAULT_DRAIN_LINGER,
19166    };
19167    let executor =
19168      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19169    let owners_saturated = Arc::new(Barrier::new(3));
19170    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
19171    let begin_lending = Arc::new(Barrier::new(3));
19172    let release_dependencies = Arc::new(Barrier::new(3));
19173    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
19174    let mut owners = Vec::new();
19175
19176    for owner_index in 0..2 {
19177      let owner_executor = Arc::clone(&executor);
19178      let owners_saturated = Arc::clone(&owners_saturated);
19179      let dependency_queued_tx = dependency_queued_tx.clone();
19180      let begin_lending = Arc::clone(&begin_lending);
19181      let release_dependencies = Arc::clone(&release_dependencies);
19182      let dependency_started_tx = dependency_started_tx.clone();
19183      owners.push(executor.schedule_blocking(move || {
19184        let owner_thread = std::thread::current().id();
19185        owners_saturated.wait();
19186        let child_started = dependency_started_tx.clone();
19187        let child_release = Arc::clone(&release_dependencies);
19188        let child = owner_executor.schedule_blocking(move || {
19189          assert_eq!(
19190            std::thread::current().id(),
19191            owner_thread,
19192            "an owner frame must execute only its own exact dependency on its lent lane"
19193          );
19194          child_started.send(owner_index).unwrap();
19195          child_release.wait();
19196          owner_index
19197        });
19198        dependency_queued_tx.send(()).unwrap();
19199        begin_lending.wait();
19200
19201        let mut result = None;
19202        {
19203          let mut future = std::pin::pin!(async {
19204            result = Some(child.await.unwrap());
19205          });
19206          assert_eq!(
19207            owner_executor.block_on(future.as_mut()),
19208            BlockOnOutcome::Completed
19209          );
19210        }
19211        result.unwrap()
19212      }));
19213    }
19214    drop(dependency_queued_tx);
19215    drop(dependency_started_tx);
19216    owners_saturated.wait();
19217
19218    for _ in 0..2 {
19219      dependency_queued_rx
19220        .recv_timeout(Duration::from_secs(2))
19221        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
19222    }
19223    let (unrelated_tx, unrelated_rx) = mpsc::channel();
19224    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
19225    begin_lending.wait();
19226
19227    let mut started = Vec::new();
19228    for _ in 0..2 {
19229      started.push(
19230        dependency_started_rx
19231          .recv_timeout(Duration::from_secs(2))
19232          .expect("each owner must service its own exact dependency"),
19233      );
19234    }
19235    started.sort_unstable();
19236    assert_eq!(started, vec![0, 1]);
19237    assert!(
19238      unrelated_rx.try_recv().is_err(),
19239      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
19240    );
19241
19242    release_dependencies.wait();
19243    for (owner_index, owner) in owners.into_iter().enumerate() {
19244      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
19245    }
19246    unrelated_rx
19247      .recv_timeout(Duration::from_secs(2))
19248      .expect("the unrelated job must run after an owner releases a counted lane");
19249    futures::executor::block_on(unrelated).unwrap();
19250  }
19251
19252  #[cfg(not(target_family = "wasm"))]
19253  #[test]
19254  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
19255    use std::sync::{Mutex, mpsc};
19256
19257    const DEPENDENCY_COUNT: usize = 4;
19258
19259    let options = RuntimeOptions {
19260      flavor: RuntimeFlavor::MultiThread,
19261      worker_threads: 3,
19262      max_blocking_tasks: 1,
19263      thread_name_prefix: "parked-unrelated-worker".to_string(),
19264      park_deadline: None,
19265      drain_linger: DEFAULT_DRAIN_LINGER,
19266    };
19267    let executor =
19268      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19269    let (parked_tx, parked_rx) = mpsc::channel();
19270    let (release_parked_tx, release_parked_rx) = mpsc::channel();
19271    executor.pool.spawn(move || {
19272      parked_tx.send(()).unwrap();
19273      release_parked_rx.recv().unwrap();
19274    });
19275    parked_rx
19276      .recv_timeout(Duration::from_secs(2))
19277      .expect("the unrelated Rayon worker must remain occupied");
19278
19279    let owner_executor = Arc::clone(&executor);
19280    let owner = executor.schedule_blocking(move || {
19281      let owner = BLOCKING_OWNER
19282        .with(std::cell::Cell::get)
19283        .expect("the blocking closure must carry its exact owner frame");
19284      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
19285      let results = Arc::new(Mutex::new(Vec::new()));
19286      let descendant_results = Arc::clone(&results);
19287      let descendant_executor = Arc::clone(&owner_executor);
19288      rayon::scope(move |scope| {
19289        scope.spawn(move |_| {
19290          assert_eq!(
19291            BLOCKING_OWNER.with(std::cell::Cell::get),
19292            None,
19293            "the parked owner must force its descendant onto the remaining worker"
19294          );
19295          let _owner = BlockingOwnerGuard::enter(Some(owner));
19296          descendant_started_tx.send(()).unwrap();
19297          for value in 0..DEPENDENCY_COUNT {
19298            let handle = descendant_executor.schedule_blocking(move || value);
19299            let mut result = None;
19300            {
19301              let mut future = std::pin::pin!(async {
19302                result = Some(handle.await.unwrap());
19303              });
19304              assert_eq!(
19305                descendant_executor.block_on(future.as_mut()),
19306                BlockOnOutcome::Completed
19307              );
19308            }
19309            descendant_results.lock().unwrap().push(result.unwrap());
19310          }
19311        });
19312        descendant_started_rx
19313          .recv_timeout(Duration::from_secs(2))
19314          .expect("the nested descendant must start on the only unoccupied worker");
19315      });
19316      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19317    });
19318
19319    assert_eq!(
19320      futures::executor::block_on(owner).unwrap(),
19321      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
19322    );
19323
19324    let (idle_tx, idle_rx) = mpsc::channel();
19325    let idle_executor = Arc::clone(&executor);
19326    let idle_waiter = std::thread::spawn(move || {
19327      idle_executor.begin_shutdown();
19328      idle_executor.wait_until_scheduler_idle();
19329      idle_tx.send(()).unwrap();
19330    });
19331    idle_rx
19332      .recv_timeout(Duration::from_secs(2))
19333      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
19334
19335    release_parked_tx.send(()).unwrap();
19336    idle_waiter.join().unwrap();
19337  }
19338
19339  #[cfg(not(target_family = "wasm"))]
19340  #[test]
19341  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
19342    use std::sync::{Condvar, Mutex, mpsc};
19343
19344    let options = RuntimeOptions {
19345      flavor: RuntimeFlavor::MultiThread,
19346      worker_threads: 3,
19347      max_blocking_tasks: 1,
19348      thread_name_prefix: "same-owner-rearm".to_string(),
19349      park_deadline: None,
19350      drain_linger: DEFAULT_DRAIN_LINGER,
19351    };
19352    let executor =
19353      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19354    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
19355    let (job_started_tx, job_started_rx) = mpsc::channel();
19356    let owner_executor = Arc::clone(&executor);
19357    let owner_releases = Arc::clone(&releases);
19358    let owner = executor.schedule_blocking(move || {
19359      let owner = BLOCKING_OWNER
19360        .with(std::cell::Cell::get)
19361        .expect("the blocking closure must carry its exact owner frame");
19362      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
19363      let results = Arc::new(Mutex::new(Vec::new()));
19364      let scope_results = Arc::clone(&results);
19365      rayon::scope(move |scope| {
19366        for index in 0..2 {
19367          let descendant_ready_tx = descendant_ready_tx.clone();
19368          let descendant_executor = Arc::clone(&owner_executor);
19369          let descendant_results = Arc::clone(&scope_results);
19370          let job_started_tx = job_started_tx.clone();
19371          let releases = Arc::clone(&owner_releases);
19372          scope.spawn(move |_| {
19373            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
19374            let _owner = BlockingOwnerGuard::enter(Some(owner));
19375            descendant_ready_tx.send(()).unwrap();
19376            let handle = descendant_executor.schedule_blocking(move || {
19377              job_started_tx.send(index).unwrap();
19378              let (released, changed) = &*releases;
19379              let mut state = released.lock().unwrap();
19380              while !state[index] {
19381                state = changed.wait(state).unwrap();
19382              }
19383              index
19384            });
19385            let mut result = None;
19386            {
19387              let mut future = std::pin::pin!(async {
19388                result = Some(handle.await.unwrap());
19389              });
19390              assert_eq!(
19391                descendant_executor.block_on(future.as_mut()),
19392                BlockOnOutcome::Completed
19393              );
19394            }
19395            descendant_results.lock().unwrap().push(result.unwrap());
19396          });
19397        }
19398        drop(descendant_ready_tx);
19399        for _ in 0..2 {
19400          descendant_ready_rx
19401            .recv_timeout(Duration::from_secs(2))
19402            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
19403        }
19404      });
19405      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
19406    });
19407
19408    let first = job_started_rx
19409      .recv_timeout(Duration::from_secs(2))
19410      .expect("one exact dependency must borrow the owner lane");
19411    assert!(
19412      job_started_rx
19413        .recv_timeout(Duration::from_millis(100))
19414        .is_err(),
19415      "one owner frame must not service two dependencies concurrently"
19416    );
19417    let unrelated_parker = Arc::new(DriverParker::default());
19418    executor
19419      .parked_drivers
19420      .register_with_role(&unrelated_parker, true, None);
19421    {
19422      let (released, changed) = &*releases;
19423      released.lock().unwrap()[first] = true;
19424      changed.notify_all();
19425    }
19426
19427    let second = job_started_rx
19428      .recv_timeout(Duration::from_secs(2))
19429      .expect("completion must rearm one parked same-owner dependency");
19430    assert_ne!(second, first);
19431    assert!(
19432      !unrelated_parker.consume_permit(),
19433      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
19434    );
19435    executor.parked_drivers.deregister(&unrelated_parker);
19436    {
19437      let (released, changed) = &*releases;
19438      released.lock().unwrap()[second] = true;
19439      changed.notify_all();
19440    }
19441
19442    let mut results = futures::executor::block_on(owner).unwrap();
19443    results.sort_unstable();
19444    assert_eq!(results, vec![0, 1]);
19445  }
19446
19447  #[cfg(not(target_family = "wasm"))]
19448  #[test]
19449  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
19450    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
19451    let owner = executor.fresh_owner_token();
19452    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19453    let failed_dependency = Arc::new(TaskDependency::default());
19454    failed_dependency.set_owned(BlockingDependency {
19455      job: BlockingJobId(u64::MAX - 21),
19456      owner: Some(owner),
19457    });
19458    let parked_dependency = Arc::new(TaskDependency::default());
19459    parked_dependency.set_owned(BlockingDependency {
19460      job: BlockingJobId(u64::MAX - 22),
19461      owner: Some(owner),
19462    });
19463    let parker = Arc::new(DriverParker::default());
19464
19465    let queue = executor.blocking_queue.lock().unwrap();
19466    let claimant_executor = Arc::clone(&executor);
19467    let claimant_dependency = Arc::clone(&failed_dependency);
19468    let claimant = std::thread::spawn(move || {
19469      let _owner = BlockingOwnerGuard::enter(Some(owner));
19470      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
19471    });
19472    wait_until("the failed claimant to reserve the owner lane", || {
19473      !executor.active_blocking_owners.is_available(owner)
19474    });
19475    executor
19476      .parked_drivers
19477      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
19478    failed_dependency.clear();
19479    drop(queue);
19480
19481    assert!(
19482      !claimant.join().unwrap(),
19483      "the cancelled dependency must not execute"
19484    );
19485    assert!(
19486      parker.consume_permit(),
19487      "releasing a failed exact claim must hand the owner lane to another parked dependency"
19488    );
19489  }
19490
19491  #[cfg(not(target_family = "wasm"))]
19492  #[test]
19493  fn exact_owner_lending_does_not_start_blocking_after_stop() {
19494    use std::sync::mpsc;
19495
19496    let options = RuntimeOptions {
19497      flavor: RuntimeFlavor::MultiThread,
19498      worker_threads: 2,
19499      max_blocking_tasks: 1,
19500      thread_name_prefix: "stopped-exact-owner".to_string(),
19501      park_deadline: None,
19502      drain_linger: DEFAULT_DRAIN_LINGER,
19503    };
19504    let executor =
19505      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19506    let ran = Arc::new(AtomicBool::new(false));
19507    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
19508    let (release_claim_tx, release_claim_rx) = mpsc::channel();
19509    let owner_executor = Arc::clone(&executor);
19510    let child_ran = Arc::clone(&ran);
19511    let owner = executor.schedule_blocking(move || {
19512      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
19513        *slot.borrow_mut() = Some(Box::new(move || {
19514          claim_entered_tx.send(()).unwrap();
19515          release_claim_rx.recv().unwrap();
19516        }));
19517      });
19518      let child = owner_executor.schedule_blocking(move || {
19519        child_ran.store(true, Ordering::SeqCst);
19520      });
19521      let mut future = std::pin::pin!(async move {
19522        let _ = child.await;
19523      });
19524      owner_executor.block_on(future.as_mut())
19525    });
19526
19527    claim_entered_rx
19528      .recv_timeout(Duration::from_secs(2))
19529      .expect("the cooperative driver must pause before the exact-owner queue claim");
19530    assert_eq!(
19531      executor.blocking_queue.lock().unwrap().jobs.len(),
19532      1,
19533      "the exact dependency must remain queued before stop publication"
19534    );
19535
19536    // Pin the reported race: the cooperative driver already passed its loop's
19537    // stop check, then shutdown publishes stop before the exact queue claim.
19538    executor.stop.begin_shutdown();
19539    release_claim_tx.send(()).unwrap();
19540
19541    assert_eq!(
19542      futures::executor::block_on(owner).unwrap(),
19543      BlockOnOutcome::Stopped,
19544      "the cooperative driver must stop instead of executing its queued dependency"
19545    );
19546    assert!(
19547      !ran.load(Ordering::SeqCst),
19548      "exact-owner lending started blocking work after stop"
19549    );
19550    assert_eq!(
19551      executor.blocking_queue.lock().unwrap().jobs.len(),
19552      1,
19553      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
19554    );
19555
19556    executor.begin_shutdown();
19557    executor.wait_until_scheduler_idle();
19558    assert!(executor.blocking_queue.lock().unwrap().is_empty());
19559    assert!(!ran.load(Ordering::SeqCst));
19560  }
19561
19562  #[cfg(not(target_family = "wasm"))]
19563  #[test]
19564  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
19565    const DEPENDENCY_COUNT: usize = 1024;
19566
19567    let options = RuntimeOptions {
19568      flavor: RuntimeFlavor::MultiThread,
19569      worker_threads: 2,
19570      max_blocking_tasks: 1,
19571      thread_name_prefix: "linear-owner-lending".to_string(),
19572      park_deadline: None,
19573      drain_linger: DEFAULT_DRAIN_LINGER,
19574    };
19575    let executor =
19576      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19577    let completed = Arc::new(AtomicUsize::new(0));
19578
19579    for index in 0..DEPENDENCY_COUNT {
19580      let owner = executor.fresh_owner_token();
19581      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19582      let dependency = TaskDependency::new(u64::MAX, Some(owner));
19583      let blocking = BlockingDependency {
19584        job: BlockingJobId(u64::MAX - index as u64),
19585        owner: Some(owner),
19586      };
19587      dependency.set_owned(blocking);
19588      let completed = Arc::clone(&completed);
19589      executor
19590        .blocking_queue
19591        .lock()
19592        .unwrap()
19593        .push(QueuedBlocking {
19594          id: blocking.job,
19595          run: Box::new(move || {
19596            completed.fetch_add(1, Ordering::SeqCst);
19597          }),
19598        });
19599
19600      let _owner = BlockingOwnerGuard::enter(Some(owner));
19601      assert!(executor.try_owned_blocking_over_cap(&dependency));
19602    }
19603
19604    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
19605    assert_eq!(
19606      executor.owner_lending_attempts.load(Ordering::Relaxed),
19607      DEPENDENCY_COUNT,
19608      "targeted lending must perform one O(1) claim attempt per exact dependency"
19609    );
19610    let queue = executor.blocking_queue.lock().unwrap();
19611    assert!(queue.is_empty());
19612    assert_eq!(queue.head, None);
19613    assert_eq!(queue.tail, None);
19614  }
19615
19616  #[cfg(not(target_family = "wasm"))]
19617  #[test]
19618  fn one_dependency_interspersed_owner_publications_stays_linear() {
19619    const PUBLICATION_COUNT: usize = 1024;
19620
19621    let executor = multi_thread_executor(2, None, "linear-publication-lending");
19622    let owner = executor.fresh_owner_token();
19623    let wrong_owner = executor.fresh_owner_token();
19624    let dependency = TaskDependency::new(u64::MAX, Some(owner));
19625    let completed = Arc::new(AtomicUsize::new(0));
19626    for index in 0..PUBLICATION_COUNT {
19627      let cancelled_source = TaskDependency::default();
19628      cancelled_source.set_owned(BlockingDependency {
19629        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
19630        owner: Some(owner),
19631      });
19632      dependency.set_borrowed(
19633        &cancelled_source
19634          .get()
19635          .expect("the source publication must initially be live"),
19636      );
19637      cancelled_source.clear();
19638
19639      dependency.set_owned(BlockingDependency {
19640        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
19641        owner: Some(wrong_owner),
19642      });
19643      let blocking = BlockingDependency {
19644        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
19645        owner: Some(owner),
19646      };
19647      dependency.set_owned(blocking);
19648      let completed = Arc::clone(&completed);
19649      executor
19650        .blocking_queue
19651        .lock()
19652        .unwrap()
19653        .push(QueuedBlocking {
19654          id: blocking.job,
19655          run: Box::new(move || {
19656            completed.fetch_add(1, Ordering::SeqCst);
19657          }),
19658        });
19659    }
19660    dependency.reset_entry_visits();
19661
19662    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19663    let _owner = BlockingOwnerGuard::enter(Some(owner));
19664    for _ in 0..PUBLICATION_COUNT {
19665      assert!(
19666        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19667        "the owner-handoff predicate must find the next exact publication"
19668      );
19669      assert!(
19670        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19671        "the park-availability predicate must find the next serviceable publication"
19672      );
19673      assert!(executor.try_owned_blocking_over_cap(&dependency));
19674    }
19675
19676    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
19677    assert!(
19678      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19679      "wrong-owner publications must not satisfy an exact handoff"
19680    );
19681    assert!(
19682      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19683      "wrong-owner publications must not make the owner's lane appear serviceable"
19684    );
19685    assert!(
19686      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
19687      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
19688      dependency.entry_visits()
19689    );
19690    assert!(executor.blocking_queue.lock().unwrap().is_empty());
19691  }
19692
19693  #[cfg(not(target_family = "wasm"))]
19694  #[test]
19695  fn binding_unowned_dependency_updates_exact_owner_predicate() {
19696    let owner = BlockingOwnerToken {
19697      executor_id: u64::MAX - 200,
19698      frame: u64::MAX - 201,
19699    };
19700    let dependency = TaskDependency::default();
19701    let blocking = BlockingDependency {
19702      job: BlockingJobId(u64::MAX - 202),
19703      owner: None,
19704    };
19705    dependency.set_owned(blocking);
19706
19707    assert!(
19708      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19709      "an unowned publication must not satisfy an exact owner handoff"
19710    );
19711    assert!(
19712      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
19713      "an unowned publication is available to the ambient owner"
19714    );
19715
19716    let selected = dependency
19717      .select_for_owner(owner)
19718      .expect("the owner must bind the unowned publication");
19719    assert_eq!(selected.publication.dependency.owner, Some(owner));
19720    assert!(
19721      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
19722      "binding must immediately publish the entry to exact-owner predicates"
19723    );
19724    assert!(dependency.try_claim(&selected));
19725    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
19726  }
19727
19728  #[cfg(not(target_family = "wasm"))]
19729  #[test]
19730  fn owner_selection_preserves_unowned_and_exact_publication_order() {
19731    let owner = BlockingOwnerToken {
19732      executor_id: u64::MAX - 210,
19733      frame: u64::MAX - 211,
19734    };
19735    let dependency = TaskDependency::default();
19736    let first = BlockingDependency {
19737      job: BlockingJobId(u64::MAX - 212),
19738      owner: None,
19739    };
19740    let second = BlockingDependency {
19741      job: BlockingJobId(u64::MAX - 213),
19742      owner: Some(owner),
19743    };
19744    dependency.set_owned(first);
19745    dependency.set_owned(second);
19746
19747    let selected = dependency
19748      .select_for_owner(owner)
19749      .expect("the first publication must be found");
19750    assert_eq!(
19751      selected.publication.dependency,
19752      BlockingDependency {
19753        owner: Some(owner),
19754        ..first
19755      }
19756    );
19757    assert!(dependency.try_claim(&selected));
19758
19759    let selected = dependency
19760      .select_for_owner(owner)
19761      .expect("the second publication must remain eligible");
19762    assert_eq!(selected.publication.dependency, second);
19763  }
19764
19765  #[cfg(not(target_family = "wasm"))]
19766  #[test]
19767  fn blocking_queue_exact_removal_reclaims_fifo_links() {
19768    const JOB_COUNT: u64 = 4096;
19769
19770    let mut queue = BlockingQueue {
19771      closed: false,
19772      head: None,
19773      tail: None,
19774      jobs: FxHashMap::default(),
19775    };
19776    for id in 1..=JOB_COUNT {
19777      queue.push(QueuedBlocking {
19778        id: BlockingJobId(id),
19779        run: Box::new(|| {}),
19780      });
19781    }
19782    for id in (2..=JOB_COUNT).step_by(2) {
19783      drop(
19784        queue
19785          .remove(BlockingJobId(id))
19786          .expect("even job must remain indexed"),
19787      );
19788    }
19789    for id in (1..=JOB_COUNT).step_by(2) {
19790      drop(
19791        queue
19792          .remove(BlockingJobId(id))
19793          .expect("odd job must remain indexed"),
19794      );
19795    }
19796    assert!(queue.jobs.is_empty());
19797    assert_eq!(queue.head, None);
19798    assert_eq!(queue.tail, None);
19799
19800    let order = Arc::new(Mutex::new(Vec::new()));
19801    for id in 1..=3 {
19802      let order = Arc::clone(&order);
19803      queue.push(QueuedBlocking {
19804        id: BlockingJobId(id),
19805        run: Box::new(move || order.lock().unwrap().push(id)),
19806      });
19807    }
19808    drop(
19809      queue
19810        .remove(BlockingJobId(2))
19811        .expect("middle job must be removable"),
19812    );
19813    queue.pop_front().expect("FIFO head must remain linked")();
19814    queue.pop_front().expect("FIFO tail must remain linked")();
19815    assert!(queue.pop_front().is_none());
19816    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
19817    assert_eq!(queue.head, None);
19818    assert_eq!(queue.tail, None);
19819  }
19820
19821  #[cfg(not(target_family = "wasm"))]
19822  #[test]
19823  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
19824    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
19825
19826    if std::env::var_os(CHILD_ENV).is_some() {
19827      configure(RuntimeOptions {
19828        flavor: RuntimeFlavor::MultiThread,
19829        worker_threads: 2,
19830        max_blocking_tasks: 1,
19831        thread_name_prefix: "dependency-lending-metrics".to_string(),
19832        park_deadline: None,
19833        drain_linger: DEFAULT_DRAIN_LINGER,
19834      })
19835      .unwrap();
19836
19837      let outer = spawn_blocking(|| {
19838        let inner = spawn_blocking(|| 42usize);
19839        block_on(inner).unwrap()
19840      });
19841      assert_eq!(block_on(outer).unwrap(), 42);
19842
19843      // Result delivery wakes the joiner from inside the blocking closure,
19844      // immediately before the scheduler-owned metrics guard retires. Use
19845      // shutdown quiescence before sampling completed/live counters so this
19846      // test does not race that final bookkeeping.
19847      shutdown().unwrap();
19848      let snapshot = metrics();
19849      assert_eq!(snapshot.blocking_tasks_started, 2);
19850      assert_eq!(snapshot.blocking_tasks_completed, 2);
19851      assert_eq!(snapshot.active_blocking_tasks, 0);
19852      assert_eq!(
19853        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
19854        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
19855      );
19856      return;
19857    }
19858
19859    let output = std::process::Command::new(std::env::current_exe().unwrap())
19860      .arg("--exact")
19861      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
19862      .arg("--nocapture")
19863      .env(CHILD_ENV, "1")
19864      .output()
19865      .expect("the dependency-lending metrics subprocess must start");
19866    assert!(
19867      output.status.success(),
19868      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
19869      output.status.code(),
19870      String::from_utf8_lossy(&output.stdout),
19871      String::from_utf8_lossy(&output.stderr)
19872    );
19873  }
19874
19875  #[cfg(not(target_family = "wasm"))]
19876  #[test]
19877  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
19878    use std::sync::mpsc;
19879
19880    let (done_tx, done_rx) = mpsc::channel();
19881    let runner = std::thread::spawn(move || {
19882      let options = RuntimeOptions {
19883        flavor: RuntimeFlavor::MultiThread,
19884        worker_threads: 2,
19885        max_blocking_tasks: 1,
19886        thread_name_prefix: "exact-blocking-job".to_string(),
19887        park_deadline: None,
19888        drain_linger: DEFAULT_DRAIN_LINGER,
19889      };
19890      let executor =
19891        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
19892      let outer_exec = Arc::clone(&executor);
19893      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
19894      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
19895      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
19896      let outer = executor.schedule_blocking(move || {
19897        let sibling = outer_exec.schedule_blocking(move || {
19898          sibling_started_tx.send(()).unwrap();
19899          release_sibling_rx.recv().unwrap();
19900        });
19901        let awaited = outer_exec.schedule_blocking(move || {
19902          awaited_ran_tx.send(()).unwrap();
19903          17usize
19904        });
19905        let mut value = None;
19906        {
19907          let mut future = std::pin::pin!(async {
19908            value = Some(awaited.await.unwrap());
19909          });
19910          outer_exec.block_on(future.as_mut());
19911        }
19912        (sibling, value.unwrap())
19913      });
19914
19915      awaited_ran_rx
19916        .recv_timeout(Duration::from_secs(2))
19917        .expect("the exact awaited job must bypass the earlier detached sibling");
19918      let (sibling, value) = futures::executor::block_on(outer).unwrap();
19919      assert_eq!(value, 17);
19920      sibling_started_rx
19921        .recv_timeout(Duration::from_secs(2))
19922        .expect("the sibling must run after the owner releases its counted slot");
19923      release_sibling_tx.send(()).unwrap();
19924      futures::executor::block_on(sibling).unwrap();
19925      done_tx.send(()).unwrap();
19926    });
19927    done_rx
19928      .recv_timeout(Duration::from_secs(5))
19929      .expect("same-owner sibling selection deadlocked the exact awaited job");
19930    runner.join().unwrap();
19931  }
19932
19933  #[cfg(not(target_family = "wasm"))]
19934  #[test]
19935  fn blocking_dependency_propagates_through_async_join_lineage() {
19936    use std::sync::mpsc;
19937
19938    let (done_tx, done_rx) = mpsc::channel();
19939    let runner = std::thread::spawn(move || {
19940      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
19941      let owner_controller = Arc::clone(&controller);
19942      let (child_tx, child_rx) = mpsc::channel();
19943      let outer = controller
19944        .try_spawn_blocking(move || {
19945          let async_controller = Arc::clone(&owner_controller);
19946          let async_child = owner_controller
19947            .try_spawn(async move {
19948              async_controller
19949                .try_spawn_blocking(move || {
19950                  child_tx.send(()).unwrap();
19951                  23usize
19952                })
19953                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
19954                .await
19955                .unwrap()
19956            })
19957            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
19958          let mut value = None;
19959          {
19960            let mut future = std::pin::pin!(async {
19961              value = Some(async_child.await.unwrap());
19962            });
19963            owner_controller.block_on(future.as_mut());
19964          }
19965          value.unwrap()
19966        })
19967        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
19968
19969      child_rx
19970        .recv_timeout(Duration::from_secs(2))
19971        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
19972      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
19973      controller.shutdown().unwrap();
19974      done_tx.send(()).unwrap();
19975    });
19976    done_rx
19977      .recv_timeout(Duration::from_secs(5))
19978      .expect("blocking dependency lineage deadlocked");
19979    runner.join().unwrap();
19980  }
19981
19982  #[cfg(not(target_family = "wasm"))]
19983  #[test]
19984  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
19985    use std::sync::mpsc;
19986
19987    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
19988    let outer_controller = Arc::clone(&controller);
19989    let (child_queued_tx, child_queued_rx) = mpsc::channel();
19990    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
19991    let (release_outer_tx, release_outer_rx) = oneshot::channel();
19992    let outer = controller
19993      .try_spawn_blocking(move || {
19994        let child_controller = Arc::clone(&outer_controller);
19995        let child = outer_controller
19996          .try_spawn(async move {
19997            let blocking = child_controller
19998              .try_spawn_blocking(move || {
19999                unrelated_ran_tx.send(()).unwrap();
20000                31usize
20001              })
20002              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
20003            child_queued_tx.send(()).unwrap();
20004            blocking.await.unwrap()
20005          })
20006          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
20007        let mut released = false;
20008        {
20009          let mut future = std::pin::pin!(async {
20010            release_outer_rx.await.unwrap();
20011            released = true;
20012          });
20013          outer_controller.block_on(future.as_mut());
20014        }
20015        assert!(released);
20016        child
20017      })
20018      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
20019
20020    child_queued_rx
20021      .recv_timeout(Duration::from_secs(2))
20022      .expect("the cooperative driver must service the unrelated async runnable");
20023    assert!(
20024      unrelated_ran_rx
20025        .recv_timeout(Duration::from_millis(200))
20026        .is_err(),
20027      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
20028    );
20029
20030    release_outer_tx.send(()).unwrap();
20031    let child = futures::executor::block_on(outer).unwrap();
20032    unrelated_ran_rx
20033      .recv_timeout(Duration::from_secs(2))
20034      .expect("the unrelated blocking job must run after the owner releases its counted slot");
20035    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
20036    controller.shutdown().unwrap();
20037  }
20038
20039  #[cfg(not(target_family = "wasm"))]
20040  #[test]
20041  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
20042    let parent = Arc::new(TaskDependency::default());
20043    let _context = TaskDependencyGuard::enter(&parent);
20044    let dependency = BlockingDependency {
20045      job: BlockingJobId(u64::MAX),
20046      owner: None,
20047    };
20048    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20049    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20050      receiver,
20051      dependency,
20052      published: PublishedBlockingDependencies::default(),
20053    });
20054    let waker = futures::task::noop_waker();
20055    let mut cx = Context::from_waker(&waker);
20056
20057    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20058    assert_eq!(parent.get_dependency(), Some(dependency));
20059    drop(handle);
20060    assert_eq!(
20061      parent.get_dependency(),
20062      None,
20063      "an abandoned JoinHandle must clear stale dependency lineage"
20064    );
20065    drop(sender);
20066  }
20067
20068  #[cfg(not(target_family = "wasm"))]
20069  #[test]
20070  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
20071    let owner = BlockingOwnerToken {
20072      executor_id: u64::MAX - 40,
20073      frame: u64::MAX - 41,
20074    };
20075    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20076    let _context = TaskDependencyGuard::enter(&parent);
20077    let dependency = BlockingDependency {
20078      job: BlockingJobId(u64::MAX - 42),
20079      owner: None,
20080    };
20081    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20082    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20083      receiver,
20084      dependency,
20085      published: PublishedBlockingDependencies::default(),
20086    });
20087    let waker = futures::task::noop_waker();
20088    let mut cx = Context::from_waker(&waker);
20089
20090    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20091    assert_eq!(
20092      parent.get_dependency(),
20093      Some(BlockingDependency {
20094        owner: Some(owner),
20095        ..dependency
20096      }),
20097      "the ambient task owner must enrich an untagged direct blocking dependency"
20098    );
20099    drop(handle);
20100    assert_eq!(
20101      parent.get_dependency(),
20102      None,
20103      "detach cleanup must match the stable job id after owner enrichment"
20104    );
20105    drop(sender);
20106  }
20107
20108  #[cfg(not(target_family = "wasm"))]
20109  #[test]
20110  fn one_poll_preserves_every_observed_blocking_dependency() {
20111    let parent = Arc::new(TaskDependency::default());
20112    let _context = TaskDependencyGuard::enter(&parent);
20113    let first = BlockingDependency {
20114      job: BlockingJobId(u64::MAX - 60),
20115      owner: None,
20116    };
20117    let second = BlockingDependency {
20118      job: BlockingJobId(u64::MAX - 61),
20119      owner: None,
20120    };
20121    let (first_sender, first_receiver) =
20122      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20123    let (second_sender, second_receiver) =
20124      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20125    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
20126      receiver: first_receiver,
20127      dependency: first,
20128      published: PublishedBlockingDependencies::default(),
20129    });
20130    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
20131      receiver: second_receiver,
20132      dependency: second,
20133      published: PublishedBlockingDependencies::default(),
20134    });
20135    let waker = futures::task::noop_waker();
20136    let mut cx = Context::from_waker(&waker);
20137
20138    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
20139    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
20140    assert_eq!(
20141      parent.get_dependencies(),
20142      vec![first, second],
20143      "publishing a later dependency in one user poll must not replace an earlier dependency"
20144    );
20145
20146    drop(first_handle);
20147    drop(second_handle);
20148    drop(first_sender);
20149    drop(second_sender);
20150  }
20151
20152  #[cfg(not(target_family = "wasm"))]
20153  #[test]
20154  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
20155    let owner = BlockingOwnerToken {
20156      executor_id: u64::MAX - 900,
20157      frame: u64::MAX - 901,
20158    };
20159    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20160    let _context = TaskDependencyGuard::enter(&parent);
20161    let waker = futures::task::noop_waker();
20162    let mut cx = Context::from_waker(&waker);
20163
20164    let j1 = BlockingDependency {
20165      job: BlockingJobId(u64::MAX - 910),
20166      owner: Some(owner),
20167    };
20168    let j2 = BlockingDependency {
20169      job: BlockingJobId(u64::MAX - 911),
20170      owner: Some(owner),
20171    };
20172
20173    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20174    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20175
20176    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20177      receiver: r1,
20178      dependency: j1,
20179      published: PublishedBlockingDependencies::default(),
20180    });
20181    // TWO Pending polls in one pass. Each publishes, so without coalescing the
20182    // handle leaves two live entries for J1 and can retire only the newer one.
20183    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20184    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20185    let entries_after_double_poll = parent
20186      .state
20187      .lock()
20188      .unwrap_or_else(std::sync::PoisonError::into_inner)
20189      .current
20190      .len();
20191    drop(h1);
20192
20193    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20194      receiver: r2,
20195      dependency: j2,
20196      published: PublishedBlockingDependencies::default(),
20197    });
20198    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20199
20200    assert_eq!(
20201      entries_after_double_poll, 1,
20202      "a repeat Pending poll must supersede the handle's own previous publication"
20203    );
20204    let selected = parent
20205      .select_for_owner(owner)
20206      .expect("a live dependency must be selectable");
20207    assert_eq!(
20208      selected.publication.dependency.job, j2.job,
20209      "a DETACHED job must not be selected for owner lending ahead of the live one"
20210    );
20211    drop((s1, s2));
20212  }
20213
20214  #[cfg(not(target_family = "wasm"))]
20215  #[test]
20216  fn handle_polled_under_a_nested_frame_retires_both_publications() {
20217    let owner = BlockingOwnerToken {
20218      executor_id: u64::MAX - 920,
20219      frame: u64::MAX - 921,
20220    };
20221    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20222    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20223    let waker = futures::task::noop_waker();
20224    let mut cx = Context::from_waker(&waker);
20225
20226    let j1 = BlockingDependency {
20227      job: BlockingJobId(u64::MAX - 930),
20228      owner: Some(owner),
20229    };
20230    let j2 = BlockingDependency {
20231      job: BlockingJobId(u64::MAX - 931),
20232      owner: Some(owner),
20233    };
20234    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20235    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20236    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20237      receiver: r1,
20238      dependency: j1,
20239      published: Default::default(),
20240    });
20241
20242    {
20243      let _outer_ctx = TaskDependencyGuard::enter(&outer);
20244      // A handle can publish into more than one frame: once here, and again
20245      // under a nested re-entrant frame. Detaching must retire BOTH, or the
20246      // forgotten entry keeps a detached job selectable for owner lending.
20247      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20248      {
20249        let _inner_ctx = TaskDependencyGuard::enter(&inner);
20250        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20251      }
20252      drop(h1);
20253      assert!(
20254        outer.get_dependencies().is_empty(),
20255        "detaching must retire the publication made in the outer frame too"
20256      );
20257      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20258        receiver: r2,
20259        dependency: j2,
20260        published: Default::default(),
20261      });
20262      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20263      let selected = outer.select_for_owner(owner).expect("selectable");
20264      assert_eq!(
20265        selected.publication.dependency.job, j2.job,
20266        "a detached job published in an outer frame must not outrank a live one"
20267      );
20268      drop(h2);
20269    }
20270    drop((s1, s2));
20271  }
20272
20273  #[cfg(not(target_family = "wasm"))]
20274  #[test]
20275  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
20276    const FRAME_COUNT: usize = 64;
20277    let waker = futures::task::noop_waker();
20278    let mut cx = Context::from_waker(&waker);
20279    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20280    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20281      receiver,
20282      dependency: BlockingDependency {
20283        job: BlockingJobId(u64::MAX - 940),
20284        owner: None,
20285      },
20286      published: PublishedBlockingDependencies::default(),
20287    });
20288    let mut frames = Vec::new();
20289    for _ in 0..FRAME_COUNT {
20290      let frame = Arc::new(TaskDependency::default());
20291      frames.push(Arc::downgrade(&frame));
20292      let _context = TaskDependencyGuard::enter(&frame);
20293      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20294    }
20295    // A handle carried through a sequence of completed frames must not keep
20296    // any of them alive: it remembers each publication by `Weak`, and prunes
20297    // the expired tokens on its next publication, so the history stays at the
20298    // live frame count instead of growing with every frame ever seen.
20299    let retained = frames
20300      .iter()
20301      .filter(|frame| frame.strong_count() > 0)
20302      .count();
20303    assert_eq!(
20304      retained, 0,
20305      "a completed frame must not be kept alive by a pending handle's publication history"
20306    );
20307    drop(handle);
20308    drop(sender);
20309  }
20310
20311  #[cfg(not(target_family = "wasm"))]
20312  #[test]
20313  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
20314    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
20315    // sequence, so H1 must keep the position its FIRST poll gave it: if
20316    // re-polling appended a replacement at the tail, H2 would be lent first,
20317    // and an H2 that waits on H1 would deadlock the owner's single lane.
20318    let owner = BlockingOwnerToken {
20319      executor_id: u64::MAX - 950,
20320      frame: u64::MAX - 951,
20321    };
20322    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
20323    let _context = TaskDependencyGuard::enter(&parent);
20324    let waker = futures::task::noop_waker();
20325    let mut cx = Context::from_waker(&waker);
20326
20327    let j1 = BlockingDependency {
20328      job: BlockingJobId(u64::MAX - 960),
20329      owner: Some(owner),
20330    };
20331    let j2 = BlockingDependency {
20332      job: BlockingJobId(u64::MAX - 961),
20333      owner: Some(owner),
20334    };
20335    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20336    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20337    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
20338      receiver: r1,
20339      dependency: j1,
20340      published: PublishedBlockingDependencies::default(),
20341    });
20342    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
20343      receiver: r2,
20344      dependency: j2,
20345      published: PublishedBlockingDependencies::default(),
20346    });
20347
20348    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20349    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
20350    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
20351
20352    let selected = parent
20353      .select_for_owner(owner)
20354      .expect("a live dependency must be selectable");
20355    assert_eq!(
20356      selected.publication.dependency.job, j1.job,
20357      "re-polling a handle must not push its dependency behind one published later"
20358    );
20359    drop((h1, h2, s1, s2));
20360  }
20361
20362  #[cfg(not(target_family = "wasm"))]
20363  #[test]
20364  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
20365    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
20366    // `TaskDependency`) but each drains `current` at the start of its poll,
20367    // which is what a real task poll does. A drained frame no longer holds the
20368    // entry, so its token stops standing and is dropped on the next
20369    // publication: the history tracks frames that still hold an entry, not
20370    // every frame the handle has ever been polled under.
20371    const FRAME_COUNT: usize = 256;
20372    let waker = futures::task::noop_waker();
20373    let mut cx = Context::from_waker(&waker);
20374    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20375    let mut handle = JoinHandle(JoinHandleInner::Blocking {
20376      receiver,
20377      dependency: BlockingDependency {
20378        job: BlockingJobId(u64::MAX - 970),
20379        owner: None,
20380      },
20381      published: PublishedBlockingDependencies::default(),
20382    });
20383    let mut retained = Vec::new();
20384    for _ in 0..FRAME_COUNT {
20385      let frame = Arc::new(TaskDependency::default());
20386      retained.push(Arc::clone(&frame));
20387      let _context = TaskDependencyGuard::enter(&frame);
20388      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20389      // The frame's next poll drains what the previous one published.
20390      frame.begin_poll();
20391    }
20392    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
20393      panic!("the handle must still be a blocking handle");
20394    };
20395    let remembered = usize::from(published.first.is_some()) + published.rest.len();
20396    assert!(
20397      remembered <= 2,
20398      "a drained frame must not stay in the publication history, remembered={remembered}"
20399    );
20400    drop(handle);
20401    drop(sender);
20402  }
20403
20404  #[cfg(not(target_family = "wasm"))]
20405  #[test]
20406  fn mass_detach_of_pending_blocking_handles_stays_linear() {
20407    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
20408    // poll pass -- so `begin_poll` never runs to drain `current` and all N
20409    // entries are simultaneously registered and still pending -- and are then
20410    // dropped together. Retiring a handle's own entry is O(1) and
20411    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
20412    //
20413    // This is the shape that made an earlier attempt at this optimisation
20414    // quadratic: it replaced the job index with a scan of `current` on every
20415    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
20416    // the dependency mutex.
20417    const HANDLE_COUNT: usize = 512;
20418
20419    let parent = Arc::new(TaskDependency::default());
20420    let _context = TaskDependencyGuard::enter(&parent);
20421    let waker = futures::task::noop_waker();
20422    let mut cx = Context::from_waker(&waker);
20423
20424    let mut senders = Vec::with_capacity(HANDLE_COUNT);
20425    let mut handles = Vec::with_capacity(HANDLE_COUNT);
20426    for index in 0..HANDLE_COUNT {
20427      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
20428      senders.push(sender);
20429      handles.push(JoinHandle(JoinHandleInner::Blocking {
20430        receiver,
20431        dependency: BlockingDependency {
20432          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
20433          owner: None,
20434        },
20435        published: PublishedBlockingDependencies::default(),
20436      }));
20437    }
20438    for handle in &mut handles {
20439      assert!(Pin::new(handle).poll(&mut cx).is_pending());
20440    }
20441    assert_eq!(
20442      parent
20443        .state
20444        .lock()
20445        .unwrap_or_else(std::sync::PoisonError::into_inner)
20446        .current
20447        .len(),
20448      HANDLE_COUNT,
20449      "every handle must be registered and still pending before the detach"
20450    );
20451
20452    parent.reset_entry_visits();
20453    drop(handles);
20454
20455    assert!(
20456      parent.entry_visits() <= HANDLE_COUNT * 4,
20457      "mass-detach must visit O(n) entries across n handles, visits={}",
20458      parent.entry_visits()
20459    );
20460    assert_eq!(
20461      parent.get_dependency(),
20462      None,
20463      "every detached handle must have retired its own publication"
20464    );
20465    drop(senders);
20466  }
20467
20468  #[cfg(not(target_family = "wasm"))]
20469  #[test]
20470  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
20471    use std::sync::mpsc;
20472
20473    struct ReenterDependencyContext {
20474      dependency: Arc<TaskDependency>,
20475      woke: mpsc::Sender<()>,
20476    }
20477
20478    impl std::task::Wake for ReenterDependencyContext {
20479      fn wake(self: Arc<Self>) {
20480        let _context = TaskDependencyGuard::enter(&self.dependency);
20481        self.woke.send(()).unwrap();
20482      }
20483    }
20484
20485    let parent = Arc::new(TaskDependency::default());
20486    let nested = Arc::new(TaskDependency::default());
20487    let (woke_tx, woke_rx) = mpsc::channel();
20488    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
20489      dependency: Arc::clone(&nested),
20490      woke: woke_tx,
20491    }));
20492    parent.register_waiter(&waiter);
20493    let blocking = BlockingDependency {
20494      job: BlockingJobId(u64::MAX - 62),
20495      owner: None,
20496    };
20497
20498    let _context = TaskDependencyGuard::enter(&parent);
20499    let mut published = PublishedBlockingDependencies::default();
20500    record_blocking_dependency(&mut published, blocking);
20501    woke_rx
20502      .recv_timeout(Duration::from_secs(1))
20503      .expect("dependency notification must permit reentrant TLS context access");
20504
20505    parent.register_waiter(&waiter);
20506    clear_blocking_dependency(published);
20507    woke_rx
20508      .recv_timeout(Duration::from_secs(1))
20509      .expect("dependency clearing must permit reentrant TLS context access");
20510  }
20511
20512  #[cfg(not(target_family = "wasm"))]
20513  #[test]
20514  fn dependency_free_begin_poll_does_not_lock_state() {
20515    use std::sync::mpsc;
20516
20517    let dependency = Arc::new(TaskDependency::default());
20518    let state = dependency.state.lock().unwrap();
20519    let (completed_tx, completed_rx) = mpsc::channel();
20520    let polling_dependency = Arc::clone(&dependency);
20521    let polling = std::thread::spawn(move || {
20522      polling_dependency.begin_poll();
20523      completed_tx.send(()).unwrap();
20524    });
20525
20526    let completed_without_lock = completed_rx
20527      .recv_timeout(Duration::from_millis(200))
20528      .is_ok();
20529    drop(state);
20530    polling.join().unwrap();
20531    assert!(
20532      completed_without_lock,
20533      "a task with no published dependency must skip the dependency-state mutex"
20534    );
20535  }
20536
20537  #[cfg(not(target_family = "wasm"))]
20538  #[test]
20539  fn deep_dependency_liveness_chain_is_iterative() {
20540    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
20541
20542    if std::env::var_os(CHILD_ENV).is_some() {
20543      const DEPTH: usize = 100_000;
20544
20545      let mut links = Vec::with_capacity(DEPTH + 1);
20546      links.push(DependencyLink::root());
20547      for _ in 0..DEPTH {
20548        links.push(DependencyLink::derived(links.last().unwrap()));
20549      }
20550      assert!(links.last().unwrap().is_live());
20551      links[0].cancel();
20552      assert!(!links.last().unwrap().is_live());
20553      return;
20554    }
20555
20556    let output = std::process::Command::new(std::env::current_exe().unwrap())
20557      .arg("--exact")
20558      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
20559      .arg("--nocapture")
20560      .env(CHILD_ENV, "1")
20561      .output()
20562      .expect("the subprocess test must start");
20563    assert!(
20564      output.status.success(),
20565      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
20566      output.status.code(),
20567      String::from_utf8_lossy(&output.stdout),
20568      String::from_utf8_lossy(&output.stderr)
20569    );
20570  }
20571
20572  #[cfg(not(target_family = "wasm"))]
20573  #[test]
20574  fn task_dependency_set_contains_panicking_waiter() {
20575    struct PanicWake;
20576    impl std::task::Wake for PanicWake {
20577      fn wake(self: Arc<Self>) {
20578        panic!("intentional TaskDependency::set waiter panic");
20579      }
20580    }
20581
20582    let dependency = TaskDependency::default();
20583    let blocking = BlockingDependency {
20584      job: BlockingJobId(u64::MAX),
20585      owner: None,
20586    };
20587    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20588
20589    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
20590    assert!(
20591      result.is_ok(),
20592      "set notification must not unwind its caller"
20593    );
20594    assert_eq!(
20595      dependency.get_dependency(),
20596      Some(blocking),
20597      "set must commit before notifying its waiter"
20598    );
20599  }
20600
20601  #[cfg(not(target_family = "wasm"))]
20602  #[test]
20603  fn task_dependency_clear_contains_panicking_waiter() {
20604    struct PanicWake;
20605    impl std::task::Wake for PanicWake {
20606      fn wake(self: Arc<Self>) {
20607        panic!("intentional TaskDependency::clear waiter panic");
20608      }
20609    }
20610
20611    let dependency = TaskDependency::default();
20612    dependency.set_owned(BlockingDependency {
20613      job: BlockingJobId(u64::MAX),
20614      owner: None,
20615    });
20616    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20617
20618    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
20619    assert!(
20620      result.is_ok(),
20621      "clear notification must not unwind its caller"
20622    );
20623    assert_eq!(
20624      dependency.get_dependency(),
20625      None,
20626      "clear must commit before notifying its waiter"
20627    );
20628  }
20629
20630  #[cfg(not(target_family = "wasm"))]
20631  #[test]
20632  fn task_dependency_clear_if_contains_panicking_waiter() {
20633    struct PanicWake;
20634    impl std::task::Wake for PanicWake {
20635      fn wake(self: Arc<Self>) {
20636        panic!("intentional TaskDependency::clear_if waiter panic");
20637      }
20638    }
20639
20640    let dependency = TaskDependency::default();
20641    let blocking = BlockingDependency {
20642      job: BlockingJobId(u64::MAX),
20643      owner: None,
20644    };
20645    let sequence = dependency.set_owned(blocking);
20646    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
20647
20648    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
20649    assert!(
20650      result.is_ok(),
20651      "retire notification must not unwind its caller"
20652    );
20653    assert_eq!(
20654      dependency.get_dependency(),
20655      None,
20656      "retire must commit before notifying its waiter"
20657    );
20658  }
20659
20660  #[cfg(not(target_family = "wasm"))]
20661  #[test]
20662  fn task_dependency_waiter_replacement_contains_panicking_drop() {
20663    use std::sync::mpsc;
20664
20665    const GENERATION: u64 = u64::MAX - 20;
20666
20667    struct PanicOnDropWake {
20668      dropped: mpsc::Sender<Option<u64>>,
20669    }
20670
20671    #[expect(
20672      clippy::manual_noop_waker,
20673      reason = "the test observes the waker payload's Drop"
20674    )]
20675    impl std::task::Wake for PanicOnDropWake {
20676      fn wake(self: Arc<Self>) {}
20677    }
20678
20679    impl Drop for PanicOnDropWake {
20680      fn drop(&mut self) {
20681        self
20682          .dropped
20683          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20684          .unwrap();
20685        panic!("intentional replaced dependency waiter destructor panic");
20686      }
20687    }
20688
20689    let dependency = TaskDependency::new(GENERATION, None);
20690    let (dropped_tx, dropped_rx) = mpsc::channel();
20691    let waiter = Waker::from(Arc::new(PanicOnDropWake {
20692      dropped: dropped_tx,
20693    }));
20694    dependency.register_waiter(&waiter);
20695    drop(waiter);
20696
20697    let replacement = futures::task::noop_waker();
20698    let result = catch_unwind(AssertUnwindSafe(|| {
20699      dependency.register_waiter(&replacement)
20700    }));
20701    assert!(
20702      result.is_ok(),
20703      "replacing a hostile waiter must not unwind registration"
20704    );
20705    assert_eq!(
20706      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20707      Some(GENERATION),
20708      "the replaced waiter must be destroyed under its runtime generation"
20709    );
20710  }
20711
20712  #[cfg(not(target_family = "wasm"))]
20713  #[test]
20714  fn task_dependency_retirement_contains_stored_waiter_drop() {
20715    use std::sync::mpsc;
20716
20717    const GENERATION: u64 = u64::MAX - 21;
20718
20719    struct PanicOnDropWake {
20720      dropped: mpsc::Sender<Option<u64>>,
20721    }
20722
20723    #[expect(
20724      clippy::manual_noop_waker,
20725      reason = "the test observes the waker payload's Drop"
20726    )]
20727    impl std::task::Wake for PanicOnDropWake {
20728      fn wake(self: Arc<Self>) {}
20729    }
20730
20731    impl Drop for PanicOnDropWake {
20732      fn drop(&mut self) {
20733        self
20734          .dropped
20735          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
20736          .unwrap();
20737        panic!("intentional stored dependency waiter destructor panic");
20738      }
20739    }
20740
20741    let dependency = TaskDependency::new(GENERATION, None);
20742    let (dropped_tx, dropped_rx) = mpsc::channel();
20743    let waiter = Waker::from(Arc::new(PanicOnDropWake {
20744      dropped: dropped_tx,
20745    }));
20746    dependency.register_waiter(&waiter);
20747    drop(waiter);
20748
20749    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
20750    assert!(
20751      result.is_ok(),
20752      "retiring a hostile stored waiter must not unwind"
20753    );
20754    assert_eq!(
20755      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
20756      Some(GENERATION),
20757      "the retained waiter must be destroyed under its runtime generation"
20758    );
20759  }
20760
20761  #[cfg(not(target_family = "wasm"))]
20762  #[test]
20763  fn detaching_task_clears_dependency_waiter_parent_retention() {
20764    struct RetainingWake {
20765      retained_parent: Arc<()>,
20766    }
20767
20768    impl std::task::Wake for RetainingWake {
20769      fn wake(self: Arc<Self>) {
20770        let _ = &self.retained_parent;
20771      }
20772    }
20773
20774    let dependency = Arc::new(TaskDependency::default());
20775    let retained_parent = Arc::new(());
20776    let retained_parent_weak = Arc::downgrade(&retained_parent);
20777    let waiter = Waker::from(Arc::new(RetainingWake {
20778      retained_parent: Arc::clone(&retained_parent),
20779    }));
20780    dependency.register_waiter(&waiter);
20781    drop(waiter);
20782    drop(retained_parent);
20783    assert!(
20784      retained_parent_weak.upgrade().is_some(),
20785      "the stored dependency waiter must retain its parent before detachment"
20786    );
20787
20788    let (runnable, task) = async_task::spawn(
20789      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20790      |_| {},
20791    );
20792    drop(JoinHandle(JoinHandleInner::Task {
20793      task: task.fallible(),
20794      awaiter: None,
20795      dependency: Arc::clone(&dependency),
20796    }));
20797    assert!(
20798      retained_parent_weak.upgrade().is_none(),
20799      "detachment must clear the dependency waiter instead of retaining its parent task"
20800    );
20801    drop(runnable);
20802  }
20803
20804  #[cfg(not(target_family = "wasm"))]
20805  #[test]
20806  fn detaching_task_clears_many_parent_publications_linearly() {
20807    const PUBLICATION_COUNT: usize = 1024;
20808
20809    struct CountWake(Arc<AtomicUsize>);
20810
20811    impl std::task::Wake for CountWake {
20812      fn wake(self: Arc<Self>) {
20813        self.0.fetch_add(1, Ordering::SeqCst);
20814      }
20815    }
20816
20817    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
20818      dependency
20819        .state
20820        .lock()
20821        .unwrap_or_else(std::sync::PoisonError::into_inner)
20822        .current
20823        .back()
20824        .expect("the sentinel publication must be appended")
20825        .publication
20826        .clone()
20827    }
20828
20829    let owner = BlockingOwnerToken {
20830      executor_id: u64::MAX - 300,
20831      frame: u64::MAX - 301,
20832    };
20833    let other_owner = BlockingOwnerToken {
20834      executor_id: u64::MAX - 302,
20835      frame: u64::MAX - 303,
20836    };
20837    let child = Arc::new(TaskDependency::default());
20838    for index in 0..PUBLICATION_COUNT {
20839      child.set_owned(BlockingDependency {
20840        job: BlockingJobId(1_000_000 + index as u64),
20841        owner: (index % 2 == 0).then_some(owner),
20842      });
20843    }
20844    let child_publications = child.snapshot_all_live();
20845    let source = child_publications[0].clone();
20846
20847    let parent = Arc::new(TaskDependency::default());
20848    let _context = TaskDependencyGuard::enter(&parent);
20849    let (runnable, task) = async_task::spawn(
20850      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
20851      |_| {},
20852    );
20853    let mut handle = JoinHandle(JoinHandleInner::Task {
20854      task: task.fallible(),
20855      awaiter: None,
20856      dependency: Arc::clone(&child),
20857    });
20858    let waker = futures::task::noop_waker();
20859    let mut cx = Context::from_waker(&waker);
20860    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
20861    assert_eq!(
20862      parent.get_dependencies().len(),
20863      PUBLICATION_COUNT,
20864      "polling the child handle must propagate every current publication"
20865    );
20866
20867    let mut wrong_claim_source = source.clone();
20868    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
20869    parent.set_borrowed(&wrong_claim_source);
20870    let wrong_claim_publication = latest_publication(&parent);
20871
20872    let mut wrong_job_source = source.clone();
20873    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
20874    parent.set_borrowed(&wrong_job_source);
20875    let wrong_job_publication = latest_publication(&parent);
20876
20877    let wrong_immediate_source = source.derived(None);
20878    parent.set_borrowed(&wrong_immediate_source);
20879    let wrong_immediate_publication = latest_publication(&parent);
20880
20881    let wakes = Arc::new(AtomicUsize::new(0));
20882    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
20883    parent.reset_entry_visits();
20884    drop(handle);
20885
20886    assert_eq!(
20887      wakes.load(Ordering::SeqCst),
20888      1,
20889      "one batched parent cleanup must emit one waiter notification"
20890    );
20891    assert!(
20892      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
20893      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
20894      parent.entry_visits()
20895    );
20896    assert!(
20897      wrong_claim_publication.is_live(),
20898      "claim identity must remain part of the match"
20899    );
20900    assert!(
20901      wrong_job_publication.is_live(),
20902      "job identity must remain part of the match"
20903    );
20904    assert!(
20905      wrong_immediate_publication.is_live(),
20906      "only an exact immediate-parent link may be retired"
20907    );
20908
20909    {
20910      let state = parent
20911        .state
20912        .lock()
20913        .unwrap_or_else(std::sync::PoisonError::into_inner);
20914      assert_eq!(state.current.len(), 3);
20915      // The `job_sequences` index is gone -- a blocking handle retires its own
20916      // entry by sequence -- so "the index retains no stale sequence" is
20917      // structurally impossible rather than something to assert. The
20918      // substantive property that assertion protected, that only the three
20919      // NON-matching sentinels survived the clear, is checked directly on the
20920      // authoritative structure.
20921      assert!(
20922        state
20923          .current
20924          .iter()
20925          .all(|entry| entry.publication.is_live()),
20926        "only the three non-matching sentinels may remain, all still live"
20927      );
20928      assert!(state.unowned_sequences.is_empty());
20929      assert_eq!(state.owner_sequences.len(), 1);
20930      assert_eq!(
20931        state.owner_sequences.get(&owner).map(VecDeque::len),
20932        Some(3)
20933      );
20934    }
20935    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
20936    assert!(
20937      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
20938      "cancelled unowned publications must not survive in availability predicates"
20939    );
20940    drop(runnable);
20941  }
20942
20943  #[cfg(not(target_family = "wasm"))]
20944  #[test]
20945  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
20946    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
20947
20948    if std::env::var_os(CHILD_ENV).is_some() {
20949      struct PanicWake;
20950      impl std::task::Wake for PanicWake {
20951        fn wake(self: Arc<Self>) {
20952          panic!("intentional dependency waker panic");
20953        }
20954      }
20955
20956      struct PanicOnDrop;
20957      impl Drop for PanicOnDrop {
20958        fn drop(&mut self) {
20959          panic!("intentional buffered blocking result destructor panic");
20960        }
20961      }
20962
20963      let parent = Arc::new(TaskDependency::default());
20964      let dependency = BlockingDependency {
20965        job: BlockingJobId(u64::MAX - 1),
20966        owner: None,
20967      };
20968      // The handle is built by hand rather than by polling, so hand it the
20969      // publication token a Pending poll would have recorded; detach retires
20970      // the entry by sequence.
20971      let sequence = parent.set_owned(dependency);
20972      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
20973      let _context = TaskDependencyGuard::enter(&parent);
20974      let (sender, receiver) = oneshot::channel();
20975      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
20976        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
20977      ));
20978      drop(JoinHandle(JoinHandleInner::Blocking {
20979        receiver,
20980        dependency,
20981        published: PublishedBlockingDependencies {
20982          first: Some(PublishedBlockingDependency {
20983            state: Arc::downgrade(&parent),
20984            sequence,
20985          }),
20986          rest: Vec::new(),
20987        },
20988      }));
20989      assert_eq!(parent.get_dependency(), None);
20990      return;
20991    }
20992
20993    let output = std::process::Command::new(std::env::current_exe().unwrap())
20994      .arg("--exact")
20995      .arg(
20996        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
20997      )
20998      .arg("--nocapture")
20999      .env(CHILD_ENV, "1")
21000      .output()
21001      .expect("the subprocess test must start");
21002    assert!(
21003      output.status.success(),
21004      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
21005      output.status.code(),
21006      String::from_utf8_lossy(&output.stdout),
21007      String::from_utf8_lossy(&output.stderr)
21008    );
21009  }
21010
21011  #[cfg(not(target_family = "wasm"))]
21012  #[test]
21013  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
21014    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
21015    // that does NOT own a counted blocking slot and re-enters `block_on`
21016    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
21017    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
21018    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
21019    // driver could use the over-cap fallback, the inner jobs would run while
21020    // both holders still occupy slots and push peak active blocking above 2.
21021    // The cap must be honored AND everything must still complete.
21022    use std::sync::{Barrier, mpsc};
21023    use std::time::Duration;
21024
21025    let (done_tx, done_rx) = mpsc::channel();
21026    let peak_out = Arc::new(Mutex::new(None));
21027    let peak_slot = Arc::clone(&peak_out);
21028    let runner = std::thread::spawn(move || {
21029      let metrics = Arc::new(RuntimeMetrics::default());
21030      let options = RuntimeOptions {
21031        flavor: RuntimeFlavor::MultiThread,
21032        worker_threads: 4,
21033        max_blocking_tasks: 2,
21034        thread_name_prefix: "rd1-cap".to_string(),
21035        park_deadline: None,
21036        drain_linger: DEFAULT_DRAIN_LINGER,
21037      };
21038      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21039
21040      let holder_count = 2usize; // saturates max_blocking_tasks
21041      let driver_count = 2usize;
21042
21043      // Holders signal once they hold a counted slot, then wait to be released.
21044      let (held_tx, held_rx) = mpsc::channel();
21045      let release = Arc::new(Barrier::new(holder_count + 1));
21046
21047      let mut holder_handles = Vec::new();
21048      for _ in 0..holder_count {
21049        let held_tx = held_tx.clone();
21050        let release = Arc::clone(&release);
21051        let handle = executor.schedule_blocking(move || {
21052          held_tx.send(()).unwrap();
21053          release.wait();
21054          0usize
21055        });
21056        holder_handles.push(handle);
21057      }
21058      // Wait until both holders hold their slots (cap fully saturated).
21059      for _ in 0..holder_count {
21060        held_rx.recv().unwrap();
21061      }
21062
21063      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
21064      // an inner spawn_blocking.
21065      let mut driver_tasks = Vec::new();
21066      for _ in 0..driver_count {
21067        let exec = Arc::clone(&executor);
21068        let body = async move {
21069          let inner_exec = Arc::clone(&exec);
21070          let mut inner = std::pin::pin!(async move {
21071            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21072            assert_eq!(v, 5);
21073          });
21074          exec.block_on(inner.as_mut());
21075        };
21076        let scheduler = Arc::clone(&executor);
21077        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
21078        executor.schedule(runnable);
21079        driver_tasks.push(task);
21080      }
21081
21082      // Let the driver tasks reach block_on. They must park until a holder
21083      // frees a slot instead of running their inner jobs over the cap.
21084      std::thread::sleep(Duration::from_millis(300));
21085
21086      // Release the holders so the inner jobs can run within the cap.
21087      release.wait();
21088
21089      for task in driver_tasks {
21090        futures::executor::block_on(task);
21091      }
21092      for handle in holder_handles {
21093        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21094      }
21095
21096      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21097      let _ = done_tx.send(());
21098    });
21099
21100    match done_rx.recv_timeout(Duration::from_secs(10)) {
21101      Ok(()) => runner.join().unwrap(),
21102      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
21103    }
21104    let peak = peak_out.lock().unwrap().expect("peak captured");
21105    assert!(
21106      peak <= 2,
21107      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21108    );
21109  }
21110
21111  #[cfg(not(target_family = "wasm"))]
21112  #[test]
21113  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
21114    // Blocking-owner isolation invariant: a counted blocking OWNER that
21115    // re-enters cooperative `block_on` can drive a plain queued runnable via
21116    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
21117    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
21118    // the cap is saturated, the inner job must wait for a slot instead of
21119    // borrowing the owner's token.
21120    //
21121    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
21122    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
21123    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
21124    // drainers, no replacement drainer can be spawned, so O itself runs R via
21125    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
21126    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
21127    // runs within the cap and the peak remains 2. Everything must still
21128    // complete.
21129    use std::sync::{Barrier, mpsc};
21130    use std::time::Duration;
21131
21132    let (done_tx, done_rx) = mpsc::channel();
21133    let peak_out = Arc::new(Mutex::new(None));
21134    let peak_slot = Arc::clone(&peak_out);
21135    let runner = std::thread::spawn(move || {
21136      let metrics = Arc::new(RuntimeMetrics::default());
21137      let options = RuntimeOptions {
21138        flavor: RuntimeFlavor::MultiThread,
21139        worker_threads: 2,
21140        max_blocking_tasks: 2,
21141        thread_name_prefix: "rd1-owner-runnable".to_string(),
21142        park_deadline: None,
21143        drain_linger: DEFAULT_DRAIN_LINGER,
21144      };
21145      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21146
21147      let saturated = Arc::new(Barrier::new(2));
21148      let release = Arc::new(Barrier::new(2));
21149
21150      // Holder H2: holds the second counted slot until released. Not the driver.
21151      let h2_saturated = Arc::clone(&saturated);
21152      let h2_release = Arc::clone(&release);
21153      let h2 = executor.schedule_blocking(move || {
21154        h2_saturated.wait();
21155        h2_release.wait();
21156        0usize
21157      });
21158
21159      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
21160      // spawns plain runnable R and drives it cooperatively via `block_on`.
21161      let o_exec = Arc::clone(&executor);
21162      let o_saturated = Arc::clone(&saturated);
21163      let o = executor.schedule_blocking(move || {
21164        o_saturated.wait(); // both slots held -> cap saturated
21165
21166        let r_exec = Arc::clone(&o_exec);
21167        let body = async move {
21168          let inner_exec = Arc::clone(&r_exec);
21169          let mut inner = std::pin::pin!(async move {
21170            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21171            assert_eq!(v, 5);
21172          });
21173          r_exec.block_on(inner.as_mut());
21174        };
21175        let sched = Arc::clone(&o_exec);
21176        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21177        o_exec.schedule(runnable);
21178
21179        let mut wait_r = std::pin::pin!(async move {
21180          task.await;
21181        });
21182        o_exec.block_on(wait_r.as_mut());
21183        1usize
21184      });
21185
21186      // Give O time to drive R into its inner `block_on`; R must be parked
21187      // waiting for a slot.
21188      std::thread::sleep(Duration::from_millis(300));
21189      // Release H2 so the inner job can run within the cap.
21190      release.wait();
21191
21192      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21193      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
21194
21195      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
21196      let _ = done_tx.send(());
21197    });
21198
21199    match done_rx.recv_timeout(Duration::from_secs(10)) {
21200      Ok(()) => runner.join().unwrap(),
21201      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
21202    }
21203    let peak = peak_out.lock().unwrap().expect("peak captured");
21204    assert!(
21205      peak <= 2,
21206      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
21207    );
21208  }
21209
21210  #[cfg(not(target_family = "wasm"))]
21211  #[test]
21212  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
21213    // The over-cap fallback must select the exact pending JoinHandle job, not
21214    // the front of the shared blocking queue.
21215    //
21216    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
21217    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
21218    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
21219    // The exact dependency must skip U and run D. Because U can only run once
21220    // a real slot frees after O completes, D must STRICTLY precede U.
21221    use std::sync::{Barrier, mpsc};
21222
21223    let (done_tx, done_rx) = mpsc::channel();
21224    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
21225    let order_read = Arc::clone(&order_out);
21226    let runner = std::thread::spawn(move || {
21227      let metrics = Arc::new(RuntimeMetrics::default());
21228      let options = RuntimeOptions {
21229        flavor: RuntimeFlavor::MultiThread,
21230        worker_threads: 2,
21231        max_blocking_tasks: 2,
21232        thread_name_prefix: "rd1-unrelated".to_string(),
21233        park_deadline: None,
21234        drain_linger: DEFAULT_DRAIN_LINGER,
21235      };
21236      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
21237
21238      // O and H signal once they each hold a counted slot (cap saturated), then
21239      // wait so U is queued before O nests. H stays held until the very end.
21240      let (held_tx, held_rx) = mpsc::channel();
21241      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
21242      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
21243
21244      // Holder H: holds slot 2 for the whole test.
21245      let h_held = held_tx.clone();
21246      let h_rel = Arc::clone(&h_release);
21247      let h = executor.schedule_blocking(move || {
21248        h_held.send(()).unwrap();
21249        h_rel.wait();
21250        0usize
21251      });
21252
21253      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
21254      // its OWN descendant D.
21255      let o_exec = Arc::clone(&executor);
21256      let o_ready = Arc::clone(&u_ready);
21257      let o_order = Arc::clone(&order_out);
21258      let o = executor.schedule_blocking(move || {
21259        held_tx.send(()).unwrap();
21260        o_ready.wait(); // wait until U is queued ahead of D
21261        let inner_exec = Arc::clone(&o_exec);
21262        let d_order = Arc::clone(&o_order);
21263        let mut inner = std::pin::pin!(async move {
21264          let v = inner_exec
21265            .schedule_blocking(move || {
21266              d_order.lock().unwrap().push("D");
21267              5usize
21268            })
21269            .await
21270            .unwrap();
21271          assert_eq!(v, 5);
21272        });
21273        o_exec.block_on(inner.as_mut());
21274        1usize
21275      });
21276
21277      // Wait until BOTH O and H hold their slots -> cap saturated.
21278      held_rx.recv().unwrap();
21279      held_rx.recv().unwrap();
21280
21281      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
21282      let u_order = Arc::clone(&order_out);
21283      let u = executor.schedule_blocking(move || {
21284        u_order.lock().unwrap().push("U");
21285        2usize
21286      });
21287
21288      // Release O to nest now that U is queued ahead of D.
21289      u_ready.wait();
21290
21291      // O makes progress by running D over cap; U only runs after O frees a slot.
21292      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
21293      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
21294
21295      // Release H and finish.
21296      h_release.wait();
21297      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
21298
21299      let _ = done_tx.send(());
21300    });
21301
21302    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
21303      Ok(()) => runner.join().unwrap(),
21304      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
21305    }
21306    let order = order_read.lock().unwrap().clone();
21307    assert_eq!(
21308      order,
21309      vec!["D", "U"],
21310      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
21311    );
21312  }
21313
21314  #[cfg(not(target_family = "wasm"))]
21315  #[test]
21316  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
21317    use std::sync::{Barrier, mpsc};
21318
21319    let opts = RuntimeOptions {
21320      flavor: RuntimeFlavor::MultiThread,
21321      worker_threads: 2,
21322      max_blocking_tasks: 2,
21323      thread_name_prefix: "rd1-scope".to_string(),
21324      park_deadline: None,
21325      drain_linger: DEFAULT_DRAIN_LINGER,
21326    };
21327    let exec1 =
21328      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21329    let exec2 =
21330      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21331    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21332
21333    let holder_count = opts.max_blocking_tasks;
21334    let (held_tx, held_rx) = mpsc::channel();
21335    let release = Arc::new(Barrier::new(holder_count + 1));
21336    let mut holder_handles = Vec::new();
21337    for _ in 0..holder_count {
21338      let held_tx = held_tx.clone();
21339      let release = Arc::clone(&release);
21340      holder_handles.push(exec2.schedule_blocking(move || {
21341        held_tx.send(()).unwrap();
21342        release.wait();
21343        0usize
21344      }));
21345    }
21346    for _ in 0..holder_count {
21347      held_rx.recv().unwrap();
21348    }
21349    assert_eq!(
21350      exec2.active_blocking.load(Ordering::Acquire),
21351      opts.max_blocking_tasks
21352    );
21353
21354    let exact = BlockingJobId(u64::MAX);
21355    let ran = Arc::new(AtomicBool::new(false));
21356    let ran_job = Arc::clone(&ran);
21357    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
21358      id: exact,
21359      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21360    });
21361    let dependency = TaskDependency::default();
21362
21363    let token1 = exec1.fresh_owner_token();
21364    {
21365      let _owner = BlockingOwnerGuard::enter(Some(token1));
21366      dependency.set_owned(BlockingDependency {
21367        job: exact,
21368        owner: Some(token1),
21369      });
21370      assert!(
21371        !exec2.try_owned_blocking_over_cap(&dependency),
21372        "a foreign-executor ambient token must fail the executor gate"
21373      );
21374    }
21375
21376    let token2 = exec2.fresh_owner_token();
21377    {
21378      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
21379      let _owner = BlockingOwnerGuard::enter(Some(token2));
21380      dependency.set_owned(BlockingDependency {
21381        job: BlockingJobId(u64::MAX - 1),
21382        owner: Some(token2),
21383      });
21384      assert!(
21385        !exec2.try_owned_blocking_over_cap(&dependency),
21386        "a foreign or unrelated dependency id must fail the exact-job gate"
21387      );
21388      dependency.set_owned(BlockingDependency {
21389        job: BlockingJobId(exact.0 - 2),
21390        owner: Some(token2),
21391      });
21392      assert!(
21393        !exec2.try_owned_blocking_over_cap(&dependency),
21394        "a different job id must not consume the queued dependency"
21395      );
21396      dependency.set_owned(BlockingDependency {
21397        job: exact,
21398        owner: Some(token2),
21399      });
21400      assert!(
21401        exec2.try_owned_blocking_over_cap(&dependency),
21402        "the exact awaited job must run over cap"
21403      );
21404    }
21405    assert!(
21406      ran.load(Ordering::SeqCst),
21407      "the exact dependency must have run"
21408    );
21409
21410    release.wait();
21411    for handle in holder_handles {
21412      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
21413    }
21414  }
21415
21416  #[cfg(not(target_family = "wasm"))]
21417  #[test]
21418  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
21419    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
21420    let first_owner = executor.fresh_owner_token();
21421    let second_owner = executor.fresh_owner_token();
21422    let _first_owner =
21423      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
21424    let _second_owner =
21425      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
21426    let exact = BlockingJobId(u64::MAX - 30);
21427    let dependency = TaskDependency::default();
21428    dependency.set_owned(BlockingDependency {
21429      job: exact,
21430      owner: None,
21431    });
21432    let ran = Arc::new(AtomicBool::new(false));
21433    let ran_job = Arc::clone(&ran);
21434    executor
21435      .blocking_queue
21436      .lock()
21437      .unwrap()
21438      .push(QueuedBlocking {
21439        id: exact,
21440        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21441      });
21442
21443    assert!(
21444      !executor.try_owned_blocking_over_cap(&dependency),
21445      "an untagged driver must not choose between multiple active owner frames"
21446    );
21447    assert!(!ran.load(Ordering::SeqCst));
21448    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
21449    assert!(
21450      queued.is_some(),
21451      "ambiguous ownership must leave the exact job queued"
21452    );
21453  }
21454
21455  #[cfg(not(target_family = "wasm"))]
21456  #[test]
21457  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
21458    let executor = multi_thread_executor(2, None, "live-parked-dependency");
21459    let owner = executor.fresh_owner_token();
21460    let dependency = TaskDependency::new(u64::MAX, Some(owner));
21461    let stale = BlockingJobId(u64::MAX - 10);
21462    let live = BlockingJobId(u64::MAX - 9);
21463    dependency.set_owned(BlockingDependency {
21464      job: stale,
21465      owner: Some(owner),
21466    });
21467    dependency.begin_poll();
21468    dependency.set_owned(BlockingDependency {
21469      job: live,
21470      owner: Some(owner),
21471    });
21472
21473    let stale_ran = Arc::new(AtomicBool::new(false));
21474    let stale_job_ran = Arc::clone(&stale_ran);
21475    let live_ran = Arc::new(AtomicBool::new(false));
21476    let live_job_ran = Arc::clone(&live_ran);
21477    {
21478      let mut queue = executor
21479        .blocking_queue
21480        .lock()
21481        .unwrap_or_else(std::sync::PoisonError::into_inner);
21482      queue.push(QueuedBlocking {
21483        id: stale,
21484        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
21485      });
21486      queue.push(QueuedBlocking {
21487        id: live,
21488        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
21489      });
21490    }
21491
21492    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21493    let _owner = BlockingOwnerGuard::enter(Some(owner));
21494    assert!(
21495      executor.try_owned_blocking_over_cap(&dependency),
21496      "the owner lane must claim the currently published dependency"
21497    );
21498    assert!(
21499      live_ran.load(Ordering::SeqCst),
21500      "the live dependency must run"
21501    );
21502    assert!(
21503      !stale_ran.load(Ordering::SeqCst),
21504      "the stale registered id must remain queued"
21505    );
21506    let remaining = executor
21507      .blocking_queue
21508      .lock()
21509      .unwrap_or_else(std::sync::PoisonError::into_inner)
21510      .jobs
21511      .keys()
21512      .copied()
21513      .collect::<Vec<_>>();
21514    assert_eq!(remaining, vec![stale]);
21515  }
21516
21517  #[cfg(not(target_family = "wasm"))]
21518  #[test]
21519  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
21520    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
21521    let owner = executor.fresh_owner_token();
21522    let child = TaskDependency::new_task(u64::MAX);
21523    let parent = TaskDependency::new(u64::MAX, Some(owner));
21524    let exact = BlockingDependency {
21525      job: BlockingJobId(u64::MAX - 31),
21526      owner: Some(owner),
21527    };
21528
21529    child.set_owned(exact);
21530    let stale = child
21531      .get()
21532      .expect("the child must publish its direct dependency");
21533    child.begin_poll();
21534    parent.set_borrowed(&stale);
21535
21536    let ran = Arc::new(AtomicBool::new(false));
21537    let ran_job = Arc::clone(&ran);
21538    executor
21539      .blocking_queue
21540      .lock()
21541      .unwrap()
21542      .push(QueuedBlocking {
21543        id: exact.job,
21544        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21545      });
21546    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21547    let _owner = BlockingOwnerGuard::enter(Some(owner));
21548    assert!(
21549      !executor.try_owned_blocking_over_cap(&parent),
21550      "a publication copied before child cancellation must remain cancelled after parent install"
21551    );
21552    assert!(!ran.load(Ordering::SeqCst));
21553
21554    child.set_owned(exact);
21555    parent.set_borrowed(
21556      &child
21557        .get()
21558        .expect("the replacement publication must be live"),
21559    );
21560    assert!(
21561      executor.try_owned_blocking_over_cap(&parent),
21562      "a fresh child publication must still authorize its exact queued job"
21563    );
21564    assert!(ran.load(Ordering::SeqCst));
21565  }
21566
21567  #[cfg(not(target_family = "wasm"))]
21568  #[test]
21569  fn intermediate_repoll_does_not_cancel_child_source_claim() {
21570    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
21571    let owner = executor.fresh_owner_token();
21572    let child = TaskDependency::new_task(u64::MAX);
21573    let intermediate = TaskDependency::new_task(u64::MAX);
21574    let parent = TaskDependency::new(u64::MAX, Some(owner));
21575    let exact = BlockingDependency {
21576      job: BlockingJobId(u64::MAX - 43),
21577      owner: Some(owner),
21578    };
21579
21580    child.set_owned(exact);
21581    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
21582    parent.set_borrowed(
21583      &intermediate
21584        .get()
21585        .expect("the intermediate publication must be live"),
21586    );
21587
21588    intermediate.begin_poll();
21589    assert!(
21590      child.get().is_some(),
21591      "withdrawing an intermediate hop must not cancel the child's source claim"
21592    );
21593    assert!(
21594      parent.get().is_none(),
21595      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
21596    );
21597
21598    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
21599    parent.set_borrowed(
21600      &intermediate
21601        .get()
21602        .expect("the intermediate must republish the still-live source"),
21603    );
21604    let ran = Arc::new(AtomicBool::new(false));
21605    let ran_job = Arc::clone(&ran);
21606    executor
21607      .blocking_queue
21608      .lock()
21609      .unwrap()
21610      .push(QueuedBlocking {
21611        id: exact.job,
21612        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21613      });
21614
21615    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21616    let _owner = BlockingOwnerGuard::enter(Some(owner));
21617    assert!(
21618      executor.try_owned_blocking_over_cap(&parent),
21619      "a fresh chain through the repolled intermediate must retain the exact source claim"
21620    );
21621    assert!(ran.load(Ordering::SeqCst));
21622  }
21623
21624  #[cfg(not(target_family = "wasm"))]
21625  #[test]
21626  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
21627    use std::sync::mpsc;
21628
21629    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
21630    let owner = executor.fresh_owner_token();
21631    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21632    let child = Arc::new(TaskDependency::new_task(u64::MAX));
21633    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
21634    let exact = BlockingDependency {
21635      job: BlockingJobId(u64::MAX - 44),
21636      owner: Some(owner),
21637    };
21638    child.set_owned(exact);
21639    parent.set_borrowed(&child.get().expect("the child source must be live"));
21640
21641    let ran = Arc::new(AtomicBool::new(false));
21642    let ran_job = Arc::clone(&ran);
21643    executor
21644      .blocking_queue
21645      .lock()
21646      .unwrap()
21647      .push(QueuedBlocking {
21648        id: exact.job,
21649        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
21650      });
21651
21652    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
21653    let (release_claim_tx, release_claim_rx) = mpsc::channel();
21654    let claimant_executor = Arc::clone(&executor);
21655    let claimant_parent = Arc::clone(&parent);
21656    let claimant = std::thread::spawn(move || {
21657      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
21658        *slot.borrow_mut() = Some(Box::new(move || {
21659          claim_entered_tx.send(()).unwrap();
21660          release_claim_rx.recv().unwrap();
21661        }));
21662      });
21663      let _owner = BlockingOwnerGuard::enter(Some(owner));
21664      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
21665    });
21666    claim_entered_rx
21667      .recv_timeout(Duration::from_secs(2))
21668      .expect("the exact claimant must hold the shared transition lock");
21669
21670    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
21671    let withdrawing_child = Arc::clone(&child);
21672    let withdrawal = std::thread::spawn(move || {
21673      withdrawing_child.begin_poll();
21674      withdrawn_tx.send(()).unwrap();
21675    });
21676    let withdrew_before_claim = withdrawn_rx
21677      .recv_timeout(Duration::from_millis(200))
21678      .is_ok();
21679    release_claim_tx.send(()).unwrap();
21680
21681    assert!(
21682      claimant.join().unwrap(),
21683      "the claim that linearized first must execute the exact job"
21684    );
21685    withdrawal.join().unwrap();
21686    assert!(
21687      !withdrew_before_claim,
21688      "source withdrawal must not interleave after claim validation but before claim commit"
21689    );
21690    assert!(ran.load(Ordering::SeqCst));
21691  }
21692
21693  #[cfg(not(target_family = "wasm"))]
21694  #[test]
21695  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
21696    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
21697    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
21698    // executor whose worker set the marker. A worker of executor A that re-enters
21699    // `block_on` on executor B must take the PARKING branch instead -- it owns
21700    // none of B's accounting, so driving B's queue would make it an unaccounted
21701    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
21702    //
21703    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
21704    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
21705    // `block_on` on each executor with the SAME probe shape and observe whether
21706    // the cooperative `run_one` driver actually ran a queued runnable.
21707    //
21708    // The probe future returns `Pending` once (self-waking so the parking branch
21709    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
21710    // `run_one` at least once before the future completes. A single runnable is
21711    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
21712    // no background drainer is ever spawned to steal it). After `block_on`
21713    // returns: the cooperative branch will have driven that runnable
21714    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
21715    // (`runnable_polls == 0`, still queued).
21716    struct PendThenReady {
21717      polls: usize,
21718    }
21719    impl Future for PendThenReady {
21720      type Output = ();
21721      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21722        self.polls += 1;
21723        if self.polls >= 2 {
21724          Poll::Ready(())
21725        } else {
21726          // Self-wake so the parking branch (`futures::executor::block_on`)
21727          // re-polls to completion without any external waker; the cooperative
21728          // branch re-polls unconditionally each loop iteration regardless.
21729          cx.waker().wake_by_ref();
21730          Poll::Pending
21731        }
21732      }
21733    }
21734
21735    // Push one already-resolvable runnable directly onto `target`'s queue (no
21736    // `schedule`, so no drainer is spawned) and detach its task.
21737    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
21738      let scheduler = Arc::clone(target);
21739      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
21740      target.push_fifo_raw(runnable);
21741      task.detach();
21742    }
21743
21744    let opts = RuntimeOptions {
21745      flavor: RuntimeFlavor::MultiThread,
21746      worker_threads: 2,
21747      max_blocking_tasks: 2,
21748      thread_name_prefix: "rd-onpool-scope".to_string(),
21749      park_deadline: None,
21750      drain_linger: DEFAULT_DRAIN_LINGER,
21751    };
21752    let exec1 =
21753      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21754    let exec2 =
21755      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
21756    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
21757
21758    // Simulate being on exec1's pool worker for the whole scenario.
21759    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
21760
21761    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
21762    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
21763    // touch its queue.
21764    enqueue_runnable(&exec2);
21765    {
21766      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21767      exec2.block_on(probe.as_mut());
21768    }
21769    assert_eq!(
21770      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
21771      0,
21772      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
21773    );
21774    assert_eq!(
21775      exec2.injector.len(),
21776      1,
21777      "the foreign call must leave exec2's runnable untouched on its queue"
21778    );
21779
21780    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
21781    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
21782    // runnable via `run_one`.
21783    enqueue_runnable(&exec1);
21784    {
21785      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
21786      exec1.block_on(probe.as_mut());
21787    }
21788    assert_eq!(
21789      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
21790      1,
21791      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
21792    );
21793    assert!(
21794      exec1.injector.is_empty(),
21795      "the cooperative branch must have drained exec1's queued runnable"
21796    );
21797
21798    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
21799    // (steal-drain replaces the former `queue.lock().clear()`).
21800    while exec2.steal_one().is_some() {}
21801  }
21802
21803  #[cfg(not(target_family = "wasm"))]
21804  #[test]
21805  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
21806    // A long-lived blocking consumer may consume the entire configured
21807    // blocking allowance, but it must not consume the reserve lane required
21808    // by the async producers that feed it.
21809    use std::sync::mpsc;
21810    use std::time::Duration;
21811
21812    const PRODUCER_COUNT: usize = 8;
21813
21814    fn schedule_pool_workload(
21815      executor: &Arc<MultiThreadExecutor>,
21816      tx: &mpsc::Sender<Option<usize>>,
21817      count: usize,
21818    ) {
21819      for i in 0..count {
21820        let tx = tx.clone();
21821        let scheduler = Arc::clone(executor);
21822        let (runnable, task) =
21823          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
21824            scheduler.schedule(r);
21825          });
21826        executor.schedule(runnable);
21827        task.detach();
21828      }
21829      let tx = tx.clone();
21830      let scheduler = Arc::clone(executor);
21831      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
21832        scheduler.schedule(r)
21833      });
21834      executor.schedule(runnable);
21835      task.detach();
21836    }
21837
21838    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
21839    let (done_tx, done_rx) = mpsc::sync_channel(1);
21840    let runner = std::thread::spawn(move || {
21841      let options = RuntimeOptions {
21842        flavor: RuntimeFlavor::MultiThread,
21843        worker_threads: 1,
21844        max_blocking_tasks: 1,
21845        thread_name_prefix: "rd2-pre-fix".to_string(),
21846        park_deadline: None,
21847        drain_linger: DEFAULT_DRAIN_LINGER,
21848      }
21849      .validate()
21850      .expect("production MultiThread options must provide a reserve lane");
21851      let executor =
21852        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21853
21854      let (tx, rx) = mpsc::channel::<Option<usize>>();
21855      let (started_tx, started_rx) = mpsc::sync_channel(1);
21856      let handle = executor.schedule_blocking(move || {
21857        started_tx.send(()).unwrap();
21858        let mut sum = 0usize;
21859        while let Ok(msg) = rx.recv() {
21860          match msg {
21861            Some(value) => sum += value,
21862            None => break,
21863          }
21864        }
21865        sum
21866      });
21867      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21868      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
21869      let sum = futures::executor::block_on(handle).unwrap();
21870      done_tx.send(sum).unwrap();
21871    });
21872
21873    match done_rx.recv_timeout(Duration::from_secs(10)) {
21874      Ok(sum) => {
21875        runner.join().unwrap();
21876        assert_eq!(sum, expected_sum);
21877      }
21878      Err(error) => {
21879        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
21880      }
21881    }
21882  }
21883
21884  #[test]
21885  fn current_thread_blocking_work_completes_inline() {
21886    let metrics = Arc::new(RuntimeMetrics::default());
21887    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
21888      CurrentThreadExecutor::new(Arc::clone(&metrics)),
21889    )));
21890    let registration = backend.work.try_register_work().unwrap();
21891
21892    let value =
21893      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
21894        .expect("blocking task should complete");
21895
21896    assert_eq!(value, 7);
21897    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
21898    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
21899  }
21900
21901  #[cfg(not(target_family = "wasm"))]
21902  #[test]
21903  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
21904    use std::sync::mpsc;
21905
21906    let controller = Arc::new(current_thread_controller(
21907      "current-thread-blocking-admission",
21908    ));
21909    let backend = controller.backend();
21910    let (first_started_tx, first_started_rx) = mpsc::channel();
21911    let (release_first_tx, release_first_rx) = mpsc::channel();
21912    let first_controller = Arc::clone(&controller);
21913    let first = std::thread::spawn(move || {
21914      let handle = first_controller
21915        .try_spawn_blocking(move || {
21916          first_started_tx.send(()).unwrap();
21917          release_first_rx.recv().unwrap();
21918          1usize
21919        })
21920        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
21921      futures::executor::block_on(handle).unwrap()
21922    });
21923    first_started_rx
21924      .recv_timeout(Duration::from_secs(1))
21925      .unwrap();
21926
21927    let (second_started_tx, second_started_rx) = mpsc::channel();
21928    let second_controller = Arc::clone(&controller);
21929    let second = std::thread::spawn(move || {
21930      let handle = second_controller
21931        .try_spawn_blocking(move || {
21932          second_started_tx.send(()).unwrap();
21933          2usize
21934        })
21935        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
21936      futures::executor::block_on(handle).unwrap()
21937    });
21938
21939    wait_until("both CurrentThread blocking calls to be accepted", || {
21940      backend
21941        .work
21942        .state
21943        .lock()
21944        .unwrap_or_else(std::sync::PoisonError::into_inner)
21945        .active
21946        == 2
21947    });
21948    assert!(
21949      second_started_rx
21950        .recv_timeout(Duration::from_millis(100))
21951        .is_err(),
21952      "CurrentThread must not execute two blocking closures concurrently"
21953    );
21954
21955    release_first_tx.send(()).unwrap();
21956    second_started_rx
21957      .recv_timeout(Duration::from_secs(1))
21958      .expect("the waiting blocking call must acquire the released lane");
21959    assert_eq!(first.join().unwrap(), 1);
21960    assert_eq!(second.join().unwrap(), 2);
21961    assert_eq!(
21962      controller
21963        .metrics
21964        .blocking_tasks_started
21965        .load(Ordering::Relaxed),
21966      2
21967    );
21968    assert_eq!(
21969      controller
21970        .metrics
21971        .blocking_tasks_completed
21972        .load(Ordering::Relaxed),
21973      2
21974    );
21975    assert_eq!(
21976      controller
21977        .metrics
21978        .active_blocking_tasks
21979        .load(Ordering::Relaxed),
21980      0
21981    );
21982    assert_eq!(
21983      controller
21984        .metrics
21985        .max_active_blocking_tasks
21986        .load(Ordering::Relaxed),
21987      1,
21988      "the public active-lane metric must remain within CurrentThread's reported capacity"
21989    );
21990    controller.shutdown().unwrap();
21991  }
21992
21993  #[cfg(not(target_family = "wasm"))]
21994  #[test]
21995  fn current_thread_nested_blocking_reuses_the_active_lane() {
21996    use std::sync::mpsc;
21997
21998    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
21999    let (done_tx, done_rx) = mpsc::channel();
22000    let runner_controller = Arc::clone(&controller);
22001    let runner = std::thread::spawn(move || {
22002      let nested_controller = Arc::clone(&runner_controller);
22003      let outer = runner_controller
22004        .try_spawn_blocking(move || {
22005          let inner = nested_controller
22006            .try_spawn_blocking(|| 41usize)
22007            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
22008          futures::executor::block_on(inner).unwrap() + 1
22009        })
22010        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
22011      done_tx
22012        .send(futures::executor::block_on(outer).unwrap())
22013        .unwrap();
22014    });
22015
22016    assert_eq!(
22017      done_rx
22018        .recv_timeout(Duration::from_secs(2))
22019        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
22020      42
22021    );
22022    runner.join().unwrap();
22023    assert_eq!(
22024      controller
22025        .metrics
22026        .blocking_tasks_started
22027        .load(Ordering::Relaxed),
22028      2
22029    );
22030    assert_eq!(
22031      controller
22032        .metrics
22033        .blocking_tasks_completed
22034        .load(Ordering::Relaxed),
22035      2
22036    );
22037    assert_eq!(
22038      controller
22039        .metrics
22040        .active_blocking_tasks
22041        .load(Ordering::Relaxed),
22042      0
22043    );
22044    assert_eq!(
22045      controller
22046        .metrics
22047        .max_active_blocking_tasks
22048        .load(Ordering::Relaxed),
22049      1,
22050      "nested work borrows the outer lane instead of reporting a second physical lane"
22051    );
22052    controller.shutdown().unwrap();
22053  }
22054
22055  #[cfg(not(target_family = "wasm"))]
22056  #[test]
22057  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
22058    use std::sync::mpsc;
22059
22060    struct HotFuture {
22061      stop: Arc<AtomicBool>,
22062    }
22063
22064    impl Future for HotFuture {
22065      type Output = ();
22066
22067      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
22068        if self.stop.load(Ordering::SeqCst) {
22069          Poll::Ready(())
22070        } else {
22071          cx.waker().wake_by_ref();
22072          Poll::Pending
22073        }
22074      }
22075    }
22076
22077    let stop = Arc::new(AtomicBool::new(false));
22078    let (ready_tx, ready_rx) = mpsc::channel();
22079    let (exact_tx, exact_rx) = mpsc::channel();
22080    let (done_tx, done_rx) = mpsc::channel();
22081    let runner_stop = Arc::clone(&stop);
22082    let runner = std::thread::spawn(move || {
22083      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22084        RuntimeMetrics::default(),
22085      )));
22086      let owner = executor.fresh_owner_token();
22087      let job = BlockingJobId(u64::MAX - 70);
22088      let (result_tx, result_rx) = oneshot::channel();
22089      {
22090        let mut state = executor
22091          .blocking_admission
22092          .state
22093          .lock()
22094          .unwrap_or_else(std::sync::PoisonError::into_inner);
22095        state.owner = Some(owner);
22096        let exact_stop = Arc::clone(&runner_stop);
22097        state.queue.push(QueuedBlocking {
22098          id: job,
22099          run: Box::new(move || {
22100            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
22101            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
22102          }),
22103        });
22104      }
22105      let exact = JoinHandle(JoinHandleInner::Blocking {
22106        receiver: result_rx,
22107        dependency: BlockingDependency {
22108          job,
22109          owner: Some(owner),
22110        },
22111        published: PublishedBlockingDependencies::default(),
22112      });
22113      let _owner = executor.enter_blocking_owner(owner, false);
22114
22115      let scheduler = Arc::clone(&executor);
22116      let hot_stop = Arc::clone(&runner_stop);
22117      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
22118        scheduler.schedule(runnable);
22119      });
22120      executor.schedule(runnable);
22121      ready_tx.send(()).unwrap();
22122
22123      let mut value = None;
22124      {
22125        let mut future = std::pin::pin!(async {
22126          value = Some(exact.await.unwrap());
22127          hot_task.await;
22128        });
22129        assert_eq!(
22130          executor.block_on(future.as_mut()),
22131          BlockOnOutcome::Completed
22132        );
22133      }
22134      executor
22135        .blocking_admission
22136        .state
22137        .lock()
22138        .unwrap_or_else(std::sync::PoisonError::into_inner)
22139        .owner = None;
22140      done_tx.send(value.unwrap()).unwrap();
22141    });
22142
22143    ready_rx
22144      .recv_timeout(Duration::from_secs(2))
22145      .expect("the fairness driver must start");
22146    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
22147    stop.store(true, Ordering::SeqCst);
22148    assert_eq!(
22149      done_rx
22150        .recv_timeout(Duration::from_secs(2))
22151        .expect("the CurrentThread driver must retire"),
22152      41
22153    );
22154    runner.join().unwrap();
22155    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
22156    assert_eq!(
22157      exact_before_stop,
22158      Some(true),
22159      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
22160    );
22161  }
22162
22163  #[cfg(not(target_family = "wasm"))]
22164  #[test]
22165  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
22166    use std::sync::mpsc;
22167
22168    let controller = Arc::new(current_thread_controller(
22169      "current-thread-cross-driver-blocking",
22170    ));
22171    let backend = controller.backend();
22172    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22173      panic!("configured CurrentThread backend must create a CurrentThread executor");
22174    };
22175    let executor = Arc::clone(executor);
22176    drop(backend);
22177
22178    let (release_driver_tx, release_driver_rx) = oneshot::channel();
22179    let (driver_started_tx, driver_started_rx) = mpsc::channel();
22180    let driver_controller = Arc::clone(&controller);
22181    let driver = std::thread::spawn(move || {
22182      driver_started_tx.send(std::thread::current().id()).unwrap();
22183      block_on_with_controller(&driver_controller, async {
22184        release_driver_rx
22185          .await
22186          .expect("driver release sender must remain alive");
22187      });
22188    });
22189    let driver_thread = driver_started_rx
22190      .recv_timeout(Duration::from_secs(1))
22191      .unwrap();
22192    wait_until("the secondary CurrentThread driver to park", || {
22193      executor
22194        .stop
22195        .parkers
22196        .lock()
22197        .unwrap_or_else(std::sync::PoisonError::into_inner)
22198        .iter()
22199        .filter_map(Weak::upgrade)
22200        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22201    });
22202
22203    let (child_polled_tx, child_polled_rx) = mpsc::channel();
22204    let (release_child_tx, release_child_rx) = mpsc::channel();
22205    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
22206    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
22207    let (owner_done_tx, owner_done_rx) = mpsc::channel();
22208    let owner_controller = Arc::clone(&controller);
22209    let owner = std::thread::spawn(move || {
22210      let owner_thread = std::thread::current().id();
22211      let await_controller = Arc::clone(&owner_controller);
22212      let child_controller = Arc::clone(&owner_controller);
22213      let outer = owner_controller
22214        .try_spawn_blocking(move || {
22215          let spawn_controller = Arc::clone(&child_controller);
22216          let child = spawn_controller
22217            .try_spawn(async move {
22218              child_polled_tx.send(std::thread::current().id()).unwrap();
22219              release_child_rx.recv().unwrap();
22220              child_controller
22221                .try_spawn_blocking(move || {
22222                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
22223                  41usize
22224                })
22225                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
22226                .await
22227                .unwrap()
22228                + 1
22229            })
22230            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
22231
22232          assert_eq!(
22233            child_polled_rx
22234              .recv_timeout(Duration::from_secs(1))
22235              .unwrap(),
22236            driver_thread,
22237            "the secondary driver must own the child poll that reaches blocking admission"
22238          );
22239          owner_awaiting_tx.send(owner_thread).unwrap();
22240          block_on_with_controller(&await_controller, child).unwrap()
22241        })
22242        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22243      owner_done_tx
22244        .send(futures::executor::block_on(outer))
22245        .unwrap();
22246    });
22247
22248    let owner_thread = owner_awaiting_rx
22249      .recv_timeout(Duration::from_secs(1))
22250      .unwrap();
22251    wait_until(
22252      "the blocking owner to park while awaiting the cross-driver child",
22253      || {
22254        executor
22255          .stop
22256          .parkers
22257          .lock()
22258          .unwrap_or_else(std::sync::PoisonError::into_inner)
22259          .iter()
22260          .filter_map(Weak::upgrade)
22261          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
22262      },
22263    );
22264    release_child_tx.send(()).unwrap();
22265
22266    assert_eq!(
22267      blocking_ran_rx
22268        .recv_timeout(Duration::from_secs(2))
22269        .unwrap(),
22270      owner_thread,
22271      "the exact descendant must be serviced by the lane-owning driver"
22272    );
22273    assert_eq!(
22274      owner_done_rx
22275        .recv_timeout(Duration::from_secs(2))
22276        .expect("cross-driver blocking dependency must not deadlock")
22277        .unwrap(),
22278      42
22279    );
22280    release_driver_tx.send(()).unwrap();
22281    owner.join().unwrap();
22282    driver.join().unwrap();
22283    controller.shutdown().unwrap();
22284    assert_eq!(
22285      controller
22286        .metrics
22287        .blocking_tasks_started
22288        .load(Ordering::Relaxed),
22289      2
22290    );
22291    assert_eq!(
22292      controller
22293        .metrics
22294        .blocking_tasks_completed
22295        .load(Ordering::Relaxed),
22296      2
22297    );
22298    assert_eq!(
22299      controller
22300        .metrics
22301        .active_blocking_tasks
22302        .load(Ordering::Relaxed),
22303      0
22304    );
22305    assert_eq!(
22306      controller
22307        .metrics
22308        .max_active_blocking_tasks
22309        .load(Ordering::Relaxed),
22310      1,
22311      "cross-driver dependency service must reuse the owner's physical lane"
22312    );
22313  }
22314
22315  #[cfg(not(target_family = "wasm"))]
22316  #[test]
22317  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
22318    use std::sync::mpsc;
22319
22320    let controller = Arc::new(current_thread_controller(
22321      "current-thread-blocking-stop-race",
22322    ));
22323    let backend = controller.backend();
22324    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22325      panic!("configured CurrentThread backend must create a CurrentThread executor");
22326    };
22327    let executor = Arc::clone(executor);
22328    drop(backend);
22329
22330    let ran = Arc::new(AtomicBool::new(false));
22331    let blocking_ran = Arc::clone(&ran);
22332    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
22333    let (release_admission_tx, release_admission_rx) = mpsc::channel();
22334    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
22335    let submit_controller = Arc::clone(&controller);
22336    let submitter = std::thread::spawn(move || {
22337      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22338        *slot.borrow_mut() = Some(Box::new(move || {
22339          admission_locked_tx.send(()).unwrap();
22340          release_admission_rx.recv().unwrap();
22341        }));
22342      });
22343      let handle = submit_controller
22344        .try_spawn_blocking(move || {
22345          blocking_ran.store(true, Ordering::SeqCst);
22346        })
22347        .unwrap_or_else(|_| {
22348          panic!("the submission linearized before shutdown and must be accepted")
22349        });
22350      blocking_result_tx
22351        .send(futures::executor::block_on(handle))
22352        .unwrap();
22353    });
22354    admission_locked_rx
22355      .recv_timeout(Duration::from_secs(2))
22356      .expect("the blocking submitter must pause while holding the admission lock");
22357
22358    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
22359    let shutdown_controller = Arc::clone(&controller);
22360    let shutdown = std::thread::spawn(move || {
22361      shutdown_result_tx
22362        .send(shutdown_controller.shutdown())
22363        .unwrap();
22364    });
22365    wait_until("CurrentThread shutdown to publish generation stop", || {
22366      executor.stop.is_stopping()
22367    });
22368    release_admission_tx.send(()).unwrap();
22369
22370    let blocking_error = blocking_result_rx
22371      .recv_timeout(Duration::from_secs(2))
22372      .expect("the admitted blocking handle must resolve")
22373      .expect_err("stop publication must reject the blocking lane claim");
22374    assert_eq!(
22375      blocking_error.to_string(),
22376      "the async runtime stopped before the blocking task could start"
22377    );
22378    assert!(
22379      !ran.load(Ordering::SeqCst),
22380      "blocking work must not start after stop publication"
22381    );
22382    shutdown_result_rx
22383      .recv_timeout(Duration::from_secs(2))
22384      .expect("shutdown must finish after the rejected admission retires")
22385      .unwrap();
22386    submitter.join().unwrap();
22387    shutdown.join().unwrap();
22388  }
22389
22390  #[cfg(not(target_family = "wasm"))]
22391  #[test]
22392  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
22393    use std::sync::mpsc;
22394
22395    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22396      RuntimeMetrics::default(),
22397    )));
22398    let ran = Arc::new(AtomicBool::new(false));
22399    let job_ran = Arc::clone(&ran);
22400    {
22401      let mut state = executor
22402        .blocking_admission
22403        .state
22404        .lock()
22405        .unwrap_or_else(std::sync::PoisonError::into_inner);
22406      state.queue.push(QueuedBlocking {
22407        id: BlockingJobId(u64::MAX - 71),
22408        run: Box::new(move || {
22409          job_ran.store(true, Ordering::SeqCst);
22410        }),
22411      });
22412    }
22413
22414    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22415    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22416    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22417    let claim_executor = Arc::clone(&executor);
22418    let claimant = std::thread::spawn(move || {
22419      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22420        *slot.borrow_mut() = Some(Box::new(move || {
22421          claim_locked_tx.send(()).unwrap();
22422          release_claim_rx.recv().unwrap();
22423        }));
22424      });
22425      claim_result_tx
22426        .send(claim_executor.run_one_blocking())
22427        .unwrap();
22428    });
22429    claim_locked_rx
22430      .recv_timeout(Duration::from_secs(2))
22431      .expect("the FIFO claimant must pause while holding the admission lock");
22432
22433    executor.stop.begin_shutdown();
22434    release_claim_tx.send(()).unwrap();
22435    assert!(
22436      !claim_result_rx
22437        .recv_timeout(Duration::from_secs(2))
22438        .expect("the stopped FIFO claim must finish"),
22439      "stop publication must reject the queued FIFO claim"
22440    );
22441    claimant.join().unwrap();
22442    assert!(
22443      !ran.load(Ordering::SeqCst),
22444      "queued FIFO work must not start after stop publication"
22445    );
22446    assert!(
22447      !executor
22448        .blocking_admission
22449        .state
22450        .lock()
22451        .unwrap_or_else(std::sync::PoisonError::into_inner)
22452        .queue
22453        .is_empty(),
22454      "the rejected FIFO job must remain queued for shutdown cancellation"
22455    );
22456
22457    executor.close_blocking_admission();
22458    assert!(
22459      executor
22460        .blocking_admission
22461        .state
22462        .lock()
22463        .unwrap_or_else(std::sync::PoisonError::into_inner)
22464        .queue
22465        .is_empty()
22466    );
22467  }
22468
22469  #[cfg(not(target_family = "wasm"))]
22470  #[test]
22471  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22472    use std::sync::mpsc;
22473
22474    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22475      RuntimeMetrics::default(),
22476    )));
22477    let ran = Arc::new(AtomicBool::new(false));
22478    let job_ran = Arc::clone(&ran);
22479    {
22480      let mut state = executor
22481        .blocking_admission
22482        .state
22483        .lock()
22484        .unwrap_or_else(std::sync::PoisonError::into_inner);
22485      state.queue.push(QueuedBlocking {
22486        id: BlockingJobId(u64::MAX - 73),
22487        run: Box::new(move || {
22488          job_ran.store(true, Ordering::SeqCst);
22489        }),
22490      });
22491    }
22492
22493    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22494    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22495    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22496    let claim_executor = Arc::clone(&executor);
22497    let claimant = std::thread::spawn(move || {
22498      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22499        *slot.borrow_mut() = Some(Box::new(move || {
22500          claim_locked_tx.send(()).unwrap();
22501          release_claim_rx.recv().unwrap();
22502        }));
22503      });
22504      claim_result_tx
22505        .send(claim_executor.run_one_blocking())
22506        .unwrap();
22507    });
22508    claim_locked_rx
22509      .recv_timeout(Duration::from_secs(2))
22510      .expect("the FIFO claimant must pause while holding the admission lock");
22511
22512    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22513    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22514    let cancelling_executor = Arc::clone(&executor);
22515    let cancellation = std::thread::spawn(move || {
22516      let cancelling = cancelling_executor
22517        .begin_failed_dispatch_cancellation()
22518        .expect("the test must publish terminal dispatch cancellation");
22519      cancellation_published_tx.send(()).unwrap();
22520      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22521      cancellation_done_tx.send(()).unwrap();
22522    });
22523    cancellation_published_rx
22524      .recv_timeout(Duration::from_secs(2))
22525      .expect("terminal dispatch cancellation must publish before queue cancellation");
22526    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22527    assert!(
22528      matches!(
22529        cancellation_done_rx.try_recv(),
22530        Err(mpsc::TryRecvError::Empty)
22531      ),
22532      "terminal cancellation must wait for the blocking-admission lock"
22533    );
22534
22535    release_claim_tx.send(()).unwrap();
22536    assert!(
22537      !claim_result_rx
22538        .recv_timeout(Duration::from_secs(2))
22539        .expect("the cancelled FIFO claim must finish"),
22540      "terminal cancellation publication must reject the queued FIFO claim"
22541    );
22542    cancellation_done_rx
22543      .recv_timeout(Duration::from_secs(2))
22544      .expect("terminal cancellation must drain the rejected FIFO job");
22545    claimant.join().unwrap();
22546    cancellation.join().unwrap();
22547
22548    assert!(
22549      !ran.load(Ordering::SeqCst),
22550      "terminally cancelled FIFO work must not start"
22551    );
22552    assert!(
22553      executor
22554        .blocking_admission
22555        .state
22556        .lock()
22557        .unwrap_or_else(std::sync::PoisonError::into_inner)
22558        .queue
22559        .is_empty(),
22560      "terminal cancellation must drain the rejected FIFO job"
22561    );
22562    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22563  }
22564
22565  #[cfg(not(target_family = "wasm"))]
22566  #[test]
22567  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
22568    use std::sync::mpsc;
22569
22570    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22571      RuntimeMetrics::default(),
22572    )));
22573    let owner = executor.fresh_owner_token();
22574    let job = BlockingJobId(u64::MAX - 72);
22575    let ran = Arc::new(AtomicBool::new(false));
22576    let job_ran = Arc::clone(&ran);
22577    {
22578      let mut state = executor
22579        .blocking_admission
22580        .state
22581        .lock()
22582        .unwrap_or_else(std::sync::PoisonError::into_inner);
22583      state.owner = Some(owner);
22584      state.queue.push(QueuedBlocking {
22585        id: job,
22586        run: Box::new(move || {
22587          job_ran.store(true, Ordering::SeqCst);
22588        }),
22589      });
22590    }
22591    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22592    dependency.set_owned(BlockingDependency {
22593      job,
22594      owner: Some(owner),
22595    });
22596
22597    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22598    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22599    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22600    let claim_executor = Arc::clone(&executor);
22601    let claim_dependency = Arc::clone(&dependency);
22602    let claimant = std::thread::spawn(move || {
22603      let _owner = claim_executor.enter_blocking_owner(owner, false);
22604      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22605        *slot.borrow_mut() = Some(Box::new(move || {
22606          claim_locked_tx.send(()).unwrap();
22607          release_claim_rx.recv().unwrap();
22608        }));
22609      });
22610      claim_result_tx
22611        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22612        .unwrap();
22613    });
22614    claim_locked_rx
22615      .recv_timeout(Duration::from_secs(2))
22616      .expect("the owner claimant must pause while holding the admission lock");
22617
22618    executor.stop.begin_shutdown();
22619    release_claim_tx.send(()).unwrap();
22620    assert!(
22621      !claim_result_rx
22622        .recv_timeout(Duration::from_secs(2))
22623        .expect("the stopped owner claim must finish"),
22624      "stop publication must reject the exact-owner claim"
22625    );
22626    claimant.join().unwrap();
22627    assert!(
22628      !ran.load(Ordering::SeqCst),
22629      "queued exact-owner work must not start after stop publication"
22630    );
22631    assert!(
22632      !executor
22633        .blocking_admission
22634        .state
22635        .lock()
22636        .unwrap_or_else(std::sync::PoisonError::into_inner)
22637        .queue
22638        .is_empty(),
22639      "the rejected exact-owner job must remain queued for shutdown cancellation"
22640    );
22641
22642    executor
22643      .blocking_admission
22644      .state
22645      .lock()
22646      .unwrap_or_else(std::sync::PoisonError::into_inner)
22647      .owner = None;
22648    executor.close_blocking_admission();
22649    assert!(
22650      executor
22651        .blocking_admission
22652        .state
22653        .lock()
22654        .unwrap_or_else(std::sync::PoisonError::into_inner)
22655        .queue
22656        .is_empty()
22657    );
22658  }
22659
22660  #[cfg(not(target_family = "wasm"))]
22661  #[test]
22662  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
22663    use std::sync::mpsc;
22664
22665    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
22666      RuntimeMetrics::default(),
22667    )));
22668    let owner = executor.fresh_owner_token();
22669    let job = BlockingJobId(u64::MAX - 74);
22670    let ran = Arc::new(AtomicBool::new(false));
22671    let job_ran = Arc::clone(&ran);
22672    {
22673      let mut state = executor
22674        .blocking_admission
22675        .state
22676        .lock()
22677        .unwrap_or_else(std::sync::PoisonError::into_inner);
22678      state.owner = Some(owner);
22679      state.queue.push(QueuedBlocking {
22680        id: job,
22681        run: Box::new(move || {
22682          job_ran.store(true, Ordering::SeqCst);
22683        }),
22684      });
22685    }
22686    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22687    dependency.set_owned(BlockingDependency {
22688      job,
22689      owner: Some(owner),
22690    });
22691
22692    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
22693    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22694    let (claim_result_tx, claim_result_rx) = mpsc::channel();
22695    let claim_executor = Arc::clone(&executor);
22696    let claim_dependency = Arc::clone(&dependency);
22697    let claimant = std::thread::spawn(move || {
22698      let _owner = claim_executor.enter_blocking_owner(owner, false);
22699      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
22700        *slot.borrow_mut() = Some(Box::new(move || {
22701          claim_locked_tx.send(()).unwrap();
22702          release_claim_rx.recv().unwrap();
22703        }));
22704      });
22705      claim_result_tx
22706        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
22707        .unwrap();
22708    });
22709    claim_locked_rx
22710      .recv_timeout(Duration::from_secs(2))
22711      .expect("the owner claimant must pause while holding the admission lock");
22712
22713    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
22714    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
22715    let cancelling_executor = Arc::clone(&executor);
22716    let cancellation = std::thread::spawn(move || {
22717      let cancelling = cancelling_executor
22718        .begin_failed_dispatch_cancellation()
22719        .expect("the test must publish terminal dispatch cancellation");
22720      cancellation_published_tx.send(()).unwrap();
22721      cancelling_executor.cancel_work_after_host_dispatch_failure_claimed(cancelling);
22722      cancellation_done_tx.send(()).unwrap();
22723    });
22724    cancellation_published_rx
22725      .recv_timeout(Duration::from_secs(2))
22726      .expect("terminal dispatch cancellation must publish before queue cancellation");
22727    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22728    assert!(
22729      matches!(
22730        cancellation_done_rx.try_recv(),
22731        Err(mpsc::TryRecvError::Empty)
22732      ),
22733      "terminal cancellation must wait for the blocking-admission lock"
22734    );
22735
22736    release_claim_tx.send(()).unwrap();
22737    assert!(
22738      !claim_result_rx
22739        .recv_timeout(Duration::from_secs(2))
22740        .expect("the cancelled owner claim must finish"),
22741      "terminal cancellation publication must reject the exact-owner claim"
22742    );
22743    cancellation_done_rx
22744      .recv_timeout(Duration::from_secs(2))
22745      .expect("terminal cancellation must drain the rejected exact-owner job");
22746    claimant.join().unwrap();
22747    cancellation.join().unwrap();
22748
22749    assert!(
22750      !ran.load(Ordering::SeqCst),
22751      "terminally cancelled exact-owner work must not start"
22752    );
22753    assert!(
22754      executor
22755        .blocking_admission
22756        .state
22757        .lock()
22758        .unwrap_or_else(std::sync::PoisonError::into_inner)
22759        .queue
22760        .is_empty(),
22761      "terminal cancellation must drain the rejected exact-owner job"
22762    );
22763    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
22764  }
22765
22766  #[cfg(not(target_family = "wasm"))]
22767  #[test]
22768  fn current_thread_shutdown_cancels_waiting_blocking_work() {
22769    use std::sync::mpsc;
22770
22771    struct DropSignal(mpsc::Sender<()>);
22772
22773    impl Drop for DropSignal {
22774      fn drop(&mut self) {
22775        let _ = self.0.send(());
22776      }
22777    }
22778
22779    let controller = Arc::new(current_thread_controller(
22780      "current-thread-blocking-shutdown",
22781    ));
22782    let backend = controller.backend();
22783    let (first_started_tx, first_started_rx) = mpsc::channel();
22784    let (release_first_tx, release_first_rx) = mpsc::channel();
22785    let first_controller = Arc::clone(&controller);
22786    let first = std::thread::spawn(move || {
22787      let handle = first_controller
22788        .try_spawn_blocking(move || {
22789          first_started_tx.send(()).unwrap();
22790          release_first_rx.recv().unwrap();
22791        })
22792        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
22793      futures::executor::block_on(handle)
22794    });
22795    first_started_rx
22796      .recv_timeout(Duration::from_secs(1))
22797      .unwrap();
22798
22799    let waiting_ran = Arc::new(AtomicBool::new(false));
22800    let waiting_ran_task = Arc::clone(&waiting_ran);
22801    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
22802    let waiting_drop = DropSignal(waiting_dropped_tx);
22803    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
22804    let waiting_controller = Arc::clone(&controller);
22805    let waiting = std::thread::spawn(move || {
22806      let handle = waiting_controller
22807        .try_spawn_blocking(move || {
22808          let _waiting_drop = waiting_drop;
22809          waiting_ran_task.store(true, Ordering::SeqCst);
22810        })
22811        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
22812      waiting_result_tx
22813        .send(futures::executor::block_on(handle))
22814        .unwrap();
22815    });
22816    wait_until("both CurrentThread blocking calls to be accepted", || {
22817      backend
22818        .work
22819        .state
22820        .lock()
22821        .unwrap_or_else(std::sync::PoisonError::into_inner)
22822        .active
22823        == 2
22824    });
22825
22826    let (shutdown_tx, shutdown_rx) = mpsc::channel();
22827    let shutdown_controller = Arc::clone(&controller);
22828    let shutdown = std::thread::spawn(move || {
22829      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
22830    });
22831    waiting_dropped_rx
22832      .recv_timeout(Duration::from_secs(1))
22833      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
22834    assert!(
22835      !waiting_ran.load(Ordering::SeqCst),
22836      "waiting blocking work must not start"
22837    );
22838    assert!(
22839      waiting_result_rx
22840        .recv_timeout(Duration::from_secs(1))
22841        .unwrap()
22842        .is_err(),
22843      "the waiting blocking handle must resolve as cancelled"
22844    );
22845    assert!(
22846      shutdown_rx
22847        .recv_timeout(Duration::from_millis(100))
22848        .is_err(),
22849      "shutdown must still wait for the blocking closure that already owns the lane"
22850    );
22851
22852    release_first_tx.send(()).unwrap();
22853    first.join().unwrap().unwrap();
22854    waiting.join().unwrap();
22855    shutdown_rx
22856      .recv_timeout(Duration::from_secs(1))
22857      .unwrap()
22858      .unwrap();
22859    shutdown.join().unwrap();
22860    assert_eq!(
22861      controller
22862        .metrics
22863        .blocking_tasks_started
22864        .load(Ordering::Relaxed),
22865      1
22866    );
22867    assert_eq!(
22868      controller
22869        .metrics
22870        .blocking_tasks_completed
22871        .load(Ordering::Relaxed),
22872      1
22873    );
22874    assert_eq!(
22875      controller
22876        .metrics
22877        .active_blocking_tasks
22878        .load(Ordering::Relaxed),
22879      0
22880    );
22881  }
22882
22883  #[cfg(not(target_family = "wasm"))]
22884  #[test]
22885  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
22886    use std::{panic::panic_any, sync::mpsc};
22887
22888    struct ReentrantPayload {
22889      controller: Arc<RuntimeController>,
22890      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
22891    }
22892
22893    impl Drop for ReentrantPayload {
22894      fn drop(&mut self) {
22895        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
22896        let shutdown = self.controller.shutdown();
22897        self.result.send((generation, shutdown)).unwrap();
22898      }
22899    }
22900
22901    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
22902
22903    impl Drop for PanicPayloadOnDrop {
22904      fn drop(&mut self) {
22905        panic_any(self.0.take().expect("the result must panic only once"));
22906      }
22907    }
22908
22909    let controller = Arc::new(current_thread_controller(
22910      "current-thread-panic-payload-generation",
22911    ));
22912    let backend = controller.backend();
22913    let generation = backend.generation();
22914    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
22915      panic!("configured CurrentThread backend must create a CurrentThread executor");
22916    };
22917    let executor = Arc::clone(executor);
22918    drop(backend);
22919
22920    // Hold the physical lane long enough to force scheduler-owned delivery.
22921    // Dropping the receiver then makes the queued sender destroy the result.
22922    let owner = executor.fresh_owner_token();
22923    executor
22924      .blocking_admission
22925      .state
22926      .lock()
22927      .unwrap_or_else(std::sync::PoisonError::into_inner)
22928      .owner = Some(owner);
22929    let (result_tx, result_rx) = mpsc::channel();
22930    let payload_controller = Arc::clone(&controller);
22931    let handle = controller
22932      .try_spawn_blocking(move || {
22933        PanicPayloadOnDrop(Some(ReentrantPayload {
22934          controller: payload_controller,
22935          result: result_tx,
22936        }))
22937      })
22938      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22939    drop(handle);
22940    executor
22941      .blocking_admission
22942      .state
22943      .lock()
22944      .unwrap_or_else(std::sync::PoisonError::into_inner)
22945      .owner = None;
22946    let dispatch = current_thread_dispatch(&executor);
22947    controller.drive_current_thread_dispatch(dispatch, false);
22948
22949    let (observed_generation, shutdown) = result_rx
22950      .recv_timeout(Duration::from_secs(1))
22951      .expect("the caught result panic payload must be destroyed in the scheduler turn");
22952    assert_eq!(observed_generation, Some(generation));
22953    let error = shutdown.expect_err("payload destruction is still work in the active generation");
22954    assert!(error.to_string().contains("work running on that runtime"));
22955    controller.shutdown().unwrap();
22956  }
22957
22958  #[test]
22959  fn current_thread_detached_blocking_result_drop_is_contained() {
22960    use std::sync::mpsc;
22961
22962    struct PanicOnDrop(mpsc::Sender<()>);
22963
22964    impl Drop for PanicOnDrop {
22965      fn drop(&mut self) {
22966        self.0.send(()).unwrap();
22967        panic!("intentional CurrentThread blocking result destructor panic");
22968      }
22969    }
22970
22971    let controller = current_thread_controller("current-thread-blocking-result-drop");
22972    let (dropped_tx, dropped_rx) = mpsc::channel();
22973    let handle = controller
22974      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
22975      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
22976
22977    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
22978    dropped_rx
22979      .recv_timeout(Duration::from_secs(1))
22980      .expect("dropping the detached handle must reclaim its blocking result");
22981    assert!(
22982      result.is_ok(),
22983      "the blocking result destructor panic must be contained"
22984    );
22985    controller.shutdown().unwrap();
22986  }
22987
22988  // ---- Runtime option validation and configuration immutability -------------
22989  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
22990  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
22991
22992  #[test]
22993  fn validate_rejects_zero_worker_threads() {
22994    let error = RuntimeOptions {
22995      flavor: RuntimeFlavor::MultiThread,
22996      worker_threads: 0,
22997      max_blocking_tasks: 1,
22998      thread_name_prefix: "rd8".to_string(),
22999      park_deadline: None,
23000      drain_linger: DEFAULT_DRAIN_LINGER,
23001    }
23002    .validate()
23003    .expect_err("worker_threads == 0 must be rejected");
23004    assert_eq!(
23005      error.to_string(),
23006      "worker_threads must be greater than zero"
23007    );
23008  }
23009
23010  #[test]
23011  fn validate_rejects_zero_max_blocking_tasks() {
23012    let error = RuntimeOptions {
23013      flavor: RuntimeFlavor::MultiThread,
23014      worker_threads: 2,
23015      max_blocking_tasks: 0,
23016      thread_name_prefix: "rd8".to_string(),
23017      park_deadline: None,
23018      drain_linger: DEFAULT_DRAIN_LINGER,
23019    }
23020    .validate()
23021    .expect_err("max_blocking_tasks == 0 must be rejected");
23022    assert_eq!(
23023      error.to_string(),
23024      "max_blocking_tasks must be greater than zero"
23025    );
23026  }
23027
23028  #[test]
23029  fn validate_current_thread_coerces_worker_threads_to_one() {
23030    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
23031    // clamped to `.min(worker_threads)` == 1.
23032    let validated = RuntimeOptions {
23033      flavor: RuntimeFlavor::CurrentThread,
23034      worker_threads: 8,
23035      max_blocking_tasks: 8,
23036      thread_name_prefix: "rd8".to_string(),
23037      park_deadline: None,
23038      drain_linger: DEFAULT_DRAIN_LINGER,
23039    }
23040    .validate()
23041    .expect("CurrentThread options must validate");
23042    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
23043    assert_eq!(validated.worker_threads, 1);
23044    assert_eq!(validated.max_blocking_tasks, 1);
23045  }
23046
23047  // Native-only: `validate()` rejects `MultiThread` under `cfg(target_family = "wasm")`
23048  // (after applying the clamp), so this success assertion is valid off-wasm only.
23049  #[cfg(not(target_family = "wasm"))]
23050  #[test]
23051  fn validate_reserves_one_worker_from_blocking_admission() {
23052    let validated = RuntimeOptions {
23053      flavor: RuntimeFlavor::MultiThread,
23054      worker_threads: 2,
23055      max_blocking_tasks: 8,
23056      thread_name_prefix: "rd8".to_string(),
23057      park_deadline: None,
23058      drain_linger: DEFAULT_DRAIN_LINGER,
23059    }
23060    .validate()
23061    .expect("MultiThread options must validate");
23062    assert_eq!(validated.worker_threads, 2);
23063    assert_eq!(validated.max_blocking_tasks, 1);
23064  }
23065
23066  #[cfg(not(target_family = "wasm"))]
23067  #[test]
23068  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
23069    let validated = RuntimeOptions {
23070      flavor: RuntimeFlavor::MultiThread,
23071      worker_threads: 1,
23072      max_blocking_tasks: 8,
23073      thread_name_prefix: "rd8-minimum".to_string(),
23074      park_deadline: None,
23075      drain_linger: DEFAULT_DRAIN_LINGER,
23076    }
23077    .validate()
23078    .expect("MultiThread options must validate");
23079    assert_eq!(validated.worker_threads, 2);
23080    assert_eq!(validated.max_blocking_tasks, 1);
23081  }
23082
23083  #[cfg(not(target_family = "wasm"))]
23084  #[test]
23085  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
23086    let simulated_32_bit = RuntimeOptions {
23087      flavor: RuntimeFlavor::MultiThread,
23088      worker_threads: 256,
23089      max_blocking_tasks: 256,
23090      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
23091      park_deadline: None,
23092      drain_linger: DEFAULT_DRAIN_LINGER,
23093    }
23094    .validate_with_rayon_max_threads(Some(255))
23095    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
23096    assert_eq!(simulated_32_bit.worker_threads, 255);
23097    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
23098
23099    let rayon_max_threads = max_num_threads();
23100    let requested_threads = rayon_max_threads
23101      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
23102      .checked_add(1)
23103      .expect("supported worker counts must leave room for an oversized request");
23104    let controller = RuntimeController::new();
23105    controller
23106      .configure(RuntimeOptions {
23107        flavor: RuntimeFlavor::MultiThread,
23108        worker_threads: requested_threads,
23109        max_blocking_tasks: requested_threads,
23110        thread_name_prefix: "rd8-rayon-cap".to_string(),
23111        park_deadline: None,
23112        drain_linger: DEFAULT_DRAIN_LINGER,
23113      })
23114      .expect("an oversized request must clamp to the production worker ceiling");
23115
23116    let reported = controller.options();
23117    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
23118    assert_eq!(reported.worker_threads, expected_workers);
23119    assert_eq!(
23120      reported.max_blocking_tasks,
23121      expected_workers - 1,
23122      "the blocking cap must reserve a lane from the physically realizable worker count"
23123    );
23124  }
23125
23126  #[cfg(not(target_family = "wasm"))]
23127  #[test]
23128  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
23129    // u64::MAX microseconds (the exact value an unvalidated
23130    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
23131    // via the removed OnceLock) must clamp to the ceiling everywhere a
23132    // budget enters the system: full options validation, a partial patch
23133    // through the controller, and the executor's defensive constructor
23134    // clamp for direct unvalidated test construction.
23135    let extreme = Duration::from_micros(u64::MAX);
23136    let validated = RuntimeOptions {
23137      flavor: RuntimeFlavor::MultiThread,
23138      worker_threads: 2,
23139      max_blocking_tasks: 1,
23140      thread_name_prefix: "linger-clamp".to_string(),
23141      park_deadline: None,
23142      drain_linger: extreme,
23143    }
23144    .validate_with_rayon_max_threads(Some(2))
23145    .expect("an extreme linger budget must clamp, not fail validation");
23146    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
23147
23148    let controller = RuntimeController::new();
23149    controller
23150      .configure_partial(RuntimeOptionsPatch {
23151        drain_linger: Some(extreme),
23152        ..RuntimeOptionsPatch::default()
23153      })
23154      .expect("an extreme linger patch must clamp, not fail validation");
23155    assert_eq!(
23156      controller.options().drain_linger,
23157      MAX_DRAIN_LINGER,
23158      "introspection must report the clamped budget, not the requested one"
23159    );
23160    controller
23161      .configure_partial(RuntimeOptionsPatch {
23162        drain_linger: Some(Duration::ZERO),
23163        ..RuntimeOptionsPatch::default()
23164      })
23165      .expect("ZERO (lingering disabled) must remain a valid budget");
23166    assert_eq!(controller.options().drain_linger, Duration::ZERO);
23167
23168    let metrics = Arc::new(RuntimeMetrics::default());
23169    let unvalidated = RuntimeOptions {
23170      flavor: RuntimeFlavor::MultiThread,
23171      worker_threads: 2,
23172      max_blocking_tasks: 2,
23173      thread_name_prefix: "linger-clamp-exec".to_string(),
23174      park_deadline: None,
23175      drain_linger: extreme,
23176    };
23177    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
23178      .expect("the executor must construct with an extreme unvalidated budget");
23179    assert_eq!(
23180      executor.drain_linger,
23181      Some(MAX_DRAIN_LINGER),
23182      "the constructor's defensive clamp must bound unvalidated budgets"
23183    );
23184
23185    let metrics = Arc::new(RuntimeMetrics::default());
23186    let disabled = RuntimeOptions {
23187      flavor: RuntimeFlavor::MultiThread,
23188      worker_threads: 2,
23189      max_blocking_tasks: 2,
23190      thread_name_prefix: "linger-clamp-off".to_string(),
23191      park_deadline: None,
23192      drain_linger: Duration::ZERO,
23193    };
23194    let executor = MultiThreadExecutor::new(&disabled, metrics)
23195      .expect("the executor must construct with lingering disabled");
23196    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
23197  }
23198
23199  #[cfg(not(target_family = "wasm"))]
23200  #[test]
23201  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
23202    let controller = RuntimeController::new();
23203    let options = controller.options();
23204    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
23205    assert_eq!(
23206      options.max_blocking_tasks,
23207      options.worker_threads.saturating_sub(1).max(1),
23208      "the unconfigured Rust API must use the same validated reserve as the binding"
23209    );
23210  }
23211
23212  #[cfg(not(target_family = "wasm"))]
23213  #[test]
23214  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
23215    use std::{sync::mpsc, time::Duration};
23216
23217    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
23218    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
23219    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
23220    let first_controller = Arc::clone(&controller);
23221    let first = std::thread::spawn(move || {
23222      first_controller
23223        .configure_partial_inner(
23224          RuntimeOptionsPatch {
23225            max_blocking_tasks: Some(3),
23226            ..RuntimeOptionsPatch::default()
23227          },
23228          |candidate| {
23229            assert_eq!(
23230              (candidate.worker_threads, candidate.max_blocking_tasks),
23231              (8, 3)
23232            );
23233            first_merged_tx
23234              .send(())
23235              .expect("the race coordinator must still be listening");
23236            release_first_rx
23237              .recv_timeout(Duration::from_secs(5))
23238              .expect("the first partial configuration must be released");
23239          },
23240        )
23241        .expect("the first partial configuration must commit");
23242    });
23243    first_merged_rx
23244      .recv_timeout(Duration::from_secs(5))
23245      .expect("the first partial configuration must hold the controller lock after merging");
23246
23247    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
23248    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
23249    let second_controller = Arc::clone(&controller);
23250    let second = std::thread::spawn(move || {
23251      second_started_tx
23252        .send(())
23253        .expect("the race coordinator must still be listening");
23254      second_controller
23255        .configure_partial_inner(
23256          RuntimeOptionsPatch {
23257            worker_threads: Some(6),
23258            ..RuntimeOptionsPatch::default()
23259          },
23260          |candidate| {
23261            second_candidate_tx
23262              .send((candidate.worker_threads, candidate.max_blocking_tasks))
23263              .expect("the race coordinator must still be listening");
23264          },
23265        )
23266        .expect("the second partial configuration must commit");
23267    });
23268    second_started_rx
23269      .recv_timeout(Duration::from_secs(5))
23270      .expect("the second partial configuration thread must start");
23271    release_first_tx
23272      .send(())
23273      .expect("the first partial configuration must still be waiting");
23274
23275    first
23276      .join()
23277      .expect("the first configuration thread must not panic");
23278    second
23279      .join()
23280      .expect("the second configuration thread must not panic");
23281    assert_eq!(
23282      second_candidate_rx
23283        .recv_timeout(Duration::from_secs(5))
23284        .expect("the second partial configuration must expose its merged candidate"),
23285      (6, 3),
23286      "the second caller must merge against the first caller's committed options"
23287    );
23288
23289    let options = controller.options();
23290    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
23291    assert_eq!(options.thread_name_prefix, "partial-config-race");
23292  }
23293
23294  #[cfg(not(target_family = "wasm"))]
23295  #[test]
23296  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
23297    use std::{sync::mpsc, time::Duration};
23298
23299    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
23300    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
23301    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
23302    let invalid_controller = Arc::clone(&controller);
23303    let invalid = std::thread::spawn(move || {
23304      invalid_controller
23305        .configure_partial_inner(
23306          RuntimeOptionsPatch {
23307            worker_threads: Some(0),
23308            ..RuntimeOptionsPatch::default()
23309          },
23310          |candidate| {
23311            assert_eq!(
23312              (candidate.worker_threads, candidate.max_blocking_tasks),
23313              (0, 2)
23314            );
23315            invalid_merged_tx
23316              .send(())
23317              .expect("the race coordinator must still be listening");
23318            release_invalid_rx
23319              .recv_timeout(Duration::from_secs(5))
23320              .expect("the invalid partial configuration must be released");
23321          },
23322        )
23323        .expect_err("the invalid partial configuration must be rejected")
23324        .to_string()
23325    });
23326    invalid_merged_rx
23327      .recv_timeout(Duration::from_secs(5))
23328      .expect("the invalid partial configuration must hold the lock after merging");
23329
23330    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
23331    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
23332    let valid_controller = Arc::clone(&controller);
23333    let valid = std::thread::spawn(move || {
23334      valid_started_tx
23335        .send(())
23336        .expect("the race coordinator must still be listening");
23337      valid_controller
23338        .configure_partial_inner(
23339          RuntimeOptionsPatch {
23340            max_blocking_tasks: Some(4),
23341            ..RuntimeOptionsPatch::default()
23342          },
23343          |candidate| {
23344            valid_candidate_tx
23345              .send((candidate.worker_threads, candidate.max_blocking_tasks))
23346              .expect("the race coordinator must still be listening");
23347          },
23348        )
23349        .expect("the valid partial configuration must commit");
23350    });
23351    valid_started_rx
23352      .recv_timeout(Duration::from_secs(5))
23353      .expect("the valid partial configuration thread must start");
23354    release_invalid_tx
23355      .send(())
23356      .expect("the invalid partial configuration must still be waiting");
23357
23358    assert_eq!(
23359      invalid
23360        .join()
23361        .expect("the invalid configuration thread must not panic"),
23362      "worker_threads must be greater than zero"
23363    );
23364    valid
23365      .join()
23366      .expect("the valid configuration thread must not panic");
23367    assert_eq!(
23368      valid_candidate_rx
23369        .recv_timeout(Duration::from_secs(5))
23370        .expect("the valid partial configuration must expose its merged candidate"),
23371      (6, 4),
23372      "the valid caller must merge against the last committed options, not the rejected candidate"
23373    );
23374
23375    let options = controller.options();
23376    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
23377    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
23378  }
23379
23380  #[test]
23381  fn configure_after_backend_started_is_rejected() {
23382    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
23383    // so starting it is cheap and spawns no OS threads.
23384    let controller = RuntimeController::new();
23385    controller
23386      .configure(RuntimeOptions {
23387        flavor: RuntimeFlavor::CurrentThread,
23388        worker_threads: 1,
23389        max_blocking_tasks: 1,
23390        thread_name_prefix: "rd8".to_string(),
23391        park_deadline: None,
23392        drain_linger: DEFAULT_DRAIN_LINGER,
23393      })
23394      .expect("first configure before the backend exists must succeed");
23395
23396    // Materialize the backend; further configuration must now be refused.
23397    let _backend = controller.backend();
23398
23399    let error = controller
23400      .configure(RuntimeOptions::default())
23401      .expect_err("configure after the backend started must be rejected");
23402    assert_eq!(
23403      error.to_string(),
23404      "the async runtime configuration is frozen; configure it before the first async call"
23405    );
23406  }
23407
23408  #[test]
23409  fn partial_configuration_after_backend_started_is_rejected() {
23410    let controller = current_thread_controller("partial-config-frozen");
23411    let _backend = controller.backend();
23412
23413    let error = controller
23414      .configure_partial(RuntimeOptionsPatch {
23415        worker_threads: Some(2),
23416        ..RuntimeOptionsPatch::default()
23417      })
23418      .expect_err("partial configuration after the backend started must be rejected");
23419    assert_eq!(
23420      error.to_string(),
23421      "the async runtime configuration is frozen; configure it before the first async call"
23422    );
23423  }
23424
23425  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
23426    let controller = RuntimeController::new();
23427    controller
23428      .configure(RuntimeOptions {
23429        flavor: RuntimeFlavor::CurrentThread,
23430        worker_threads: 1,
23431        max_blocking_tasks: 1,
23432        thread_name_prefix: thread_name_prefix.to_string(),
23433        park_deadline: None,
23434        drain_linger: DEFAULT_DRAIN_LINGER,
23435      })
23436      .expect("CurrentThread test configuration must be accepted");
23437    controller
23438  }
23439
23440  #[cfg(not(target_family = "wasm"))]
23441  fn multi_thread_controller(
23442    thread_name_prefix: &str,
23443    worker_threads: usize,
23444    max_blocking_tasks: usize,
23445  ) -> RuntimeController {
23446    let controller = RuntimeController::new();
23447    controller
23448      .configure(RuntimeOptions {
23449        flavor: RuntimeFlavor::MultiThread,
23450        worker_threads,
23451        max_blocking_tasks,
23452        thread_name_prefix: thread_name_prefix.to_string(),
23453        park_deadline: None,
23454        drain_linger: DEFAULT_DRAIN_LINGER,
23455      })
23456      .expect("MultiThread test configuration must be accepted");
23457    controller
23458  }
23459
23460  #[test]
23461  fn blocking_backend_creation_failure_preserves_diagnostic() {
23462    let controller = current_thread_controller("blocking-backend-creation-failure");
23463    let ran = Arc::new(AtomicBool::new(false));
23464    let work_ran = Arc::clone(&ran);
23465
23466    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23467    let Err((error, work)) = controller.try_spawn_blocking(move || {
23468      work_ran.store(true, Ordering::SeqCst);
23469      17usize
23470    }) else {
23471      panic!("the injected backend creation failure must reject blocking work");
23472    };
23473    assert_eq!(
23474      error.to_string(),
23475      "injected async runtime backend creation failure"
23476    );
23477    assert!(
23478      !ran.load(Ordering::SeqCst),
23479      "rejected blocking work must not run"
23480    );
23481    assert_eq!(
23482      work(),
23483      17,
23484      "the rejected blocking closure must remain usable"
23485    );
23486    assert!(ran.load(Ordering::SeqCst));
23487
23488    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23489    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
23490      .expect_err("infallible blocking submission must return a failed join handle");
23491    assert_eq!(
23492      error.to_string(),
23493      "injected async runtime backend creation failure"
23494    );
23495
23496    let handle = controller
23497      .try_spawn_blocking(|| 31usize)
23498      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
23499    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
23500    controller.shutdown().unwrap();
23501  }
23502
23503  #[cfg(not(target_family = "wasm"))]
23504  #[test]
23505  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
23506    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
23507
23508    if std::env::var_os(CHILD_ENV).is_some() {
23509      struct PollProbe {
23510        polls: Arc<AtomicUsize>,
23511      }
23512
23513      impl Future for PollProbe {
23514        type Output = ();
23515
23516        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23517          self.polls.fetch_add(1, Ordering::SeqCst);
23518          Poll::Ready(())
23519        }
23520      }
23521
23522      shutdown().expect("the isolated runtime must stop before block_on admission");
23523      let polls = Arc::new(AtomicUsize::new(0));
23524      let mut future = std::pin::pin!(PollProbe {
23525        polls: Arc::clone(&polls)
23526      });
23527      let error = try_block_on_dyn(future.as_mut())
23528        .expect_err("the stopped runtime must reject the borrowed future");
23529      assert_eq!(
23530        error.to_string(),
23531        "the async runtime is stopped; call start before submitting work"
23532      );
23533      assert_eq!(
23534        polls.load(Ordering::SeqCst),
23535        0,
23536        "rejected block_on must not poll its future"
23537      );
23538
23539      start().expect("the isolated runtime must restart");
23540      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
23541      assert_eq!(polls.load(Ordering::SeqCst), 1);
23542      shutdown().expect("the restarted isolated runtime must stop");
23543      return;
23544    }
23545
23546    let output = std::process::Command::new(std::env::current_exe().unwrap())
23547      .arg("--exact")
23548      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
23549      .arg("--nocapture")
23550      .env(CHILD_ENV, "1")
23551      .output()
23552      .expect("the stopped block_on subprocess must start");
23553    assert!(
23554      output.status.success(),
23555      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
23556      output.status.code(),
23557      String::from_utf8_lossy(&output.stdout),
23558      String::from_utf8_lossy(&output.stderr)
23559    );
23560  }
23561
23562  #[cfg(not(target_family = "wasm"))]
23563  #[test]
23564  fn try_block_on_dyn_reports_concurrent_shutdown() {
23565    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
23566
23567    if std::env::var_os(CHILD_ENV).is_some() {
23568      use std::sync::mpsc;
23569
23570      struct PendingProbe {
23571        entered: Option<mpsc::Sender<()>>,
23572        complete: Arc<AtomicBool>,
23573      }
23574
23575      impl Future for PendingProbe {
23576        type Output = ();
23577
23578        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23579          if let Some(entered) = self.entered.take() {
23580            entered.send(()).unwrap();
23581          }
23582          if self.complete.load(Ordering::SeqCst) {
23583            Poll::Ready(())
23584          } else {
23585            Poll::Pending
23586          }
23587        }
23588      }
23589
23590      configure(RuntimeOptions {
23591        flavor: RuntimeFlavor::CurrentThread,
23592        worker_threads: 1,
23593        max_blocking_tasks: 1,
23594        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
23595        park_deadline: None,
23596        drain_linger: DEFAULT_DRAIN_LINGER,
23597      })
23598      .expect("the isolated runtime must accept CurrentThread configuration");
23599      start().expect("the isolated runtime must start");
23600
23601      let (entered_tx, entered_rx) = mpsc::channel();
23602      let (error_tx, error_rx) = mpsc::channel();
23603      let (retry_tx, retry_rx) = mpsc::channel();
23604      let complete = Arc::new(AtomicBool::new(false));
23605      let future_complete = Arc::clone(&complete);
23606      let runner = std::thread::spawn(move || {
23607        let mut future = std::pin::pin!(PendingProbe {
23608          entered: Some(entered_tx),
23609          complete: future_complete
23610        });
23611        let error = try_block_on_dyn(future.as_mut())
23612          .expect_err("concurrent shutdown must interrupt the borrowed future")
23613          .to_string();
23614        error_tx.send(error).unwrap();
23615        retry_rx.recv().unwrap();
23616        try_block_on_dyn(future.as_mut())
23617          .expect("restart must accept the same interrupted borrowed future");
23618      });
23619      entered_rx
23620        .recv_timeout(Duration::from_secs(2))
23621        .expect("the borrowed future must be polled before shutdown");
23622      shutdown().expect("shutdown must wait for the active block_on scope to retire");
23623      assert_eq!(
23624        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
23625        "the async runtime stopped before block_on completed its future"
23626      );
23627      start().expect("the isolated runtime must restart after the interrupted drive");
23628      complete.store(true, Ordering::SeqCst);
23629      retry_tx.send(()).unwrap();
23630      runner.join().unwrap();
23631      shutdown().expect("the retried isolated runtime must stop");
23632      return;
23633    }
23634
23635    let output = std::process::Command::new(std::env::current_exe().unwrap())
23636      .arg("--exact")
23637      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
23638      .arg("--nocapture")
23639      .env(CHILD_ENV, "1")
23640      .output()
23641      .expect("the concurrent block_on shutdown subprocess must start");
23642    assert!(
23643      output.status.success(),
23644      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
23645      output.status.code(),
23646      String::from_utf8_lossy(&output.stdout),
23647      String::from_utf8_lossy(&output.stderr)
23648    );
23649  }
23650
23651  #[test]
23652  fn initial_lifecycle_start_preserves_lazy_configuration() {
23653    let controller = RuntimeController::new();
23654    controller
23655      .start()
23656      .expect("initial lifecycle start must succeed");
23657    controller
23658      .configure(RuntimeOptions {
23659        flavor: RuntimeFlavor::CurrentThread,
23660        worker_threads: 1,
23661        max_blocking_tasks: 1,
23662        thread_name_prefix: "lifecycle-initial-start".to_string(),
23663        park_deadline: None,
23664        drain_linger: DEFAULT_DRAIN_LINGER,
23665      })
23666      .expect("module-registration start must not consume the configuration window");
23667
23668    let state = controller
23669      .state
23670      .lock()
23671      .unwrap_or_else(std::sync::PoisonError::into_inner);
23672    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23673  }
23674
23675  #[test]
23676  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
23677    let controller = RuntimeController::new();
23678
23679    for _ in 0..3 {
23680      controller
23681        .start()
23682        .expect("zero-work lifecycle start must succeed");
23683      assert!(matches!(
23684        &controller
23685          .state
23686          .lock()
23687          .unwrap_or_else(std::sync::PoisonError::into_inner)
23688          .lifecycle,
23689        RuntimeLifecycle::Initial
23690      ));
23691
23692      controller
23693        .shutdown()
23694        .expect("zero-work lifecycle shutdown must succeed");
23695      assert!(matches!(
23696        &controller
23697          .state
23698          .lock()
23699          .unwrap_or_else(std::sync::PoisonError::into_inner)
23700          .lifecycle,
23701        RuntimeLifecycle::StoppedBeforeFirstUse
23702      ));
23703    }
23704
23705    controller
23706      .start()
23707      .expect("a later zero-work restart must remain lazy");
23708    controller
23709      .configure(RuntimeOptions {
23710        flavor: RuntimeFlavor::CurrentThread,
23711        worker_threads: 1,
23712        max_blocking_tasks: 1,
23713        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
23714        park_deadline: None,
23715        drain_linger: DEFAULT_DRAIN_LINGER,
23716      })
23717      .expect("zero-work lifecycle cycles must not consume the configuration window");
23718
23719    let state = controller
23720      .state
23721      .lock()
23722      .unwrap_or_else(std::sync::PoisonError::into_inner);
23723    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23724  }
23725
23726  #[cfg(not(target_family = "wasm"))]
23727  #[test]
23728  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
23729    use std::sync::mpsc;
23730
23731    struct BlockingDropFuture {
23732      entered: mpsc::Sender<()>,
23733      release: mpsc::Receiver<()>,
23734    }
23735
23736    impl Future for BlockingDropFuture {
23737      type Output = ();
23738
23739      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23740        Poll::Pending
23741      }
23742    }
23743
23744    impl Drop for BlockingDropFuture {
23745      fn drop(&mut self) {
23746        self.entered.send(()).unwrap();
23747        self.release.recv().unwrap();
23748      }
23749    }
23750
23751    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
23752    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23753    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23754    let submit_controller = Arc::clone(&controller);
23755    let submitter = std::thread::spawn(move || {
23756      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23757      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23758        entered: drop_entered_tx,
23759        release: release_drop_rx,
23760      }));
23761      assert!(
23762        result.is_err(),
23763        "the injected backend creation failure must reject the submission"
23764      );
23765    });
23766    drop_entered_rx
23767      .recv_timeout(Duration::from_secs(2))
23768      .expect("the rejected future destructor must start");
23769
23770    let retry_ran = Arc::new(AtomicBool::new(false));
23771    let retry_ran_task = Arc::clone(&retry_ran);
23772    let Err(retry) = controller.try_spawn_detached(async move {
23773      retry_ran_task.store(true, Ordering::SeqCst);
23774    }) else {
23775      panic!("backend creation must remain closed during rejected destruction");
23776    };
23777    drop(retry);
23778    assert!(!retry_ran.load(Ordering::SeqCst));
23779
23780    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23781    let shutdown_controller = Arc::clone(&controller);
23782    let shutdown = std::thread::spawn(move || {
23783      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23784    });
23785    wait_until("initial shutdown to close runtime admission", || {
23786      matches!(
23787        controller
23788          .state
23789          .lock()
23790          .unwrap_or_else(std::sync::PoisonError::into_inner)
23791          .lifecycle,
23792        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
23793      )
23794    });
23795    assert!(
23796      shutdown_rx
23797        .recv_timeout(Duration::from_millis(200))
23798        .is_err(),
23799      "initial shutdown must wait for rejected user state destruction"
23800    );
23801    assert!(
23802      controller.try_spawn_detached(async {}).is_err(),
23803      "shutdown must keep first-generation admission closed while it waits"
23804    );
23805
23806    let (start_tx, start_rx) = mpsc::channel();
23807    let start_controller = Arc::clone(&controller);
23808    let starter = std::thread::spawn(move || {
23809      start_tx.send(start_controller.start()).unwrap();
23810    });
23811    assert!(
23812      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23813      "start must wait for the zero-backend shutdown transition to finish"
23814    );
23815
23816    release_drop_tx.send(()).unwrap();
23817    submitter.join().unwrap();
23818    shutdown_rx
23819      .recv_timeout(Duration::from_secs(2))
23820      .unwrap()
23821      .unwrap();
23822    shutdown.join().unwrap();
23823    start_rx
23824      .recv_timeout(Duration::from_secs(2))
23825      .unwrap()
23826      .unwrap();
23827    starter.join().unwrap();
23828
23829    let ran = Arc::new(AtomicBool::new(false));
23830    let ran_task = Arc::clone(&ran);
23831    controller
23832      .try_spawn_detached(async move {
23833        ran_task.store(true, Ordering::SeqCst);
23834      })
23835      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
23836    let dispatch = controller_current_thread_dispatch(&controller);
23837    controller.drive_current_thread_dispatch(dispatch, false);
23838    assert!(ran.load(Ordering::SeqCst));
23839    controller.shutdown().unwrap();
23840  }
23841
23842  #[cfg(not(target_family = "wasm"))]
23843  #[test]
23844  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
23845    use std::sync::mpsc;
23846
23847    struct BlockingDropFuture {
23848      entered: mpsc::Sender<()>,
23849      release: mpsc::Receiver<()>,
23850    }
23851
23852    impl Future for BlockingDropFuture {
23853      type Output = ();
23854
23855      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23856        Poll::Pending
23857      }
23858    }
23859
23860    impl Drop for BlockingDropFuture {
23861      fn drop(&mut self) {
23862        self.entered.send(()).unwrap();
23863        self.release.recv().unwrap();
23864      }
23865    }
23866
23867    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
23868    let old_generation = controller.backend().generation();
23869    controller.shutdown().unwrap();
23870
23871    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
23872    let (release_drop_tx, release_drop_rx) = mpsc::channel();
23873    let submit_controller = Arc::clone(&controller);
23874    let submitter = std::thread::spawn(move || {
23875      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
23876        entered: drop_entered_tx,
23877        release: release_drop_rx,
23878      }));
23879      assert!(
23880        result.is_err(),
23881        "the stopped runtime must reject the submission"
23882      );
23883    });
23884    drop_entered_rx
23885      .recv_timeout(Duration::from_secs(2))
23886      .unwrap();
23887
23888    let (shutdown_tx, shutdown_rx) = mpsc::channel();
23889    let shutdown_controller = Arc::clone(&controller);
23890    let shutdown = std::thread::spawn(move || {
23891      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
23892    });
23893    wait_until(
23894      "stopped shutdown to enter its zero-backend transition",
23895      || {
23896        matches!(
23897          controller
23898            .state
23899            .lock()
23900            .unwrap_or_else(std::sync::PoisonError::into_inner)
23901            .lifecycle,
23902          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
23903        )
23904      },
23905    );
23906
23907    let (start_tx, start_rx) = mpsc::channel();
23908    let start_controller = Arc::clone(&controller);
23909    let starter = std::thread::spawn(move || {
23910      start_tx.send(start_controller.start()).unwrap();
23911    });
23912    assert!(
23913      shutdown_rx
23914        .recv_timeout(Duration::from_millis(200))
23915        .is_err(),
23916      "stopped shutdown must wait for rejected destruction"
23917    );
23918    assert!(
23919      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
23920      "restart must wait for the stopped zero-backend transition"
23921    );
23922
23923    release_drop_tx.send(()).unwrap();
23924    submitter.join().unwrap();
23925    shutdown_rx
23926      .recv_timeout(Duration::from_secs(2))
23927      .unwrap()
23928      .unwrap();
23929    shutdown.join().unwrap();
23930    start_rx
23931      .recv_timeout(Duration::from_secs(2))
23932      .unwrap()
23933      .unwrap();
23934    starter.join().unwrap();
23935
23936    let new_generation = controller.backend().generation();
23937    assert_ne!(new_generation, old_generation);
23938    controller.shutdown().unwrap();
23939  }
23940
23941  #[test]
23942  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
23943    use std::sync::mpsc;
23944
23945    struct ShutdownOnDrop {
23946      controller: Arc<RuntimeController>,
23947      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
23948    }
23949
23950    impl Future for ShutdownOnDrop {
23951      type Output = ();
23952
23953      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
23954        Poll::Pending
23955      }
23956    }
23957
23958    impl Drop for ShutdownOnDrop {
23959      fn drop(&mut self) {
23960        self.result.send(self.controller.shutdown()).unwrap();
23961      }
23962    }
23963
23964    let controller = Arc::new(current_thread_controller(
23965      "failed-initial-reentrant-shutdown",
23966    ));
23967    let (result_tx, result_rx) = mpsc::channel();
23968    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
23969    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
23970      controller: Arc::clone(&controller),
23971      result: result_tx,
23972    }));
23973    assert!(rejected.is_err());
23974
23975    let error = result_rx
23976      .recv_timeout(Duration::from_secs(1))
23977      .expect("the rejected destructor must not self-deadlock")
23978      .expect_err("rejected destruction cannot wait for its own shutdown");
23979    assert!(error.to_string().contains("rejected submission"));
23980    {
23981      let state = controller
23982        .state
23983        .lock()
23984        .unwrap_or_else(std::sync::PoisonError::into_inner);
23985      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
23986      assert_eq!(state.rejected_drops, 0);
23987    }
23988
23989    let handle = controller
23990      .try_spawn(async { 23usize })
23991      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
23992    let dispatch = controller_current_thread_dispatch(&controller);
23993    controller.drive_current_thread_dispatch(dispatch, false);
23994    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
23995    controller.shutdown().unwrap();
23996  }
23997
23998  #[test]
23999  fn failed_initial_submission_destructor_cannot_reenter_start() {
24000    use std::sync::mpsc;
24001
24002    struct StartOnDrop {
24003      controller: Arc<RuntimeController>,
24004      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
24005      release: mpsc::Receiver<()>,
24006    }
24007
24008    impl Future for StartOnDrop {
24009      type Output = ();
24010
24011      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24012        Poll::Pending
24013      }
24014    }
24015
24016    impl Drop for StartOnDrop {
24017      fn drop(&mut self) {
24018        self.result.send(self.controller.start()).unwrap();
24019        self.release.recv().unwrap();
24020      }
24021    }
24022
24023    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
24024    let (result_tx, result_rx) = mpsc::channel();
24025    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24026    let (submission_tx, submission_rx) = mpsc::channel();
24027    let submit_controller = Arc::clone(&controller);
24028    let submitter = std::thread::spawn(move || {
24029      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24030      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
24031        controller: Arc::clone(&submit_controller),
24032        result: result_tx,
24033        release: release_drop_rx,
24034      }));
24035      submission_tx.send(rejected).unwrap();
24036    });
24037
24038    let error = result_rx
24039      .recv_timeout(Duration::from_secs(1))
24040      .expect("the rejected destructor must not self-deadlock")
24041      .expect_err("rejected destruction must not report a successful runtime start");
24042    assert!(error.to_string().contains("rejected submission"));
24043    {
24044      let state = controller
24045        .state
24046        .lock()
24047        .unwrap_or_else(std::sync::PoisonError::into_inner);
24048      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24049      assert_eq!(state.rejected_drops, 1);
24050    }
24051
24052    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
24053    let (external_start_tx, external_start_rx) = mpsc::channel();
24054    let start_controller = Arc::clone(&controller);
24055    let starter = std::thread::spawn(move || {
24056      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
24057        *slot.borrow_mut() = Some(Box::new(move || {
24058          wait_entered_tx.send(()).unwrap();
24059        }));
24060      });
24061      external_start_tx.send(start_controller.start()).unwrap();
24062    });
24063    wait_entered_rx
24064      .recv_timeout(Duration::from_secs(1))
24065      .expect("external start must reach the Initial rejected-destruction wait");
24066    assert!(
24067      external_start_rx.try_recv().is_err(),
24068      "external start must not return while rejected destruction remains active"
24069    );
24070
24071    release_drop_tx.send(()).unwrap();
24072    assert!(
24073      submission_rx
24074        .recv_timeout(Duration::from_secs(2))
24075        .unwrap()
24076        .is_err()
24077    );
24078    submitter.join().unwrap();
24079    external_start_rx
24080      .recv_timeout(Duration::from_secs(2))
24081      .unwrap()
24082      .unwrap();
24083    starter.join().unwrap();
24084
24085    {
24086      let state = controller
24087        .state
24088        .lock()
24089        .unwrap_or_else(std::sync::PoisonError::into_inner);
24090      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
24091      assert_eq!(state.rejected_drops, 0);
24092    }
24093
24094    let handle = controller
24095      .try_spawn(async { 31usize })
24096      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
24097    let dispatch = controller_current_thread_dispatch(&controller);
24098    controller.drive_current_thread_dispatch(dispatch, false);
24099    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
24100    controller.shutdown().unwrap();
24101  }
24102
24103  #[test]
24104  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
24105    use std::sync::mpsc;
24106
24107    struct BlockingDropFuture {
24108      controller: Arc<RuntimeController>,
24109      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24110      release: mpsc::Receiver<()>,
24111    }
24112
24113    impl Future for BlockingDropFuture {
24114      type Output = ();
24115
24116      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24117        Poll::Pending
24118      }
24119    }
24120
24121    impl Drop for BlockingDropFuture {
24122      fn drop(&mut self) {
24123        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
24124        self
24125          .entered
24126          .send((generation, self.controller.shutdown()))
24127          .unwrap();
24128        self.release.recv().unwrap();
24129      }
24130    }
24131
24132    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
24133    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24134    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24135    let (runner_done_tx, runner_done_rx) = mpsc::channel();
24136    let runner_controller = Arc::clone(&controller);
24137    let runner = std::thread::spawn(move || {
24138      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
24139      let result = catch_unwind(AssertUnwindSafe(|| {
24140        block_on_with_controller(
24141          &runner_controller,
24142          BlockingDropFuture {
24143            controller: Arc::clone(&runner_controller),
24144            entered: drop_entered_tx,
24145            release: release_drop_rx,
24146          },
24147        );
24148      }));
24149      runner_done_tx.send(result.is_err()).unwrap();
24150    });
24151
24152    let (generation, reentrant_shutdown) = drop_entered_rx
24153      .recv_timeout(Duration::from_secs(2))
24154      .expect("failed block_on acquisition must destroy its owned future");
24155    assert_eq!(generation, None, "no runtime generation was created");
24156    let error = reentrant_shutdown
24157      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
24158    assert!(error.to_string().contains("rejected submission"));
24159
24160    let Err(retry_error) = controller.try_backend() else {
24161      panic!("backend retry must remain closed during rejected block_on destruction");
24162    };
24163    assert!(retry_error.to_string().contains("rejected submission"));
24164
24165    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24166    let shutdown_controller = Arc::clone(&controller);
24167    let shutdown = std::thread::spawn(move || {
24168      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24169    });
24170    assert!(
24171      shutdown_rx
24172        .recv_timeout(Duration::from_millis(200))
24173        .is_err(),
24174      "initial shutdown must wait for rejected block_on destruction"
24175    );
24176
24177    release_drop_tx.send(()).unwrap();
24178    assert!(
24179      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
24180      "block_on must preserve its infallible panic contract after contained cleanup"
24181    );
24182    runner.join().unwrap();
24183    shutdown_rx
24184      .recv_timeout(Duration::from_secs(2))
24185      .unwrap()
24186      .unwrap();
24187    shutdown.join().unwrap();
24188
24189    controller
24190      .start()
24191      .expect("zero-generation shutdown must remain restartable");
24192    let ran = Arc::new(AtomicBool::new(false));
24193    let ran_task = Arc::clone(&ran);
24194    controller
24195      .try_spawn_detached(async move {
24196        ran_task.store(true, Ordering::SeqCst);
24197      })
24198      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
24199    let dispatch = controller_current_thread_dispatch(&controller);
24200    controller.drive_current_thread_dispatch(dispatch, false);
24201    assert!(ran.load(Ordering::SeqCst));
24202    controller.shutdown().unwrap();
24203  }
24204
24205  #[test]
24206  fn public_block_on_contains_poll_and_drop_double_panic() {
24207    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
24208
24209    if std::env::var_os(CHILD_ENV).is_some() {
24210      struct PanicInPollAndDrop;
24211
24212      impl Future for PanicInPollAndDrop {
24213        type Output = ();
24214
24215        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24216          panic!("intentional block_on poll panic");
24217        }
24218      }
24219
24220      impl Drop for PanicInPollAndDrop {
24221        fn drop(&mut self) {
24222          panic!("intentional block_on future destructor panic");
24223        }
24224      }
24225
24226      let controller = current_thread_controller("block-on-double-panic");
24227      assert!(
24228        catch_unwind(AssertUnwindSafe(|| {
24229          block_on_with_controller(&controller, PanicInPollAndDrop);
24230        }))
24231        .is_err(),
24232        "the original poll panic must still reach the caller"
24233      );
24234      controller
24235        .shutdown()
24236        .expect("contained future destruction must not strand the runtime");
24237      return;
24238    }
24239
24240    let output = std::process::Command::new(std::env::current_exe().unwrap())
24241      .arg("--exact")
24242      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
24243      .arg("--nocapture")
24244      .env(CHILD_ENV, "1")
24245      .output()
24246      .expect("the subprocess test must start");
24247    assert!(
24248      output.status.success(),
24249      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
24250      output.status.code(),
24251      String::from_utf8_lossy(&output.stdout),
24252      String::from_utf8_lossy(&output.stderr)
24253    );
24254  }
24255
24256  #[test]
24257  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
24258    let controller = RuntimeController::new();
24259    controller
24260      .start()
24261      .expect("initial lifecycle start must succeed");
24262    controller
24263      .shutdown()
24264      .expect("zero-work lifecycle shutdown must succeed");
24265    controller
24266      .start()
24267      .expect("zero-work lifecycle restart must remain lazy");
24268    controller
24269      .configure(RuntimeOptions {
24270        flavor: RuntimeFlavor::CurrentThread,
24271        worker_threads: 1,
24272        max_blocking_tasks: 1,
24273        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
24274        park_deadline: None,
24275        drain_linger: DEFAULT_DRAIN_LINGER,
24276      })
24277      .expect("configuration before first real async use must succeed");
24278
24279    let first_generation = controller.backend().generation();
24280    controller
24281      .shutdown()
24282      .expect("the first real generation must stop");
24283    assert!(matches!(
24284      &controller
24285        .state
24286        .lock()
24287        .unwrap_or_else(std::sync::PoisonError::into_inner)
24288        .lifecycle,
24289      RuntimeLifecycle::Stopped
24290    ));
24291    let error = controller
24292      .configure(RuntimeOptions::default())
24293      .expect_err("a real generation must permanently freeze configuration");
24294    assert_eq!(
24295      error.to_string(),
24296      "the async runtime configuration is frozen; configure it before the first async call"
24297    );
24298
24299    controller
24300      .start()
24301      .expect("the stopped real generation must restart");
24302    let restarted_generation = {
24303      let state = controller
24304        .state
24305        .lock()
24306        .unwrap_or_else(std::sync::PoisonError::into_inner);
24307      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
24308        panic!("a real generation restart must eagerly create its replacement backend");
24309      };
24310      backend.generation()
24311    };
24312    assert_ne!(
24313      restarted_generation, first_generation,
24314      "a real generation restart must not reuse the retired backend"
24315    );
24316    controller
24317      .shutdown()
24318      .expect("the replacement generation must stop");
24319  }
24320
24321  #[test]
24322  fn runtime_controller_rejects_submissions_after_shutdown() {
24323    let controller = current_thread_controller("lifecycle-reject");
24324    controller.start().expect("runtime must start");
24325    drop(controller.backend());
24326    controller.shutdown().expect("runtime must stop");
24327
24328    let async_ran = Arc::new(AtomicBool::new(false));
24329    let async_ran_task = Arc::clone(&async_ran);
24330    let Err(rejected_future) = controller.try_spawn_detached(async move {
24331      async_ran_task.store(true, Ordering::SeqCst);
24332    }) else {
24333      panic!("shutdown must reject async work");
24334    };
24335    assert!(
24336      !async_ran.load(Ordering::SeqCst),
24337      "a rejected future must not be polled"
24338    );
24339    futures::executor::block_on(rejected_future);
24340    assert!(
24341      async_ran.load(Ordering::SeqCst),
24342      "the original future must remain usable"
24343    );
24344
24345    let blocking_ran = Arc::new(AtomicBool::new(false));
24346    let blocking_ran_task = Arc::clone(&blocking_ran);
24347    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
24348      blocking_ran_task.store(true, Ordering::SeqCst);
24349      7
24350    }) else {
24351      panic!("shutdown must reject blocking work");
24352    };
24353    assert_eq!(
24354      blocking_error.to_string(),
24355      "the async runtime is stopped; call start before submitting work"
24356    );
24357    assert!(
24358      !blocking_ran.load(Ordering::SeqCst),
24359      "rejected blocking work must not run"
24360    );
24361    assert_eq!(
24362      rejected_work(),
24363      7,
24364      "the original closure must remain usable"
24365    );
24366
24367    let config_error = controller
24368      .configure(RuntimeOptions::default())
24369      .expect_err("shutdown must not make runtime configuration mutable again");
24370    assert_eq!(
24371      config_error.to_string(),
24372      "the async runtime configuration is frozen; configure it before the first async call"
24373    );
24374
24375    let state = controller
24376      .state
24377      .lock()
24378      .unwrap_or_else(std::sync::PoisonError::into_inner);
24379    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
24380  }
24381
24382  #[test]
24383  fn rejected_future_can_be_resubmitted_after_restart() {
24384    let controller = current_thread_controller("lifecycle-retry-future");
24385    controller.start().expect("runtime must start");
24386    drop(controller.backend());
24387    controller.shutdown().expect("runtime must stop");
24388
24389    let runs = Arc::new(AtomicUsize::new(0));
24390    let runs_task = Arc::clone(&runs);
24391    let future = async move {
24392      runs_task.fetch_add(1, Ordering::SeqCst);
24393      23
24394    };
24395    let Err((error, future)) = controller.try_spawn(future) else {
24396      panic!("stopped runtime must reject the future");
24397    };
24398    assert!(error.to_string().contains("runtime is stopped"));
24399    assert_eq!(runs.load(Ordering::SeqCst), 0);
24400
24401    controller.start().expect("runtime must restart");
24402    let handle = controller
24403      .try_spawn(future)
24404      .unwrap_or_else(|_| panic!("restart must accept retained future"));
24405    let dispatch = controller_current_thread_dispatch(&controller);
24406    controller.drive_current_thread_dispatch(dispatch, false);
24407    assert_eq!(
24408      futures::executor::block_on(handle).expect("retained future must complete"),
24409      23
24410    );
24411    assert_eq!(runs.load(Ordering::SeqCst), 1);
24412    controller
24413      .shutdown()
24414      .expect("replacement runtime must stop");
24415  }
24416
24417  #[test]
24418  fn runtime_controller_start_after_shutdown_accepts_work() {
24419    let controller = current_thread_controller("lifecycle-restart");
24420    controller.start().expect("runtime must start");
24421    drop(controller.backend());
24422    controller.shutdown().expect("runtime must stop");
24423    controller.start().expect("runtime must restart");
24424
24425    let async_ran = Arc::new(AtomicBool::new(false));
24426    let async_ran_task = Arc::clone(&async_ran);
24427    controller
24428      .try_spawn_detached(async move {
24429        async_ran_task.store(true, Ordering::SeqCst);
24430      })
24431      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
24432    assert!(
24433      !async_ran.load(Ordering::SeqCst),
24434      "hostless CurrentThread submission must remain enqueue-only"
24435    );
24436    let dispatch = controller_current_thread_dispatch(&controller);
24437    controller.drive_current_thread_dispatch(dispatch, false);
24438    assert!(
24439      async_ran.load(Ordering::SeqCst),
24440      "restarted runtime must poll submitted work"
24441    );
24442
24443    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
24444      panic!("restarted runtime must accept blocking work");
24445    };
24446    assert_eq!(
24447      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
24448      11
24449    );
24450  }
24451
24452  #[test]
24453  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
24454    let controller = current_thread_controller("stale-host-generation");
24455    let old_generation = controller.backend().generation();
24456    let old_dispatch = controller_current_thread_dispatch(&controller);
24457    controller.shutdown().unwrap();
24458    controller.start().unwrap();
24459    let new_generation = controller.backend().generation();
24460    assert_ne!(new_generation, old_generation);
24461
24462    let ran = Arc::new(AtomicBool::new(false));
24463    let ran_task = Arc::clone(&ran);
24464    controller
24465      .try_spawn_detached(async move {
24466        ran_task.store(true, Ordering::SeqCst);
24467      })
24468      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
24469    let new_dispatch = controller_current_thread_dispatch(&controller);
24470    assert_ne!(new_dispatch, old_dispatch);
24471
24472    controller.drive_current_thread_dispatch(old_dispatch, false);
24473    assert!(
24474      !ran.load(Ordering::SeqCst),
24475      "a callback from the retired generation must not touch the restarted executor"
24476    );
24477    controller.drive_current_thread_dispatch(new_dispatch, false);
24478    assert!(ran.load(Ordering::SeqCst));
24479    controller.shutdown().unwrap();
24480  }
24481
24482  #[test]
24483  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
24484    let controller = current_thread_controller("stale-host-cancellation");
24485    let old_generation = controller.backend().generation();
24486    let old_dispatch = controller_current_thread_dispatch(&controller);
24487    controller.shutdown().unwrap();
24488    controller.start().unwrap();
24489    let backend = controller.backend();
24490    let new_generation = backend.generation();
24491    assert_ne!(new_generation, old_generation);
24492    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24493      panic!("configured CurrentThread backend must create a CurrentThread executor");
24494    };
24495    let new_dispatch = current_thread_dispatch(executor);
24496    assert_ne!(new_dispatch, old_dispatch);
24497
24498    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
24499    assert_eq!(
24500      executor.dispatch_pending.load(Ordering::Acquire),
24501      new_dispatch,
24502      "a delayed failure from the retired generation must not release the replacement latch"
24503    );
24504
24505    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
24506    assert_eq!(
24507      executor.dispatch_pending.load(Ordering::Acquire),
24508      0,
24509      "the live generation must be able to cancel its accepted dispatch"
24510    );
24511    drop(backend);
24512    controller.shutdown().unwrap();
24513  }
24514
24515  #[cfg(not(target_family = "wasm"))]
24516  #[test]
24517  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
24518    use std::sync::mpsc;
24519
24520    type ProbeResult = (
24521      &'static str,
24522      Option<u64>,
24523      Result<(), String>,
24524      Result<(), String>,
24525      Result<(), String>,
24526    );
24527
24528    struct StaleGenerationProbe {
24529      label: &'static str,
24530      controller: Arc<RuntimeController>,
24531      result: mpsc::Sender<ProbeResult>,
24532    }
24533
24534    impl Drop for StaleGenerationProbe {
24535      fn drop(&mut self) {
24536        let generation = active_runtime_generation();
24537        let submission = match self.controller.try_spawn(async {}) {
24538          Ok(handle) => {
24539            drop(handle);
24540            Ok(())
24541          }
24542          Err((error, future)) => {
24543            drop(future);
24544            Err(error.to_string())
24545          }
24546        };
24547        let start = self.controller.start().map_err(|error| error.to_string());
24548        let shutdown = self
24549          .controller
24550          .shutdown()
24551          .map_err(|error| error.to_string());
24552        self
24553          .result
24554          .send((self.label, generation, submission, start, shutdown))
24555          .unwrap();
24556      }
24557    }
24558
24559    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
24560    let old_generation = controller.backend().generation();
24561    let (result_tx, result_rx) = mpsc::channel();
24562
24563    let async_controller = Arc::clone(&controller);
24564    let async_result = result_tx.clone();
24565    let async_handle = controller
24566      .try_spawn(async move {
24567        StaleGenerationProbe {
24568          label: "async",
24569          controller: async_controller,
24570          result: async_result,
24571        }
24572      })
24573      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
24574    let old_dispatch = controller_current_thread_dispatch(&controller);
24575    controller.drive_current_thread_dispatch(old_dispatch, false);
24576
24577    let ready_controller = Arc::clone(&controller);
24578    let ready_handle = controller
24579      .try_spawn_blocking(move || StaleGenerationProbe {
24580        label: "ready",
24581        controller: ready_controller,
24582        result: result_tx,
24583      })
24584      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
24585
24586    controller.shutdown().unwrap();
24587    controller.start().unwrap();
24588    let new_generation = controller.backend().generation();
24589    assert_ne!(new_generation, old_generation);
24590
24591    drop(async_handle);
24592    let async_result = result_rx
24593      .recv_timeout(Duration::from_secs(2))
24594      .expect("the retained async output must be destroyed without waiting on the replacement");
24595    drop(ready_handle);
24596    let ready_result = result_rx
24597      .recv_timeout(Duration::from_secs(2))
24598      .expect("the retained immediate output must be destroyed without touching the replacement");
24599
24600    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
24601      let (label, generation, submission, start, shutdown) = result;
24602      assert_eq!(label, expected_label);
24603      assert_eq!(generation, Some(old_generation));
24604      for error in [submission, start, shutdown] {
24605        assert!(
24606          error
24607            .expect_err("retired-generation output destruction must be rejected")
24608            .contains("retired generation")
24609        );
24610      }
24611    }
24612
24613    let survivor = controller
24614      .try_spawn(async { 29usize })
24615      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
24616    let new_dispatch = controller_current_thread_dispatch(&controller);
24617    controller.drive_current_thread_dispatch(new_dispatch, false);
24618    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
24619    controller.shutdown().unwrap();
24620  }
24621
24622  #[cfg(not(target_family = "wasm"))]
24623  #[test]
24624  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
24625    use std::sync::mpsc;
24626
24627    struct ShutdownOnDrop {
24628      controller: Arc<RuntimeController>,
24629      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
24630    }
24631
24632    impl Drop for ShutdownOnDrop {
24633      fn drop(&mut self) {
24634        self
24635          .result
24636          .send((active_runtime_generation(), self.controller.shutdown()))
24637          .unwrap();
24638      }
24639    }
24640
24641    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
24642    let old_generation = controller.backend().generation();
24643    let (result_tx, result_rx) = mpsc::channel();
24644    let old_handle = controller
24645      .try_spawn_blocking({
24646        let controller = Arc::clone(&controller);
24647        move || ShutdownOnDrop {
24648          controller,
24649          result: result_tx,
24650        }
24651      })
24652      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
24653    wait_until(
24654      "the first-generation blocking result to be buffered",
24655      || {
24656        controller
24657          .metrics
24658          .blocking_tasks_completed
24659          .load(Ordering::Acquire)
24660          == 1
24661      },
24662    );
24663    controller.shutdown().unwrap();
24664    controller.start().unwrap();
24665    let new_generation = controller.backend().generation();
24666    assert_ne!(new_generation, old_generation);
24667
24668    let (holder_started_tx, holder_started_rx) = mpsc::channel();
24669    let (release_holder_tx, release_holder_rx) = mpsc::channel();
24670    let holder = controller
24671      .try_spawn_blocking(move || {
24672        holder_started_tx.send(()).unwrap();
24673        release_holder_rx.recv().unwrap();
24674      })
24675      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
24676    holder_started_rx
24677      .recv_timeout(Duration::from_secs(2))
24678      .unwrap();
24679
24680    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24681    let shutdown_controller = Arc::clone(&controller);
24682    let shutdown = std::thread::spawn(move || {
24683      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24684    });
24685    wait_until("the replacement generation to enter Stopping", || {
24686      matches!(
24687        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
24688        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
24689      )
24690    });
24691
24692    let dropper = std::thread::spawn(move || drop(old_handle));
24693    let (observed_generation, reentrant_shutdown) =
24694      match result_rx.recv_timeout(Duration::from_secs(2)) {
24695        Ok(result) => result,
24696        Err(error) => {
24697          release_holder_tx.send(()).unwrap();
24698          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
24699          let _ = dropper.join();
24700          let _ = shutdown.join();
24701          panic!("retained first-generation output waited on replacement shutdown: {error}");
24702        }
24703      };
24704    assert_eq!(observed_generation, Some(old_generation));
24705    let error = reentrant_shutdown
24706      .expect_err("a retained first-generation output must not control replacement shutdown");
24707    assert!(error.to_string().contains("retired generation"));
24708
24709    release_holder_tx.send(()).unwrap();
24710    shutdown_rx
24711      .recv_timeout(Duration::from_secs(2))
24712      .unwrap()
24713      .unwrap();
24714    dropper.join().unwrap();
24715    shutdown.join().unwrap();
24716    futures::executor::block_on(holder).unwrap();
24717  }
24718
24719  #[cfg(not(target_family = "wasm"))]
24720  #[test]
24721  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
24722    use std::sync::mpsc;
24723
24724    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
24725    let generation = controller.backend().generation();
24726    let dispatch = controller_current_thread_dispatch(&controller);
24727    let (admitted_tx, admitted_rx) = mpsc::channel();
24728    let (release_tx, release_rx) = mpsc::channel();
24729    let driver_controller = Arc::clone(&controller);
24730    let driver = std::thread::spawn(move || {
24731      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
24732        *slot.borrow_mut() = Some(Box::new(move || {
24733          admitted_tx.send(()).unwrap();
24734          release_rx.recv().unwrap();
24735        }));
24736      });
24737      driver_controller.drive_current_thread_dispatch(dispatch, false);
24738    });
24739    admitted_rx
24740      .recv_timeout(Duration::from_secs(2))
24741      .expect("the host turn must claim its scheduler role before execution");
24742
24743    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24744    let shutdown_controller = Arc::clone(&controller);
24745    let shutdown = std::thread::spawn(move || {
24746      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24747    });
24748    wait_until(
24749      "shutdown to close the admitted CurrentThread generation",
24750      || {
24751        let state = controller
24752          .state
24753          .lock()
24754          .unwrap_or_else(std::sync::PoisonError::into_inner);
24755        matches!(
24756          &state.lifecycle,
24757          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
24758        )
24759      },
24760    );
24761    assert!(
24762      shutdown_rx
24763        .recv_timeout(Duration::from_millis(200))
24764        .is_err(),
24765      "shutdown must wait for an admitted host callback even before it starts draining"
24766    );
24767
24768    release_tx.send(()).unwrap();
24769    driver.join().unwrap();
24770    shutdown_rx
24771      .recv_timeout(Duration::from_secs(2))
24772      .unwrap()
24773      .unwrap();
24774    shutdown.join().unwrap();
24775  }
24776
24777  #[test]
24778  fn spawn_racing_shutdown_cannot_recreate_backend() {
24779    use std::sync::mpsc;
24780
24781    let controller = Arc::new(current_thread_controller("lifecycle-race"));
24782    controller.start().expect("runtime must start");
24783    drop(controller.backend());
24784
24785    let (at_hook_tx, at_hook_rx) = mpsc::channel();
24786    let (release_tx, release_rx) = mpsc::channel();
24787    let ran = Arc::new(AtomicBool::new(false));
24788    let ran_task = Arc::clone(&ran);
24789    let worker_controller = Arc::clone(&controller);
24790    let worker = std::thread::spawn(move || {
24791      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
24792        *slot.borrow_mut() = Some(Box::new(move || {
24793          at_hook_tx
24794            .send(())
24795            .expect("test coordinator must still be listening");
24796          release_rx
24797            .recv()
24798            .expect("test coordinator must release the submission");
24799        }));
24800      });
24801
24802      let future = async move {
24803        ran_task.store(true, Ordering::SeqCst);
24804      };
24805      match worker_controller.try_spawn_detached(future) {
24806        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
24807        Err(future) => drop(future),
24808      }
24809    });
24810
24811    at_hook_rx
24812      .recv_timeout(Duration::from_secs(5))
24813      .expect("submitting thread must stop before taking the controller lock");
24814    controller.shutdown().expect("runtime must stop");
24815    release_tx
24816      .send(())
24817      .expect("submitting thread must still be waiting");
24818    worker.join().expect("submitting thread must not panic");
24819
24820    assert!(
24821      !ran.load(Ordering::SeqCst),
24822      "the rejected task must never run"
24823    );
24824    let state = controller
24825      .state
24826      .lock()
24827      .unwrap_or_else(std::sync::PoisonError::into_inner);
24828    assert!(
24829      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
24830      "the rejected submission must not lazily recreate a backend"
24831    );
24832  }
24833
24834  #[cfg(not(target_family = "wasm"))]
24835  #[test]
24836  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
24837    use std::sync::mpsc;
24838
24839    struct BlockingDropFuture {
24840      work: Arc<GenerationWork>,
24841      entered: mpsc::Sender<(Option<u64>, usize)>,
24842      release: mpsc::Receiver<()>,
24843    }
24844
24845    impl Future for BlockingDropFuture {
24846      type Output = ();
24847
24848      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
24849        Poll::Pending
24850      }
24851    }
24852
24853    impl Drop for BlockingDropFuture {
24854      fn drop(&mut self) {
24855        let active = self
24856          .work
24857          .state
24858          .lock()
24859          .unwrap_or_else(std::sync::PoisonError::into_inner)
24860          .active;
24861        self
24862          .entered
24863          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24864          .unwrap();
24865        self.release.recv().unwrap();
24866      }
24867    }
24868
24869    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
24870    let backend = controller.backend();
24871    let old_generation = backend.generation();
24872    let work = Arc::clone(&backend.work);
24873    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24874      panic!("configured CurrentThread backend must create a CurrentThread executor");
24875    };
24876    let executor = Arc::clone(executor);
24877    drop(backend);
24878
24879    let (registered_tx, registered_rx) = mpsc::channel();
24880    let (release_submission_tx, release_submission_rx) = mpsc::channel();
24881    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
24882    let (release_drop_tx, release_drop_rx) = mpsc::channel();
24883    let submit_controller = Arc::clone(&controller);
24884    let submit_work = Arc::clone(&work);
24885    let submitter = std::thread::spawn(move || {
24886      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
24887        *slot.borrow_mut() = Some(Box::new(move || {
24888          registered_tx.send(()).unwrap();
24889          release_submission_rx.recv().unwrap();
24890        }));
24891      });
24892      submit_controller
24893        .try_spawn_detached(BlockingDropFuture {
24894          work: submit_work,
24895          entered: drop_entered_tx,
24896          release: release_drop_rx,
24897        })
24898        .unwrap_or_else(|_| {
24899          panic!("the submission linearized before shutdown and must be accepted")
24900        });
24901    });
24902    registered_rx
24903      .recv_timeout(Duration::from_secs(2))
24904      .expect("the submitting thread must register generation work before shutdown");
24905
24906    let (shutdown_tx, shutdown_rx) = mpsc::channel();
24907    let shutdown_controller = Arc::clone(&controller);
24908    let shutdown = std::thread::spawn(move || {
24909      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
24910    });
24911    wait_until("CurrentThread shutdown to close the runnable queue", || {
24912      executor
24913        .queue
24914        .lock()
24915        .unwrap_or_else(std::sync::PoisonError::into_inner)
24916        .closed
24917    });
24918    release_submission_tx.send(()).unwrap();
24919
24920    let (drop_generation, active_during_drop) = drop_entered_rx
24921      .recv_timeout(Duration::from_secs(2))
24922      .expect("the closed queue must destroy the accepted unpolled future");
24923    assert_eq!(drop_generation, Some(old_generation));
24924    assert_eq!(
24925      active_during_drop, 1,
24926      "accepted generation work must remain registered through user future destruction"
24927    );
24928
24929    let (start_tx, start_rx) = mpsc::channel();
24930    let start_controller = Arc::clone(&controller);
24931    let starter = std::thread::spawn(move || {
24932      start_tx.send(start_controller.start()).unwrap();
24933    });
24934    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
24935    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
24936    let shutdown_overlapped = early_shutdown.is_some();
24937    let restart_overlapped = early_start.is_some();
24938
24939    release_drop_tx.send(()).unwrap();
24940    submitter.join().unwrap();
24941    let shutdown_result = early_shutdown.unwrap_or_else(|| {
24942      shutdown_rx
24943        .recv_timeout(Duration::from_secs(2))
24944        .expect("shutdown must finish after drop")
24945    });
24946    shutdown_result.unwrap();
24947    shutdown.join().unwrap();
24948    let start_result = early_start.unwrap_or_else(|| {
24949      start_rx
24950        .recv_timeout(Duration::from_secs(2))
24951        .expect("restart must follow shutdown")
24952    });
24953    start_result.unwrap();
24954    starter.join().unwrap();
24955
24956    assert!(
24957      !shutdown_overlapped,
24958      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
24959    );
24960    assert!(
24961      !restart_overlapped,
24962      "restart must not create a new generation while an accepted old-generation future is being destroyed"
24963    );
24964    assert_ne!(controller.backend().generation(), old_generation);
24965    controller.shutdown().unwrap();
24966  }
24967
24968  #[cfg(not(target_family = "wasm"))]
24969  #[test]
24970  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
24971    use std::sync::mpsc;
24972
24973    struct BlockingDrop {
24974      work: Arc<GenerationWork>,
24975      entered: mpsc::Sender<(Option<u64>, usize)>,
24976      release: mpsc::Receiver<()>,
24977    }
24978
24979    impl Drop for BlockingDrop {
24980      fn drop(&mut self) {
24981        let active = self
24982          .work
24983          .state
24984          .lock()
24985          .unwrap_or_else(std::sync::PoisonError::into_inner)
24986          .active;
24987        self
24988          .entered
24989          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
24990          .unwrap();
24991        self.release.recv().unwrap();
24992      }
24993    }
24994
24995    let controller = Arc::new(multi_thread_controller(
24996      "accepted-blocking-submission-drop",
24997      2,
24998      1,
24999    ));
25000    let backend = controller.backend();
25001    let old_generation = backend.generation();
25002    let work = Arc::clone(&backend.work);
25003    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25004      panic!("configured MultiThread backend must create a MultiThread executor");
25005    };
25006    let executor = Arc::downgrade(executor);
25007    drop(backend);
25008
25009    let (registered_tx, registered_rx) = mpsc::channel();
25010    let (release_submission_tx, release_submission_rx) = mpsc::channel();
25011    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25012    let (release_drop_tx, release_drop_rx) = mpsc::channel();
25013    let submit_controller = Arc::clone(&controller);
25014    let submitter = std::thread::spawn(move || {
25015      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
25016        *slot.borrow_mut() = Some(Box::new(move || {
25017          registered_tx.send(()).unwrap();
25018          release_submission_rx.recv().unwrap();
25019        }));
25020      });
25021      let drop_signal = BlockingDrop {
25022        work,
25023        entered: drop_entered_tx,
25024        release: release_drop_rx,
25025      };
25026      let handle = submit_controller
25027        .try_spawn_blocking(move || {
25028          let _drop_signal = drop_signal;
25029        })
25030        .unwrap_or_else(|_| {
25031          panic!("the submission linearized before shutdown and must be accepted")
25032        });
25033      assert!(
25034        futures::executor::block_on(handle).is_err(),
25035        "the executor must cancel work that reaches its queue after shutdown closed it"
25036      );
25037    });
25038    registered_rx
25039      .recv_timeout(Duration::from_secs(2))
25040      .expect("the submitting thread must register generation work before shutdown");
25041
25042    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25043    let shutdown_controller = Arc::clone(&controller);
25044    let shutdown = std::thread::spawn(move || {
25045      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25046    });
25047    wait_until("MultiThread shutdown to close the blocking queue", || {
25048      executor
25049        .upgrade()
25050        .expect("the retiring executor must remain alive until shutdown completes")
25051        .blocking_queue
25052        .lock()
25053        .unwrap_or_else(std::sync::PoisonError::into_inner)
25054        .closed
25055    });
25056    release_submission_tx.send(()).unwrap();
25057
25058    let (drop_generation, active_during_drop) = drop_entered_rx
25059      .recv_timeout(Duration::from_secs(2))
25060      .expect("the closed queue must destroy the accepted blocking closure");
25061    assert_eq!(drop_generation, Some(old_generation));
25062    assert_eq!(
25063      active_during_drop, 1,
25064      "accepted generation work must remain registered through blocking closure destruction"
25065    );
25066
25067    let (start_tx, start_rx) = mpsc::channel();
25068    let start_controller = Arc::clone(&controller);
25069    let starter = std::thread::spawn(move || {
25070      start_tx.send(start_controller.start()).unwrap();
25071    });
25072    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
25073    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
25074    let shutdown_overlapped = early_shutdown.is_some();
25075    let restart_overlapped = early_start.is_some();
25076
25077    release_drop_tx.send(()).unwrap();
25078    submitter.join().unwrap();
25079    let shutdown_result = early_shutdown.unwrap_or_else(|| {
25080      shutdown_rx
25081        .recv_timeout(Duration::from_secs(2))
25082        .expect("shutdown must finish after drop")
25083    });
25084    shutdown_result.unwrap();
25085    shutdown.join().unwrap();
25086    let start_result = early_start.unwrap_or_else(|| {
25087      start_rx
25088        .recv_timeout(Duration::from_secs(2))
25089        .expect("restart must follow shutdown")
25090    });
25091    start_result.unwrap();
25092    starter.join().unwrap();
25093
25094    assert!(
25095      !shutdown_overlapped,
25096      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
25097    );
25098    assert!(
25099      !restart_overlapped,
25100      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
25101    );
25102    assert_ne!(controller.backend().generation(), old_generation);
25103    controller.shutdown().unwrap();
25104  }
25105
25106  #[cfg(not(target_family = "wasm"))]
25107  #[test]
25108  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
25109    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
25110    let options = controller.options();
25111    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
25112
25113    let backend = controller.backend();
25114    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25115      panic!("configured MultiThread backend must create a Rayon executor");
25116    };
25117    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
25118    let lifecycle = Arc::clone(&executor.worker_lifecycle);
25119    drop(backend);
25120
25121    controller.shutdown().expect("runtime must stop");
25122    assert_eq!(
25123      lifecycle.remaining(),
25124      0,
25125      "shutdown must observe every physical worker exit"
25126    );
25127  }
25128
25129  #[cfg(not(target_family = "wasm"))]
25130  #[test]
25131  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
25132    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
25133    // includes worker TLS-destructor completion -- `wait_for_all_workers`
25134    // joins the OS threads, and an OS thread only becomes joinable after its
25135    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
25136    // worker's destructor and sampling the counter on the shutdown thread at
25137    // the very instant `shutdown()` returns: a barrier that excluded the
25138    // destructors would race that sample and fail loudly.
25139    //
25140    // The destructor must never do more than record. The previous shape
25141    // parked it on a condvar until the harness released it -- on Windows TLS
25142    // destructors run under the process loader lock, so the first parked
25143    // worker blocked the second from even ENTERING its destructor, the
25144    // release condition (both entered) became unsatisfiable, and the parked
25145    // destructor froze every thread spawn/exit in the process: the
25146    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
25147    // `WorkerTlsDropAction`).
25148    use std::sync::mpsc;
25149
25150    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
25151    let backend = controller.backend();
25152    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25153      panic!("configured MultiThread backend must create a Rayon executor");
25154    };
25155    let worker_count = executor.pool.current_num_threads();
25156    let completed = Arc::new(AtomicUsize::new(0));
25157    let probe_completed = Arc::clone(&completed);
25158    executor.pool.broadcast(move |_| {
25159      WORKER_TLS_DROP_PROBE.with(|slot| {
25160        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25161          action: Some(WorkerTlsDropAction::Record {
25162            completed: Arc::clone(&probe_completed),
25163          }),
25164        });
25165        assert!(
25166          previous.is_none(),
25167          "each worker must install exactly one TLS drop probe"
25168        );
25169      });
25170    });
25171    drop(backend);
25172
25173    assert_eq!(
25174      completed.load(Ordering::SeqCst),
25175      0,
25176      "no worker TLS destructor may run before shutdown retires the workers"
25177    );
25178
25179    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25180    let shutdown_controller = Arc::clone(&controller);
25181    let sample_completed = Arc::clone(&completed);
25182    let shutdown = std::thread::spawn(move || {
25183      let result = shutdown_controller.shutdown();
25184      // Sample on the SAME thread, before any further synchronization could
25185      // mask a barrier gap: at this instant every destructor must already
25186      // have recorded itself.
25187      let seen_at_return = sample_completed.load(Ordering::SeqCst);
25188      shutdown_tx.send((result, seen_at_return)).unwrap();
25189    });
25190    let (shutdown_result, seen_at_return) = shutdown_rx
25191      .recv_timeout(Duration::from_secs(10))
25192      .expect("shutdown must finish once worker TLS destructors have run");
25193    shutdown_result.unwrap();
25194    assert_eq!(
25195      seen_at_return, worker_count,
25196      "every worker TLS destructor must have completed before shutdown() returned: \
25197       the worker-join barrier must include TLS-destructor completion"
25198    );
25199    join_within(
25200      "the shutdown thread exits after teardown",
25201      shutdown,
25202      Duration::from_secs(30),
25203    );
25204  }
25205
25206  #[cfg(not(target_family = "wasm"))]
25207  #[test]
25208  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
25209    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
25210
25211    if std::env::var_os(CHILD_ENV).is_some() {
25212      use std::sync::mpsc;
25213
25214      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
25215      let backend = controller.backend();
25216      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
25217        panic!("configured MultiThread backend must create a Rayon executor");
25218      };
25219      let installed = Arc::new(AtomicBool::new(false));
25220      let (completed_tx, completed_rx) = mpsc::channel();
25221      let probe_controller = Arc::clone(&controller);
25222      let probe_installed = Arc::clone(&installed);
25223      executor.pool.broadcast(move |_| {
25224        if probe_installed.swap(true, Ordering::SeqCst) {
25225          return;
25226        }
25227        WORKER_TLS_DROP_PROBE.with(|slot| {
25228          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
25229            action: Some(WorkerTlsDropAction::ReenterLifecycle {
25230              controller: Arc::clone(&probe_controller),
25231              completed: completed_tx.clone(),
25232            }),
25233          });
25234          assert!(
25235            previous.is_none(),
25236            "the selected worker must have no prior TLS probe"
25237          );
25238        });
25239      });
25240      assert!(
25241        installed.load(Ordering::SeqCst),
25242        "one worker must install the lifecycle probe"
25243      );
25244      drop(backend);
25245
25246      controller
25247        .shutdown()
25248        .expect("external shutdown must join the retiring workers");
25249      let (start, shutdown) = completed_rx
25250        .recv_timeout(Duration::from_secs(2))
25251        .expect("the TLS destructor must complete both lifecycle reentry attempts");
25252      assert_eq!(
25253        start.unwrap_err(),
25254        "cannot restart the async runtime from work in the generation being stopped"
25255      );
25256      assert_eq!(
25257        shutdown.unwrap_err(),
25258        "cannot wait for async runtime shutdown from work in the generation being stopped"
25259      );
25260      return;
25261    }
25262
25263    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
25264      .arg("--exact")
25265      .arg(
25266        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
25267      )
25268      .arg("--nocapture")
25269      .env(CHILD_ENV, "1")
25270      .stdout(std::process::Stdio::piped())
25271      .stderr(std::process::Stdio::piped())
25272      .spawn()
25273      .expect("the TLS lifecycle-reentry subprocess must start");
25274    let deadline = Instant::now() + Duration::from_secs(5);
25275    let status = loop {
25276      if let Some(status) = child
25277        .try_wait()
25278        .expect("the subprocess status must be readable")
25279      {
25280        break status;
25281      }
25282      if Instant::now() >= deadline {
25283        let _ = child.kill();
25284        let output = child
25285          .wait_with_output()
25286          .expect("the timed-out subprocess must be reaped");
25287        panic!(
25288          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
25289          String::from_utf8_lossy(&output.stdout),
25290          String::from_utf8_lossy(&output.stderr)
25291        );
25292      }
25293      std::thread::sleep(Duration::from_millis(10));
25294    };
25295    let output = child
25296      .wait_with_output()
25297      .expect("the completed subprocess output must be read");
25298    assert!(
25299      status.success(),
25300      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
25301      status.code(),
25302      String::from_utf8_lossy(&output.stdout),
25303      String::from_utf8_lossy(&output.stderr)
25304    );
25305  }
25306
25307  #[cfg(not(target_family = "wasm"))]
25308  #[test]
25309  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
25310    use std::sync::mpsc;
25311
25312    struct DropSignal(mpsc::Sender<()>);
25313    impl Drop for DropSignal {
25314      fn drop(&mut self) {
25315        let _ = self.0.send(());
25316      }
25317    }
25318
25319    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
25320    let (polled_tx, polled_rx) = mpsc::channel();
25321    let (dropped_tx, dropped_rx) = mpsc::channel();
25322    controller
25323      .try_spawn_detached(async move {
25324        let _drop_signal = DropSignal(dropped_tx);
25325        polled_tx.send(()).unwrap();
25326        std::future::pending::<()>().await;
25327      })
25328      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
25329    polled_rx
25330      .recv_timeout(Duration::from_secs(5))
25331      .expect("the pending task must be polled before shutdown");
25332
25333    controller
25334      .shutdown()
25335      .expect("shutdown must cancel accepted async work");
25336    dropped_rx
25337      .recv_timeout(Duration::from_secs(5))
25338      .expect("shutdown must drop the accepted task future before returning");
25339  }
25340
25341  #[cfg(not(target_family = "wasm"))]
25342  #[test]
25343  fn current_thread_shutdown_outranks_perpetual_self_wake() {
25344    use std::sync::mpsc;
25345
25346    struct SelfWakingPending {
25347      entered: Option<mpsc::Sender<()>>,
25348    }
25349
25350    impl Future for SelfWakingPending {
25351      type Output = ();
25352
25353      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25354        if let Some(entered) = self.entered.take() {
25355          entered.send(()).unwrap();
25356        }
25357        cx.waker().wake_by_ref();
25358        Poll::Pending
25359      }
25360    }
25361
25362    let controller = Arc::new(current_thread_controller(
25363      "current-thread-self-wake-shutdown",
25364    ));
25365    let (entered_tx, entered_rx) = mpsc::channel();
25366    let (outcome_tx, outcome_rx) = mpsc::channel();
25367    let block_on_controller = Arc::clone(&controller);
25368    let runner = std::thread::spawn(move || {
25369      let mut future = std::pin::pin!(SelfWakingPending {
25370        entered: Some(entered_tx)
25371      });
25372      outcome_tx
25373        .send(block_on_controller.block_on(future.as_mut()))
25374        .unwrap();
25375    });
25376    entered_rx
25377      .recv_timeout(Duration::from_secs(2))
25378      .expect("the self-waking future must be polled before shutdown");
25379
25380    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25381    let shutdown_controller = Arc::clone(&controller);
25382    let shutdown = std::thread::spawn(move || {
25383      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25384    });
25385    shutdown_rx
25386      .recv_timeout(Duration::from_secs(2))
25387      .expect("shutdown must outrank a stored self-wake permit")
25388      .unwrap();
25389    assert_eq!(
25390      outcome_rx
25391        .recv_timeout(Duration::from_secs(2))
25392        .expect("the raw block_on driver must report shutdown"),
25393      BlockOnOutcome::Stopped
25394    );
25395    runner.join().unwrap();
25396    shutdown.join().unwrap();
25397  }
25398
25399  #[cfg(not(target_family = "wasm"))]
25400  #[test]
25401  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
25402    use std::sync::mpsc;
25403
25404    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
25405    let backend = controller.backend();
25406    let executor = match &backend.executor {
25407      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25408      RuntimeExecutor::CurrentThread(_) => unreachable!(),
25409    };
25410    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
25411    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
25412    for _ in 0..2 {
25413      let worker_ready_tx = worker_ready_tx.clone();
25414      let release_workers = Arc::clone(&release_workers);
25415      executor.pool.spawn(move || {
25416        worker_ready_tx.send(()).unwrap();
25417        let (released, changed) = &*release_workers;
25418        let mut released = released
25419          .lock()
25420          .unwrap_or_else(std::sync::PoisonError::into_inner);
25421        while !*released {
25422          released = changed
25423            .wait(released)
25424            .unwrap_or_else(std::sync::PoisonError::into_inner);
25425        }
25426      });
25427    }
25428    drop(worker_ready_tx);
25429    for _ in 0..2 {
25430      worker_ready_rx
25431        .recv_timeout(Duration::from_secs(5))
25432        .expect("both workers must be occupied before the FIFO runnable is submitted");
25433    }
25434    drop(executor);
25435    drop(backend);
25436
25437    let polls = Arc::new(AtomicUsize::new(0));
25438    let handle = controller.spawn(StopPollProbe {
25439      polls: Arc::clone(&polls),
25440      first_poll: None,
25441    });
25442    wait_until("the FIFO runnable to be queued", || {
25443      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
25444    });
25445
25446    let observed_queue = Arc::new(Mutex::new(None));
25447    let hook_observed_queue = Arc::clone(&observed_queue);
25448    let hook_metrics = Arc::clone(&controller.metrics);
25449    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25450      *slot.borrow_mut() = Some(Box::new(move || {
25451        let (released, changed) = &*release_workers;
25452        *released
25453          .lock()
25454          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
25455        changed.notify_all();
25456        wait_until("the stopped FIFO runnable claim", || {
25457          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
25458        });
25459        *hook_observed_queue
25460          .lock()
25461          .unwrap_or_else(std::sync::PoisonError::into_inner) =
25462          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
25463      }));
25464    });
25465
25466    controller
25467      .shutdown()
25468      .expect("shutdown must cancel the stopped FIFO claim");
25469    assert_eq!(polls.load(Ordering::SeqCst), 0);
25470    assert_eq!(
25471      *observed_queue
25472        .lock()
25473        .unwrap_or_else(std::sync::PoisonError::into_inner),
25474      Some(0)
25475    );
25476    assert_eq!(
25477      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25478      0
25479    );
25480    drop(handle);
25481  }
25482
25483  #[cfg(not(target_family = "wasm"))]
25484  #[test]
25485  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
25486    use std::sync::mpsc;
25487
25488    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
25489    let backend = controller.backend();
25490    let executor = match &backend.executor {
25491      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
25492      RuntimeExecutor::CurrentThread(_) => unreachable!(),
25493    };
25494    let polls = Arc::new(AtomicUsize::new(0));
25495    let scheduler = Arc::downgrade(&executor);
25496    let (runnable, task) = async_task::spawn(
25497      StopPollProbe {
25498        polls: Arc::clone(&polls),
25499        first_poll: None,
25500      },
25501      move |runnable| {
25502        if let Some(executor) = scheduler.upgrade() {
25503          executor.schedule(runnable);
25504        }
25505      },
25506    );
25507    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
25508    let (release_slot_tx, release_slot_rx) = mpsc::channel();
25509    let (claim_done_tx, claim_done_rx) = mpsc::channel();
25510    let worker_executor = Arc::clone(&executor);
25511    let worker_metrics = Arc::clone(&controller.metrics);
25512    executor.pool.spawn(move || {
25513      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
25514      worker_executor.schedule(runnable);
25515      slot_ready_tx.send(()).unwrap();
25516      release_slot_rx.recv().unwrap();
25517      let claimed = worker_executor.run_one_runnable();
25518      claim_done_tx
25519        .send((
25520          claimed,
25521          worker_metrics.queued_runnables.load(Ordering::SeqCst),
25522        ))
25523        .unwrap();
25524    });
25525    slot_ready_rx
25526      .recv_timeout(Duration::from_secs(5))
25527      .expect("the worker must publish the LIFO runnable before shutdown");
25528    assert_eq!(
25529      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25530      1
25531    );
25532    drop(executor);
25533    drop(backend);
25534
25535    let observed_claim = Arc::new(Mutex::new(None));
25536    let hook_observed_claim = Arc::clone(&observed_claim);
25537    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
25538      *slot.borrow_mut() = Some(Box::new(move || {
25539        release_slot_tx.send(()).unwrap();
25540        let result = claim_done_rx
25541          .recv_timeout(Duration::from_secs(5))
25542          .expect("the stopped LIFO runnable claim must complete before abort");
25543        *hook_observed_claim
25544          .lock()
25545          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
25546      }));
25547    });
25548
25549    controller
25550      .shutdown()
25551      .expect("shutdown must cancel the stopped LIFO claim");
25552    assert_eq!(polls.load(Ordering::SeqCst), 0);
25553    assert_eq!(
25554      *observed_claim
25555        .lock()
25556        .unwrap_or_else(std::sync::PoisonError::into_inner),
25557      Some((true, 0))
25558    );
25559    assert_eq!(
25560      controller.metrics.queued_runnables.load(Ordering::SeqCst),
25561      0
25562    );
25563    drop(task);
25564  }
25565
25566  #[cfg(not(target_family = "wasm"))]
25567  #[test]
25568  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
25569    use std::sync::mpsc;
25570
25571    struct BlockingDrop {
25572      entered: mpsc::Sender<()>,
25573      release: mpsc::Receiver<()>,
25574    }
25575
25576    impl Drop for BlockingDrop {
25577      fn drop(&mut self) {
25578        self.entered.send(()).unwrap();
25579        self.release.recv().unwrap();
25580      }
25581    }
25582
25583    struct TwoPollProbe {
25584      first_poll: Option<mpsc::Sender<()>>,
25585      release_first_poll: mpsc::Receiver<()>,
25586      second_poll: mpsc::Sender<()>,
25587    }
25588
25589    impl Future for TwoPollProbe {
25590      type Output = ();
25591
25592      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
25593        if let Some(first_poll) = self.first_poll.take() {
25594          first_poll.send(()).unwrap();
25595          self.release_first_poll.recv().unwrap();
25596        } else {
25597          self.second_poll.send(()).unwrap();
25598        }
25599        cx.waker().wake_by_ref();
25600        Poll::Pending
25601      }
25602    }
25603
25604    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
25605    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
25606    let (release_drop_tx, release_drop_rx) = mpsc::channel();
25607    let blocking_drop = BlockingDrop {
25608      entered: drop_entered_tx,
25609      release: release_drop_rx,
25610    };
25611    controller
25612      .try_spawn_detached(async move {
25613        let _drop = blocking_drop;
25614        std::future::pending::<()>().await;
25615      })
25616      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
25617
25618    let (first_poll_tx, first_poll_rx) = mpsc::channel();
25619    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
25620    let (second_poll_tx, second_poll_rx) = mpsc::channel();
25621    let (outcome_tx, outcome_rx) = mpsc::channel();
25622    let runner_controller = Arc::clone(&controller);
25623    let runner = std::thread::spawn(move || {
25624      let mut future = std::pin::pin!(TwoPollProbe {
25625        first_poll: Some(first_poll_tx),
25626        release_first_poll: release_first_poll_rx,
25627        second_poll: second_poll_tx,
25628      });
25629      outcome_tx
25630        .send(runner_controller.block_on(future.as_mut()))
25631        .unwrap();
25632    });
25633    first_poll_rx
25634      .recv_timeout(Duration::from_secs(2))
25635      .expect("the active block_on poll must enter before shutdown");
25636
25637    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25638    let shutdown_controller = Arc::clone(&controller);
25639    let shutdown = std::thread::spawn(move || {
25640      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25641    });
25642    drop_entered_rx
25643      .recv_timeout(Duration::from_secs(2))
25644      .expect("shutdown must begin destroying queued work");
25645
25646    release_first_poll_tx.send(()).unwrap();
25647    let polled_again = second_poll_rx
25648      .recv_timeout(Duration::from_millis(200))
25649      .is_ok();
25650    release_drop_tx.send(()).unwrap();
25651
25652    assert!(
25653      !polled_again,
25654      "an active block_on driver must observe generation stop before queued user cleanup blocks"
25655    );
25656    assert_eq!(
25657      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25658      BlockOnOutcome::Stopped
25659    );
25660    shutdown_rx
25661      .recv_timeout(Duration::from_secs(2))
25662      .unwrap()
25663      .unwrap();
25664    runner.join().unwrap();
25665    shutdown.join().unwrap();
25666  }
25667
25668  #[cfg(not(target_family = "wasm"))]
25669  #[test]
25670  fn shutdown_cancels_nested_block_on_within_deadline() {
25671    use std::sync::mpsc;
25672
25673    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
25674    let task_controller = Arc::clone(&controller);
25675    let (entered_tx, entered_rx) = mpsc::channel();
25676    let task = controller
25677      .try_spawn(async move {
25678        entered_tx.send(()).unwrap();
25679        block_on_with_controller(&task_controller, std::future::pending::<()>());
25680      })
25681      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
25682    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
25683
25684    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25685    let shutdown_controller = Arc::clone(&controller);
25686    let shutdown = std::thread::spawn(move || {
25687      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25688    });
25689    shutdown_rx
25690      .recv_timeout(Duration::from_secs(2))
25691      .expect("shutdown must wake and cancel an active nested block_on")
25692      .unwrap();
25693    shutdown.join().unwrap();
25694    assert!(
25695      futures::executor::block_on(task).is_err(),
25696      "the cancelled nested block_on task must resolve as stopped"
25697    );
25698  }
25699
25700  #[cfg(not(target_family = "wasm"))]
25701  #[test]
25702  fn stopped_public_block_on_drops_future_before_generation_retirement() {
25703    use std::sync::mpsc;
25704
25705    struct PendingDrop {
25706      entered: Option<mpsc::Sender<()>>,
25707      dropped: mpsc::Sender<Option<u64>>,
25708      release: mpsc::Receiver<()>,
25709    }
25710
25711    impl Future for PendingDrop {
25712      type Output = ();
25713
25714      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25715        if let Some(entered) = self.entered.take() {
25716          entered.send(()).unwrap();
25717        }
25718        Poll::Pending
25719      }
25720    }
25721
25722    impl Drop for PendingDrop {
25723      fn drop(&mut self) {
25724        self
25725          .dropped
25726          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25727          .unwrap();
25728        self.release.recv().unwrap();
25729      }
25730    }
25731
25732    let controller = Arc::new(current_thread_controller(
25733      "public-block-on-generation-destruction",
25734    ));
25735    let generation = controller.backend().generation();
25736    let (entered_tx, entered_rx) = mpsc::channel();
25737    let (dropped_tx, dropped_rx) = mpsc::channel();
25738    let (release_tx, release_rx) = mpsc::channel();
25739    let (runner_done_tx, runner_done_rx) = mpsc::channel();
25740    let block_controller = Arc::clone(&controller);
25741    let runner = std::thread::spawn(move || {
25742      let result = catch_unwind(AssertUnwindSafe(|| {
25743        block_on_with_controller(
25744          &block_controller,
25745          PendingDrop {
25746            entered: Some(entered_tx),
25747            dropped: dropped_tx,
25748            release: release_rx,
25749          },
25750        );
25751      }));
25752      runner_done_tx.send(result.is_err()).unwrap();
25753    });
25754    entered_rx
25755      .recv_timeout(Duration::from_secs(2))
25756      .expect("the public block_on future must be polled before shutdown");
25757
25758    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25759    let shutdown_controller = Arc::clone(&controller);
25760    let shutdown = std::thread::spawn(move || {
25761      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25762    });
25763
25764    assert_eq!(
25765      dropped_rx
25766        .recv_timeout(Duration::from_secs(2))
25767        .expect("shutdown must wake block_on and begin destroying its future"),
25768      Some(generation),
25769      "the stopped future must be destroyed under its retiring generation identity"
25770    );
25771    assert!(
25772      shutdown_rx
25773        .recv_timeout(Duration::from_millis(200))
25774        .is_err(),
25775      "shutdown must not publish Stopped while the public block_on future destructor is running"
25776    );
25777
25778    release_tx.send(()).unwrap();
25779    assert!(
25780      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
25781      "the public helper must retain its existing early-stop panic behavior"
25782    );
25783    shutdown_rx
25784      .recv_timeout(Duration::from_secs(2))
25785      .unwrap()
25786      .unwrap();
25787    runner.join().unwrap();
25788    shutdown.join().unwrap();
25789  }
25790
25791  #[cfg(not(target_family = "wasm"))]
25792  #[test]
25793  fn shutdown_contains_panicking_nested_block_on_future_drop() {
25794    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
25795
25796    if std::env::var_os(CHILD_ENV).is_some() {
25797      use std::sync::mpsc;
25798
25799      struct PendingPanicOnDrop {
25800        entered: Option<mpsc::Sender<()>>,
25801      }
25802
25803      impl Future for PendingPanicOnDrop {
25804        type Output = ();
25805
25806        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
25807          if let Some(entered) = self.entered.take() {
25808            entered.send(()).unwrap();
25809          }
25810          Poll::Pending
25811        }
25812      }
25813
25814      impl Drop for PendingPanicOnDrop {
25815        fn drop(&mut self) {
25816          panic!("intentional nested block_on future destructor panic");
25817        }
25818      }
25819
25820      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
25821      let task_controller = Arc::clone(&controller);
25822      let (entered_tx, entered_rx) = mpsc::channel();
25823      let task = controller
25824        .try_spawn(async move {
25825          block_on_with_controller(
25826            &task_controller,
25827            PendingPanicOnDrop {
25828              entered: Some(entered_tx),
25829            },
25830          );
25831        })
25832        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
25833      entered_rx
25834        .recv_timeout(Duration::from_secs(2))
25835        .expect("the nested block_on future must be polled before shutdown");
25836
25837      controller
25838        .shutdown()
25839        .expect("shutdown must contain nested future destruction");
25840      assert!(
25841        futures::executor::block_on(task).is_err(),
25842        "shutdown cancellation must still surface as a task error"
25843      );
25844      return;
25845    }
25846
25847    let output = std::process::Command::new(std::env::current_exe().unwrap())
25848      .arg("--exact")
25849      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
25850      .arg("--nocapture")
25851      .env(CHILD_ENV, "1")
25852      .output()
25853      .expect("the subprocess test must start");
25854    assert!(
25855      output.status.success(),
25856      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
25857      output.status.code(),
25858      String::from_utf8_lossy(&output.stdout),
25859      String::from_utf8_lossy(&output.stderr)
25860    );
25861  }
25862
25863  #[test]
25864  fn panicking_future_drop_is_contained_before_async_task_drop() {
25865    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
25866
25867    if std::env::var_os(CHILD_ENV).is_some() {
25868      struct PanicOnDrop;
25869      impl Drop for PanicOnDrop {
25870        fn drop(&mut self) {
25871          panic!("intentional future destructor panic");
25872        }
25873      }
25874
25875      let controller = current_thread_controller("future-drop-subprocess");
25876      let bomb = PanicOnDrop;
25877      controller
25878        .try_spawn_detached(async move {
25879          let _bomb = bomb;
25880          std::future::pending::<()>().await;
25881        })
25882        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
25883      controller
25884        .shutdown()
25885        .expect("shutdown must contain the future destructor panic");
25886      return;
25887    }
25888
25889    let output = std::process::Command::new(std::env::current_exe().unwrap())
25890      .arg("--exact")
25891      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
25892      .arg("--nocapture")
25893      .env(CHILD_ENV, "1")
25894      .output()
25895      .expect("the subprocess test must start");
25896    assert!(
25897      output.status.success(),
25898      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25899      output.status.code(),
25900      String::from_utf8_lossy(&output.stdout),
25901      String::from_utf8_lossy(&output.stderr)
25902    );
25903  }
25904
25905  #[test]
25906  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
25907    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
25908
25909    if std::env::var_os(CHILD_ENV).is_some() {
25910      struct PanicOnDrop;
25911      impl Drop for PanicOnDrop {
25912        fn drop(&mut self) {
25913          panic!("intentional detached output destructor panic");
25914        }
25915      }
25916
25917      let controller = current_thread_controller("output-drop-subprocess");
25918      let handle = controller
25919        .try_spawn(async { PanicOnDrop })
25920        .unwrap_or_else(|_| panic!("runtime must accept the task"));
25921      drop(handle);
25922      let dispatch = controller_current_thread_dispatch(&controller);
25923      controller.drive_current_thread_dispatch(dispatch, false);
25924      controller.shutdown().unwrap();
25925      return;
25926    }
25927
25928    let output = std::process::Command::new(std::env::current_exe().unwrap())
25929      .arg("--exact")
25930      .arg(
25931        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
25932      )
25933      .arg("--nocapture")
25934      .env(CHILD_ENV, "1")
25935      .output()
25936      .expect("the subprocess test must start");
25937    assert!(
25938      output.status.success(),
25939      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
25940      output.status.code(),
25941      String::from_utf8_lossy(&output.stdout),
25942      String::from_utf8_lossy(&output.stderr)
25943    );
25944  }
25945
25946  #[cfg(not(target_family = "wasm"))]
25947  #[test]
25948  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
25949    use std::sync::mpsc;
25950
25951    struct BlockingDrop {
25952      controller: Arc<RuntimeController>,
25953      entered: mpsc::Sender<Option<u64>>,
25954      release: mpsc::Receiver<()>,
25955      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
25956    }
25957
25958    impl Drop for BlockingDrop {
25959      fn drop(&mut self) {
25960        self
25961          .entered
25962          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
25963          .unwrap();
25964        self.release.recv().unwrap();
25965        self
25966          .reentrant_shutdown
25967          .send(self.controller.shutdown())
25968          .unwrap();
25969      }
25970    }
25971
25972    let controller = Arc::new(current_thread_controller(
25973      "current-thread-host-drain-output-retirement",
25974    ));
25975    let old_generation = controller.backend().generation();
25976    let (entered_tx, entered_rx) = mpsc::channel();
25977    let (release_tx, release_rx) = mpsc::channel();
25978    let (reentrant_tx, reentrant_rx) = mpsc::channel();
25979    let output_controller = Arc::clone(&controller);
25980    let handle = controller
25981      .try_spawn(async move {
25982        BlockingDrop {
25983          controller: output_controller,
25984          entered: entered_tx,
25985          release: release_rx,
25986          reentrant_shutdown: reentrant_tx,
25987        }
25988      })
25989      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
25990    drop(handle);
25991    let dispatch = controller_current_thread_dispatch(&controller);
25992
25993    let driver_controller = Arc::clone(&controller);
25994    let driver =
25995      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
25996    assert_eq!(
25997      entered_rx
25998        .recv_timeout(Duration::from_secs(2))
25999        .expect("the host turn must begin destroying the detached output"),
26000      Some(old_generation),
26001      "detached output destruction must retain the generation that produced it"
26002    );
26003
26004    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26005    let shutdown_controller = Arc::clone(&controller);
26006    let shutdown = std::thread::spawn(move || {
26007      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26008    });
26009    wait_until(
26010      "CurrentThread shutdown to enter the old generation's stopping state",
26011      || {
26012        let state = controller
26013          .state
26014          .lock()
26015          .unwrap_or_else(std::sync::PoisonError::into_inner);
26016        matches!(
26017          &state.lifecycle,
26018          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
26019        )
26020      },
26021    );
26022
26023    let (restart_tx, restart_rx) = mpsc::channel();
26024    let restart_controller = Arc::clone(&controller);
26025    let restart = std::thread::spawn(move || {
26026      restart_controller.start().unwrap();
26027      restart_tx
26028        .send(restart_controller.backend().generation())
26029        .unwrap();
26030    });
26031
26032    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
26033    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
26034    let shutdown_overlapped = early_shutdown.is_some();
26035    let restart_overlapped = early_restart.is_some();
26036    release_tx.send(()).unwrap();
26037
26038    let reentrant_error = reentrant_rx
26039      .recv_timeout(Duration::from_secs(2))
26040      .expect("the retiring output destructor must finish its lifecycle check")
26041      .expect_err("old-generation output destruction must not shut down a replacement runtime");
26042    assert!(
26043      reentrant_error
26044        .to_string()
26045        .contains("generation being stopped"),
26046      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
26047    );
26048
26049    let shutdown_result = early_shutdown.unwrap_or_else(|| {
26050      shutdown_rx
26051        .recv_timeout(Duration::from_secs(2))
26052        .expect("shutdown must finish after detached output destruction retires")
26053    });
26054    shutdown_result.unwrap();
26055    let new_generation = early_restart.unwrap_or_else(|| {
26056      restart_rx
26057        .recv_timeout(Duration::from_secs(2))
26058        .expect("restart must finish after the old host turn retires")
26059    });
26060
26061    driver.join().unwrap();
26062    shutdown.join().unwrap();
26063    restart.join().unwrap();
26064    assert!(
26065      !shutdown_overlapped,
26066      "shutdown must not publish Stopped while a host turn is destroying detached output"
26067    );
26068    assert!(
26069      !restart_overlapped,
26070      "restart must not overlap a host turn from the retiring generation"
26071    );
26072    assert_ne!(new_generation, old_generation);
26073    controller.shutdown().unwrap();
26074  }
26075
26076  #[cfg(not(target_family = "wasm"))]
26077  #[test]
26078  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
26079    use std::sync::mpsc;
26080
26081    struct ShutdownOnDrop {
26082      controller: Arc<RuntimeController>,
26083      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26084    }
26085
26086    impl Drop for ShutdownOnDrop {
26087      fn drop(&mut self) {
26088        let _ = self.result.send(self.controller.shutdown());
26089      }
26090    }
26091
26092    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
26093    let (running_tx, running_rx) = mpsc::channel();
26094    let (release_tx, release_rx) = mpsc::channel();
26095    let running = controller
26096      .try_spawn_blocking(move || {
26097        running_tx.send(()).unwrap();
26098        release_rx.recv().unwrap();
26099      })
26100      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26101    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26102
26103    let (reentrant_tx, reentrant_rx) = mpsc::channel();
26104    let bomb = ShutdownOnDrop {
26105      controller: Arc::clone(&controller),
26106      result: reentrant_tx,
26107    };
26108    let queued = controller
26109      .try_spawn_blocking(move || {
26110        let _bomb = &bomb;
26111      })
26112      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26113
26114    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26115    let shutdown_controller = Arc::clone(&controller);
26116    let shutdown = std::thread::spawn(move || {
26117      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26118    });
26119    let error = reentrant_rx
26120      .recv_timeout(Duration::from_secs(2))
26121      .expect("queued destruction must not self-deadlock")
26122      .expect_err("the retiring generation must reject reentrant shutdown");
26123    assert!(error.to_string().contains("generation being stopped"));
26124    release_tx.send(()).unwrap();
26125    shutdown_rx
26126      .recv_timeout(Duration::from_secs(2))
26127      .unwrap()
26128      .unwrap();
26129    shutdown.join().unwrap();
26130    futures::executor::block_on(running).unwrap();
26131    assert!(futures::executor::block_on(queued).is_err());
26132  }
26133
26134  #[cfg(not(target_family = "wasm"))]
26135  #[test]
26136  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
26137    use std::sync::mpsc;
26138
26139    struct ShutdownAfterRelease {
26140      controller: Arc<RuntimeController>,
26141      entered: mpsc::Sender<()>,
26142      release: mpsc::Receiver<()>,
26143      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26144    }
26145
26146    impl Drop for ShutdownAfterRelease {
26147      fn drop(&mut self) {
26148        self.entered.send(()).unwrap();
26149        self.release.recv().unwrap();
26150        self.result.send(self.controller.shutdown()).unwrap();
26151      }
26152    }
26153
26154    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
26155    let (holder_started_tx, holder_started_rx) = mpsc::channel();
26156    let (release_holder_tx, release_holder_rx) = mpsc::channel();
26157    let holder = controller
26158      .try_spawn_blocking(move || {
26159        holder_started_tx.send(()).unwrap();
26160        release_holder_rx.recv().unwrap();
26161      })
26162      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26163    holder_started_rx
26164      .recv_timeout(Duration::from_secs(2))
26165      .unwrap();
26166
26167    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26168    let shutdown_controller = Arc::clone(&controller);
26169    let shutdown = std::thread::spawn(move || {
26170      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26171    });
26172    wait_until("the runtime entered Stopping", || {
26173      matches!(
26174        controller
26175          .state
26176          .lock()
26177          .unwrap_or_else(std::sync::PoisonError::into_inner)
26178          .lifecycle,
26179        RuntimeLifecycle::Stopping(_)
26180      )
26181    });
26182
26183    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26184    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26185    let (reentrant_tx, reentrant_rx) = mpsc::channel();
26186    let rejected_controller = Arc::clone(&controller);
26187    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
26188    let rejected = std::thread::spawn(move || {
26189      let bomb = ShutdownAfterRelease {
26190        controller: Arc::clone(&rejected_controller),
26191        entered: drop_entered_tx,
26192        release: release_drop_rx,
26193        result: reentrant_tx,
26194      };
26195      let handle = rejected_controller.spawn(async move {
26196        let _bomb = bomb;
26197        std::future::pending::<()>().await;
26198      });
26199      rejected_done_tx
26200        .send(futures::executor::block_on(handle))
26201        .unwrap();
26202    });
26203    drop_entered_rx
26204      .recv_timeout(Duration::from_secs(2))
26205      .unwrap();
26206
26207    release_holder_tx.send(()).unwrap();
26208    assert!(
26209      shutdown_rx
26210        .recv_timeout(Duration::from_millis(200))
26211        .is_err(),
26212      "shutdown crossed into Stopped before rejected destruction finished"
26213    );
26214
26215    let (start_tx, start_rx) = mpsc::channel();
26216    let start_controller = Arc::clone(&controller);
26217    let starter = std::thread::spawn(move || {
26218      start_tx.send(start_controller.start()).unwrap();
26219    });
26220    assert!(
26221      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26222      "restart crossed into a new generation before rejected destruction finished"
26223    );
26224
26225    release_drop_tx.send(()).unwrap();
26226    let error = reentrant_rx
26227      .recv_timeout(Duration::from_secs(2))
26228      .expect("the rejected destructor must complete")
26229      .expect_err("the retiring generation must reject the destructor's shutdown");
26230    assert!(error.to_string().contains("generation being stopped"));
26231    assert!(
26232      rejected_done_rx
26233        .recv_timeout(Duration::from_secs(2))
26234        .unwrap()
26235        .is_err()
26236    );
26237    rejected.join().unwrap();
26238
26239    shutdown_rx
26240      .recv_timeout(Duration::from_secs(2))
26241      .unwrap()
26242      .unwrap();
26243    shutdown.join().unwrap();
26244    start_rx
26245      .recv_timeout(Duration::from_secs(2))
26246      .unwrap()
26247      .unwrap();
26248    starter.join().unwrap();
26249    futures::executor::block_on(holder).unwrap();
26250    controller.shutdown().unwrap();
26251  }
26252
26253  #[cfg(not(target_family = "wasm"))]
26254  #[test]
26255  fn shutdown_contains_hostile_caught_panic_payload() {
26256    use std::{panic::panic_any, sync::mpsc};
26257
26258    struct PanicAgain;
26259    impl Drop for PanicAgain {
26260      fn drop(&mut self) {
26261        panic!("caught panic payload destructor escaped");
26262      }
26263    }
26264
26265    struct PanicPayloadOnDrop(mpsc::Sender<()>);
26266    impl Drop for PanicPayloadOnDrop {
26267      fn drop(&mut self) {
26268        let _ = self.0.send(());
26269        panic_any(PanicAgain);
26270      }
26271    }
26272
26273    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
26274    let (running_tx, running_rx) = mpsc::channel();
26275    let (release_tx, release_rx) = mpsc::channel();
26276    let running = controller
26277      .try_spawn_blocking(move || {
26278        running_tx.send(()).unwrap();
26279        release_rx.recv().unwrap();
26280      })
26281      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
26282    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26283
26284    let (drop_tx, drop_rx) = mpsc::channel();
26285    let payload = PanicPayloadOnDrop(drop_tx);
26286    let queued = controller
26287      .try_spawn_blocking(move || {
26288        let _payload = &payload;
26289      })
26290      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
26291
26292    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26293    let shutdown_controller = Arc::clone(&controller);
26294    let shutdown = std::thread::spawn(move || {
26295      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26296    });
26297    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
26298    release_tx.send(()).unwrap();
26299    shutdown_rx
26300      .recv_timeout(Duration::from_secs(2))
26301      .expect("hostile panic payload destruction must remain contained")
26302      .unwrap();
26303    shutdown
26304      .join()
26305      .expect("the caught payload must not panic during destruction");
26306    futures::executor::block_on(running).unwrap();
26307    assert!(futures::executor::block_on(queued).is_err());
26308  }
26309
26310  #[cfg(not(target_family = "wasm"))]
26311  #[test]
26312  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
26313    use std::sync::mpsc;
26314
26315    struct DropSignal(mpsc::Sender<()>);
26316    impl Drop for DropSignal {
26317      fn drop(&mut self) {
26318        let _ = self.0.send(());
26319      }
26320    }
26321
26322    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
26323    let (running_tx, running_rx) = mpsc::channel();
26324    let (release_tx, release_rx) = mpsc::channel();
26325    let running = controller
26326      .try_spawn_blocking(move || {
26327        running_tx.send(()).unwrap();
26328        release_rx.recv().unwrap();
26329        7usize
26330      })
26331      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26332    running_rx
26333      .recv_timeout(Duration::from_secs(5))
26334      .expect("the first blocking job must hold the only blocking slot");
26335
26336    let queued_ran = Arc::new(AtomicBool::new(false));
26337    let queued_ran_work = Arc::clone(&queued_ran);
26338    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
26339    let queued_drop_signal = DropSignal(queued_dropped_tx);
26340    let queued = controller
26341      .try_spawn_blocking(move || {
26342        let _drop_signal = queued_drop_signal;
26343        queued_ran_work.store(true, Ordering::SeqCst);
26344        11usize
26345      })
26346      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26347
26348    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26349    let shutdown_controller = Arc::clone(&controller);
26350    let shutdown = std::thread::spawn(move || {
26351      shutdown_controller.shutdown().unwrap();
26352      shutdown_done_tx.send(()).unwrap();
26353    });
26354
26355    queued_dropped_rx
26356      .recv_timeout(Duration::from_secs(5))
26357      .expect("shutdown must drop queued blocking work");
26358    assert!(
26359      !queued_ran.load(Ordering::SeqCst),
26360      "queued blocking work must not execute"
26361    );
26362    assert!(
26363      shutdown_done_rx.try_recv().is_err(),
26364      "shutdown must wait while an accepted blocking closure is still running"
26365    );
26366
26367    release_tx.send(()).unwrap();
26368    shutdown_done_rx
26369      .recv_timeout(Duration::from_secs(5))
26370      .expect("shutdown must finish after running blocking work retires");
26371    shutdown.join().unwrap();
26372    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
26373    assert!(
26374      futures::executor::block_on(queued).is_err(),
26375      "the queued blocking handle must resolve as cancelled"
26376    );
26377  }
26378
26379  #[cfg(not(target_family = "wasm"))]
26380  #[test]
26381  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
26382    use std::sync::mpsc;
26383
26384    struct PanicOnDrop(mpsc::Sender<()>);
26385    impl Drop for PanicOnDrop {
26386      fn drop(&mut self) {
26387        let _ = self.0.send(());
26388        panic!("intentional queued blocking destructor panic");
26389      }
26390    }
26391
26392    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
26393    let (running_tx, running_rx) = mpsc::channel();
26394    let (release_tx, release_rx) = mpsc::channel();
26395    let running = controller
26396      .try_spawn_blocking(move || {
26397        running_tx.send(()).unwrap();
26398        release_rx.recv().unwrap();
26399      })
26400      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26401    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26402
26403    let (drop_tx, drop_rx) = mpsc::channel();
26404    let panic_on_drop = PanicOnDrop(drop_tx);
26405    let queued = controller
26406      .try_spawn_blocking(move || {
26407        let _keep_captured_until_run = &panic_on_drop;
26408      })
26409      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
26410
26411    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26412    let shutdown_controller = Arc::clone(&controller);
26413    let shutdown = std::thread::spawn(move || {
26414      shutdown_controller.shutdown().unwrap();
26415      shutdown_done_tx.send(()).unwrap();
26416    });
26417
26418    drop_rx
26419      .recv_timeout(Duration::from_secs(5))
26420      .expect("shutdown must attempt to drop the queued closure");
26421    release_tx.send(()).unwrap();
26422    shutdown_done_rx
26423      .recv_timeout(Duration::from_secs(5))
26424      .expect("a panicking queued destructor must not strand shutdown");
26425    shutdown
26426      .join()
26427      .expect("shutdown must contain the destructor panic");
26428    futures::executor::block_on(running).unwrap();
26429    assert!(futures::executor::block_on(queued).is_err());
26430
26431    controller
26432      .start()
26433      .expect("the stopped controller must restart");
26434    let restarted = controller
26435      .try_spawn_blocking(|| 17usize)
26436      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26437    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
26438    controller.shutdown().unwrap();
26439  }
26440
26441  #[cfg(not(target_family = "wasm"))]
26442  #[test]
26443  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
26444    use std::{panic::panic_any, sync::mpsc};
26445
26446    struct ReentrantPayload {
26447      controller: Arc<RuntimeController>,
26448      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26449    }
26450
26451    impl Drop for ReentrantPayload {
26452      fn drop(&mut self) {
26453        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26454        let shutdown = self.controller.shutdown();
26455        self.result.send((generation, shutdown)).unwrap();
26456      }
26457    }
26458
26459    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
26460
26461    impl Drop for PanicPayloadOnDrop {
26462      fn drop(&mut self) {
26463        panic_any(self.0.take().expect("the result must panic only once"));
26464      }
26465    }
26466
26467    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
26468    let generation = controller.backend().generation();
26469    let (started_tx, started_rx) = mpsc::channel();
26470    let (release_tx, release_rx) = mpsc::channel();
26471    let (result_tx, result_rx) = mpsc::channel();
26472    let payload_controller = Arc::clone(&controller);
26473    let handle = controller
26474      .try_spawn_blocking(move || {
26475        started_tx.send(()).unwrap();
26476        release_rx.recv().unwrap();
26477        PanicPayloadOnDrop(Some(ReentrantPayload {
26478          controller: payload_controller,
26479          result: result_tx,
26480        }))
26481      })
26482      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26483    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26484
26485    // Detach the receiver before the result is delivered. Sending then drops
26486    // the result on the worker, whose destructor intentionally panics.
26487    drop(handle);
26488    release_tx.send(()).unwrap();
26489    let (observed_generation, reentrant_shutdown) = result_rx
26490      .recv_timeout(Duration::from_secs(5))
26491      .expect("the dropped result panic payload must not self-deadlock");
26492    assert_eq!(observed_generation, Some(generation));
26493    let error =
26494      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
26495    assert!(error.to_string().contains("work running on that runtime"));
26496
26497    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26498    let shutdown_controller = Arc::clone(&controller);
26499    let shutdown = std::thread::spawn(move || {
26500      shutdown_controller.shutdown().unwrap();
26501      shutdown_done_tx.send(()).unwrap();
26502    });
26503    shutdown_done_rx
26504      .recv_timeout(Duration::from_secs(5))
26505      .expect("a dropped-result destructor panic must not strand shutdown");
26506    shutdown
26507      .join()
26508      .expect("shutdown must contain the destructor panic");
26509
26510    controller
26511      .start()
26512      .expect("the stopped controller must restart");
26513    let restarted = controller
26514      .try_spawn_blocking(|| 19usize)
26515      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26516    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
26517    controller.shutdown().unwrap();
26518  }
26519
26520  #[cfg(not(target_family = "wasm"))]
26521  #[test]
26522  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
26523    use std::sync::mpsc;
26524
26525    struct PanicOnDrop(mpsc::Sender<()>);
26526
26527    impl Drop for PanicOnDrop {
26528      fn drop(&mut self) {
26529        self.0.send(()).unwrap();
26530        panic!("intentional buffered blocking result destructor panic");
26531      }
26532    }
26533
26534    let options = RuntimeOptions {
26535      flavor: RuntimeFlavor::MultiThread,
26536      worker_threads: 2,
26537      max_blocking_tasks: 1,
26538      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
26539      park_deadline: None,
26540      drain_linger: DEFAULT_DRAIN_LINGER,
26541    };
26542    let executor =
26543      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
26544    let (started_tx, started_rx) = mpsc::channel();
26545    let (dropped_tx, dropped_rx) = mpsc::channel();
26546    let handle = executor.schedule_blocking(move || {
26547      started_tx.send(()).unwrap();
26548      PanicOnDrop(dropped_tx)
26549    });
26550    started_rx
26551      .recv_timeout(Duration::from_secs(2))
26552      .expect("the blocking closure must run");
26553    wait_until("the completed blocking result to be buffered", || {
26554      executor.active_blocking.load(Ordering::Acquire) == 0
26555    });
26556    assert!(
26557      dropped_rx.try_recv().is_err(),
26558      "the buffered result must remain owned by the live receiver"
26559    );
26560
26561    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
26562    assert!(
26563      dropped.is_ok(),
26564      "dropping the buffered result must contain its destructor panic"
26565    );
26566    dropped_rx
26567      .recv_timeout(Duration::from_secs(2))
26568      .expect("dropping the receiver must reclaim the buffered result");
26569  }
26570
26571  #[cfg(not(target_family = "wasm"))]
26572  #[test]
26573  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
26574    use std::sync::mpsc;
26575
26576    struct PanicOnDrop(mpsc::Sender<()>);
26577    impl Drop for PanicOnDrop {
26578      fn drop(&mut self) {
26579        let _ = self.0.send(());
26580        panic!("intentional late blocking destructor panic");
26581      }
26582    }
26583
26584    let executor = multi_thread_executor(2, None, "closed-queue-drop");
26585    executor.begin_shutdown();
26586
26587    let (drop_tx, drop_rx) = mpsc::channel();
26588    let panic_on_drop = PanicOnDrop(drop_tx);
26589    let handle = executor.schedule_blocking(move || {
26590      let _keep_captured_until_run = &panic_on_drop;
26591    });
26592
26593    drop_rx
26594      .recv_timeout(Duration::from_secs(5))
26595      .expect("the closed queue must retire the rejected closure");
26596    assert!(
26597      futures::executor::block_on(handle).is_err(),
26598      "the rejected blocking handle must resolve as cancelled"
26599    );
26600  }
26601
26602  #[cfg(not(target_family = "wasm"))]
26603  #[test]
26604  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
26605    struct PanicWake;
26606    impl std::task::Wake for PanicWake {
26607      fn wake(self: Arc<Self>) {
26608        panic!("intentional timer waker panic");
26609      }
26610    }
26611
26612    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
26613    let backend = controller.backend();
26614    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26615      panic!("configured MultiThread backend must create a Rayon executor");
26616    };
26617    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
26618    let waker = Waker::from(Arc::new(PanicWake));
26619    let mut cx = Context::from_waker(&waker);
26620    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
26621    drop(backend);
26622
26623    controller
26624      .shutdown()
26625      .expect("shutdown must contain the timer waker panic");
26626    controller
26627      .start()
26628      .expect("the stopped controller must restart");
26629    let restarted = controller
26630      .try_spawn_blocking(|| 23usize)
26631      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
26632    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
26633    controller.shutdown().unwrap();
26634  }
26635
26636  #[cfg(not(target_family = "wasm"))]
26637  #[test]
26638  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
26639    use std::sync::mpsc;
26640
26641    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
26642    let backend = controller.backend();
26643    let old_generation = backend.generation();
26644    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26645      panic!("configured MultiThread backend must create a Rayon executor");
26646    };
26647    let old_workers = Arc::clone(&executor.worker_lifecycle);
26648    drop(backend);
26649
26650    let (running_tx, running_rx) = mpsc::channel();
26651    let (release_tx, release_rx) = mpsc::channel();
26652    let running = controller
26653      .try_spawn_blocking(move || {
26654        running_tx.send(()).unwrap();
26655        release_rx.recv().unwrap();
26656      })
26657      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
26658    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26659
26660    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
26661    let shutdown_controller = Arc::clone(&controller);
26662    let shutdown = std::thread::spawn(move || {
26663      shutdown_controller.shutdown().unwrap();
26664      shutdown_done_tx.send(()).unwrap();
26665    });
26666    wait_until("controller entered Stopping", || {
26667      let state = controller
26668        .state
26669        .lock()
26670        .unwrap_or_else(std::sync::PoisonError::into_inner);
26671      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
26672    });
26673
26674    let (start_done_tx, start_done_rx) = mpsc::channel();
26675    let start_controller = Arc::clone(&controller);
26676    let starter = std::thread::spawn(move || {
26677      start_controller.start().unwrap();
26678      start_done_tx.send(()).unwrap();
26679    });
26680    assert!(
26681      start_done_rx.try_recv().is_err(),
26682      "start must wait while shutdown is quiescing"
26683    );
26684    assert!(
26685      old_workers.remaining() > 0,
26686      "the held old generation must still own workers"
26687    );
26688
26689    release_tx.send(()).unwrap();
26690    futures::executor::block_on(running).unwrap();
26691    shutdown_done_rx
26692      .recv_timeout(Duration::from_secs(5))
26693      .unwrap();
26694    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
26695    shutdown.join().unwrap();
26696    starter.join().unwrap();
26697    assert_eq!(
26698      old_workers.remaining(),
26699      0,
26700      "restart may begin only after every old worker exits"
26701    );
26702
26703    let backend = controller.backend();
26704    assert_ne!(backend.generation(), old_generation);
26705    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
26706      panic!("restarted backend must remain MultiThread");
26707    };
26708    assert_eq!(
26709      executor.pool.current_num_threads(),
26710      controller.options().worker_threads
26711    );
26712    drop(backend);
26713    controller.shutdown().unwrap();
26714  }
26715
26716  #[cfg(not(target_family = "wasm"))]
26717  #[test]
26718  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
26719    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
26720    let task_controller = Arc::clone(&controller);
26721    let handle = controller
26722      .try_spawn(async move {
26723        task_controller
26724          .shutdown()
26725          .expect_err("runtime work must not wait for its own generation")
26726          .to_string()
26727      })
26728      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
26729    let message = futures::executor::block_on(handle).unwrap();
26730    assert!(message.contains("cannot shut down"));
26731    controller.shutdown().unwrap();
26732  }
26733
26734  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
26735  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
26736  // global `RUNTIME`) and run the workload on a child thread guarded by a
26737  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
26738  // wedging the whole suite.
26739
26740  #[cfg(not(target_family = "wasm"))]
26741  #[test]
26742  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
26743    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
26744    // blocking job while `active_blocking >= max_blocking`. The guard is
26745    // exercised deterministically (no sleeps, no incidental worker overlap):
26746    //
26747    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
26748    //      occupies a counted slot (`take_blocking` increments `active_blocking`
26749    //      BEFORE running the closure), signals `held`, then parks on a release
26750    //      barrier -- so all slots are provably occupied before we proceed.
26751    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
26752    //      With the cap intact `take_blocking` returns `None` for them, so the
26753    //      idle workers cannot pop them: no extra may signal `extra_started`.
26754    //   3. We release the holders, then assert EVERY job (holders + extras)
26755    //      completes with the right result and the recorded peak never exceeded
26756    //      the cap.
26757    //
26758    // Decisive property: if the `active >= self.max_blocking` guard were removed,
26759    // the two idle workers would pop the extras while both holders are still
26760    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
26761    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
26762    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
26763    // can never self-deadlock on the over-cap extras.
26764    use std::sync::{Barrier, mpsc};
26765    use std::time::Duration;
26766
26767    let (done_tx, done_rx) = mpsc::channel();
26768    let peak_out = Arc::new(Mutex::new(None));
26769    let peak_slot = Arc::clone(&peak_out);
26770    let runner = std::thread::spawn(move || {
26771      let metrics = Arc::new(RuntimeMetrics::default());
26772      let options = RuntimeOptions {
26773        flavor: RuntimeFlavor::MultiThread,
26774        worker_threads: 4,
26775        max_blocking_tasks: 2,
26776        thread_name_prefix: "rd9-blk-cap".to_string(),
26777        park_deadline: None,
26778        drain_linger: DEFAULT_DRAIN_LINGER,
26779      };
26780      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26781
26782      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
26783      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
26784
26785      // Holders: signal once they hold a counted slot, then park until released.
26786      let (held_tx, held_rx) = mpsc::channel();
26787      let release = Arc::new(Barrier::new(holder_count + 1));
26788      let mut holder_handles = Vec::new();
26789      for _ in 0..holder_count {
26790        let held_tx = held_tx.clone();
26791        let release = Arc::clone(&release);
26792        holder_handles.push(executor.schedule_blocking(move || {
26793          held_tx.send(()).unwrap();
26794          release.wait();
26795          0usize
26796        }));
26797      }
26798      // Wait until every slot is provably occupied (cap fully saturated).
26799      for _ in 0..holder_count {
26800        held_rx.recv().unwrap();
26801      }
26802
26803      // Queue the beyond-cap extras. Each signals the instant it begins running.
26804      let (extra_started_tx, extra_started_rx) = mpsc::channel();
26805      let mut extra_handles = Vec::new();
26806      for i in 0..extra_count {
26807        let extra_started_tx = extra_started_tx.clone();
26808        extra_handles.push(executor.schedule_blocking(move || {
26809          extra_started_tx.send(i).unwrap();
26810          i * 2
26811        }));
26812      }
26813
26814      // With the cap intact, `take_blocking` returns `None` while both slots are
26815      // held, so no extra can have started running yet.
26816      assert!(
26817        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
26818        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
26819      );
26820
26821      // Release the holders so the queued extras can run within the cap.
26822      release.wait();
26823
26824      for handle in holder_handles {
26825        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
26826      }
26827      // Each handle owns its own oneshot receiver, so awaiting in order returns
26828      // each extra's own result regardless of the order the pool ran them.
26829      for (i, handle) in extra_handles.into_iter().enumerate() {
26830        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
26831      }
26832
26833      // `blocking_tasks_started` is incremented at job entry, so once every result
26834      // is in, all holder + extra starts are recorded.
26835      assert_eq!(
26836        metrics.blocking_tasks_started.load(Ordering::Relaxed),
26837        (holder_count + extra_count) as u64
26838      );
26839
26840      *peak_slot
26841        .lock()
26842        .unwrap_or_else(std::sync::PoisonError::into_inner) =
26843        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
26844      let _ = done_tx.send(());
26845    });
26846
26847    match done_rx.recv_timeout(Duration::from_secs(10)) {
26848      Ok(()) => runner.join().unwrap(),
26849      Err(error) => panic!("blocking-cap test did not complete ({error})"),
26850    }
26851    let peak = peak_out
26852      .lock()
26853      .unwrap_or_else(std::sync::PoisonError::into_inner)
26854      .expect("peak captured");
26855    assert!(
26856      peak >= 1,
26857      "at least one blocking job must have run (peak {peak})"
26858    );
26859    assert!(
26860      peak <= 2,
26861      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
26862    );
26863  }
26864
26865  #[cfg(not(target_family = "wasm"))]
26866  #[test]
26867  fn multi_thread_drain_budget_completes_large_runnable_batch() {
26868    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
26869    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
26870    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
26871    // still complete every task, with the runnable counters settling back to 0.
26872    use std::sync::mpsc;
26873    use std::time::{Duration, Instant};
26874
26875    let (done_tx, done_rx) = mpsc::channel();
26876    let runner = std::thread::spawn(move || {
26877      let metrics = Arc::new(RuntimeMetrics::default());
26878      let options = RuntimeOptions {
26879        flavor: RuntimeFlavor::MultiThread,
26880        worker_threads: 2,
26881        max_blocking_tasks: 2,
26882        thread_name_prefix: "rd9-budget".to_string(),
26883        park_deadline: None,
26884        drain_linger: DEFAULT_DRAIN_LINGER,
26885      };
26886      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
26887
26888      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
26889      let completed = Arc::new(AtomicU64::new(0));
26890      let mut tasks = Vec::new();
26891      for _ in 0..task_count {
26892        let completed = Arc::clone(&completed);
26893        let scheduler = Arc::clone(&executor);
26894        let (runnable, task) = async_task::spawn(
26895          async move {
26896            completed.fetch_add(1, Ordering::Relaxed);
26897          },
26898          move |r| scheduler.schedule(r),
26899        );
26900        executor.schedule(runnable);
26901        tasks.push(task);
26902      }
26903      for task in tasks {
26904        futures::executor::block_on(task);
26905      }
26906
26907      // Every immediately-ready future is polled exactly once; `runnable_polls`
26908      // (incremented at poll entry, before completion) is therefore exact here.
26909      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
26910      assert_eq!(
26911        metrics.runnable_polls.load(Ordering::Relaxed),
26912        task_count as u64
26913      );
26914
26915      // The active-runnable guard for the LAST task drops just after its Task
26916      // resolves (i.e. just after `block_on` returns), so give the counters a
26917      // brief, bounded moment to settle to 0 rather than asserting them racily.
26918      let deadline = Instant::now() + Duration::from_secs(5);
26919      loop {
26920        let active = metrics.active_runnables.load(Ordering::Relaxed);
26921        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
26922        if active == 0 && queued == 0 {
26923          break;
26924        }
26925        assert!(
26926          Instant::now() < deadline,
26927          "runnable counters did not settle to 0: active={active} queued={queued}"
26928        );
26929        std::thread::yield_now();
26930      }
26931      let _ = done_tx.send(());
26932    });
26933
26934    match done_rx.recv_timeout(Duration::from_secs(15)) {
26935      Ok(()) => runner.join().unwrap(),
26936      Err(error) => panic!("drain-budget test did not complete ({error})"),
26937    }
26938  }
26939
26940  #[cfg(not(target_family = "wasm"))]
26941  #[test]
26942  fn multi_thread_spawned_panic_surfaces_as_join_error() {
26943    // A panicking spawned future must surface as `Err(JoinError)` carrying the
26944    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
26945    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
26946    // drive the resulting `JoinHandle` on this executor -- without touching the
26947    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
26948    // `panic!("x")` message round-trips as `"x"`.
26949    use std::sync::mpsc;
26950    use std::time::Duration;
26951
26952    let (done_tx, done_rx) = mpsc::channel();
26953    let runner = std::thread::spawn(move || {
26954      let metrics = Arc::new(RuntimeMetrics::default());
26955      let options = RuntimeOptions {
26956        flavor: RuntimeFlavor::MultiThread,
26957        worker_threads: 2,
26958        max_blocking_tasks: 2,
26959        thread_name_prefix: "rd9-panic".to_string(),
26960        park_deadline: None,
26961        drain_linger: DEFAULT_DRAIN_LINGER,
26962      };
26963      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
26964
26965      let scheduler = Arc::clone(&executor);
26966      let wrapped = async {
26967        AssertUnwindSafe(async { panic!("x") })
26968          .catch_unwind()
26969          .await
26970          .map(|()| ContainedTaskOutput::new((), u64::MAX))
26971          .map_err(panic_payload_to_join_error)
26972      };
26973      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
26974      executor.schedule(runnable);
26975
26976      let result: Result<(), JoinError> =
26977        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
26978          task: task.fallible(),
26979          awaiter: None,
26980          dependency: Arc::new(TaskDependency::default()),
26981        }));
26982      let _ = done_tx.send(result);
26983    });
26984
26985    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
26986      Ok(result) => {
26987        runner.join().unwrap();
26988        result
26989      }
26990      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
26991    };
26992    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
26993    assert_eq!(error.to_string(), "x");
26994  }
26995
26996  #[test]
26997  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
26998    use std::sync::mpsc;
26999
27000    let metrics = Arc::new(RuntimeMetrics::default());
27001    metrics.tasks_spawned.store(1, Ordering::Relaxed);
27002    metrics.tasks_completed.store(2, Ordering::Relaxed);
27003    metrics.tasks_panicked.store(3, Ordering::Relaxed);
27004    metrics.runnable_schedules.store(4, Ordering::Relaxed);
27005    metrics.runnable_polls.store(5, Ordering::Relaxed);
27006    metrics
27007      .blocking_tasks_scheduled
27008      .store(14, Ordering::Relaxed);
27009    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
27010    metrics
27011      .blocking_tasks_completed
27012      .store(11, Ordering::Relaxed);
27013    metrics.runnable_scheduled();
27014    let (guards_live_tx, guards_live_rx) = mpsc::channel();
27015    let (release_tx, release_rx) = mpsc::channel();
27016    let guard_metrics = Arc::clone(&metrics);
27017    let guard_thread = std::thread::spawn(move || {
27018      let runnable_guard = guard_metrics.runnable_started();
27019      let blocking_guard = guard_metrics.blocking_started(true);
27020      guards_live_tx.send(()).unwrap();
27021      release_rx.recv().unwrap();
27022      drop(runnable_guard);
27023      drop(blocking_guard);
27024    });
27025    guards_live_rx.recv().unwrap();
27026    let fingerprint_before_reset = metrics.progress_fingerprint();
27027
27028    metrics.reset();
27029
27030    assert!(
27031      metrics.progress_fingerprint() != fingerprint_before_reset,
27032      "a reset must advance the deadlock detector generation even if counters later repeat"
27033    );
27034    assert_eq!(
27035      metrics.tasks_spawned.load(Ordering::Relaxed),
27036      0,
27037      "tasks_spawned"
27038    );
27039    assert_eq!(
27040      metrics.tasks_completed.load(Ordering::Relaxed),
27041      0,
27042      "tasks_completed"
27043    );
27044    assert_eq!(
27045      metrics.tasks_panicked.load(Ordering::Relaxed),
27046      0,
27047      "tasks_panicked"
27048    );
27049    assert_eq!(
27050      metrics.runnable_schedules.load(Ordering::Relaxed),
27051      0,
27052      "runnable_schedules"
27053    );
27054    assert_eq!(
27055      metrics.runnable_polls.load(Ordering::Relaxed),
27056      0,
27057      "runnable_polls"
27058    );
27059    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
27060    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
27061    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
27062    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
27063    assert_eq!(
27064      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
27065      0,
27066      "blocking_tasks_scheduled"
27067    );
27068    assert_eq!(
27069      metrics.blocking_tasks_started.load(Ordering::Relaxed),
27070      0,
27071      "blocking_tasks_started"
27072    );
27073    assert_eq!(
27074      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
27075      0,
27076      "blocking_tasks_completed"
27077    );
27078    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
27079    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
27080
27081    release_tx.send(()).unwrap();
27082    guard_thread.join().unwrap();
27083    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
27084    assert_eq!(
27085      metrics.active_blocking_tasks.load(Ordering::Relaxed),
27086      0,
27087      "a live blocking guard completed after reset without unsigned underflow"
27088    );
27089    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
27090  }
27091
27092  #[test]
27093  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
27094    use std::sync::mpsc;
27095
27096    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
27097      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
27098        *slot.borrow_mut() = Some(Box::new(move || {
27099          entered.send(()).unwrap();
27100          release.recv().unwrap();
27101        }));
27102      });
27103    }
27104
27105    let options = RuntimeOptions {
27106      flavor: RuntimeFlavor::CurrentThread,
27107      worker_threads: 1,
27108      max_blocking_tasks: 1,
27109      thread_name_prefix: "metrics-snapshot".to_string(),
27110      park_deadline: None,
27111      drain_linger: DEFAULT_DRAIN_LINGER,
27112    };
27113
27114    let queued_metrics = Arc::new(RuntimeMetrics::default());
27115    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
27116    let (queued_release_tx, queued_release_rx) = mpsc::channel();
27117    let queued_writer_metrics = Arc::clone(&queued_metrics);
27118    let queued_writer = std::thread::spawn(move || {
27119      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
27120      queued_writer_metrics.runnable_scheduled();
27121    });
27122    queued_entered_rx.recv().unwrap();
27123    assert_eq!(
27124      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
27125      0
27126    );
27127    let snapshot = queued_metrics.snapshot(&options);
27128    assert_eq!(snapshot.queued_runnables, 1);
27129    assert_eq!(snapshot.max_queued_runnables, 1);
27130    queued_release_tx.send(()).unwrap();
27131    queued_writer.join().unwrap();
27132    queued_metrics.runnable_cancelled();
27133
27134    let active_metrics = Arc::new(RuntimeMetrics::default());
27135    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
27136    let (active_entered_tx, active_entered_rx) = mpsc::channel();
27137    let (active_release_tx, active_release_rx) = mpsc::channel();
27138    let active_writer_metrics = Arc::clone(&active_metrics);
27139    let active_writer = std::thread::spawn(move || {
27140      pause_next_high_water_update(active_entered_tx, active_release_rx);
27141      let _active = active_writer_metrics.runnable_started();
27142    });
27143    active_entered_rx.recv().unwrap();
27144    assert_eq!(
27145      active_metrics.max_active_runnables.load(Ordering::Relaxed),
27146      0
27147    );
27148    let snapshot = active_metrics.snapshot(&options);
27149    assert_eq!(snapshot.active_runnables, 1);
27150    assert_eq!(snapshot.max_active_runnables, 1);
27151    active_release_tx.send(()).unwrap();
27152    active_writer.join().unwrap();
27153
27154    let blocking_metrics = Arc::new(RuntimeMetrics::default());
27155    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
27156    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
27157    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
27158    let blocking_writer = std::thread::spawn(move || {
27159      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
27160      let _active = blocking_writer_metrics.blocking_started(true);
27161    });
27162    blocking_entered_rx.recv().unwrap();
27163    assert_eq!(
27164      blocking_metrics
27165        .max_active_blocking_tasks
27166        .load(Ordering::Relaxed),
27167      0
27168    );
27169    let snapshot = blocking_metrics.snapshot(&options);
27170    assert_eq!(snapshot.active_blocking_tasks, 1);
27171    assert_eq!(snapshot.max_active_blocking_tasks, 1);
27172    blocking_release_tx.send(()).unwrap();
27173    blocking_writer.join().unwrap();
27174  }
27175
27176  #[test]
27177  fn runtime_metrics_reset_fails_before_generation_wraparound() {
27178    let metrics = RuntimeMetrics::default();
27179    metrics
27180      .reset_generation
27181      .store(u64::MAX - 1, Ordering::SeqCst);
27182    metrics.tasks_spawned.store(7, Ordering::Relaxed);
27183
27184    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
27185    assert!(exhausted.is_err());
27186    assert!(!metrics.reset_lock.is_poisoned());
27187    assert_eq!(
27188      metrics.reset_generation.load(Ordering::SeqCst),
27189      u64::MAX - 1
27190    );
27191    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
27192  }
27193
27194  // ---- Self-detecting block_on deadlocks -------------------------------------
27195  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
27196  // park decision with no pending wake panics with the typed diagnostic; (2) a
27197  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
27198  // lives at the PARK DECISION, not at Pending-return; (3) an
27199  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
27200  // progress RESETS the deadline instead of firing; (5) the
27201  // foreign/napi whole-build park is excluded from the deadline entirely.
27202
27203  /// A future that never completes and never wakes anyone: once its driver
27204  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
27205  struct NeverReady;
27206  impl Future for NeverReady {
27207    type Output = ();
27208    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27209      Poll::Pending
27210    }
27211  }
27212
27213  #[cfg(not(target_family = "wasm"))]
27214  struct ReadyAfterOwnerLanePublication {
27215    dependency: BlockingDependency,
27216    ready: Arc<AtomicBool>,
27217  }
27218
27219  #[cfg(not(target_family = "wasm"))]
27220  impl Future for ReadyAfterOwnerLanePublication {
27221    type Output = ();
27222
27223    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27224      if self.ready.load(Ordering::SeqCst) {
27225        Poll::Ready(())
27226      } else {
27227        // This shape publishes on every pending poll and never retires; the
27228        // owner-lane assertions read the state, not the token.
27229        let mut published = PublishedBlockingDependencies::default();
27230        record_blocking_dependency(&mut published, self.dependency);
27231        Poll::Pending
27232      }
27233    }
27234  }
27235
27236  #[test]
27237  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
27238    // Shape (1): on a threadless build a CT park decision with an empty queue
27239    // and no wake token pending is a PROVABLE deadlock (no other thread exists
27240    // to deliver the wake) -- `block_on` must panic immediately with the typed
27241    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
27242    // runs on a child thread bounded by recv_timeout so a missing detection
27243    // (a hang) fails the test rather than wedging the suite.
27244    use std::sync::mpsc;
27245
27246    let (done_tx, done_rx) = mpsc::channel();
27247    let runner = std::thread::spawn(move || {
27248      let metrics = Arc::new(RuntimeMetrics::default());
27249      // Native stand-in for the threadless wasm build; no deadline because the
27250      // certain case is not timing-based.
27251      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27252
27253      let payload = catch_unwind(AssertUnwindSafe(|| {
27254        let mut future = std::pin::pin!(NeverReady);
27255        executor.block_on(future.as_mut());
27256      }))
27257      .expect_err("a threadless park with an empty queue and no pending wake must panic");
27258      let diagnostic = payload
27259        .downcast_ref::<BlockOnDeadlock>()
27260        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
27261      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
27262      assert_eq!(
27263        diagnostic.park_deadline, None,
27264        "the certain case carries no deadline"
27265      );
27266      let message = diagnostic.to_string();
27267      assert!(
27268        message.contains("block_on") && message.contains("JS"),
27269        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
27270      );
27271      let _ = done_tx.send(());
27272    });
27273
27274    match done_rx.recv_timeout(Duration::from_secs(10)) {
27275      Ok(()) => runner.join().unwrap(),
27276      Err(error) => panic!(
27277        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
27278      ),
27279    }
27280  }
27281
27282  #[test]
27283  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
27284    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
27285    // `Pending` but wakes itself synchronously during the poll leaves
27286    // a wake token pending, so the loop must consume the token and re-poll --
27287    // NOT panic -- even on a threadless build. "Pending returned" alone is
27288    // never a deadlock.
27289    use std::sync::mpsc;
27290
27291    struct PendThenReady {
27292      polls: usize,
27293    }
27294    impl Future for PendThenReady {
27295      type Output = ();
27296      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
27297        self.polls += 1;
27298        if self.polls >= 2 {
27299          Poll::Ready(())
27300        } else {
27301          cx.waker().wake_by_ref();
27302          Poll::Pending
27303        }
27304      }
27305    }
27306
27307    let (done_tx, done_rx) = mpsc::channel();
27308    let runner = std::thread::spawn(move || {
27309      let metrics = Arc::new(RuntimeMetrics::default());
27310      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
27311      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
27312      executor.block_on(future.as_mut());
27313      assert_eq!(
27314        future.polls, 2,
27315        "the self-woken future must have been re-polled to completion"
27316      );
27317      let _ = done_tx.send(());
27318    });
27319
27320    match done_rx.recv_timeout(Duration::from_secs(10)) {
27321      Ok(()) => runner.join().unwrap(),
27322      Err(error) => panic!(
27323        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
27324      ),
27325    }
27326  }
27327
27328  #[cfg(not(target_family = "wasm"))]
27329  #[test]
27330  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
27331    // Threaded CT is NOT provably dead at the park decision (another OS thread
27332    // could deliver a wake later), so detection there is deadline-based and
27333    // opt-in. A park that outlives the armed deadline with ZERO runtime
27334    // progress must panic with the typed diagnostic.
27335    use std::sync::mpsc;
27336
27337    let (done_tx, done_rx) = mpsc::channel();
27338    let runner = std::thread::spawn(move || {
27339      let metrics = Arc::new(RuntimeMetrics::default());
27340      let executor = Arc::new(CurrentThreadExecutor::with_detection(
27341        metrics,
27342        false,
27343        Some(Duration::from_millis(50)),
27344      ));
27345
27346      let payload = catch_unwind(AssertUnwindSafe(|| {
27347        let mut future = std::pin::pin!(NeverReady);
27348        executor.block_on(future.as_mut());
27349      }))
27350      .expect_err("a dead native CT park must fire the armed deadline");
27351      let diagnostic = payload
27352        .downcast_ref::<BlockOnDeadlock>()
27353        .expect("typed payload");
27354      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
27355      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27356      // Teardown inside the bounded window: a wedged executor drop must fail
27357      // the recv below loudly, not hang the final join silently.
27358      drop(executor);
27359      let _ = done_tx.send(());
27360    });
27361
27362    match done_rx.recv_timeout(Duration::from_secs(10)) {
27363      Ok(()) => join_within(
27364        "the CT deadline-fire runner exits after teardown",
27365        runner,
27366        Duration::from_secs(30),
27367      ),
27368      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
27369    }
27370  }
27371
27372  #[cfg(not(target_family = "wasm"))]
27373  #[test]
27374  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
27375    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
27376    // runtime is alive (e.g. a slow JS plugin with other
27377    // work proceeding), so the deadline must re-arm instead of firing.
27378    // Progress is injected by advancing the very counter the reset reads --
27379    // the exact seam, isolated from wake delivery (production advances it via
27380    // real runnable polls).
27381    //
27382    // Deadline/cadence ratio: the reset is only exercised when at least one
27383    // tick lands inside EVERY armed window, so the window must dwarf the
27384    // injector thread's worst-case scheduling stall. On starved CI hosts
27385    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
27386    // can overshoot past a 60ms window, leaving one window with zero
27387    // progress -- which IS a deadline fire by design. Ticks every ~25ms
27388    // against a 1s window tolerate a ~975ms stall while the park is still
27389    // held across multiple full windows, so the repeated RE-ARM (not merely
27390    // the first arm) is what keeps the park alive.
27391    use std::sync::mpsc;
27392
27393    let deadline = Duration::from_millis(1000);
27394    let metrics = Arc::new(RuntimeMetrics::default());
27395    let executor = Arc::new(CurrentThreadExecutor::with_detection(
27396      Arc::clone(&metrics),
27397      false,
27398      Some(deadline),
27399    ));
27400
27401    let (value_tx, value_rx) = oneshot::channel::<usize>();
27402    let (done_tx, done_rx) = mpsc::channel();
27403    let runner = std::thread::spawn(move || {
27404      let mut output = None;
27405      {
27406        let mut future = std::pin::pin!(async {
27407          output = Some(value_rx.await.unwrap());
27408        });
27409        executor.block_on(future.as_mut());
27410      }
27411      assert_eq!(output, Some(9));
27412      // Teardown inside the bounded window (see `join_within`).
27413      drop(executor);
27414      let _ = done_tx.send(());
27415    });
27416
27417    // Hold the park across several full deadline windows, every one
27418    // containing many injected progress ticks, then deliver the (legitimately
27419    // late) wake. The loop is elapsed-based so an oversleeping iteration
27420    // extends the hold (more windows) instead of thinning the tick supply,
27421    // and the final tick lands at most one iteration before the wake.
27422    let hold_until = Instant::now() + deadline * 5 / 2;
27423    while Instant::now() < hold_until {
27424      std::thread::sleep(Duration::from_millis(25));
27425      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27426    }
27427    value_tx.send(9).unwrap();
27428
27429    match done_rx.recv_timeout(Duration::from_secs(30)) {
27430      Ok(()) => join_within(
27431        "the CT deadline-reset runner exits after teardown",
27432        runner,
27433        Duration::from_secs(30),
27434      ),
27435      Err(error) => panic!(
27436        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
27437      ),
27438    }
27439  }
27440
27441  #[cfg(not(target_family = "wasm"))]
27442  #[test]
27443  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
27444    // Shape (3): the MT deadline applies to the DriverParker inside
27445    // `cooperative_block_on`. Run the cooperative branch directly on this
27446    // thread with the test-only on-pool marker so the panic is observable as
27447    // a typed payload without rayon in between.
27448    use std::sync::mpsc;
27449
27450    let (done_tx, done_rx) = mpsc::channel();
27451    let runner = std::thread::spawn(move || {
27452      let options = RuntimeOptions {
27453        flavor: RuntimeFlavor::MultiThread,
27454        worker_threads: 1,
27455        max_blocking_tasks: 1,
27456        thread_name_prefix: "deadline-fire".to_string(),
27457        park_deadline: Some(Duration::from_millis(50)),
27458        drain_linger: DEFAULT_DRAIN_LINGER,
27459      };
27460      let executor =
27461        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27462
27463      let payload = {
27464        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27465        catch_unwind(AssertUnwindSafe(|| {
27466          let mut future = std::pin::pin!(NeverReady);
27467          executor.block_on(future.as_mut());
27468        }))
27469        .expect_err("a dead cooperative park must fire the armed deadline")
27470      };
27471      let diagnostic = payload
27472        .downcast_ref::<BlockOnDeadlock>()
27473        .expect("typed payload");
27474      assert_eq!(
27475        diagnostic.kind,
27476        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
27477      );
27478      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
27479      let message = diagnostic.to_string();
27480      assert!(
27481        message.contains("block_on"),
27482        "diagnostic must name the hazard, got: {message}"
27483      );
27484      assert_eq!(
27485        executor.parked_drivers.count.load(Ordering::SeqCst),
27486        0,
27487        "the firing driver must deregister itself before panicking"
27488      );
27489      // Teardown inside the bounded window (see `join_within`).
27490      drop(executor);
27491      let _ = done_tx.send(());
27492    });
27493
27494    match done_rx.recv_timeout(Duration::from_secs(10)) {
27495      Ok(()) => join_within(
27496        "the MT deadline-fire runner exits after executor teardown",
27497        runner,
27498        Duration::from_secs(30),
27499      ),
27500      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
27501    }
27502  }
27503
27504  #[cfg(not(target_family = "wasm"))]
27505  #[test]
27506  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
27507    // Shape (4), MT side of the progress reset: a legitimately slow wake under
27508    // an armed deadline must NOT fire while the executor's
27509    // metrics keep advancing. Topology: the sole worker is parked as a
27510    // cooperative driver (real pool thread, real DriverParker park); the test
27511    // injects progress ticks across several full deadline windows, then
27512    // delivers the late wake.
27513    //
27514    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
27515    // so the window must dwarf the injector thread's worst-case scheduling
27516    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
27517    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
27518    // and a full window with zero progress IS a deadline fire by design.
27519    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
27520    // still holding the park across multiple full windows, proving the
27521    // repeated RE-ARM rather than a single lucky first window.
27522    use std::sync::mpsc;
27523
27524    let (done_tx, done_rx) = mpsc::channel();
27525    let runner = std::thread::spawn(move || {
27526      let deadline = Duration::from_millis(1000);
27527      let metrics = Arc::new(RuntimeMetrics::default());
27528      let options = RuntimeOptions {
27529        flavor: RuntimeFlavor::MultiThread,
27530        worker_threads: 1,
27531        max_blocking_tasks: 1,
27532        thread_name_prefix: "deadline-reset".to_string(),
27533        park_deadline: Some(deadline),
27534        drain_linger: DEFAULT_DRAIN_LINGER,
27535      };
27536      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
27537
27538      // Park the sole worker as a cooperative driver awaiting a gate (same
27539      // topology as the miss-compensation test).
27540      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27541      let td_exec = Arc::clone(&executor);
27542      let completed = Arc::new(AtomicBool::new(false));
27543      let td_flag = Arc::clone(&completed);
27544      let td_body = async move {
27545        let mut inner = std::pin::pin!(async move {
27546          assert_eq!(gate_rx.await.unwrap(), 9);
27547        });
27548        td_exec.block_on(inner.as_mut());
27549        td_flag.store(true, Ordering::SeqCst);
27550      };
27551      let sched = Arc::clone(&executor);
27552      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
27553      executor.schedule(td_runnable);
27554      wait_until("the sole driver parked", || {
27555        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
27556      });
27557
27558      // Outlive the armed deadline several times over, with every window
27559      // containing many injected progress ticks (the counter the reset
27560      // reads). Elapsed-based so an oversleeping iteration extends the hold
27561      // instead of thinning the tick supply, and the final tick lands at
27562      // most one iteration before the late wake below.
27563      let hold_until = Instant::now() + deadline * 5 / 2;
27564      while Instant::now() < hold_until {
27565        std::thread::sleep(Duration::from_millis(25));
27566        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
27567      }
27568      assert!(
27569        !completed.load(Ordering::SeqCst),
27570        "the driver must still be parked awaiting its late wake"
27571      );
27572      gate_tx.send(9).unwrap();
27573      futures::executor::block_on(td_task);
27574      assert!(
27575        completed.load(Ordering::SeqCst),
27576        "the late wake must complete the parked driver"
27577      );
27578      // Teardown inside the bounded window (see `join_within`).
27579      drop(executor);
27580      let _ = done_tx.send(());
27581    });
27582
27583    match done_rx.recv_timeout(Duration::from_secs(30)) {
27584      Ok(()) => join_within(
27585        "the MT deadline-reset runner exits after executor teardown",
27586        runner,
27587        Duration::from_secs(30),
27588      ),
27589      Err(error) => panic!(
27590        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
27591      ),
27592    }
27593  }
27594
27595  #[cfg(not(target_family = "wasm"))]
27596  #[test]
27597  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
27598    // Shape (5): the foreign/napi caller thread parks in
27599    // `futures::executor::block_on` for the ENTIRE build -- the normal
27600    // production shape. Even with an aggressively short deadline armed and
27601    // ZERO executor progress for many windows, that park must never fire.
27602    use std::sync::mpsc;
27603
27604    let (done_tx, done_rx) = mpsc::channel();
27605    let runner = std::thread::spawn(move || {
27606      let options = RuntimeOptions {
27607        flavor: RuntimeFlavor::MultiThread,
27608        worker_threads: 2,
27609        max_blocking_tasks: 2,
27610        thread_name_prefix: "foreign-exempt".to_string(),
27611        park_deadline: Some(Duration::from_millis(40)),
27612        drain_linger: DEFAULT_DRAIN_LINGER,
27613      };
27614      let executor =
27615        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27616
27617      let (value_tx, value_rx) = oneshot::channel::<usize>();
27618      let waker_thread = std::thread::spawn(move || {
27619        // Well past several deadline windows, with zero executor progress.
27620        std::thread::sleep(Duration::from_millis(300));
27621        let _ = value_tx.send(7);
27622      });
27623
27624      let started = Instant::now();
27625      let mut output = None;
27626      {
27627        let mut future = std::pin::pin!(async {
27628          output = Some(value_rx.await.unwrap());
27629        });
27630        // This (non-pool) thread takes the parking branch: exempt by design.
27631        executor.block_on(future.as_mut());
27632      }
27633      assert_eq!(output, Some(7));
27634      assert!(
27635        started.elapsed() >= Duration::from_millis(250),
27636        "the park must genuinely have outlived the 40ms deadline many times over"
27637      );
27638      waker_thread.join().unwrap();
27639      // Teardown inside the bounded window: this test was one of the silent
27640      // wedges on the Windows CI runners -- every assertion above had
27641      // passed, so the hang lived in the unbounded residue (executor drop /
27642      // thread teardown). Keep that phase inside the loud bounds.
27643      drop(executor);
27644      let _ = done_tx.send(());
27645    });
27646
27647    match done_rx.recv_timeout(Duration::from_secs(10)) {
27648      Ok(()) => join_within(
27649        "the exempt foreign-park runner exits after executor teardown",
27650        runner,
27651        Duration::from_secs(30),
27652      ),
27653      Err(error) => {
27654        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
27655      }
27656    }
27657  }
27658
27659  #[cfg(not(target_family = "wasm"))]
27660  #[test]
27661  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
27662    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
27663    // hook instead of a wall-clock lottery. Interleaving:
27664    //   (1) the cooperative park's deadline expires with genuinely zero
27665    //       progress (nothing else touches the executor before the park);
27666    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
27667    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
27668    //       pops this parker, stores the permit and reports the wake as
27669    //       DELIVERED (so no drainer is spawned). The wake was counted as
27670    //       delivered TO US, so the post-deregister permit re-check is the
27671    //       first-line catch; queued work and the enqueue counters in the
27672    //       fingerprint are further backstops;
27673    //   (3) the expiry decision runs. It must treat the permit as a wake, run
27674    //       the job (which sends the gate), and complete the awaited future
27675    //       instead of reporting `BlockOnDeadlock`.
27676    use std::sync::mpsc;
27677
27678    let (done_tx, done_rx) = mpsc::channel();
27679    let runner = std::thread::spawn(move || {
27680      let options = RuntimeOptions {
27681        flavor: RuntimeFlavor::MultiThread,
27682        worker_threads: 1,
27683        max_blocking_tasks: 1,
27684        thread_name_prefix: "edge-wake".to_string(),
27685        park_deadline: Some(Duration::from_millis(40)),
27686        drain_linger: DEFAULT_DRAIN_LINGER,
27687      };
27688      let executor =
27689        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27690
27691      // Until the racing job sends this gate, the awaited future is Pending.
27692      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27693      // (registered_before_wake, registered_after_wake) observed by the hook.
27694      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
27695
27696      let hook_exec = Arc::clone(&executor);
27697      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
27698        *slot.borrow_mut() = Some(Box::new(move || {
27699          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27700          // The racing schedule: pushes the job, then `wake_one` pops the
27701          // still-registered parker (permit stored, wake counted as
27702          // delivered, no drainer).
27703          let handle = hook_exec.schedule_blocking(move || {
27704            let _ = gate_tx.send(5usize);
27705            0usize
27706          });
27707          drop(handle); // result unobserved; the gate send is the signal
27708          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
27709          hook_saw_tx.send((before, after)).unwrap();
27710        }));
27711      });
27712
27713      // The test-only on-pool marker runs the cooperative branch and its
27714      // thread-local hook on THIS thread.
27715      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27716      let mut output = None;
27717      {
27718        let mut future = std::pin::pin!(async {
27719          output = Some(gate_rx.await.unwrap());
27720        });
27721        executor.block_on(future.as_mut());
27722      }
27723      // Clear the hook slot BEFORE any assert can panic: a hook left
27724      // installed captures an executor Arc, and dropping the last executor
27725      // Arc from a TLS destructor at thread exit is loader-lock hostile on
27726      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
27727      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
27728      assert_eq!(
27729        output,
27730        Some(5),
27731        "the edge-delivered wake's job must have run and completed the future"
27732      );
27733
27734      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
27735      assert_eq!(
27736        before, 1,
27737        "the hook must observe the parker STILL registered (race window)"
27738      );
27739      assert_eq!(
27740        after, 0,
27741        "the racing wake_one must have popped the parker (wake DELIVERED)"
27742      );
27743      // Teardown inside the bounded window (see `join_within`).
27744      drop(executor);
27745      let _ = done_tx.send(());
27746    });
27747
27748    match done_rx.recv_timeout(Duration::from_secs(10)) {
27749      Ok(()) => join_within(
27750        "the expiry-edge-wake runner exits after executor teardown",
27751        runner,
27752        Duration::from_secs(30),
27753      ),
27754      Err(error) => panic!(
27755        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
27756      ),
27757    }
27758  }
27759
27760  #[cfg(not(target_family = "wasm"))]
27761  #[test]
27762  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
27763    // Enqueue/fingerprint invariant: both runnable and blocking submissions
27764    // must advance the progress fingerprint at ENQUEUE. A blocking submission
27765    // landing after `has_queued_work()` but before the final fingerprint read
27766    // has already pushed deliverable work, while its wake targets an EMPTY
27767    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
27768    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
27769    // fires in this window on the driver thread:
27770    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
27771    //     job on the real worker -- `blocking_tasks_started` provably cannot
27772    //     advance behind our back;
27773    //   * the enqueue counter must therefore save the healthy park.
27774    use std::sync::mpsc;
27775
27776    let (done_tx, done_rx) = mpsc::channel();
27777    let runner = std::thread::spawn(move || {
27778      let options = RuntimeOptions {
27779        flavor: RuntimeFlavor::MultiThread,
27780        worker_threads: 1,
27781        max_blocking_tasks: 1,
27782        thread_name_prefix: "verdict-blocking".to_string(),
27783        park_deadline: Some(Duration::from_millis(40)),
27784        drain_linger: DEFAULT_DRAIN_LINGER,
27785      };
27786      let executor =
27787        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27788      executor
27789        .active_drainers
27790        .store(executor.max_drainers, Ordering::SeqCst);
27791
27792      // Until the racing job sends this gate, the awaited future is Pending.
27793      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
27794      // Registry size observed by the hook (must be 0: already deregistered,
27795      // so no permit can possibly be delivered for this enqueue).
27796      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
27797
27798      let hook_exec = Arc::clone(&executor);
27799      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
27800        *slot.borrow_mut() = Some(Box::new(move || {
27801          let handle = hook_exec.schedule_blocking(move || {
27802            let _ = gate_tx.send(11usize);
27803            0usize
27804          });
27805          drop(handle); // result unobserved; the gate send is the signal
27806          hook_saw_tx
27807            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
27808            .unwrap();
27809        }));
27810      });
27811
27812      // The test-only on-pool marker runs the cooperative branch and its
27813      // thread-local hook on THIS thread.
27814      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27815      let mut output = None;
27816      {
27817        let mut future = std::pin::pin!(async {
27818          output = Some(gate_rx.await.unwrap());
27819        });
27820        executor.block_on(future.as_mut());
27821      }
27822      assert_eq!(
27823        output,
27824        Some(11),
27825        "the enqueued job must have been run by the surviving driver via run_one"
27826      );
27827
27828      let registered_at_hook = hook_saw_rx
27829        .recv()
27830        .expect("the verdict hook must have fired");
27831      assert_eq!(
27832        registered_at_hook, 0,
27833        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
27834      );
27835      executor.active_drainers.store(0, Ordering::SeqCst);
27836      let _ = done_tx.send(());
27837    });
27838
27839    match done_rx.recv_timeout(Duration::from_secs(10)) {
27840      Ok(()) => runner.join().unwrap(),
27841      Err(error) => panic!(
27842        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
27843      ),
27844    }
27845  }
27846
27847  #[cfg(not(target_family = "wasm"))]
27848  #[test]
27849  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
27850    // Publish only through the deadlock-admission protocol after every legacy
27851    // final check. No runnable, blocking job, permit, timer, or metrics
27852    // fingerprint changes, so only the synchronized publication epoch can
27853    // prevent the false verdict. The future observes a separate test flag on
27854    // its next poll; that flag is deliberately not a scheduler wake source.
27855    use std::sync::mpsc;
27856
27857    struct ReadyAfterPublication(Arc<AtomicBool>);
27858
27859    impl Future for ReadyAfterPublication {
27860      type Output = ();
27861
27862      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27863        if self.0.load(Ordering::SeqCst) {
27864          Poll::Ready(())
27865        } else {
27866          Poll::Pending
27867        }
27868      }
27869    }
27870
27871    let (done_tx, done_rx) = mpsc::channel();
27872    let runner = std::thread::spawn(move || {
27873      let options = RuntimeOptions {
27874        flavor: RuntimeFlavor::MultiThread,
27875        worker_threads: 1,
27876        max_blocking_tasks: 1,
27877        thread_name_prefix: "final-verdict-publication".to_string(),
27878        park_deadline: Some(Duration::from_millis(40)),
27879        drain_linger: DEFAULT_DRAIN_LINGER,
27880      };
27881      let executor =
27882        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
27883
27884      let ready = Arc::new(AtomicBool::new(false));
27885      let baseline_fingerprint = executor.metrics.progress_fingerprint();
27886      let baseline_publications = executor.deadlock_state.publications();
27887      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
27888      let hook_exec = Arc::clone(&executor);
27889      let hook_ready = Arc::clone(&ready);
27890      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
27891        *slot.borrow_mut() = Some(Box::new(move || {
27892          let publication_executor = Arc::clone(&hook_exec);
27893          std::thread::spawn(move || {
27894            let mut admission = publication_executor
27895              .begin_deadlock_admission()
27896              .expect("the deadline detector must enable admission accounting");
27897            hook_ready.store(true, Ordering::SeqCst);
27898            admission.mark_published();
27899          })
27900          .join()
27901          .unwrap();
27902          hook_saw_tx
27903            .send((
27904              hook_exec.deadlock_state.publications(),
27905              !hook_exec.has_queued_runnable(),
27906              hook_exec
27907                .blocking_queue
27908                .lock()
27909                .unwrap_or_else(std::sync::PoisonError::into_inner)
27910                .is_empty(),
27911              hook_exec.next_timer_deadline().is_none(),
27912              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
27913              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
27914            ))
27915            .unwrap();
27916        }));
27917      });
27918
27919      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
27920      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
27921      assert_eq!(
27922        executor.block_on(future.as_mut()),
27923        BlockOnOutcome::Completed,
27924        "the publication epoch must veto the first verdict and permit the next poll"
27925      );
27926      let (
27927        publications,
27928        runnable_queue_empty,
27929        blocking_queue_empty,
27930        timer_heap_empty,
27931        fingerprint_unchanged,
27932        parked_drivers,
27933      ) = hook_saw_rx
27934        .recv()
27935        .expect("the final-verdict hook must have fired");
27936      assert_eq!(
27937        publications,
27938        baseline_publications
27939          .checked_add(1)
27940          .expect("test publication epoch must not overflow"),
27941        "the cross-thread admission must publish exactly one epoch"
27942      );
27943      assert!(
27944        runnable_queue_empty,
27945        "the publication test must not queue a runnable"
27946      );
27947      assert!(
27948        blocking_queue_empty,
27949        "the publication test must not queue blocking work"
27950      );
27951      assert!(
27952        timer_heap_empty,
27953        "the publication test must not register a timer"
27954      );
27955      assert!(
27956        fingerprint_unchanged,
27957        "ordinary metrics progress must not veto the publication-only verdict"
27958      );
27959      assert_eq!(
27960        parked_drivers, 0,
27961        "the publication must land after the cooperative parker was deregistered"
27962      );
27963      let _ = done_tx.send(());
27964    });
27965
27966    match done_rx.recv_timeout(Duration::from_secs(10)) {
27967      Ok(()) => runner.join().unwrap(),
27968      Err(error) => panic!(
27969        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
27970      ),
27971    }
27972  }
27973
27974  #[cfg(not(target_family = "wasm"))]
27975  #[test]
27976  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
27977    // The exact owner lane is saturated and reserved while the cooperative
27978    // driver parks. At expiry, release selects that driver and publishes its
27979    // handoff, then pauses before unpark. The driver reaches the gated verdict
27980    // with no queue, timer, metric, or ordinary publication change. The
27981    // exact-owner publication boundary must serialize the final check. The
27982    // hook then consumes the ordinary permit and renews the lane reservation,
27983    // leaving only the pending-handoff predicate to veto the false deadlock.
27984    use std::sync::mpsc;
27985
27986    let executor = multi_thread_executor(
27987      1,
27988      Some(Duration::from_millis(40)),
27989      "deadline-selected-handoff",
27990    );
27991    let owner = executor.fresh_owner_token();
27992    executor.active_blocking_owners.register(owner);
27993    executor
27994      .active_blocking
27995      .store(executor.max_blocking, Ordering::SeqCst);
27996
27997    let ready = Arc::new(AtomicBool::new(false));
27998    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
27999    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28000    let (selected_tx, selected_rx) = mpsc::channel();
28001    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
28002    let (released_tx, released_rx) = mpsc::channel();
28003    let (renewed_tx, renewed_rx) = mpsc::channel();
28004    let release_executor = Arc::clone(&executor);
28005    let renewal_executor = Arc::clone(&executor);
28006    let release_ready = Arc::clone(&ready);
28007    let releaser = std::thread::spawn(move || {
28008      let reservation =
28009        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28010          .expect("the synthetic exact-owner lane must start reserved");
28011      reservation_ready_tx.send(()).unwrap();
28012      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
28013        *slot.borrow_mut() = Some(Box::new(move || {
28014          selected_tx.send(()).unwrap();
28015          allow_unpark_rx.recv().unwrap();
28016          let renewed = renewal_executor
28017            .active_blocking_owners
28018            .reserve(owner)
28019            .expect("the released exact-owner lane must be reservable before unpark");
28020          release_ready.store(true, Ordering::SeqCst);
28021          renewed_tx.send(renewed).unwrap();
28022        }));
28023      });
28024      release_reservation_rx.recv().unwrap();
28025      released_tx
28026        .send(release_executor.release_owner_lane_and_wake(reservation))
28027        .unwrap();
28028    });
28029    reservation_ready_rx
28030      .recv_timeout(Duration::from_secs(2))
28031      .expect("the exact-owner reservation must be established before the park");
28032
28033    let baseline_fingerprint = executor.metrics.progress_fingerprint();
28034    let baseline_publications = executor.deadlock_state.publications();
28035    let expiry_executor = Arc::clone(&executor);
28036    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
28037      *slot.borrow_mut() = Some(Box::new(move || {
28038        release_reservation_tx.send(()).unwrap();
28039        selected_rx
28040          .recv_timeout(Duration::from_secs(2))
28041          .expect("reservation release must select the still-registered exact-owner driver");
28042        assert_eq!(
28043          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
28044          0,
28045          "handoff selection must remove the driver before pausing ahead of unpark"
28046        );
28047      }));
28048    });
28049
28050    let verdict_executor = Arc::clone(&executor);
28051    let verdict_ready = Arc::clone(&ready);
28052    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28053      *slot.borrow_mut() = Some(Box::new(move || {
28054        assert!(
28055          verdict_executor.active_blocking_owners.is_available(owner),
28056          "reservation release must publish lane availability before handoff selection"
28057        );
28058        assert!(!verdict_executor.has_queued_runnable());
28059        assert!(
28060          verdict_executor
28061            .blocking_queue
28062            .lock()
28063            .unwrap_or_else(std::sync::PoisonError::into_inner)
28064            .is_empty()
28065        );
28066        assert!(verdict_executor.next_timer_deadline().is_none());
28067        assert!(
28068          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28069          "ordinary metrics progress must not save the selected-handoff verdict"
28070        );
28071        assert_eq!(
28072          verdict_executor.deadlock_state.publications(),
28073          baseline_publications
28074        );
28075        assert!(
28076          !verdict_ready.load(Ordering::SeqCst),
28077          "the selected handoff must still be paused before unpark at the gated verdict"
28078        );
28079        allow_unpark_tx.send(()).unwrap();
28080      }));
28081    });
28082    let exact_owner_executor = Arc::clone(&executor);
28083    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
28084      *slot.borrow_mut() = Some(Box::new(move |parker| {
28085        assert!(
28086          parker.consume_permit(),
28087          "the selected handoff must unpark before exact-owner final predicates run"
28088        );
28089        assert!(
28090          parker.has_owner_handoff(),
28091          "consuming the ordinary permit must leave the exact-owner handoff pending"
28092        );
28093        assert!(
28094          !exact_owner_executor
28095            .active_blocking_owners
28096            .is_available(owner),
28097          "the renewed reservation must prevent lane availability from saving the verdict"
28098        );
28099      }));
28100    });
28101
28102    {
28103      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28104      let _owner = BlockingOwnerGuard::enter(Some(owner));
28105      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28106        dependency: BlockingDependency {
28107          job: BlockingJobId(u64::MAX - 80),
28108          owner: Some(owner)
28109        },
28110        ready: Arc::clone(&ready),
28111      });
28112      assert_eq!(
28113        executor.block_on(future.as_mut()),
28114        BlockOnOutcome::Completed
28115      );
28116    }
28117    assert!(
28118      released_rx
28119        .recv_timeout(Duration::from_secs(2))
28120        .expect("the selected handoff publication must finish"),
28121      "reservation release must have selected the cooperative driver"
28122    );
28123    let renewed = renewed_rx
28124      .recv_timeout(Duration::from_secs(2))
28125      .expect("the selected handoff must renew the owner reservation before unpark");
28126    releaser.join().unwrap();
28127
28128    executor.active_blocking_owners.release(owner, renewed);
28129    executor.active_blocking.store(0, Ordering::SeqCst);
28130    executor.active_blocking_owners.unregister(owner);
28131    executor.begin_shutdown();
28132    executor.wait_until_scheduler_idle();
28133  }
28134
28135  #[cfg(not(target_family = "wasm"))]
28136  #[test]
28137  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
28138    // Release begins only after the deadline path has deregistered its parker
28139    // and completed every legacy final check. No targeted wake or ordinary
28140    // scheduler publication can save the verdict; the synchronized exact-owner
28141    // availability predicate must observe the released lane.
28142    use std::sync::mpsc;
28143
28144    let executor = multi_thread_executor(
28145      1,
28146      Some(Duration::from_millis(40)),
28147      "deadline-owner-no-parker",
28148    );
28149    let owner = executor.fresh_owner_token();
28150    executor.active_blocking_owners.register(owner);
28151    executor
28152      .active_blocking
28153      .store(executor.max_blocking, Ordering::SeqCst);
28154
28155    let ready = Arc::new(AtomicBool::new(false));
28156    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
28157    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
28158    let (released_tx, released_rx) = mpsc::channel();
28159    let release_executor = Arc::clone(&executor);
28160    let release_ready = Arc::clone(&ready);
28161    let releaser = std::thread::spawn(move || {
28162      let reservation =
28163        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
28164          .expect("the synthetic exact-owner lane must start reserved");
28165      reservation_ready_tx.send(()).unwrap();
28166      release_reservation_rx.recv().unwrap();
28167      let woke = release_executor.release_owner_lane_and_wake(reservation);
28168      release_ready.store(true, Ordering::SeqCst);
28169      released_tx.send(woke).unwrap();
28170    });
28171    reservation_ready_rx
28172      .recv_timeout(Duration::from_secs(2))
28173      .expect("the exact-owner reservation must be established before the park");
28174
28175    let baseline_fingerprint = executor.metrics.progress_fingerprint();
28176    let baseline_publications = executor.deadlock_state.publications();
28177    let verdict_executor = Arc::clone(&executor);
28178    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
28179      *slot.borrow_mut() = Some(Box::new(move || {
28180        assert_eq!(
28181          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
28182          0,
28183          "the release must begin only after deadline deregistration"
28184        );
28185        release_reservation_tx.send(()).unwrap();
28186        assert!(
28187          !released_rx
28188            .recv_timeout(Duration::from_secs(2))
28189            .expect("the no-parker owner release must finish"),
28190          "no registered parker may receive the released owner lane"
28191        );
28192        assert!(verdict_executor.active_blocking_owners.is_available(owner));
28193        assert!(!verdict_executor.has_queued_runnable());
28194        assert!(
28195          verdict_executor
28196            .blocking_queue
28197            .lock()
28198            .unwrap_or_else(std::sync::PoisonError::into_inner)
28199            .is_empty()
28200        );
28201        assert!(verdict_executor.next_timer_deadline().is_none());
28202        assert!(
28203          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
28204          "ordinary metrics progress must not save the no-parker release verdict"
28205        );
28206        assert_eq!(
28207          verdict_executor.deadlock_state.publications(),
28208          baseline_publications
28209        );
28210      }));
28211    });
28212
28213    {
28214      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28215      let _owner = BlockingOwnerGuard::enter(Some(owner));
28216      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
28217        dependency: BlockingDependency {
28218          job: BlockingJobId(u64::MAX - 81),
28219          owner: Some(owner)
28220        },
28221        ready: Arc::clone(&ready),
28222      });
28223      assert_eq!(
28224        executor.block_on(future.as_mut()),
28225        BlockOnOutcome::Completed
28226      );
28227    }
28228    releaser.join().unwrap();
28229
28230    executor.active_blocking.store(0, Ordering::SeqCst);
28231    executor.active_blocking_owners.unregister(owner);
28232    executor.begin_shutdown();
28233    executor.wait_until_scheduler_idle();
28234  }
28235
28236  #[cfg(not(target_family = "wasm"))]
28237  #[test]
28238  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
28239    // Pin the shutdown race after the final verdict gate has closed:
28240    //   1. an accepted task is pending, so shutdown abort must wake it;
28241    //   2. a cooperative block_on reaches its deadline and pauses while
28242    //      holding the closed verdict gate;
28243    //   3. shutdown publishes generation stop, transitions
28244    //      Running -> Stopping, then its abort wake blocks entering scheduler
28245    //      admission behind that gate;
28246    //   4. the gated final verdict must observe stop and return Stopped,
28247    //      reopening the gate so the abort wake and shutdown can finish.
28248    //
28249    // Before stop was published by the controller transition, step 3 blocked
28250    // inside abort before executor shutdown could set it. The driver then saw
28251    // empty queues and emitted a false BlockOnDeadlock.
28252    use std::sync::mpsc;
28253
28254    let controller = Arc::new(RuntimeController::new());
28255    controller
28256      .configure(RuntimeOptions {
28257        flavor: RuntimeFlavor::MultiThread,
28258        worker_threads: 2,
28259        max_blocking_tasks: 1,
28260        thread_name_prefix: "shutdown-gated-verdict".to_string(),
28261        park_deadline: Some(Duration::from_millis(40)),
28262        drain_linger: DEFAULT_DRAIN_LINGER,
28263      })
28264      .expect("the shutdown race test configuration must be accepted");
28265
28266    let backend = controller.backend();
28267    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28268      panic!("the configured backend must be MultiThread");
28269    };
28270    let executor_id = executor.id;
28271    let weak_executor = Arc::downgrade(executor);
28272    drop(backend);
28273
28274    let (task_polled_tx, task_polled_rx) = mpsc::channel();
28275    let task = controller
28276      .try_spawn(async move {
28277        task_polled_tx.send(()).unwrap();
28278        std::future::pending::<()>().await;
28279      })
28280      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
28281    task_polled_rx
28282      .recv_timeout(Duration::from_secs(2))
28283      .expect("the accepted task must publish an abortable pending waker");
28284
28285    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
28286    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28287    let (driver_result_tx, driver_result_rx) = mpsc::channel();
28288    let driver_controller = Arc::clone(&controller);
28289    let driver = std::thread::spawn(move || {
28290      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
28291        *slot.borrow_mut() = Some(Box::new(move || {
28292          gate_closed_tx.send(()).unwrap();
28293          release_verdict_rx.recv().unwrap();
28294        }));
28295      });
28296
28297      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28298      let mut pending = std::pin::pin!(NeverReady);
28299      let result = catch_unwind(AssertUnwindSafe(|| {
28300        driver_controller.block_on(pending.as_mut())
28301      }));
28302      let result = match result {
28303        Ok(outcome) => Ok(outcome),
28304        Err(payload) => {
28305          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28306          discard_panic_payload(payload);
28307          Err(was_deadlock)
28308        }
28309      };
28310      driver_result_tx.send(result).unwrap();
28311    });
28312
28313    gate_closed_rx
28314      .recv_timeout(Duration::from_secs(2))
28315      .expect("the cooperative driver must pause with the verdict gate closed");
28316
28317    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
28318    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28319    let shutdown_controller = Arc::clone(&controller);
28320    let shutdown = std::thread::spawn(move || {
28321      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
28322        *slot.borrow_mut() = Some(Box::new(move || {
28323          abort_blocked_tx.send(()).unwrap();
28324        }));
28325      });
28326      shutdown_result_tx
28327        .send(shutdown_controller.shutdown())
28328        .unwrap();
28329    });
28330
28331    abort_blocked_rx
28332      .recv_timeout(Duration::from_secs(2))
28333      .expect("the accepted task's abort wake must reach the closed verdict gate");
28334    let lifecycle_is_stopping = {
28335      let state = controller
28336        .state
28337        .lock()
28338        .unwrap_or_else(std::sync::PoisonError::into_inner);
28339      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
28340    };
28341    let stop_was_published = weak_executor
28342      .upgrade()
28343      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
28344
28345    release_verdict_tx.send(()).unwrap();
28346
28347    let driver_result = driver_result_rx
28348      .recv_timeout(Duration::from_secs(2))
28349      .expect("the gated driver must resolve after observing shutdown");
28350    let shutdown_result = shutdown_result_rx
28351      .recv_timeout(Duration::from_secs(5))
28352      .expect("shutdown must finish after the verdict gate reopens");
28353    // Both threads have sent their results; what remains is teardown (and on
28354    // the shutdown thread, the controller's `wait_for_all_workers` already
28355    // completed inside `shutdown()`). This test was one of the silent wedges
28356    // on the Windows CI runners -- bound the residue loudly.
28357    join_within(
28358      "the gated cooperative driver exits after teardown",
28359      driver,
28360      Duration::from_secs(30),
28361    );
28362    join_within(
28363      "the shutdown thread exits after teardown",
28364      shutdown,
28365      Duration::from_secs(30),
28366    );
28367
28368    assert!(
28369      lifecycle_is_stopping,
28370      "the abort wake must occur after Running -> Stopping"
28371    );
28372    assert!(
28373      stop_was_published,
28374      "generation stop must be visible before shutdown invokes accepted-task aborts"
28375    );
28376    assert_eq!(
28377      driver_result,
28378      Ok(BlockOnOutcome::Stopped),
28379      "shutdown must not be reported as a gated BlockOnDeadlock"
28380    );
28381    shutdown_result.unwrap();
28382    assert!(
28383      futures::executor::block_on(task).is_err(),
28384      "the accepted pending task must resolve as cancelled"
28385    );
28386  }
28387
28388  #[cfg(not(target_family = "wasm"))]
28389  #[test]
28390  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
28391    // Complement the publication-wins race above. The verdict owns both its
28392    // admission gate and the stop-publication mutex before shutdown starts.
28393    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
28394    // Running -> Stopping until the verdict finishes its synchronized checks.
28395    use std::sync::mpsc;
28396
28397    let controller = Arc::new(RuntimeController::new());
28398    controller
28399      .configure(RuntimeOptions {
28400        flavor: RuntimeFlavor::MultiThread,
28401        worker_threads: 2,
28402        max_blocking_tasks: 1,
28403        thread_name_prefix: "verdict-before-shutdown".to_string(),
28404        park_deadline: Some(Duration::from_millis(40)),
28405        drain_linger: DEFAULT_DRAIN_LINGER,
28406      })
28407      .expect("the verdict-first test configuration must be accepted");
28408
28409    let backend = controller.backend();
28410    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
28411      panic!("the configured backend must be MultiThread");
28412    };
28413    let executor_id = executor.id;
28414    drop(backend);
28415
28416    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
28417    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
28418    let (driver_result_tx, driver_result_rx) = mpsc::channel();
28419    let driver_controller = Arc::clone(&controller);
28420    let driver = std::thread::spawn(move || {
28421      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
28422        *slot.borrow_mut() = Some(Box::new(move || {
28423          verdict_locked_tx.send(()).unwrap();
28424          release_verdict_rx.recv().unwrap();
28425        }));
28426      });
28427
28428      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
28429      let mut pending = std::pin::pin!(NeverReady);
28430      let result = catch_unwind(AssertUnwindSafe(|| {
28431        driver_controller.block_on(pending.as_mut())
28432      }));
28433      let result = match result {
28434        Ok(outcome) => Ok(outcome),
28435        Err(payload) => {
28436          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
28437          discard_panic_payload(payload);
28438          Err(was_deadlock)
28439        }
28440      };
28441      driver_result_tx.send(result).unwrap();
28442    });
28443
28444    verdict_locked_rx
28445      .recv_timeout(Duration::from_secs(2))
28446      .expect("the verdict must hold the synchronized stop-publication guard");
28447
28448    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
28449    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
28450    let shutdown_controller = Arc::clone(&controller);
28451    let shutdown = std::thread::spawn(move || {
28452      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
28453        *slot.borrow_mut() = Some(Box::new(move || {
28454          shutdown_waiting_tx.send(()).unwrap();
28455        }));
28456      });
28457      shutdown_result_tx
28458        .send(shutdown_controller.shutdown())
28459        .unwrap();
28460    });
28461
28462    shutdown_waiting_rx
28463      .recv_timeout(Duration::from_secs(2))
28464      .expect("shutdown must reach the lifecycle-to-publication lock edge");
28465    let early_shutdown = shutdown_result_rx
28466      .recv_timeout(Duration::from_millis(100))
28467      .ok();
28468    let shutdown_was_blocked = early_shutdown.is_none();
28469    release_verdict_tx.send(()).unwrap();
28470
28471    let driver_result = driver_result_rx
28472      .recv_timeout(Duration::from_secs(2))
28473      .expect("the verdict-first driver must finish after release");
28474    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28475      shutdown_result_rx
28476        .recv_timeout(Duration::from_secs(5))
28477        .expect("shutdown must finish after the verdict releases publication")
28478    });
28479    driver.join().unwrap();
28480    shutdown.join().unwrap();
28481
28482    assert!(
28483      shutdown_was_blocked,
28484      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
28485    );
28486    assert_eq!(
28487      driver_result,
28488      Err(true),
28489      "a verdict that linearizes before shutdown must retain the typed deadlock"
28490    );
28491    shutdown_result.unwrap();
28492  }
28493
28494  #[cfg(not(target_family = "wasm"))]
28495  #[test]
28496  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
28497    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
28498
28499    if std::env::var_os(CHILD_ENV).is_some() {
28500      let executor =
28501        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
28502      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
28503      // hook only needs to prove that scheduler admission returns.
28504      executor
28505        .active_drainers
28506        .store(executor.max_drainers, Ordering::SeqCst);
28507
28508      let hook_returned = Arc::new(AtomicBool::new(false));
28509      let hook_flag = Arc::clone(&hook_returned);
28510      let hook_executor = Arc::clone(&executor);
28511      let previous_hook = std::panic::take_hook();
28512      std::panic::set_hook(Box::new(move |_| {
28513        let handle = hook_executor.schedule_blocking(|| ());
28514        drop(handle);
28515        hook_flag.store(true, Ordering::SeqCst);
28516      }));
28517
28518      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
28519      let result = catch_unwind(AssertUnwindSafe(|| {
28520        let mut pending = std::pin::pin!(std::future::pending::<()>());
28521        executor.block_on(pending.as_mut());
28522      }));
28523      std::panic::set_hook(previous_hook);
28524
28525      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
28526      assert!(
28527        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
28528        "the deadline must retain its structured diagnostic"
28529      );
28530      assert!(
28531        hook_returned.load(Ordering::SeqCst),
28532        "the panic hook's scheduler submission must return before unwind"
28533      );
28534
28535      executor.active_drainers.store(0, Ordering::SeqCst);
28536      executor.begin_shutdown();
28537      executor.wait_until_scheduler_idle();
28538      return;
28539    }
28540
28541    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28542      .arg("--exact")
28543      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
28544      .arg("--nocapture")
28545      .env(CHILD_ENV, "1")
28546      .stdout(std::process::Stdio::piped())
28547      .stderr(std::process::Stdio::piped())
28548      .spawn()
28549      .expect("the panic-hook subprocess must start");
28550    let deadline = Instant::now() + Duration::from_secs(5);
28551    let status = loop {
28552      if let Some(status) = child
28553        .try_wait()
28554        .expect("the subprocess status must be readable")
28555      {
28556        break status;
28557      }
28558      if Instant::now() >= deadline {
28559        let _ = child.kill();
28560        let output = child
28561          .wait_with_output()
28562          .expect("the timed-out subprocess must be reaped");
28563        panic!(
28564          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
28565          String::from_utf8_lossy(&output.stdout),
28566          String::from_utf8_lossy(&output.stderr)
28567        );
28568      }
28569      std::thread::sleep(Duration::from_millis(10));
28570    };
28571    let output = child
28572      .wait_with_output()
28573      .expect("the completed subprocess output must be read");
28574    assert!(
28575      status.success(),
28576      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
28577      status.code(),
28578      String::from_utf8_lossy(&output.stdout),
28579      String::from_utf8_lossy(&output.stderr)
28580    );
28581  }
28582
28583  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
28584  // the embedding host's env-resolution pipeline together with the read
28585  // itself; its tests live there.
28586
28587  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
28588  // The tests cover both deadlock-detection interactions. All tests build
28589  // executors LOCALLY (never the global RUNTIME or the process-global host
28590  // driver) and run hang-prone workloads on a child thread bounded by
28591  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
28592
28593  #[test]
28594  fn contained_waker_separates_wake_and_drop_panics() {
28595    struct PanicWakeAndDrop;
28596
28597    impl std::task::Wake for PanicWakeAndDrop {
28598      fn wake(self: Arc<Self>) {
28599        panic!("intentional consuming wake panic");
28600      }
28601
28602      fn wake_by_ref(self: &Arc<Self>) {
28603        panic!("intentional borrowed wake panic");
28604      }
28605    }
28606
28607    impl Drop for PanicWakeAndDrop {
28608      fn drop(&mut self) {
28609        panic!("intentional RawWaker destructor panic");
28610      }
28611    }
28612
28613    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
28614  }
28615
28616  struct TestHostileRawWakerState {
28617    clones: AtomicUsize,
28618    drop_panics: AtomicUsize,
28619    panic_on_clone: Option<usize>,
28620  }
28621
28622  struct TestHostileRawWakerToken {
28623    state: Arc<TestHostileRawWakerState>,
28624    panic_on_drop: bool,
28625  }
28626
28627  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
28628    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
28629    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
28630    assert_ne!(
28631      token.state.panic_on_clone,
28632      Some(clone),
28633      "intentional RawWaker clone panic"
28634    );
28635    let clone = Box::new(TestHostileRawWakerToken {
28636      state: Arc::clone(&token.state),
28637      panic_on_drop: true,
28638    });
28639    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
28640  }
28641
28642  unsafe fn wake_test_hostile_waker(data: *const ()) {
28643    unsafe { drop_test_hostile_waker(data) };
28644  }
28645
28646  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
28647
28648  unsafe fn drop_test_hostile_waker(data: *const ()) {
28649    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
28650    if token.panic_on_drop {
28651      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
28652      panic!("intentional cloned RawWaker destructor panic");
28653    }
28654  }
28655
28656  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
28657    clone_test_hostile_waker,
28658    wake_test_hostile_waker,
28659    wake_test_hostile_waker_by_ref,
28660    drop_test_hostile_waker,
28661  );
28662
28663  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
28664    let token = Box::new(TestHostileRawWakerToken {
28665      state,
28666      panic_on_drop: false,
28667    });
28668    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
28669    unsafe { Waker::from_raw(raw) }
28670  }
28671
28672  /// Waker that only counts. For polling a raw `Sleep` without an executor.
28673  struct CountingWake(AtomicUsize);
28674  impl std::task::Wake for CountingWake {
28675    fn wake(self: Arc<Self>) {
28676      self.0.fetch_add(1, Ordering::SeqCst);
28677    }
28678  }
28679
28680  /// One armed stub timer: the latest waker plus a cancellation flag.
28681  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
28682
28683  /// Test-local host driver: fires each registered timer from a helper OS
28684  /// thread at its deadline (re-registration under the same id refreshes the
28685  /// waker without arming a second thread, per the `TimerDriver` contract).
28686  struct StubHostTimerDriver {
28687    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
28688  }
28689
28690  impl StubHostTimerDriver {
28691    fn new() -> Self {
28692      Self {
28693        timers: Mutex::new(FxHashMap::default()),
28694      }
28695    }
28696  }
28697
28698  impl TimerDriver for StubHostTimerDriver {
28699    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28700      let mut timers = self.timers.lock().unwrap();
28701      if let Some(cell) = timers.get(&id) {
28702        *cell.0.lock().unwrap() = waker;
28703        return;
28704      }
28705      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
28706      timers.insert(id, Arc::clone(&cell));
28707      std::thread::spawn(move || {
28708        let now = Instant::now();
28709        if deadline > now {
28710          std::thread::sleep(deadline - now);
28711        }
28712        if !cell.1.load(Ordering::SeqCst) {
28713          cell.0.lock().unwrap().wake_by_ref();
28714        }
28715      });
28716    }
28717
28718    fn cancel(&self, id: TimerId) {
28719      let removed = self.timers.lock().unwrap().remove(&id);
28720      if let Some(cell) = removed {
28721        cell.1.store(true, Ordering::SeqCst);
28722      }
28723    }
28724  }
28725
28726  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
28727  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
28728  /// per timer id), never fires on its own, and reports the controllable
28729  /// `live` flag (a killed stub plays a driver whose owning host context --
28730  /// napi env in the binding -- was torn down).
28731  struct ManualStubDriver {
28732    live: AtomicBool,
28733    registers: Mutex<Vec<(TimerId, Instant)>>,
28734    cancels: Mutex<Vec<TimerId>>,
28735    pending: Mutex<FxHashMap<TimerId, Waker>>,
28736    swept: AtomicBool,
28737  }
28738
28739  impl ManualStubDriver {
28740    fn new() -> Arc<Self> {
28741      Arc::new(Self {
28742        live: AtomicBool::new(true),
28743        registers: Mutex::new(Vec::new()),
28744        cancels: Mutex::new(Vec::new()),
28745        pending: Mutex::new(FxHashMap::default()),
28746        swept: AtomicBool::new(false),
28747      })
28748    }
28749
28750    fn kill(&self) {
28751      self.live.store(false, Ordering::SeqCst);
28752    }
28753  }
28754
28755  impl TimerDriver for ManualStubDriver {
28756    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
28757      self.registers.lock().unwrap().push((id, deadline));
28758      self.pending.lock().unwrap().insert(id, waker);
28759    }
28760
28761    fn cancel(&self, id: TimerId) {
28762      self.cancels.lock().unwrap().push(id);
28763      self.pending.lock().unwrap().remove(&id);
28764    }
28765
28766    fn is_live(&self) -> bool {
28767      self.live.load(Ordering::SeqCst)
28768    }
28769
28770    fn on_swept(&self) {
28771      // The binding shape: drain the pending map and wake everything armed
28772      // here so those sleeps re-poll onto the next live registrant.
28773      self.swept.store(true, Ordering::SeqCst);
28774      let wakers: Vec<Waker> = self
28775        .pending
28776        .lock()
28777        .unwrap()
28778        .drain()
28779        .map(|(_, w)| w)
28780        .collect();
28781      for waker in wakers {
28782        waker.wake();
28783      }
28784    }
28785  }
28786
28787  struct PanickingTimerDriver {
28788    swept: AtomicBool,
28789  }
28790
28791  impl TimerDriver for PanickingTimerDriver {
28792    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28793
28794    fn cancel(&self, _id: TimerId) {}
28795
28796    fn is_live(&self) -> bool {
28797      panic!("intentional timer-driver liveness panic");
28798    }
28799
28800    fn on_swept(&self) {
28801      self.swept.store(true, Ordering::SeqCst);
28802      panic!("intentional timer-driver sweep panic");
28803    }
28804  }
28805
28806  struct PanickingRegisterTimerDriver {
28807    registers: AtomicUsize,
28808    cancels: Mutex<Vec<TimerId>>,
28809    pending: Mutex<FxHashMap<TimerId, Waker>>,
28810  }
28811
28812  impl PanickingRegisterTimerDriver {
28813    fn new() -> Arc<Self> {
28814      Arc::new(Self {
28815        registers: AtomicUsize::new(0),
28816        cancels: Mutex::new(Vec::new()),
28817        pending: Mutex::new(FxHashMap::default()),
28818      })
28819    }
28820  }
28821
28822  impl TimerDriver for PanickingRegisterTimerDriver {
28823    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
28824      self.registers.fetch_add(1, Ordering::SeqCst);
28825      self.pending.lock().unwrap().insert(id, waker);
28826      panic!("intentional timer-driver register panic after arming");
28827    }
28828
28829    fn cancel(&self, id: TimerId) {
28830      self.cancels.lock().unwrap().push(id);
28831      self.pending.lock().unwrap().remove(&id);
28832    }
28833  }
28834
28835  struct PanicBeforeRetainingTimerDriver {
28836    registers: AtomicUsize,
28837  }
28838
28839  impl TimerDriver for PanicBeforeRetainingTimerDriver {
28840    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28841      self.registers.fetch_add(1, Ordering::SeqCst);
28842      panic!("intentional timer-driver register panic before retaining its waker");
28843    }
28844
28845    fn cancel(&self, _id: TimerId) {}
28846  }
28847
28848  struct PanickingCancelDropTimerDriver {
28849    cancels: Arc<AtomicUsize>,
28850    drops: Arc<AtomicUsize>,
28851  }
28852
28853  impl TimerDriver for PanickingCancelDropTimerDriver {
28854    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28855
28856    fn cancel(&self, _id: TimerId) {
28857      self.cancels.fetch_add(1, Ordering::SeqCst);
28858      panic!("intentional timer-driver cancel panic");
28859    }
28860  }
28861
28862  impl Drop for PanickingCancelDropTimerDriver {
28863    fn drop(&mut self) {
28864      self.drops.fetch_add(1, Ordering::SeqCst);
28865      panic!("intentional timer-driver destructor panic");
28866    }
28867  }
28868
28869  struct UnregisterLaterThenPanicTimerDriver {
28870    registry: Weak<TimerDriverRegistry>,
28871    later: Mutex<Option<TimerDriverId>>,
28872    registers: AtomicUsize,
28873  }
28874
28875  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
28876    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
28877      self.registers.fetch_add(1, Ordering::SeqCst);
28878      let later = *self.later.lock().unwrap();
28879      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
28880        registry.unregister(later);
28881      }
28882      panic!("intentional earlier timer-driver panic after unregistering a later driver");
28883    }
28884
28885    fn cancel(&self, _id: TimerId) {}
28886  }
28887
28888  struct ReentrantLivenessDriver {
28889    registry: std::sync::Weak<TimerDriverRegistry>,
28890    registration: Mutex<Option<TimerDriverId>>,
28891  }
28892
28893  impl TimerDriver for ReentrantLivenessDriver {
28894    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28895
28896    fn cancel(&self, _id: TimerId) {}
28897
28898    fn is_live(&self) -> bool {
28899      let registration = *self.registration.lock().unwrap();
28900      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
28901        registry.unregister(id);
28902      }
28903      false
28904    }
28905  }
28906
28907  struct ReentrantMultiUnregisterTimerDriver {
28908    registry: std::sync::Weak<TimerDriverRegistry>,
28909    registrations: Mutex<Vec<TimerDriverId>>,
28910  }
28911
28912  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
28913    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28914
28915    fn cancel(&self, _id: TimerId) {}
28916
28917    fn is_live(&self) -> bool {
28918      if let Some(registry) = self.registry.upgrade() {
28919        for registration in self.registrations.lock().unwrap().iter().copied() {
28920          registry.unregister(registration);
28921        }
28922      }
28923      false
28924    }
28925  }
28926
28927  struct ReentrantSweepTimerDriver {
28928    registry: std::sync::Weak<TimerDriverRegistry>,
28929    registration: Mutex<Option<TimerDriverId>>,
28930    sweeps: AtomicUsize,
28931    removed_before_sweep: AtomicBool,
28932  }
28933
28934  impl TimerDriver for ReentrantSweepTimerDriver {
28935    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28936
28937    fn cancel(&self, _id: TimerId) {}
28938
28939    fn is_live(&self) -> bool {
28940      false
28941    }
28942
28943    fn on_swept(&self) {
28944      self.sweeps.fetch_add(1, Ordering::SeqCst);
28945      let registration = self.registration.lock().unwrap().take();
28946      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
28947        let state = registry
28948          .state
28949          .try_lock()
28950          .expect("the sweep callback must run outside the timer-driver registry lock");
28951        assert!(
28952          !state.entries.contains_key(&registration),
28953          "the dead driver must be unlinked before its sweep callback runs"
28954        );
28955        self.removed_before_sweep.store(true, Ordering::SeqCst);
28956        drop(state);
28957        // Mirrors JsTimerHostInner::evict: the sweep already removed this
28958        // registration, so explicit host cleanup must remain a cheap no-op.
28959        registry.unregister(registration);
28960      }
28961    }
28962  }
28963
28964  struct ReentrantDropDriver {
28965    registry: std::sync::Weak<TimerDriverRegistry>,
28966    fallback: TimerDriverId,
28967  }
28968
28969  impl TimerDriver for ReentrantDropDriver {
28970    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
28971
28972    fn cancel(&self, _id: TimerId) {}
28973  }
28974
28975  impl Drop for ReentrantDropDriver {
28976    fn drop(&mut self) {
28977      if let Some(registry) = self.registry.upgrade() {
28978        registry.unregister(self.fallback);
28979      }
28980    }
28981  }
28982
28983  /// Waker whose `wake` just latches a flag -- observable evidence that a
28984  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
28985  #[derive(Default)]
28986  struct WakeFlag(AtomicBool);
28987
28988  impl std::task::Wake for WakeFlag {
28989    fn wake(self: Arc<Self>) {
28990      self.0.store(true, Ordering::SeqCst);
28991    }
28992  }
28993
28994  #[cfg(not(target_family = "wasm"))]
28995  fn multi_thread_executor(
28996    workers: usize,
28997    park_deadline: Option<Duration>,
28998    prefix: &str,
28999  ) -> Arc<MultiThreadExecutor> {
29000    let options = RuntimeOptions {
29001      flavor: RuntimeFlavor::MultiThread,
29002      worker_threads: workers,
29003      max_blocking_tasks: workers,
29004      thread_name_prefix: prefix.to_string(),
29005      park_deadline,
29006      drain_linger: DEFAULT_DRAIN_LINGER,
29007    };
29008    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
29009  }
29010
29011  /// Local driver registry holding exactly `driver` (the common single-host
29012  /// shape of the ported pre-registry tests).
29013  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
29014    let registry = Arc::new(TimerDriverRegistry::default());
29015    registry.register(driver);
29016    registry
29017  }
29018
29019  #[cfg(not(target_family = "wasm"))]
29020  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
29021    // The MultiThread arm never touches the driver registry; empty is fine.
29022    make_sleep(
29023      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
29024      &Arc::new(TimerDriverRegistry::default()),
29025      deadline,
29026    )
29027  }
29028
29029  #[cfg(not(target_family = "wasm"))]
29030  #[test]
29031  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
29032    use std::sync::mpsc;
29033
29034    let executor = multi_thread_executor(
29035      1,
29036      Some(Duration::from_secs(1)),
29037      "timer-registration-admission",
29038    );
29039    let (entered_tx, entered_rx) = mpsc::channel();
29040    let (release_tx, release_rx) = mpsc::channel();
29041    let registration_executor = Arc::clone(&executor);
29042    let registration = std::thread::spawn(move || {
29043      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
29044        *slot.borrow_mut() = Some(Box::new(move || {
29045          entered_tx.send(()).unwrap();
29046          release_rx.recv().unwrap();
29047        }));
29048      });
29049      registration_executor.register_timer(
29050        1,
29051        Instant::now() + Duration::from_secs(60),
29052        futures::task::noop_waker(),
29053        &Arc::new(AtomicBool::new(false)),
29054      );
29055    });
29056
29057    entered_rx
29058      .recv_timeout(Duration::from_secs(2))
29059      .expect("timer registration must pause after entering verdict admission");
29060    let verdict = executor.try_begin_deadlock_verdict();
29061    let admission_blocked_verdict = verdict.is_none();
29062    drop(verdict);
29063    release_tx.send(()).unwrap();
29064    registration.join().unwrap();
29065
29066    assert!(
29067      admission_blocked_verdict,
29068      "a timer registration must remain announced until its heap entry is visible"
29069    );
29070    assert!(
29071      executor.next_timer_deadline().is_some(),
29072      "the admitted registration must publish its timer after release"
29073    );
29074    executor.begin_shutdown();
29075    executor.wait_until_scheduler_idle();
29076  }
29077
29078  #[cfg(not(target_family = "wasm"))]
29079  #[derive(Clone, Copy, Debug)]
29080  enum TimerFireAdmissionPath {
29081    Timekeeper,
29082    Cooperative,
29083    Shutdown,
29084  }
29085
29086  #[cfg(not(target_family = "wasm"))]
29087  struct ScheduleAfterTimerWakeRelease {
29088    executor: Arc<MultiThreadExecutor>,
29089    runnable: Mutex<Option<Runnable>>,
29090    entered: std::sync::mpsc::Sender<()>,
29091    release: Mutex<std::sync::mpsc::Receiver<()>>,
29092    scheduled: std::sync::mpsc::Sender<()>,
29093  }
29094
29095  #[cfg(not(target_family = "wasm"))]
29096  impl ScheduleAfterTimerWakeRelease {
29097    fn publish(&self) {
29098      self.entered.send(()).unwrap();
29099      self
29100        .release
29101        .lock()
29102        .unwrap_or_else(std::sync::PoisonError::into_inner)
29103        .recv()
29104        .unwrap();
29105      let runnable = self
29106        .runnable
29107        .lock()
29108        .unwrap_or_else(std::sync::PoisonError::into_inner)
29109        .take();
29110      if let Some(runnable) = runnable {
29111        self.executor.schedule(runnable);
29112      }
29113      self.scheduled.send(()).unwrap();
29114    }
29115  }
29116
29117  #[cfg(not(target_family = "wasm"))]
29118  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
29119    fn wake(self: Arc<Self>) {
29120      self.publish();
29121    }
29122
29123    fn wake_by_ref(self: &Arc<Self>) {
29124      self.publish();
29125    }
29126  }
29127
29128  #[cfg(not(target_family = "wasm"))]
29129  fn insert_timer_for_fire_admission_test(
29130    executor: &MultiThreadExecutor,
29131    id: TimerId,
29132    deadline: Instant,
29133    waker: Waker,
29134    fired: Arc<AtomicBool>,
29135  ) {
29136    let mut inner = executor
29137      .timers
29138      .inner
29139      .lock()
29140      .unwrap_or_else(std::sync::PoisonError::into_inner);
29141    inner.entries.insert(id, HeapTimerEntry { waker, fired });
29142    inner.queue.push(deadline, id);
29143  }
29144
29145  #[cfg(not(target_family = "wasm"))]
29146  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
29147    use std::sync::mpsc;
29148
29149    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
29150    let (runnable, task) = async_task::spawn(async {}, |_| {});
29151    task.detach();
29152    let (entered_tx, entered_rx) = mpsc::channel();
29153    let (release_tx, release_rx) = mpsc::channel();
29154    let (scheduled_tx, scheduled_rx) = mpsc::channel();
29155    let fired = Arc::new(AtomicBool::new(false));
29156    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
29157      executor: Arc::clone(&executor),
29158      runnable: Mutex::new(Some(runnable)),
29159      entered: entered_tx,
29160      release: Mutex::new(release_rx),
29161      scheduled: scheduled_tx,
29162    }));
29163
29164    let fire_thread = match path {
29165      TimerFireAdmissionPath::Timekeeper => {
29166        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
29167        None
29168      }
29169      TimerFireAdmissionPath::Cooperative => {
29170        insert_timer_for_fire_admission_test(
29171          &executor,
29172          1,
29173          Instant::now(),
29174          waker,
29175          Arc::clone(&fired),
29176        );
29177        let fire_executor = Arc::clone(&executor);
29178        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
29179      }
29180      TimerFireAdmissionPath::Shutdown => {
29181        insert_timer_for_fire_admission_test(
29182          &executor,
29183          1,
29184          Instant::now() + Duration::from_secs(60),
29185          waker,
29186          Arc::clone(&fired),
29187        );
29188        let fire_executor = Arc::clone(&executor);
29189        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
29190      }
29191    };
29192
29193    // Hang detector on a guaranteed event that needs a freshly spawned
29194    // thread (timekeeper / fire thread) to get CPU -- generous for starved
29195    // CI runners.
29196    entered_rx
29197      .recv_timeout(Duration::from_secs(10))
29198      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
29199    assert!(
29200      executor.next_timer_deadline().is_none(),
29201      "{path:?} must exercise the remove-before-wake window"
29202    );
29203    assert!(
29204      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
29205      "{path:?} must retain admission while its waker is publishing"
29206    );
29207    let verdict = executor.try_begin_deadlock_verdict();
29208    let admission_blocked_verdict = verdict.is_none();
29209    drop(verdict);
29210
29211    release_tx.send(()).unwrap();
29212    scheduled_rx
29213      .recv_timeout(Duration::from_secs(10))
29214      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
29215    if let Some(fire_thread) = fire_thread {
29216      fire_thread.join().unwrap();
29217    }
29218    assert!(
29219      admission_blocked_verdict,
29220      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
29221    );
29222
29223    executor.begin_shutdown();
29224    executor.wait_until_scheduler_idle();
29225  }
29226
29227  #[cfg(not(target_family = "wasm"))]
29228  #[test]
29229  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
29230    for path in [
29231      TimerFireAdmissionPath::Timekeeper,
29232      TimerFireAdmissionPath::Cooperative,
29233      TimerFireAdmissionPath::Shutdown,
29234    ] {
29235      assert_timer_fire_path_holds_deadlock_admission(path);
29236    }
29237  }
29238
29239  #[cfg(not(target_family = "wasm"))]
29240  #[test]
29241  fn multi_thread_sleep_fires_from_foreign_block_on() {
29242    // The napi-caller shape: a NON-pool thread parks in
29243    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
29244    // the executor, so ONLY the dedicated timekeeper can fire the timer --
29245    // this is the timekeeper's existence test.
29246    use std::sync::mpsc;
29247
29248    let (done_tx, done_rx) = mpsc::channel();
29249    let runner = std::thread::spawn(move || {
29250      let executor = multi_thread_executor(2, None, "timer-foreign");
29251      let started = Instant::now();
29252      let mut future = std::pin::pin!(async {
29253        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
29254      });
29255      executor.block_on(future.as_mut());
29256      assert!(
29257        started.elapsed() >= Duration::from_millis(80),
29258        "the sleep must not resolve before its deadline"
29259      );
29260      let _ = done_tx.send(());
29261    });
29262
29263    match done_rx.recv_timeout(Duration::from_secs(10)) {
29264      Ok(()) => runner.join().unwrap(),
29265      Err(error) => {
29266        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
29267      }
29268    }
29269  }
29270
29271  #[cfg(not(target_family = "wasm"))]
29272  #[test]
29273  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
29274    // A long timer waits on the dedicated timer thread, leaving the sole
29275    // low-level test worker available for ordinary scheduler work.
29276    use std::sync::mpsc;
29277
29278    let (done_tx, done_rx) = mpsc::channel();
29279    let runner = std::thread::spawn(move || {
29280      let executor = multi_thread_executor(1, None, "timer-drainer");
29281
29282      // Arm a long timer through a real spawned task so the sole worker
29283      // returns to rayon afterwards (the watch-idle Debouncing shape).
29284      let sleep_exec = Arc::clone(&executor);
29285      let sleep_done = Arc::new(AtomicBool::new(false));
29286      let sleep_flag = Arc::clone(&sleep_done);
29287      let sched = Arc::clone(&executor);
29288      let (sleep_runnable, sleep_task) = async_task::spawn(
29289        async move {
29290          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
29291          sleep_flag.store(true, Ordering::SeqCst);
29292        },
29293        move |r| sched.schedule(r),
29294      );
29295      executor.schedule(sleep_runnable);
29296      wait_until("the timekeeper parked on the 60s deadline", || {
29297        executor
29298          .timers
29299          .inner
29300          .lock()
29301          .unwrap()
29302          .timekeeper_parker
29303          .is_some()
29304      });
29305
29306      // Queued work must run NOW, not in 60s.
29307      let counter = Arc::new(AtomicUsize::new(0));
29308      let mut tasks = Vec::new();
29309      for _ in 0..32 {
29310        let counter = Arc::clone(&counter);
29311        let sched = Arc::clone(&executor);
29312        let (runnable, task) = async_task::spawn(
29313          async move {
29314            counter.fetch_add(1, Ordering::SeqCst);
29315          },
29316          move |r| sched.schedule(r),
29317        );
29318        executor.schedule(runnable);
29319        tasks.push(task);
29320      }
29321      for task in tasks {
29322        futures::executor::block_on(task);
29323      }
29324      assert_eq!(counter.load(Ordering::SeqCst), 32);
29325      assert!(
29326        !sleep_done.load(Ordering::SeqCst),
29327        "the 60s timer must still be pending: the work ran DURING the timer wait"
29328      );
29329      // Cancel the long sleep so the executor tears down promptly.
29330      executor.shutdown_timers();
29331      futures::executor::block_on(sleep_task);
29332      let _ = done_tx.send(());
29333    });
29334
29335    match done_rx.recv_timeout(Duration::from_secs(15)) {
29336      Ok(()) => runner.join().unwrap(),
29337      Err(error) => {
29338        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
29339      }
29340    }
29341  }
29342
29343  #[cfg(not(target_family = "wasm"))]
29344  #[test]
29345  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
29346    let executor = multi_thread_executor(2, None, "timer-lazy-start");
29347    assert!(
29348      executor.timers.thread.lock().unwrap().is_none(),
29349      "constructing a runtime without timers must not create an extra OS thread"
29350    );
29351
29352    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29353    assert!(
29354      executor.timers.thread.lock().unwrap().is_none(),
29355      "an unpolled sleep has not registered a deadline"
29356    );
29357    let waker = futures::task::noop_waker();
29358    let mut cx = Context::from_waker(&waker);
29359    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29360    assert!(
29361      executor.timers.thread.lock().unwrap().is_some(),
29362      "the first timer registration must start the dedicated service"
29363    );
29364  }
29365
29366  #[cfg(not(target_family = "wasm"))]
29367  #[test]
29368  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
29369    use std::sync::mpsc;
29370
29371    let executor = multi_thread_executor(1, None, "timer-closed-start");
29372    executor.shutdown_timers();
29373    let handle_guard = executor.timers.thread.lock().unwrap();
29374    let ensure_executor = Arc::clone(&executor);
29375    let (completed_tx, completed_rx) = mpsc::channel();
29376    let ensure = std::thread::spawn(move || {
29377      ensure_executor.timers.ensure_started().unwrap();
29378      completed_tx.send(()).unwrap();
29379    });
29380
29381    // Hang detector on a freshly spawned thread getting CPU -- generous for
29382    // starved CI runners (a real regression blocks forever on the held lock).
29383    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
29384      "ensure_started consulted or joined the timer handle before observing the closed heap",
29385    );
29386    drop(handle_guard);
29387    ensure.join().unwrap();
29388  }
29389
29390  #[cfg(not(target_family = "wasm"))]
29391  #[test]
29392  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
29393    const TIMERS: u64 = 4096;
29394
29395    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
29396    let base = Instant::now() + Duration::from_secs(3600);
29397    for id in 0..TIMERS {
29398      executor.register_timer(
29399        id,
29400        base + Duration::from_secs(id),
29401        futures::task::noop_waker(),
29402        &Arc::new(AtomicBool::new(false)),
29403      );
29404    }
29405    {
29406      let inner = executor.timers.inner.lock().unwrap();
29407      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
29408      assert_eq!(inner.queue.len(), inner.entries.len());
29409    }
29410
29411    // Preserve the earliest node while removing every later node. A lazy
29412    // BinaryHeap would retain all 4095 tombstones until that first deadline.
29413    for id in 1..TIMERS {
29414      executor.cancel_timer(id);
29415    }
29416    {
29417      let inner = executor.timers.inner.lock().unwrap();
29418      assert_eq!(inner.entries.len(), 1);
29419      assert_eq!(inner.queue.len(), 1);
29420      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
29421    }
29422
29423    executor.cancel_timer(0);
29424    {
29425      let inner = executor.timers.inner.lock().unwrap();
29426      assert!(inner.entries.is_empty());
29427      assert!(inner.queue.is_empty());
29428    }
29429    executor.shutdown_timers();
29430    executor.timers.join();
29431  }
29432
29433  #[cfg(not(target_family = "wasm"))]
29434  #[test]
29435  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
29436    let executor = multi_thread_executor(2, None, "timer-start-failure");
29437    executor
29438      .timers
29439      .fail_next_start
29440      .store(true, Ordering::SeqCst);
29441
29442    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29443    let waker = Waker::from(Arc::clone(&wake_count));
29444    let baseline_waker_refs = Arc::strong_count(&wake_count);
29445    let mut cx = Context::from_waker(&waker);
29446    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29447
29448    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
29449    assert!(
29450      failure.is_err(),
29451      "the injected timer-thread start failure must panic the poll"
29452    );
29453    {
29454      let inner = executor.timers.inner.lock().unwrap();
29455      assert!(
29456        inner.entries.is_empty(),
29457        "a failed start must not retain a timer entry"
29458      );
29459      assert!(
29460        inner.queue.is_empty(),
29461        "a failed start must not retain a stale heap node"
29462      );
29463      assert!(
29464        inner.timekeeper_parker.is_none(),
29465        "a failed start must not publish a timekeeper parker"
29466      );
29467    }
29468    assert!(
29469      executor.timers.thread.lock().unwrap().is_none(),
29470      "a failed start must not retain a thread handle"
29471    );
29472    assert_eq!(
29473      Arc::strong_count(&wake_count),
29474      baseline_waker_refs,
29475      "a failed start must release the cloned timer waker"
29476    );
29477
29478    assert!(
29479      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29480      "the same Sleep must be safely retryable after a transient start failure"
29481    );
29482    {
29483      let inner = executor.timers.inner.lock().unwrap();
29484      assert_eq!(
29485        inner.entries.len(),
29486        1,
29487        "the retry must publish exactly one timer entry"
29488      );
29489      assert_eq!(
29490        inner.queue.len(),
29491        1,
29492        "the retry must publish exactly one heap node"
29493      );
29494    }
29495    assert!(
29496      executor.timers.thread.lock().unwrap().is_some(),
29497      "the retry must start the dedicated timekeeper"
29498    );
29499
29500    drop(sleep);
29501    assert!(
29502      executor.timers.inner.lock().unwrap().entries.is_empty(),
29503      "dropping the retried Sleep must cancel its registration"
29504    );
29505    executor.shutdown_timers();
29506    executor.wait_until_scheduler_idle();
29507    assert!(
29508      executor.timers.thread.lock().unwrap().is_none(),
29509      "shutdown after a recovered start failure must join the timekeeper"
29510    );
29511  }
29512
29513  #[cfg(not(target_family = "wasm"))]
29514  #[test]
29515  fn pending_timer_preserves_every_configured_rayon_worker() {
29516    use std::sync::mpsc;
29517
29518    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
29519    let waker = futures::task::noop_waker();
29520    let mut cx = Context::from_waker(&waker);
29521    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29522    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29523    wait_until("the dedicated timer thread to park", || {
29524      executor
29525        .timers
29526        .inner
29527        .lock()
29528        .unwrap()
29529        .timekeeper_parker
29530        .is_some()
29531    });
29532
29533    let released = Arc::new((Mutex::new(false), Condvar::new()));
29534    let (started_tx, started_rx) = mpsc::channel();
29535    let (completed_tx, completed_rx) = mpsc::channel();
29536    for _ in 0..2 {
29537      let released = Arc::clone(&released);
29538      let started_tx = started_tx.clone();
29539      let completed_tx = completed_tx.clone();
29540      executor.pool.spawn(move || {
29541        started_tx.send(()).unwrap();
29542        let (lock, changed) = &*released;
29543        let mut released = lock
29544          .lock()
29545          .unwrap_or_else(std::sync::PoisonError::into_inner);
29546        while !*released {
29547          released = changed
29548            .wait(released)
29549            .unwrap_or_else(std::sync::PoisonError::into_inner);
29550        }
29551        completed_tx.send(()).unwrap();
29552      });
29553    }
29554    drop(started_tx);
29555    drop(completed_tx);
29556
29557    let first = started_rx.recv_timeout(Duration::from_secs(2));
29558    let second = started_rx.recv_timeout(Duration::from_secs(2));
29559    {
29560      let (lock, changed) = &*released;
29561      *lock
29562        .lock()
29563        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
29564      changed.notify_all();
29565    }
29566    drop(sleep);
29567    for _ in 0..2 {
29568      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29569    }
29570
29571    assert!(
29572      first.is_ok(),
29573      "the first configured Rayon worker did not start"
29574    );
29575    assert!(
29576      second.is_ok(),
29577      "a pending timer consumed one of the two configured Rayon workers"
29578    );
29579  }
29580
29581  #[cfg(not(target_family = "wasm"))]
29582  #[test]
29583  fn timekeeper_never_enters_a_blocking_closure() {
29584    use std::{
29585      sync::{Barrier, mpsc},
29586      time::Duration,
29587    };
29588
29589    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
29590    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29591    let waker = Waker::from(Arc::clone(&wake_count));
29592    let mut cx = Context::from_waker(&waker);
29593    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
29594    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29595    wait_until(
29596      "the timekeeper parked before blocking work was submitted",
29597      || {
29598        executor
29599          .timers
29600          .inner
29601          .lock()
29602          .unwrap()
29603          .timekeeper_parker
29604          .is_some()
29605      },
29606    );
29607
29608    let release = Arc::new(Barrier::new(2));
29609    let blocker_release = Arc::clone(&release);
29610    let (started_tx, started_rx) = mpsc::sync_channel(1);
29611    let blocking = executor.schedule_blocking(move || {
29612      started_tx.send(()).unwrap();
29613      blocker_release.wait();
29614    });
29615    // Hang detector (the closure start needs a worker to get CPU) --
29616    // generous for starved CI runners.
29617    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
29618
29619    wait_until(
29620      "the timer waker to fire while the Rayon worker is blocked",
29621      || wake_count.0.load(Ordering::SeqCst) != 0,
29622    );
29623    release.wait();
29624    futures::executor::block_on(blocking).unwrap();
29625    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
29626  }
29627
29628  #[cfg(not(target_family = "wasm"))]
29629  #[test]
29630  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
29631    use std::sync::mpsc;
29632
29633    struct PendingEntered(Option<mpsc::Sender<()>>);
29634
29635    impl Future for PendingEntered {
29636      type Output = ();
29637
29638      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29639        if let Some(entered) = self.0.take() {
29640          entered.send(()).unwrap();
29641        }
29642        Poll::Pending
29643      }
29644    }
29645
29646    let options = RuntimeOptions {
29647      flavor: RuntimeFlavor::MultiThread,
29648      worker_threads: 2,
29649      max_blocking_tasks: 1,
29650      thread_name_prefix: "nested-timer-wake".to_string(),
29651      park_deadline: None,
29652      drain_linger: DEFAULT_DRAIN_LINGER,
29653    };
29654    let executor =
29655      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29656
29657    let timer_exec = Arc::clone(&executor);
29658    let (timer_tx, timer_rx) = mpsc::channel();
29659    let timer_scheduler = Arc::clone(&executor);
29660    let (timer_runnable, timer_task) = async_task::spawn(
29661      async move {
29662        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
29663        timer_tx.send(()).unwrap();
29664      },
29665      move |runnable| timer_scheduler.schedule(runnable),
29666    );
29667    executor.schedule(timer_runnable);
29668    wait_until(
29669      "the dedicated timekeeper parked before nested block_on",
29670      || {
29671        executor
29672          .timers
29673          .inner
29674          .lock()
29675          .unwrap()
29676          .timekeeper_parker
29677          .is_some()
29678      },
29679    );
29680
29681    let (entered_tx, entered_rx) = mpsc::channel();
29682    let nested_exec = Arc::clone(&executor);
29683    let nested_scheduler = Arc::clone(&executor);
29684    let (nested_runnable, nested_task) = async_task::spawn(
29685      async move {
29686        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
29687        let outcome = nested_exec.block_on(pending.as_mut());
29688        drop(pending);
29689        assert_eq!(
29690          outcome,
29691          BlockOnOutcome::Stopped,
29692          "shutdown must stop the raw nested block_on driver"
29693        );
29694      },
29695      move |runnable| nested_scheduler.schedule(runnable),
29696    );
29697    executor.schedule(nested_runnable);
29698    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29699
29700    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
29701    let (release_tx, release_rx) = mpsc::channel();
29702    let blocking = executor.schedule_blocking(move || {
29703      blocking_started_tx.send(()).unwrap();
29704      release_rx.recv().unwrap();
29705    });
29706    blocking_started_rx
29707      .recv_timeout(Duration::from_secs(2))
29708      .expect("a general worker must service blocking work");
29709    timer_rx
29710      .recv_timeout(Duration::from_secs(2))
29711      .expect("the timer wake must reach the nested cooperative driver");
29712
29713    release_tx.send(()).unwrap();
29714    futures::executor::block_on(blocking).unwrap();
29715    executor.begin_shutdown();
29716    assert_eq!(
29717      futures::executor::block_on(nested_task.fallible()),
29718      Some(())
29719    );
29720    futures::executor::block_on(timer_task);
29721  }
29722
29723  #[cfg(not(target_family = "wasm"))]
29724  #[test]
29725  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
29726    // Task requirement: a NEW earliest deadline (heap peek changes) must
29727    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
29728    // timekeeper far in the future; the 60ms timer must still fire on time.
29729    use std::sync::mpsc;
29730
29731    let (done_tx, done_rx) = mpsc::channel();
29732    let runner = std::thread::spawn(move || {
29733      let executor = multi_thread_executor(2, None, "timer-rearm");
29734
29735      let long_exec = Arc::clone(&executor);
29736      let long_done = Arc::new(AtomicBool::new(false));
29737      let long_flag = Arc::clone(&long_done);
29738      let sched = Arc::clone(&executor);
29739      let (long_runnable, long_task) = async_task::spawn(
29740        async move {
29741          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
29742          long_flag.store(true, Ordering::SeqCst);
29743        },
29744        move |r| sched.schedule(r),
29745      );
29746      executor.schedule(long_runnable);
29747      wait_until("the timekeeper parked on the 60s sentinel", || {
29748        executor
29749          .timers
29750          .inner
29751          .lock()
29752          .unwrap()
29753          .timekeeper_parker
29754          .is_some()
29755      });
29756
29757      let started = Instant::now();
29758      let mut future = std::pin::pin!(async {
29759        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
29760      });
29761      executor.block_on(future.as_mut());
29762      let elapsed = started.elapsed();
29763      assert!(
29764        elapsed >= Duration::from_millis(60),
29765        "fired before its deadline: {elapsed:?}"
29766      );
29767      assert!(
29768        !long_done.load(Ordering::SeqCst),
29769        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
29770         timekeeper, not by the sentinel expiring"
29771      );
29772      executor.shutdown_timers();
29773      futures::executor::block_on(long_task);
29774      let _ = done_tx.send(());
29775    });
29776
29777    match done_rx.recv_timeout(Duration::from_secs(15)) {
29778      Ok(()) => runner.join().unwrap(),
29779      Err(error) => panic!(
29780        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
29781      ),
29782    }
29783  }
29784
29785  #[cfg(not(target_family = "wasm"))]
29786  #[test]
29787  fn multi_thread_sleep_cancels_registration_on_drop() {
29788    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
29789    // its heap registration (no waker fired later, heap drains empty).
29790    let executor = multi_thread_executor(1, None, "timer-cancel");
29791    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
29792    let waker = Waker::from(Arc::clone(&wake_count));
29793    let mut cx = Context::from_waker(&waker);
29794
29795    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
29796    // starved runner (a past-due Sleep would resolve inline instead of
29797    // registering, failing the assert below for the wrong reason).
29798    let deadline = Instant::now() + Duration::from_millis(250);
29799    let mut sleep = heap_sleep(&executor, deadline);
29800    assert!(
29801      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
29802      "must register, not resolve"
29803    );
29804    assert!(
29805      executor.next_timer_deadline().is_some(),
29806      "the registration must be in the heap"
29807    );
29808    drop(sleep);
29809    assert!(
29810      executor.next_timer_deadline().is_none(),
29811      "cancel-on-drop must retire the heap registration"
29812    );
29813    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
29814    // cancelled timer must not wake. The negative assert is structural once
29815    // the heap is empty; waiting until the wall clock is genuinely past the
29816    // deadline keeps the ghost-fire probe honest.
29817    while Instant::now() < deadline + Duration::from_millis(25) {
29818      std::thread::sleep(Duration::from_millis(10));
29819    }
29820    executor.fire_due_timers();
29821    assert_eq!(
29822      wake_count.0.load(Ordering::SeqCst),
29823      0,
29824      "a dropped Sleep's waker must never fire"
29825    );
29826  }
29827
29828  #[cfg(not(target_family = "wasm"))]
29829  #[test]
29830  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
29831    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
29832    let waker = futures::task::noop_waker();
29833    let mut cx = Context::from_waker(&waker);
29834    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
29835
29836    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29837    wait_until(
29838      "the timekeeper parked on the timer that will be cancelled",
29839      || {
29840        executor
29841          .timers
29842          .inner
29843          .lock()
29844          .unwrap()
29845          .timekeeper_parker
29846          .is_some()
29847      },
29848    );
29849
29850    drop(sleep);
29851    wait_until(
29852      "the cancelled timer's timekeeper returned to its idle wait",
29853      || {
29854        executor.next_timer_deadline().is_none()
29855          && executor
29856            .timers
29857            .inner
29858            .lock()
29859            .unwrap()
29860            .timekeeper_parker
29861            .is_none()
29862      },
29863    );
29864  }
29865
29866  #[cfg(not(target_family = "wasm"))]
29867  #[test]
29868  fn timer_waker_replacement_drops_outside_heap_lock() {
29869    use std::sync::mpsc;
29870
29871    struct DropOtherTimer(Mutex<Option<Sleep>>);
29872
29873    #[expect(
29874      clippy::manual_noop_waker,
29875      reason = "the no-op wake type has a reentrant Drop used by this regression"
29876    )]
29877    impl std::task::Wake for DropOtherTimer {
29878      fn wake(self: Arc<Self>) {}
29879    }
29880
29881    impl Drop for DropOtherTimer {
29882      fn drop(&mut self) {
29883        drop(self.0.lock().unwrap().take());
29884      }
29885    }
29886
29887    let (done_tx, done_rx) = mpsc::channel();
29888    let runner = std::thread::spawn(move || {
29889      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
29890      let deadline = Instant::now() + Duration::from_secs(120);
29891      let mut other = heap_sleep(&executor, deadline);
29892      let noop = futures::task::noop_waker();
29893      let mut noop_cx = Context::from_waker(&noop);
29894      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
29895
29896      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
29897      let mut primary = heap_sleep(&executor, deadline);
29898      let mut old_cx = Context::from_waker(&old);
29899      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
29900      drop(old);
29901
29902      let replacement = futures::task::noop_waker();
29903      let mut replacement_cx = Context::from_waker(&replacement);
29904      assert!(
29905        Pin::new(&mut primary)
29906          .poll(&mut replacement_cx)
29907          .is_pending()
29908      );
29909      drop(primary);
29910      executor.shutdown_timers();
29911      done_tx.send(()).unwrap();
29912    });
29913    // Hang detector on a freshly spawned runner thread -- generous for
29914    // starved CI runners (a real regression deadlocks forever).
29915    done_rx
29916      .recv_timeout(Duration::from_secs(10))
29917      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
29918    runner.join().unwrap();
29919  }
29920
29921  #[cfg(not(target_family = "wasm"))]
29922  #[test]
29923  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
29924    use std::sync::mpsc;
29925
29926    struct DropFinalExecutorOnWake {
29927      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
29928      completed: mpsc::Sender<()>,
29929    }
29930
29931    impl std::task::Wake for DropFinalExecutorOnWake {
29932      fn wake(self: Arc<Self>) {
29933        let executor = self
29934          .executor
29935          .lock()
29936          .unwrap_or_else(std::sync::PoisonError::into_inner)
29937          .take()
29938          .expect("the timer waker must own the final executor reference");
29939        assert_eq!(
29940          ON_TIMER_THREAD.with(std::cell::Cell::get),
29941          Some(executor.id),
29942          "the dedicated timer thread must deliver the timer wake"
29943        );
29944        drop(executor);
29945        self
29946          .completed
29947          .send(())
29948          .expect("the test receiver must outlive the timer-thread callback");
29949      }
29950    }
29951
29952    let executor = multi_thread_executor(1, None, "timer-final-arc");
29953    let (completed_tx, completed_rx) = mpsc::channel();
29954    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
29955    {
29956      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
29957        executor: Mutex::new(Some(Arc::clone(&executor))),
29958        completed: completed_tx,
29959      }));
29960      let mut cx = Context::from_waker(&waker);
29961      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29962    }
29963    wait_until(
29964      "the timer thread parked before owning the final executor Arc",
29965      || {
29966        executor
29967          .timers
29968          .inner
29969          .lock()
29970          .unwrap()
29971          .timekeeper_parker
29972          .is_some()
29973      },
29974    );
29975
29976    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
29977    // heap waker owns the final direct executor Arc and releases it from the
29978    // timer thread while `TimerHeap::drop` still owns that thread's handle.
29979    drop(executor);
29980    // Hang detector: the fire needs the (already spawned) timer thread to
29981    // reach its 80ms deadline -- generous for starved CI runners.
29982    completed_rx
29983      .recv_timeout(Duration::from_secs(10))
29984      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
29985  }
29986
29987  #[cfg(not(target_family = "wasm"))]
29988  #[test]
29989  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
29990    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
29991    // pending wakers -- a `watcher.close()` awaiting a
29992    // debounce sleep must complete, not hang. Also pins fire-on-register for
29993    // a Sleep polled after shutdown.
29994    use std::sync::mpsc;
29995
29996    let (done_tx, done_rx) = mpsc::channel();
29997    let runner = std::thread::spawn(move || {
29998      let controller = Arc::new(RuntimeController::new());
29999      // Default options: MultiThread on native.
30000      let backend = controller.backend();
30001      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
30002        panic!("default native backend must be MultiThread");
30003      };
30004      let executor = Arc::downgrade(executor);
30005      let mut late = heap_sleep(
30006        &executor
30007          .upgrade()
30008          .expect("the initial executor must be live"),
30009        Instant::now() + Duration::from_secs(120),
30010      );
30011      drop(backend);
30012
30013      let (parked_tx, parked_rx) = mpsc::channel();
30014      let block_controller = Arc::clone(&controller);
30015      let block_exec = Weak::clone(&executor);
30016      let blocker = std::thread::spawn(move || {
30017        let mut future = std::pin::pin!(async {
30018          let executor = block_exec
30019            .upgrade()
30020            .expect("the runtime must remain live while blocking");
30021          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
30022          drop(executor);
30023          parked_tx.send(()).unwrap();
30024          sleep.await;
30025        });
30026        block_controller.block_on(future.as_mut());
30027      });
30028      parked_rx.recv().unwrap();
30029      wait_until("the 120s sleep registered in the heap", || {
30030        executor
30031          .upgrade()
30032          .is_some_and(|executor| executor.next_timer_deadline().is_some())
30033      });
30034
30035      // The acceptance surface: controller shutdown while a sleep is armed.
30036      controller
30037        .shutdown()
30038        .expect("runtime shutdown must succeed");
30039      blocker
30040        .join()
30041        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
30042
30043      // A Sleep polled AFTER shutdown resolves immediately instead of
30044      // registering with a dead heap.
30045      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
30046      let mut cx = Context::from_waker(&waker);
30047      assert!(
30048        Pin::new(&mut late).poll(&mut cx).is_ready(),
30049        "a Sleep polled after shutdown must fire early, never park a closing runtime"
30050      );
30051      let _ = done_tx.send(());
30052    });
30053
30054    match done_rx.recv_timeout(Duration::from_secs(10)) {
30055      Ok(()) => runner.join().unwrap(),
30056      Err(error) => {
30057        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
30058      }
30059    }
30060  }
30061
30062  #[cfg(not(target_family = "wasm"))]
30063  #[test]
30064  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
30065    // Timer-wait/deadline invariant: the park deadline is armed
30066    // SHORTER than the next timer deadline. The cooperative driver awaiting
30067    // the sleep sits with ZERO
30068    // executor progress for several full deadline windows -- a timer wait is
30069    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
30070    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
30071    // also allows the parked driver to race the dedicated timekeeper and fire
30072    // the timer itself, pinning the cooperative timer-bounded park.
30073    use std::sync::mpsc;
30074
30075    let (done_tx, done_rx) = mpsc::channel();
30076    let runner = std::thread::spawn(move || {
30077      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
30078
30079      let task_exec = Arc::clone(&executor);
30080      let body = async move {
30081        let started = Instant::now();
30082        let mut inner = std::pin::pin!(async {
30083          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
30084        });
30085        // Re-entrant block_on from the pool worker: the cooperative park.
30086        task_exec.block_on(inner.as_mut());
30087        assert!(started.elapsed() >= Duration::from_millis(250));
30088      };
30089      let sched = Arc::clone(&executor);
30090      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
30091      executor.schedule(runnable);
30092
30093      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
30094      // by run_runnable's catch_unwind), so the task would never complete and
30095      // the bounded harness below would report the failure.
30096      futures::executor::block_on(task);
30097      // Teardown inside the bounded window: this test was one of the silent
30098      // wedges on the Windows CI runners with every assertion already
30099      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
30100      // (a timer was polled, so the timekeeper thread is live) -- exactly
30101      // the kind of phase that must fail loudly rather than hang.
30102      drop(executor);
30103      let _ = done_tx.send(());
30104    });
30105
30106    match done_rx.recv_timeout(Duration::from_secs(10)) {
30107      Ok(()) => join_within(
30108        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
30109        runner,
30110        Duration::from_secs(30),
30111      ),
30112      Err(error) => {
30113        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
30114      }
30115    }
30116  }
30117
30118  #[cfg(not(target_family = "wasm"))]
30119  #[test]
30120  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
30121    // Timer-registration/verdict race, deterministic via the verdict hook: a
30122    // timer registered AFTER a deadline park was committed bumps no progress
30123    // counter by design, lands after the permit and queued-work re-checks, and
30124    // is invisible to the fingerprint. The heap re-check must recognize that
30125    // guaranteed future wake, veto the panic, and let the driver re-park
30126    // timer-bounded until the fire's task-wake completes the future.
30127    use std::sync::mpsc;
30128
30129    // A raw Sleep the hook registers is woken by this waker, which releases
30130    // the awaited future's gate.
30131    struct GateWake {
30132      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
30133    }
30134    impl std::task::Wake for GateWake {
30135      fn wake(self: Arc<Self>) {
30136        let gate = self.gate.lock().unwrap().take();
30137        if let Some(gate) = gate {
30138          let _ = gate.send(23);
30139        }
30140      }
30141    }
30142
30143    let (done_tx, done_rx) = mpsc::channel();
30144    let runner = std::thread::spawn(move || {
30145      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
30146      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
30147      // replacement drainer behind our back; only the verdict's heap re-check
30148      // can save this park.
30149      executor
30150        .active_drainers
30151        .store(executor.max_drainers, Ordering::SeqCst);
30152
30153      // Completion condition: the timer registered inside the hook fires and
30154      // its waker (the gate) releases the awaited future.
30155      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30156      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
30157
30158      let hook_exec = Arc::clone(&executor);
30159      let hook_gate = Arc::clone(&gate_tx);
30160      // The Sleep must outlive the hook (drop cancels): park it in a slot the
30161      // runner thread later cleans up.
30162      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
30163      let hook_sleep_slot = Arc::clone(&parked_sleep);
30164      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30165        *slot.borrow_mut() = Some(Box::new(move || {
30166          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
30167          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
30168          let mut cx = Context::from_waker(&waker);
30169          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30170          *hook_sleep_slot.lock().unwrap() = Some(sleep);
30171        }));
30172      });
30173
30174      // The test-only on-pool marker runs the cooperative branch and its
30175      // thread-local hook on THIS thread.
30176      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30177      let mut output = None;
30178      {
30179        let mut future = std::pin::pin!(async {
30180          output = Some(gate_rx.await.unwrap());
30181        });
30182        executor.block_on(future.as_mut());
30183      }
30184      // Clear the hook slot BEFORE any assert can panic: this hook captures
30185      // an executor Arc and THIS executor's timekeeper is live (a timer was
30186      // polled), so a leftover hook dropped by a TLS destructor would join
30187      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
30188      // RULE at `WorkerTlsDropAction`).
30189      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30190      assert_eq!(
30191        output,
30192        Some(23),
30193        "the edge-registered timer must have fired and released the gate"
30194      );
30195      executor.active_drainers.store(0, Ordering::SeqCst);
30196      // Teardown inside the bounded window; includes the timekeeper join
30197      // (a timer was polled). See `join_within`.
30198      drop(executor);
30199      let _ = done_tx.send(());
30200    });
30201
30202    match done_rx.recv_timeout(Duration::from_secs(10)) {
30203      Ok(()) => join_within(
30204        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
30205        runner,
30206        Duration::from_secs(30),
30207      ),
30208      Err(error) => panic!(
30209        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
30210      ),
30211    }
30212  }
30213
30214  #[test]
30215  fn current_thread_sleep_without_host_driver_fails_loud() {
30216    // Missing-driver invariant: a CT runtime without a registered host driver
30217    // must fail LOUD with this exact diagnostic -- never a silent never-firing
30218    // debounce or a misleading certain-deadlock panic later.
30219    let metrics = Arc::new(RuntimeMetrics::default());
30220    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30221    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
30222
30223    let payload = catch_unwind(AssertUnwindSafe(|| {
30224      let _sleep = make_sleep(
30225        &backend,
30226        &Arc::new(TimerDriverRegistry::default()),
30227        Instant::now() + Duration::from_millis(10),
30228      );
30229    }))
30230    .expect_err("sleep_until on CurrentThread without a driver must panic");
30231    let message = payload
30232      .downcast_ref::<String>()
30233      .cloned()
30234      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
30235      .expect("panic payload must be a message");
30236    assert!(
30237      message.contains("CurrentThread runtime has no live timer driver registered"),
30238      "the diagnostic must name the missing driver, got: {message}"
30239    );
30240  }
30241
30242  #[test]
30243  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
30244    // Registry semantics: all-live snapshots with newest-live compatibility
30245    // selection, dead entries swept on contact, explicit unregister, and a
30246    // dead-only registry reading as NO driver.
30247    let registry = TimerDriverRegistry::default();
30248    let a = ManualStubDriver::new();
30249    let b = ManualStubDriver::new();
30250    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
30251    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
30252    assert_ne!(a_id, b_id, "registration handles must be distinct");
30253    assert_eq!(
30254      registry.current().map(|(id, _)| id),
30255      Some(b_id),
30256      "newest live registrant serves"
30257    );
30258    assert!(registry.has_live_driver());
30259
30260    // A dead newest (stub for: its owning env torn down) is skipped AND
30261    // swept; selection falls back to the older live registrant.
30262    b.kill();
30263    assert_eq!(
30264      registry.current().map(|(id, _)| id),
30265      Some(a_id),
30266      "dead newest must be skipped"
30267    );
30268    // The sweep is permanent: resurrecting the flag must not bring the
30269    // evicted registration back.
30270    b.live.store(true, Ordering::SeqCst);
30271    assert_eq!(
30272      registry.current().map(|(id, _)| id),
30273      Some(a_id),
30274      "swept entries stay gone"
30275    );
30276
30277    // Explicit unregister (the binding's env-cleanup path) empties the
30278    // registry; empty and dead-only both read as NO live driver.
30279    registry.unregister(a_id);
30280    assert!(registry.current().is_none());
30281    assert!(
30282      !registry.has_live_driver(),
30283      "an emptied registry must not report a live driver"
30284    );
30285  }
30286
30287  #[test]
30288  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
30289    let registry = TimerDriverRegistry::default();
30290    let fallback = ManualStubDriver::new();
30291    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30292    let panicking = Arc::new(PanickingTimerDriver {
30293      swept: AtomicBool::new(false),
30294    });
30295    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30296
30297    assert_eq!(
30298      registry.current().map(|(id, _)| id),
30299      Some(fallback_id),
30300      "a panicking liveness callback must be treated as a dead driver"
30301    );
30302    assert!(
30303      panicking.swept.load(Ordering::SeqCst),
30304      "a panicking liveness callback must still run its contained sweep hook"
30305    );
30306    assert!(
30307      registry.has_live_driver(),
30308      "the healthy fallback must remain selectable"
30309    );
30310  }
30311
30312  #[test]
30313  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
30314    use std::{sync::mpsc, time::Duration};
30315
30316    let registry = Arc::new(TimerDriverRegistry::default());
30317    let fallback = ManualStubDriver::new();
30318    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30319    let reentrant = Arc::new(ReentrantLivenessDriver {
30320      registry: Arc::downgrade(&registry),
30321      registration: Mutex::new(None),
30322    });
30323    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30324    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
30325
30326    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
30327    let selection_registry = Arc::clone(&registry);
30328    let selection_thread = std::thread::spawn(move || {
30329      selection_tx
30330        .send(selection_registry.current().map(|(id, _)| id))
30331        .unwrap();
30332    });
30333    assert_eq!(
30334      selection_rx
30335        .recv_timeout(Duration::from_secs(10))
30336        .expect("is_live re-entry deadlocked"),
30337      Some(fallback_id)
30338    );
30339    selection_thread.join().unwrap();
30340
30341    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
30342      registry: Arc::downgrade(&registry),
30343      fallback: fallback_id,
30344    });
30345    let drop_id = registry.register(Arc::clone(&drop_driver));
30346    drop(drop_driver);
30347
30348    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
30349    let drop_registry = Arc::clone(&registry);
30350    let drop_thread = std::thread::spawn(move || {
30351      drop_registry.unregister(drop_id);
30352      drop_tx.send(()).unwrap();
30353    });
30354    drop_rx
30355      .recv_timeout(Duration::from_secs(10))
30356      .expect("driver destructor re-entry deadlocked");
30357    drop_thread.join().unwrap();
30358    assert!(
30359      registry.current().is_none(),
30360      "the destructor must be able to unregister the fallback"
30361    );
30362  }
30363
30364  #[test]
30365  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
30366    const DRIVER_PAIRS: usize = 2_048;
30367
30368    let registry = Arc::new(TimerDriverRegistry::default());
30369    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
30370    let mut live = Vec::with_capacity(DRIVER_PAIRS);
30371    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
30372    for _ in 0..DRIVER_PAIRS {
30373      let driver = Arc::new(ReentrantSweepTimerDriver {
30374        registry: Arc::downgrade(&registry),
30375        registration: Mutex::new(None),
30376        sweeps: AtomicUsize::new(0),
30377        removed_before_sweep: AtomicBool::new(false),
30378      });
30379      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30380      *driver.registration.lock().unwrap() = Some(id);
30381      dead.push(driver);
30382
30383      let driver = ManualStubDriver::new();
30384      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
30385      live.push(driver);
30386    }
30387    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
30388      registry: Arc::downgrade(&registry),
30389      registration: Mutex::new(None),
30390      sweeps: AtomicUsize::new(0),
30391      removed_before_sweep: AtomicBool::new(false),
30392    });
30393    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
30394    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
30395    dead.push(dead_tail);
30396
30397    let selection = registry.live();
30398    assert_eq!(
30399      selection
30400        .as_slice()
30401        .iter()
30402        .map(|(id, _)| *id)
30403        .collect::<Vec<_>>(),
30404      live_ids,
30405      "sweeping every interleaved dead host must retain live registration order"
30406    );
30407    drop(selection);
30408    assert!(
30409      dead.iter().all(|driver| {
30410        driver.sweeps.load(Ordering::SeqCst) == 1
30411          && driver.removed_before_sweep.load(Ordering::SeqCst)
30412          && driver.registration.lock().unwrap().is_none()
30413      }),
30414      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
30415    );
30416
30417    {
30418      let state = registry
30419        .state
30420        .lock()
30421        .unwrap_or_else(std::sync::PoisonError::into_inner);
30422      assert_eq!(state.entries.len(), DRIVER_PAIRS);
30423      assert_eq!(state.oldest, live_ids.first().copied());
30424      assert_eq!(state.newest, live_ids.last().copied());
30425      assert_eq!(
30426        state
30427          .ordered_snapshot()
30428          .iter()
30429          .map(|(id, _)| *id)
30430          .collect::<Vec<_>>(),
30431        live_ids,
30432        "keyed removals must preserve the linked order of every live host"
30433      );
30434    }
30435    let second_selection = registry.live();
30436    assert_eq!(
30437      second_selection
30438        .as_slice()
30439        .iter()
30440        .map(|(id, _)| *id)
30441        .collect::<Vec<_>>(),
30442      live_ids,
30443      "a second selection must retain the same live order"
30444    );
30445    drop(second_selection);
30446    assert!(
30447      dead
30448        .iter()
30449        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
30450      "permanently swept hosts must not receive repeated eviction callbacks"
30451    );
30452    assert_eq!(
30453      registry.current().map(|(id, _)| id),
30454      live_ids.last().copied(),
30455      "newest-live compatibility selection must survive the mass sweep"
30456    );
30457
30458    drop(live);
30459    drop(dead);
30460  }
30461
30462  #[test]
30463  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
30464    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
30465
30466    if std::env::var_os(CHILD_ENV).is_some() {
30467      let registry = Arc::new(TimerDriverRegistry::default());
30468      let fallback = ManualStubDriver::new();
30469      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
30470      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
30471        registry: Arc::downgrade(&registry),
30472        registrations: Mutex::new(Vec::new()),
30473      });
30474      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
30475
30476      let first_cancels = Arc::new(AtomicUsize::new(0));
30477      let first_drops = Arc::new(AtomicUsize::new(0));
30478      let first = Arc::new(PanickingCancelDropTimerDriver {
30479        cancels: Arc::clone(&first_cancels),
30480        drops: Arc::clone(&first_drops),
30481      });
30482      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30483      drop(first);
30484
30485      let second_cancels = Arc::new(AtomicUsize::new(0));
30486      let second_drops = Arc::new(AtomicUsize::new(0));
30487      let second = Arc::new(PanickingCancelDropTimerDriver {
30488        cancels: Arc::clone(&second_cancels),
30489        drops: Arc::clone(&second_drops),
30490      });
30491      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30492      drop(second);
30493      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
30494
30495      assert_eq!(
30496        registry.current().map(|(id, _)| id),
30497        Some(fallback_id),
30498        "selection must retry after reentrant removals and preserve the healthy fallback"
30499      );
30500      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30501      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30502      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30503      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30504      assert_eq!(
30505        registry
30506          .state
30507          .lock()
30508          .unwrap_or_else(std::sync::PoisonError::into_inner)
30509          .entries
30510          .len(),
30511        1
30512      );
30513      return;
30514    }
30515
30516    let output = std::process::Command::new(std::env::current_exe().unwrap())
30517      .arg("--exact")
30518      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
30519      .arg("--nocapture")
30520      .env(CHILD_ENV, "1")
30521      .output()
30522      .expect("the hostile timer-driver snapshot subprocess test must start");
30523    assert!(
30524      output.status.success(),
30525      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30526      output.status.code(),
30527      String::from_utf8_lossy(&output.stdout),
30528      String::from_utf8_lossy(&output.stderr)
30529    );
30530  }
30531
30532  #[test]
30533  fn timer_driver_registry_live_result_contains_each_driver_drop() {
30534    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
30535
30536    if std::env::var_os(CHILD_ENV).is_some() {
30537      let registry = TimerDriverRegistry::default();
30538
30539      let first_cancels = Arc::new(AtomicUsize::new(0));
30540      let first_drops = Arc::new(AtomicUsize::new(0));
30541      let first = Arc::new(PanickingCancelDropTimerDriver {
30542        cancels: Arc::clone(&first_cancels),
30543        drops: Arc::clone(&first_drops),
30544      });
30545      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30546      drop(first);
30547
30548      let second_cancels = Arc::new(AtomicUsize::new(0));
30549      let second_drops = Arc::new(AtomicUsize::new(0));
30550      let second = Arc::new(PanickingCancelDropTimerDriver {
30551        cancels: Arc::clone(&second_cancels),
30552        drops: Arc::clone(&second_drops),
30553      });
30554      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30555      drop(second);
30556
30557      let live = registry.live();
30558      assert_eq!(live.len(), 2);
30559      registry.unregister(first_id);
30560      registry.unregister(second_id);
30561      drop(live);
30562
30563      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
30564      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
30565      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30566      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30567      assert!(
30568        registry
30569          .state
30570          .lock()
30571          .unwrap_or_else(std::sync::PoisonError::into_inner)
30572          .entries
30573          .is_empty()
30574      );
30575      return;
30576    }
30577
30578    let output = std::process::Command::new(std::env::current_exe().unwrap())
30579      .arg("--exact")
30580      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
30581      .arg("--nocapture")
30582      .env(CHILD_ENV, "1")
30583      .output()
30584      .expect("the hostile live timer-driver snapshot subprocess test must start");
30585    assert!(
30586      output.status.success(),
30587      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
30588      output.status.code(),
30589      String::from_utf8_lossy(&output.stdout),
30590      String::from_utf8_lossy(&output.stderr)
30591    );
30592  }
30593
30594  #[test]
30595  fn timer_driver_registry_drop_contains_each_driver_destructor() {
30596    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
30597
30598    if std::env::var_os(CHILD_ENV).is_some() {
30599      let first_drops = Arc::new(AtomicUsize::new(0));
30600      let second_drops = Arc::new(AtomicUsize::new(0));
30601      let registry = TimerDriverRegistry::default();
30602      let first = Arc::new(PanickingCancelDropTimerDriver {
30603        cancels: Arc::new(AtomicUsize::new(0)),
30604        drops: Arc::clone(&first_drops),
30605      });
30606      let second = Arc::new(PanickingCancelDropTimerDriver {
30607        cancels: Arc::new(AtomicUsize::new(0)),
30608        drops: Arc::clone(&second_drops),
30609      });
30610      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
30611      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
30612      drop(first);
30613      drop(second);
30614
30615      drop(registry);
30616
30617      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
30618      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
30619      return;
30620    }
30621
30622    let output = std::process::Command::new(std::env::current_exe().unwrap())
30623      .arg("--exact")
30624      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
30625      .arg("--nocapture")
30626      .env(CHILD_ENV, "1")
30627      .output()
30628      .expect("the hostile timer-driver registry drop subprocess test must start");
30629    assert!(
30630      output.status.success(),
30631      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
30632      output.status.code(),
30633      String::from_utf8_lossy(&output.stdout),
30634      String::from_utf8_lossy(&output.stderr)
30635    );
30636  }
30637
30638  #[test]
30639  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
30640    const DRIVERS: usize = 512;
30641
30642    let metrics = Arc::new(RuntimeMetrics::default());
30643    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30644    let backend =
30645      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30646    let registry = Arc::new(TimerDriverRegistry::default());
30647    let drivers = (0..DRIVERS)
30648      .map(|_| {
30649        let driver = ManualStubDriver::new();
30650        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30651        driver
30652      })
30653      .collect::<Vec<_>>();
30654    let mut sleep = make_sleep(
30655      &backend,
30656      &registry,
30657      Instant::now() + Duration::from_secs(60),
30658    );
30659    let mut cx = Context::from_waker(Waker::noop());
30660
30661    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30662    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30663    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
30664    match &sleep.inner {
30665      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
30666      #[cfg(not(target_family = "wasm"))]
30667      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30668    }
30669    for driver in &drivers {
30670      assert_eq!(
30671        driver.registers.lock().unwrap().len(),
30672        2,
30673        "a re-poll must refresh each live driver exactly once"
30674      );
30675    }
30676
30677    for driver in drivers.iter().step_by(2) {
30678      driver.kill();
30679    }
30680    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30681    match &sleep.inner {
30682      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
30683      #[cfg(not(target_family = "wasm"))]
30684      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
30685    }
30686    for (index, driver) in drivers.iter().enumerate() {
30687      let registers = driver.registers.lock().unwrap().len();
30688      let cancels = driver.cancels.lock().unwrap().clone();
30689      if index % 2 == 0 {
30690        assert_eq!(registers, 2);
30691        assert_eq!(cancels, vec![timer_id]);
30692      } else {
30693        assert_eq!(registers, 3);
30694        assert!(cancels.is_empty());
30695      }
30696    }
30697
30698    drop(sleep);
30699    for driver in drivers {
30700      assert_eq!(
30701        driver.cancels.lock().unwrap().as_slice(),
30702        &[timer_id],
30703        "every physical arm must be cancelled exactly once"
30704      );
30705    }
30706    assert_eq!(registry.pending_len(), 0);
30707    assert!(!executor.host_timers.has_pending());
30708  }
30709
30710  #[test]
30711  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
30712    let metrics = Arc::new(RuntimeMetrics::default());
30713    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30714    let backend =
30715      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30716    let healthy = ManualStubDriver::new();
30717    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30718    let deadline = Instant::now() + Duration::from_secs(60);
30719    let mut sleep = make_sleep(&backend, &registry, deadline);
30720    let mut cx = Context::from_waker(Waker::noop());
30721
30722    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30723    let timer_id = healthy.registers.lock().unwrap()[0].0;
30724
30725    let panicking = PanickingRegisterTimerDriver::new();
30726    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
30727    let result = catch_unwind(AssertUnwindSafe(|| {
30728      let _ = Pin::new(&mut sleep).poll(&mut cx);
30729    }));
30730    assert!(
30731      result.is_err(),
30732      "the hostile register panic must retain its normal propagation"
30733    );
30734    assert_eq!(
30735      healthy.registers.lock().unwrap().len(),
30736      2,
30737      "the previously armed driver must have completed its refresh before the later panic"
30738    );
30739    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
30740    assert!(
30741      panicking.pending.lock().unwrap().contains_key(&timer_id),
30742      "the hostile driver must model an arm committed before it panics"
30743    );
30744
30745    drop(sleep);
30746    assert_eq!(
30747      healthy.cancels.lock().unwrap().as_slice(),
30748      &[timer_id],
30749      "unwind must preserve cancellation ownership for the previously armed driver"
30750    );
30751    assert_eq!(
30752      panicking.cancels.lock().unwrap().as_slice(),
30753      &[timer_id],
30754      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
30755    );
30756    assert!(panicking.pending.lock().unwrap().is_empty());
30757    assert_eq!(registry.pending_len(), 0);
30758    assert!(!executor.host_timers.has_pending());
30759  }
30760
30761  #[test]
30762  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
30763    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
30764
30765    if std::env::var_os(CHILD_ENV).is_some() {
30766      let metrics = Arc::new(RuntimeMetrics::default());
30767      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30768      let backend =
30769        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30770      let registry = Arc::new(TimerDriverRegistry::default());
30771      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
30772        registry: Arc::downgrade(&registry),
30773        later: Mutex::new(None),
30774        registers: AtomicUsize::new(0),
30775      });
30776      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
30777
30778      let later_cancels = Arc::new(AtomicUsize::new(0));
30779      let later_drops = Arc::new(AtomicUsize::new(0));
30780      let later = Arc::new(PanickingCancelDropTimerDriver {
30781        cancels: Arc::clone(&later_cancels),
30782        drops: Arc::clone(&later_drops),
30783      });
30784      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
30785      *earlier.later.lock().unwrap() = Some(later_id);
30786      drop(later);
30787
30788      let mut sleep = make_sleep(
30789        &backend,
30790        &registry,
30791        Instant::now() + Duration::from_secs(60),
30792      );
30793      let mut cx = Context::from_waker(Waker::noop());
30794      let result = catch_unwind(AssertUnwindSafe(|| {
30795        let _ = Pin::new(&mut sleep).poll(&mut cx);
30796      }));
30797
30798      assert!(
30799        result.is_err(),
30800        "the earlier driver's register panic must still propagate"
30801      );
30802      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
30803      assert_eq!(
30804        later_drops.load(Ordering::SeqCst),
30805        0,
30806        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
30807      );
30808
30809      drop(sleep);
30810      assert_eq!(
30811        later_cancels.load(Ordering::SeqCst),
30812        1,
30813        "the later driver must remain owned for cancellation despite its registry removal"
30814      );
30815      assert_eq!(
30816        later_drops.load(Ordering::SeqCst),
30817        1,
30818        "the later driver's final destructor must run once behind containment"
30819      );
30820      assert_eq!(registry.pending_len(), 0);
30821      assert!(!executor.host_timers.has_pending());
30822      return;
30823    }
30824
30825    let output = std::process::Command::new(std::env::current_exe().unwrap())
30826      .arg("--exact")
30827      .arg(
30828        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
30829      )
30830      .arg("--nocapture")
30831      .env(CHILD_ENV, "1")
30832      .output()
30833      .expect("the hostile host timer snapshot subprocess test must start");
30834    assert!(
30835      output.status.success(),
30836      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
30837      output.status.code(),
30838      String::from_utf8_lossy(&output.stdout),
30839      String::from_utf8_lossy(&output.stderr)
30840    );
30841  }
30842
30843  #[test]
30844  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
30845    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
30846
30847    if std::env::var_os(CHILD_ENV).is_some() {
30848      let metrics = Arc::new(RuntimeMetrics::default());
30849      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30850      let backend =
30851        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30852      let cancels = Arc::new(AtomicUsize::new(0));
30853      let drops = Arc::new(AtomicUsize::new(0));
30854      let hostile = Arc::new(PanickingCancelDropTimerDriver {
30855        cancels: Arc::clone(&cancels),
30856        drops: Arc::clone(&drops),
30857      });
30858      let healthy = ManualStubDriver::new();
30859      let registry = Arc::new(TimerDriverRegistry::default());
30860      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
30861      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
30862      let mut sleep = make_sleep(
30863        &backend,
30864        &registry,
30865        Instant::now() + Duration::from_secs(60),
30866      );
30867      let mut cx = Context::from_waker(Waker::noop());
30868
30869      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
30870      let timer_id = healthy.registers.lock().unwrap()[0].0;
30871
30872      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
30873      // independently contain both its cancel panic and its destructor panic.
30874      registry.unregister(hostile_id);
30875      drop(hostile);
30876      drop(sleep);
30877      assert_eq!(cancels.load(Ordering::SeqCst), 1);
30878      assert_eq!(drops.load(Ordering::SeqCst), 1);
30879      assert_eq!(
30880        healthy.cancels.lock().unwrap().as_slice(),
30881        &[timer_id],
30882        "one hostile cancellation must not skip later armed drivers"
30883      );
30884      assert_eq!(registry.pending_len(), 0);
30885      assert!(!executor.host_timers.has_pending());
30886      return;
30887    }
30888
30889    let output = std::process::Command::new(std::env::current_exe().unwrap())
30890      .arg("--exact")
30891      .arg(
30892        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
30893      )
30894      .arg("--nocapture")
30895      .env(CHILD_ENV, "1")
30896      .output()
30897      .expect("the hostile timer-driver cancellation subprocess test must start");
30898    assert!(
30899      output.status.success(),
30900      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
30901      output.status.code(),
30902      String::from_utf8_lossy(&output.stdout),
30903      String::from_utf8_lossy(&output.stderr)
30904    );
30905  }
30906
30907  #[test]
30908  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
30909    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
30910
30911    if std::env::var_os(CHILD_ENV).is_some() {
30912      let metrics = Arc::new(RuntimeMetrics::default());
30913      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30914      let backend =
30915        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30916      let driver = ManualStubDriver::new();
30917      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30918      let state = Arc::new(TestHostileRawWakerState {
30919        clones: AtomicUsize::new(0),
30920        drop_panics: AtomicUsize::new(0),
30921        panic_on_clone: Some(2),
30922      });
30923
30924      let result = catch_unwind(AssertUnwindSafe(|| {
30925        let mut sleep = make_sleep(
30926          &backend,
30927          &registry,
30928          Instant::now() + Duration::from_secs(60),
30929        );
30930        let waker = test_hostile_waker(Arc::clone(&state));
30931        let mut cx = Context::from_waker(&waker);
30932        let _ = Pin::new(&mut sleep).poll(&mut cx);
30933      }));
30934      assert!(
30935        result.is_err(),
30936        "the original RawWaker clone panic must still propagate"
30937      );
30938      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
30939      assert_eq!(
30940        state.drop_panics.load(Ordering::SeqCst),
30941        1,
30942        "the first clone must be destroyed once behind a contained boundary"
30943      );
30944      assert!(driver.registers.lock().unwrap().is_empty());
30945      assert_eq!(registry.pending_len(), 0);
30946      assert!(!executor.host_timers.has_pending());
30947      return;
30948    }
30949
30950    let output = std::process::Command::new(std::env::current_exe().unwrap())
30951      .arg("--exact")
30952      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
30953      .arg("--nocapture")
30954      .env(CHILD_ENV, "1")
30955      .output()
30956      .expect("the hostile RawWaker subprocess test must start");
30957    assert!(
30958      output.status.success(),
30959      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
30960      output.status.code(),
30961      String::from_utf8_lossy(&output.stdout),
30962      String::from_utf8_lossy(&output.stderr)
30963    );
30964  }
30965
30966  #[test]
30967  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
30968    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
30969
30970    if std::env::var_os(CHILD_ENV).is_some() {
30971      let metrics = Arc::new(RuntimeMetrics::default());
30972      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
30973      let backend =
30974        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
30975      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
30976        registers: AtomicUsize::new(0),
30977      });
30978      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
30979      let state = Arc::new(TestHostileRawWakerState {
30980        clones: AtomicUsize::new(0),
30981        drop_panics: AtomicUsize::new(0),
30982        panic_on_clone: None,
30983      });
30984      let mut sleep = make_sleep(
30985        &backend,
30986        &registry,
30987        Instant::now() + Duration::from_secs(60),
30988      );
30989      let waker = test_hostile_waker(Arc::clone(&state));
30990      let mut cx = Context::from_waker(&waker);
30991
30992      let result = catch_unwind(AssertUnwindSafe(|| {
30993        let _ = Pin::new(&mut sleep).poll(&mut cx);
30994      }));
30995      assert!(
30996        result.is_err(),
30997        "the timer-driver register panic must still propagate"
30998      );
30999      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
31000      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
31001      assert_eq!(
31002        state.drop_panics.load(Ordering::SeqCst),
31003        1,
31004        "the unretained register argument must be destroyed once behind the proxy boundary"
31005      );
31006      assert_eq!(registry.pending_len(), 1);
31007      assert!(executor.host_timers.has_pending());
31008
31009      drop(sleep);
31010      assert_eq!(
31011        state.drop_panics.load(Ordering::SeqCst),
31012        3,
31013        "repoll and registry wakers must retain their existing contained cleanup"
31014      );
31015      assert_eq!(registry.pending_len(), 0);
31016      assert!(!executor.host_timers.has_pending());
31017      return;
31018    }
31019
31020    let output = std::process::Command::new(std::env::current_exe().unwrap())
31021      .arg("--exact")
31022      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
31023      .arg("--nocapture")
31024      .env(CHILD_ENV, "1")
31025      .output()
31026      .expect("the hostile register-waker subprocess test must start");
31027    assert!(
31028      output.status.success(),
31029      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
31030      output.status.code(),
31031      String::from_utf8_lossy(&output.stdout),
31032      String::from_utf8_lossy(&output.stderr)
31033    );
31034  }
31035
31036  #[test]
31037  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
31038    // A sleep armed on a driver whose host dies must keep every remaining live
31039    // registrant armed with its remaining time preserved
31040    // (absolute deadline), keep the executor's wake-token accounting at
31041    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
31042    // armed on when dropped.
31043    let metrics = Arc::new(RuntimeMetrics::default());
31044    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31045    let backend =
31046      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31047
31048    let a = ManualStubDriver::new();
31049    let b = ManualStubDriver::new();
31050    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31051    let deadline = Instant::now() + Duration::from_secs(60);
31052
31053    let (timer_id, b_id) = {
31054      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
31055      let mut cx = Context::from_waker(Waker::noop());
31056
31057      // First poll arms on A (the only registrant) and takes the wake-token.
31058      assert!(sleep.as_mut().poll(&mut cx).is_pending());
31059      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
31060      assert_eq!(armed_deadline, deadline);
31061      assert!(
31062        executor.host_timers.has_pending(),
31063        "first poll must arm the wake-token"
31064      );
31065
31066      // A's host dies; a newer live registrant appears (the binding shape:
31067      // eviction wakes every pending sleep, which then re-polls -- here the
31068      // re-poll is driven manually).
31069      a.kill();
31070      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31071      assert!(sleep.as_mut().poll(&mut cx).is_pending());
31072      assert_eq!(
31073        b.registers.lock().unwrap().as_slice(),
31074        &[(timer_id, deadline)],
31075        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
31076      );
31077      assert_eq!(
31078        a.cancels.lock().unwrap().as_slice(),
31079        &[timer_id],
31080        "the dead driver gets a best-effort cancel for the old arm"
31081      );
31082      assert_eq!(
31083        registry.current().map(|(id, _)| id),
31084        Some(b_id),
31085        "the dead driver was evicted"
31086      );
31087      assert!(
31088        executor.host_timers.has_pending(),
31089        "the wake-token survives the re-arm (still exactly this sleep's entry)"
31090      );
31091      (timer_id, b_id)
31092    };
31093
31094    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
31095    // wake-token retires.
31096    let _ = b_id;
31097    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
31098    assert!(
31099      !executor.host_timers.has_pending(),
31100      "drop must retire the wake-token"
31101    );
31102  }
31103
31104  #[test]
31105  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
31106    let metrics = Arc::new(RuntimeMetrics::default());
31107    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31108    let backend =
31109      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31110    let responsive = Arc::new(StubHostTimerDriver::new());
31111    let starved = ManualStubDriver::new();
31112    let registry = Arc::new(TimerDriverRegistry::default());
31113    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
31114    // Newest-live-only selection would arm exclusively on this host and never
31115    // receive a wake. The responsive older host must be raced in parallel.
31116    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31117
31118    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
31119    let waker = Waker::from(Arc::clone(&wake_count));
31120    let mut cx = Context::from_waker(&waker);
31121    // This deadline's pre-fire budget covers only `make_sleep` plus the very
31122    // next poll (a past-due Sleep completes inline and would bypass the
31123    // racing-arm machinery under test); 500ms dwarfs those two statements on
31124    // a starved runner without dragging the fire wait out.
31125    let deadline = Instant::now() + Duration::from_millis(500);
31126    let mut sleep = make_sleep(&backend, &registry, deadline);
31127    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31128
31129    let responsive_timer = *responsive
31130      .timers
31131      .lock()
31132      .unwrap()
31133      .keys()
31134      .next()
31135      .expect("responsive timer must be armed");
31136    assert_eq!(
31137      starved.registers.lock().unwrap().as_slice(),
31138      &[(responsive_timer, deadline)],
31139      "every live host must receive the same timer identity and absolute deadline"
31140    );
31141
31142    // Event-based fire wait (this was a fixed 140ms sleep, which required
31143    // the responsive stub's freshly spawned fire thread to get CPU inside
31144    // ~80ms on a starved CI host). The wait is structurally sound: the
31145    // starved ManualStubDriver has NO fire path (it only records), so the
31146    // one-and-only wake can come from the responsive host -- the bound is a
31147    // pure hang detector.
31148    let fire_bound = Instant::now() + Duration::from_secs(30);
31149    while wake_count.0.load(Ordering::SeqCst) == 0 {
31150      assert!(
31151        Instant::now() < fire_bound,
31152        "the responsive host must wake a sleep whose newest host is live but starved"
31153      );
31154      std::thread::sleep(Duration::from_millis(10));
31155    }
31156    // The stub fires at (or after) the shared absolute deadline, so the wall
31157    // clock is past `deadline` here and the poll completes.
31158    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31159    assert_eq!(
31160      starved.cancels.lock().unwrap().as_slice(),
31161      &[responsive_timer],
31162      "completion through the winning host must cancel the starved losing arm"
31163    );
31164  }
31165
31166  #[test]
31167  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
31168    use std::sync::mpsc;
31169
31170    struct ReentrantTimerRegistrationWake {
31171      registry: Weak<TimerDriverRegistry>,
31172      wakes: AtomicUsize,
31173      reentered: AtomicBool,
31174    }
31175
31176    impl std::task::Wake for ReentrantTimerRegistrationWake {
31177      fn wake(self: Arc<Self>) {
31178        self.wakes.fetch_add(1, Ordering::SeqCst);
31179        if let Some(registry) = self.registry.upgrade() {
31180          let _ = registry.has_live_driver();
31181          self.reentered.store(true, Ordering::SeqCst);
31182        }
31183      }
31184    }
31185
31186    let metrics = Arc::new(RuntimeMetrics::default());
31187    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31188    let backend =
31189      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31190    let registry = Arc::new(TimerDriverRegistry::default());
31191    let starved = ManualStubDriver::new();
31192    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
31193
31194    // The whole pre-fire phase (four polls plus a registration-thread round
31195    // trip) must finish while the wall clock is still BEFORE this shared
31196    // deadline: a past-due `Sleep` completes inline at poll
31197    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
31198    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
31199    // a starved GitHub macOS runner) without dragging the fire wait out.
31200    let deadline = Instant::now() + Duration::from_secs(2);
31201    let mut first = make_sleep(&backend, &registry, deadline);
31202    let mut second = make_sleep(&backend, &registry, deadline);
31203    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
31204      registry: Arc::downgrade(&registry),
31205      wakes: AtomicUsize::new(0),
31206      reentered: AtomicBool::new(false),
31207    });
31208    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
31209      registry: Arc::downgrade(&registry),
31210      wakes: AtomicUsize::new(0),
31211      reentered: AtomicBool::new(false),
31212    });
31213    let first_waker = Waker::from(Arc::clone(&first_wake));
31214    let second_waker = Waker::from(Arc::clone(&second_wake));
31215    let mut first_cx = Context::from_waker(&first_waker);
31216    let mut second_cx = Context::from_waker(&second_waker);
31217
31218    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31219    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31220    let initial = starved.registers.lock().unwrap().clone();
31221    let first_id = initial[0].0;
31222    let second_id = initial[1].0;
31223    assert_ne!(first_id, second_id);
31224    assert_eq!(
31225      registry.pending_len(),
31226      2,
31227      "one stable repoll entry is retained per sleep"
31228    );
31229
31230    let responsive = Arc::new(StubHostTimerDriver::new());
31231    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
31232    let registration_registry = Arc::clone(&registry);
31233    let registration_driver = Arc::clone(&responsive);
31234    let registration = std::thread::spawn(move || {
31235      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
31236      registered_tx.send(id).unwrap();
31237    });
31238    // Hang detector only (the regression it pins is a PERMANENT deadlock:
31239    // `register` re-entering the repoll callback), so the bound is generous
31240    // rather than a latency assertion a starved runner could trip.
31241    registered_rx
31242      .recv_timeout(Duration::from_secs(10))
31243      .expect("late driver registration or repoll callback re-entry deadlocked");
31244    registration.join().unwrap();
31245
31246    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
31247    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
31248    assert!(first_wake.reentered.load(Ordering::SeqCst));
31249    assert!(second_wake.reentered.load(Ordering::SeqCst));
31250    assert!(
31251      responsive.timers.lock().unwrap().is_empty(),
31252      "registration requests bounded re-polls instead of invoking driver callbacks inline"
31253    );
31254
31255    // Both sleeps must still be PENDING when they re-arm on the late driver;
31256    // see the deadline-margin note above. Fail with a diagnosis instead of a
31257    // misleading Pending-assert failure if a pathological stall consumed the
31258    // whole margin anyway.
31259    assert!(
31260      Instant::now() < deadline,
31261      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
31262       this runner is starved beyond the margin this test budgets for"
31263    );
31264    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
31265    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
31266    let responsive_ids = responsive
31267      .timers
31268      .lock()
31269      .unwrap()
31270      .keys()
31271      .copied()
31272      .collect::<FxHashSet<_>>();
31273    assert_eq!(
31274      responsive_ids,
31275      FxHashSet::from_iter([first_id, second_id]),
31276      "every pending sleep must re-arm its existing identity on the new driver"
31277    );
31278    assert_eq!(
31279      starved
31280        .registers
31281        .lock()
31282        .unwrap()
31283        .iter()
31284        .filter(|(id, _)| *id == first_id)
31285        .count(),
31286      2,
31287      "the old host receives only the normal waker refresh on the one re-poll"
31288    );
31289    assert_eq!(
31290      starved
31291        .registers
31292        .lock()
31293        .unwrap()
31294        .iter()
31295        .filter(|(id, _)| *id == second_id)
31296        .count(),
31297      2,
31298      "the old host receives only the normal waker refresh on the one re-poll"
31299    );
31300    assert_eq!(
31301      registry.pending_len(),
31302      2,
31303      "late registration must not duplicate pending state"
31304    );
31305
31306    // Event-based fire wait instead of a fixed sleep: the starved host has
31307    // NO fire path by construction (ManualStubDriver only records), so the
31308    // second wake per sleep can only come from the late responsive host --
31309    // "while the original host remains starved" is structural, not a
31310    // wall-clock race. The bound is purely a hang detector, generous enough
31311    // for a starved runner to schedule the stub's two fire threads.
31312    let fire_bound = Instant::now() + Duration::from_secs(30);
31313    while first_wake.wakes.load(Ordering::SeqCst) < 2
31314      || second_wake.wakes.load(Ordering::SeqCst) < 2
31315    {
31316      assert!(
31317        Instant::now() < fire_bound,
31318        "the late responsive host must fire both timers while the original host remains starved"
31319      );
31320      std::thread::sleep(Duration::from_millis(10));
31321    }
31322    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
31323    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
31324
31325    let mut cancelled = starved.cancels.lock().unwrap().clone();
31326    cancelled.sort_unstable();
31327    let mut expected = vec![first_id, second_id];
31328    expected.sort_unstable();
31329    assert_eq!(
31330      cancelled, expected,
31331      "completion must cancel both losing starved-host arms"
31332    );
31333    assert_eq!(
31334      registry.pending_len(),
31335      0,
31336      "completion retires every repoll entry"
31337    );
31338    assert!(!executor.host_timers.has_pending());
31339  }
31340
31341  #[test]
31342  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
31343    // With NO live registrant left mid-flight there is nothing to re-arm on:
31344    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
31345    // sleep silently.
31346    let metrics = Arc::new(RuntimeMetrics::default());
31347    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31348    let backend =
31349      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31350
31351    let a = ManualStubDriver::new();
31352    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31353    let mut sleep = std::pin::pin!(make_sleep(
31354      &backend,
31355      &registry,
31356      Instant::now() + Duration::from_secs(60)
31357    ));
31358    let mut cx = Context::from_waker(Waker::noop());
31359    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31360
31361    a.kill();
31362    let payload = catch_unwind(AssertUnwindSafe(|| {
31363      let _ = sleep.as_mut().poll(&mut cx);
31364    }))
31365    .expect_err("a sleep with every driver dead must panic, not pend silently");
31366    let message = payload
31367      .downcast_ref::<String>()
31368      .cloned()
31369      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
31370      .expect("panic payload must be a message");
31371    assert!(
31372      message.contains("lost every live timer driver mid-sleep"),
31373      "the diagnostic must name the mid-flight driver loss, got: {message}"
31374    );
31375  }
31376
31377  #[test]
31378  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
31379    // Sweep-eviction invariant: the registry's selection sweep can be the
31380    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
31381    // or any call failure. A retain-only sweep would strand every sleep whose
31382    // armed waker lives in the swept driver's pending map. The sweep must call
31383    // `on_swept` outside the registry lock so the driver wakes those sleeps
31384    // into re-selection.
31385    let metrics = Arc::new(RuntimeMetrics::default());
31386    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31387    let backend =
31388      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31389
31390    let a = ManualStubDriver::new();
31391    let b = ManualStubDriver::new();
31392    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
31393    let mut sleep = std::pin::pin!(make_sleep(
31394      &backend,
31395      &registry,
31396      Instant::now() + Duration::from_secs(60)
31397    ));
31398
31399    // Arm on A with an observable waker.
31400    let flag = Arc::new(WakeFlag::default());
31401    let waker = Waker::from(Arc::clone(&flag));
31402    let mut cx = Context::from_waker(&waker);
31403    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31404    assert!(!flag.0.load(Ordering::SeqCst));
31405
31406    // A's host dies; a DIFFERENT selection (any registry touch -- here a
31407    // liveness query, in production another sleep's poll) sweeps it.
31408    a.kill();
31409    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
31410    let _ = registry.has_live_driver();
31411
31412    assert!(
31413      a.swept.load(Ordering::SeqCst),
31414      "the sweep must notify the swept driver"
31415    );
31416    assert!(
31417      flag.0.load(Ordering::SeqCst),
31418      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
31419    );
31420
31421    // The woken sleep's re-poll lands on the live registrant.
31422    assert!(sleep.as_mut().poll(&mut cx).is_pending());
31423    assert_eq!(
31424      b.registers.lock().unwrap().len(),
31425      1,
31426      "the re-poll must re-arm on the live driver"
31427    );
31428  }
31429
31430  #[test]
31431  fn current_thread_sleep_fires_through_stub_host_driver() {
31432    // CT delegation: a registered host driver's fire must complete a sleep
31433    // awaited through the CT `block_on` drive loop.
31434    use std::sync::mpsc;
31435
31436    let (done_tx, done_rx) = mpsc::channel();
31437    let runner = std::thread::spawn(move || {
31438      let metrics = Arc::new(RuntimeMetrics::default());
31439      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31440      let backend =
31441        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31442      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31443
31444      let started = Instant::now();
31445      let mut future = std::pin::pin!(async {
31446        make_sleep(
31447          &backend,
31448          &registry_with(driver),
31449          Instant::now() + Duration::from_millis(60),
31450        )
31451        .await;
31452      });
31453      executor.block_on(future.as_mut());
31454      assert!(started.elapsed() >= Duration::from_millis(60));
31455      assert!(
31456        !executor.host_timers.has_pending(),
31457        "a completed sleep must retire its pending-registry entry"
31458      );
31459      let _ = done_tx.send(());
31460    });
31461
31462    match done_rx.recv_timeout(Duration::from_secs(10)) {
31463      Ok(()) => runner.join().unwrap(),
31464      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
31465    }
31466  }
31467
31468  #[test]
31469  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
31470    let metrics = Arc::new(RuntimeMetrics::default());
31471    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
31472    let backend =
31473      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31474    let drivers = Arc::new(TimerDriverRegistry::default());
31475    let driver = ManualStubDriver::new();
31476    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31477    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
31478    let mut cx = Context::from_waker(Waker::noop());
31479
31480    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
31481    let timer_id = driver.registers.lock().unwrap()[0].0;
31482    assert_eq!(drivers.pending_len(), 1);
31483
31484    executor.host_timers.shutdown();
31485    assert_eq!(
31486      drivers.pending_len(),
31487      0,
31488      "shutdown must retire the registration-repoll index before the Sleep is dropped"
31489    );
31490    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
31491    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
31492  }
31493
31494  #[test]
31495  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
31496    use std::sync::mpsc;
31497
31498    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
31499    let backend = controller.backend();
31500    let drivers = Arc::new(TimerDriverRegistry::default());
31501    let driver = ManualStubDriver::new();
31502    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
31503    let sleep = make_sleep(
31504      &backend,
31505      &drivers,
31506      Instant::now() + Duration::from_secs(120),
31507    );
31508    drop(backend);
31509
31510    let (polled_tx, polled_rx) = mpsc::channel();
31511    let block_controller = Arc::clone(&controller);
31512    let blocker = std::thread::spawn(move || {
31513      let mut announced = false;
31514      let mut sleep = std::pin::pin!(sleep);
31515      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
31516        let poll = sleep.as_mut().poll(cx);
31517        if !announced {
31518          announced = true;
31519          polled_tx.send(()).unwrap();
31520        }
31521        poll
31522      }));
31523      block_controller.block_on(future.as_mut());
31524    });
31525    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
31526
31527    let (shutdown_tx, shutdown_rx) = mpsc::channel();
31528    let shutdown_controller = Arc::clone(&controller);
31529    let shutdown = std::thread::spawn(move || {
31530      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
31531    });
31532    shutdown_rx
31533      .recv_timeout(Duration::from_secs(2))
31534      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
31535      .unwrap();
31536    shutdown.join().unwrap();
31537    blocker.join().unwrap();
31538    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
31539  }
31540
31541  #[test]
31542  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
31543    // Threadless pending-timer invariant: on a
31544    // THREADLESS build a pending host timer is NOT a wake-token source -- the
31545    // host event loop's `setTimeout` relay can only run on the very thread
31546    // that is about to park, so it can never fire while `block_on` holds it.
31547    // The certain check must panic with the typed diagnostic anyway (the
31548    // timer-backed kind, so the message names the shape), never fall through
31549    // to the park -- on the real wasip1 target that park is an untyped
31550    // "condvar wait not supported" abort. A native helper thread can fire the
31551    // timer in this test harness, but that concurrency cannot exist on the
31552    // threadless target and must not rescue the park.
31553    use std::sync::mpsc;
31554
31555    let (done_tx, done_rx) = mpsc::channel();
31556    let runner = std::thread::spawn(move || {
31557      let metrics = Arc::new(RuntimeMetrics::default());
31558      // Native stand-in for the threadless wasm build; the stub driver would
31559      // play the host loop from a helper thread, but the panic must land at
31560      // the first park decision, long before its 60ms fire.
31561      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31562      let backend =
31563        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31564      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31565
31566      let payload = catch_unwind(AssertUnwindSafe(|| {
31567        let mut future = std::pin::pin!(async {
31568          make_sleep(
31569            &backend,
31570            &registry_with(driver),
31571            Instant::now() + Duration::from_millis(60),
31572          )
31573          .await;
31574        });
31575        executor.block_on(future.as_mut());
31576      }))
31577      .expect_err("a threadless park must self-detect even while a host timer is pending");
31578      let diagnostic = payload
31579        .downcast_ref::<BlockOnDeadlock>()
31580        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31581      assert_eq!(
31582        diagnostic.kind,
31583        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
31584      );
31585      assert_eq!(
31586        diagnostic.park_deadline, None,
31587        "the certain case carries no deadline"
31588      );
31589      let message = diagnostic.to_string();
31590      assert!(
31591        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
31592        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
31593      );
31594      let _ = done_tx.send(());
31595    });
31596
31597    match done_rx.recv_timeout(Duration::from_secs(10)) {
31598      Ok(()) => runner.join().unwrap(),
31599      Err(error) => panic!(
31600        "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})"
31601      ),
31602    }
31603  }
31604
31605  #[test]
31606  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
31607    // Unpolled-sleep invariant: the registry entry only exists once the host
31608    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
31609    // creation. A created-but-never-polled Sleep has no host callback behind
31610    // it, and on a parked single thread nothing can ever poll it into
31611    // arming -- it is provably NOT a future wake source. Counting it would
31612    // mislabel the certain diagnostic as timer-backed. (The armed twin above
31613    // pins the polled side: a POLLED pending timer still certain-panics, just
31614    // with the timer-backed kind.)
31615    use std::sync::mpsc;
31616
31617    let (done_tx, done_rx) = mpsc::channel();
31618    let runner = std::thread::spawn(move || {
31619      let metrics = Arc::new(RuntimeMetrics::default());
31620      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
31621      let backend =
31622        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31623      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31624
31625      // Created and held across the park decision, but NEVER polled: no host
31626      // timer armed, no wake source.
31627      let _unpolled = make_sleep(
31628        &backend,
31629        &registry_with(driver),
31630        Instant::now() + Duration::from_secs(60),
31631      );
31632
31633      let payload = catch_unwind(AssertUnwindSafe(|| {
31634        let mut future = std::pin::pin!(NeverReady);
31635        executor.block_on(future.as_mut());
31636      }))
31637      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
31638      let diagnostic = payload
31639        .downcast_ref::<BlockOnDeadlock>()
31640        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
31641      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
31642      let _ = done_tx.send(());
31643    });
31644
31645    match done_rx.recv_timeout(Duration::from_secs(10)) {
31646      Ok(()) => runner.join().unwrap(),
31647      Err(error) => panic!(
31648        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
31649      ),
31650    }
31651  }
31652
31653  #[cfg(not(target_family = "wasm"))]
31654  #[test]
31655  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
31656    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
31657    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
31658    // legitimate (the wake is scheduled on the host loop); the deadline
31659    // verdict must not fire. Once the timer fires, the sleep completes.
31660    use std::sync::mpsc;
31661
31662    let (done_tx, done_rx) = mpsc::channel();
31663    let runner = std::thread::spawn(move || {
31664      let metrics = Arc::new(RuntimeMetrics::default());
31665      // Deadline/grace ratio: the armed park deadline doubles as the
31666      // `has_live_wait` GRACE -- once the host timer is past due by more
31667      // than one deadline window without firing, the veto stops holding BY
31668      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
31669      // the stub host's freshly spawned fire thread had to get CPU within
31670      // ~230ms of test start or the verdict fired -- the starved-CI failure.
31671      // A 2s window keeps the invariant identical (a LIVE host-timer wait
31672      // with zero progress must not be declared dead) while tolerating
31673      // multi-second scheduling stalls of the fire thread: the veto holds
31674      // until deadline+2s, and the expiry checks land on 2s boundaries, so
31675      // the fire has >4s before a verdict could stick.
31676      let executor = Arc::new(CurrentThreadExecutor::with_detection(
31677        metrics,
31678        false,
31679        Some(Duration::from_secs(2)),
31680      ));
31681      let backend =
31682        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
31683      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
31684
31685      let started = Instant::now();
31686      let mut future = std::pin::pin!(async {
31687        make_sleep(
31688          &backend,
31689          &registry_with(driver),
31690          Instant::now() + Duration::from_millis(200),
31691        )
31692        .await;
31693      });
31694      executor.block_on(future.as_mut());
31695      assert!(started.elapsed() >= Duration::from_millis(200));
31696      let _ = done_tx.send(());
31697    });
31698
31699    match done_rx.recv_timeout(Duration::from_secs(30)) {
31700      Ok(()) => runner.join().unwrap(),
31701      Err(error) => {
31702        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
31703      }
31704    }
31705  }
31706
31707  #[test]
31708  fn host_timer_unrepresentable_grace_remains_live() {
31709    let registry = HostTimerRegistry::default();
31710    let fired = Arc::new(AtomicBool::new(false));
31711    let drivers = Arc::new(TimerDriverRegistry::default());
31712    let repoll = Arc::new(HostTimerRepoll::default());
31713    assert!(
31714      Instant::now().checked_add(Duration::MAX).is_none(),
31715      "Duration::MAX must exercise the unrepresentable-grace path"
31716    );
31717    assert!(
31718      registry.register(
31719        1,
31720        Instant::now(),
31721        futures::task::noop_waker(),
31722        &fired,
31723        &drivers,
31724        &repoll,
31725      ),
31726      "the open registry must accept the timer"
31727    );
31728
31729    assert!(
31730      registry.has_live_wait(Duration::MAX),
31731      "an unrepresentable grace deadline is effectively unbounded"
31732    );
31733    registry.remove(1);
31734  }
31735
31736  #[test]
31737  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
31738    // A deadline firing inside a spawned task's poll unwinds into the spawn
31739    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
31740    // message must survive that trip (JoinError::from_panic downcasts it), or
31741    // the build error would read "async runtime task panicked" with no clue.
31742    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
31743    let expected = diagnostic.to_string();
31744    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
31745    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
31746  }
31747}