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    self.wait_until_idle_retiring(|| false, || {});
2703  }
2704
2705  /// Wait until every registered guard has retired, retiring deferred work
2706  /// on this stack whenever `has_deferred` reports some. Such work (a runnable
2707  /// rejected after the executor queue closed, or the runnables a terminal
2708  /// failure transition moved aside on a waker's stack) holds guards, so the
2709  /// generation can only become idle once the waiter drops them; `retire`
2710  /// runs with no lock held. `notify_idle_waiters` publishes new deferred
2711  /// work under the state lock, so a check made here cannot miss it.
2712  fn wait_until_idle_retiring(&self, has_deferred: impl Fn() -> bool, retire: impl Fn()) {
2713    let mut state = self
2714      .state
2715      .lock()
2716      .unwrap_or_else(std::sync::PoisonError::into_inner);
2717    while state.active != 0 {
2718      if has_deferred() {
2719        drop(state);
2720        retire();
2721        state = self
2722          .state
2723          .lock()
2724          .unwrap_or_else(std::sync::PoisonError::into_inner);
2725        continue;
2726      }
2727      state = self
2728        .idle
2729        .wait(state)
2730        .unwrap_or_else(std::sync::PoisonError::into_inner);
2731    }
2732  }
2733
2734  fn notify_idle_waiters(&self) {
2735    let _state = self
2736      .state
2737      .lock()
2738      .unwrap_or_else(std::sync::PoisonError::into_inner);
2739    self.idle.notify_all();
2740  }
2741}
2742
2743struct GenerationWorkGuard {
2744  work: Arc<GenerationWork>,
2745  task_id: Option<u64>,
2746}
2747
2748impl Drop for GenerationWorkGuard {
2749  fn drop(&mut self) {
2750    let mut state = self
2751      .work
2752      .state
2753      .lock()
2754      .unwrap_or_else(std::sync::PoisonError::into_inner);
2755    if let Some(task_id) = self.task_id {
2756      state.abort_handles.remove(&task_id);
2757    }
2758    state.active = state
2759      .active
2760      .checked_sub(1)
2761      .expect("generation work count underflow");
2762    if state.active == 0 {
2763      self.work.idle.notify_all();
2764    }
2765  }
2766}
2767
2768/// Keeps accepted async work registered until the complete task generator has
2769/// been destroyed. In particular, an executor queue may close after
2770/// registration but before the initial runnable is published. `async-task`
2771/// then drops the generator unpolled, whose compiler-generated capture order
2772/// must not be allowed to retire the generation guard before user captures.
2773struct RegisteredTaskFuture<F> {
2774  future: ManuallyDrop<F>,
2775  registration: Option<GenerationWorkGuard>,
2776  generation: u64,
2777}
2778
2779impl<F> RegisteredTaskFuture<F> {
2780  fn new(future: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2781    Self {
2782      future: ManuallyDrop::new(future),
2783      registration: Some(registration),
2784      generation,
2785    }
2786  }
2787}
2788
2789impl<F: Future> Future for RegisteredTaskFuture<F> {
2790  type Output = F::Output;
2791
2792  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2793    // SAFETY: `future` is never moved after `RegisteredTaskFuture` is pinned.
2794    let this = unsafe { self.get_unchecked_mut() };
2795    let _generation = RuntimeGenerationGuard::enter(this.generation);
2796    // SAFETY: `ManuallyDrop<F>` has the same layout as `F`, and the field is
2797    // structurally pinned for the lifetime of `self`.
2798    unsafe { Pin::new_unchecked(&mut *this.future) }.poll(cx)
2799  }
2800}
2801
2802impl<F> Drop for RegisteredTaskFuture<F> {
2803  fn drop(&mut self) {
2804    let _generation = RuntimeGenerationGuard::enter(self.generation);
2805    let _ = catch_unwind_contained(|| {
2806      // SAFETY: this is the sole destruction path for `future`.
2807      unsafe { ManuallyDrop::drop(&mut self.future) };
2808    });
2809    // Shutdown/restart may observe this generation idle only after every user
2810    // capture and caught panic payload above has retired.
2811    drop(self.registration.take());
2812  }
2813}
2814
2815#[cfg(not(target_family = "wasm"))]
2816struct RegisteredBlockingFunction<F> {
2817  function: Option<F>,
2818  registration: Option<GenerationWorkGuard>,
2819  generation: u64,
2820}
2821
2822#[cfg(not(target_family = "wasm"))]
2823impl<F> RegisteredBlockingFunction<F> {
2824  fn new(function: F, registration: GenerationWorkGuard, generation: u64) -> Self {
2825    Self {
2826      function: Some(function),
2827      registration: Some(registration),
2828      generation,
2829    }
2830  }
2831
2832  fn run<T>(mut self) -> Result<T, JoinError>
2833  where
2834    F: FnOnce() -> T,
2835  {
2836    let _generation = RuntimeGenerationGuard::enter(self.generation);
2837    let result = catch_unwind_join_error(
2838      self
2839        .function
2840        .take()
2841        .expect("blocking function must be available"),
2842    );
2843    drop(self.registration.take());
2844    result
2845  }
2846}
2847
2848#[cfg(not(target_family = "wasm"))]
2849impl<F> Drop for RegisteredBlockingFunction<F> {
2850  fn drop(&mut self) {
2851    let _generation = RuntimeGenerationGuard::enter(self.generation);
2852    if let Some(function) = self.function.take() {
2853      let _ = catch_unwind_contained(|| drop(function));
2854    }
2855    // A submission accepted by the controller may reach a queue that shutdown
2856    // already closed. Keep it registered until all captured user state above
2857    // has been destroyed on the submitting thread.
2858    drop(self.registration.take());
2859  }
2860}
2861
2862fn run_runnable(metrics: &RuntimeMetrics, runnable: Runnable) {
2863  let _active = metrics.runnable_started();
2864  let _ = catch_unwind_contained(|| runnable.run());
2865}
2866
2867#[cfg(not(target_family = "wasm"))]
2868enum RunnableClaim {
2869  Empty,
2870  Run(Runnable),
2871  Cancel(Runnable),
2872}
2873
2874static NEXT_RUNTIME_EXECUTOR_ID: AtomicU64 = AtomicU64::new(1);
2875static NEXT_CURRENT_THREAD_DISPATCH_ID: AtomicU64 = AtomicU64::new(0);
2876
2877fn next_current_thread_dispatch_id() -> u64 {
2878  next_unique_id(
2879    &NEXT_CURRENT_THREAD_DISPATCH_ID,
2880    "CurrentThread dispatch id space exhausted",
2881  )
2882  .checked_add(1)
2883  .expect("CurrentThread dispatch id space exhausted")
2884}
2885
2886struct CurrentThreadBlockingAdmission {
2887  state: Mutex<CurrentThreadBlockingAdmissionState>,
2888}
2889
2890struct CurrentThreadBlockingAdmissionState {
2891  owner: Option<BlockingOwnerToken>,
2892  closed: bool,
2893  #[cfg(not(target_family = "wasm"))]
2894  queue: BlockingQueue,
2895}
2896
2897struct CurrentThreadExecutor {
2898  id: u64,
2899  generation: u64,
2900  stop: Arc<GenerationStop>,
2901  queue: Mutex<CurrentThreadQueue>,
2902  blocking_admission: CurrentThreadBlockingAdmission,
2903  draining: AtomicBool,
2904  scheduler_idle_lock: Mutex<CurrentThreadSchedulerState>,
2905  scheduler_idle: Condvar,
2906  /// Exact nonzero internal capability for the currently published host turn.
2907  /// The host registry maps this to distinct registration-scoped delivery
2908  /// capabilities, so delayed drive/cancel calls cannot cross host attempts.
2909  dispatch_pending: AtomicU64,
2910  /// Exact capability of the one replacement dispatch allowed after a host
2911  /// scheduler rejects an accepted callback. A second matching cancellation
2912  /// fails the queued work instead of retrying forever.
2913  recovery_dispatch: AtomicU64,
2914  /// Terminal dispatch failure rejects scheduler publications and admission
2915  /// from the moment it is claimed (under `scheduler_idle_lock`) until
2916  /// `cancel_work_after_host_dispatch_failure` reopens the executor under the
2917  /// queue lock: after cancelling the queued work on a clean stack, or as
2918  /// soon as it has moved that work aside on a waker's stack.
2919  cancelling_failed_dispatch: AtomicBool,
2920  task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
2921  task_drivers: &'static CurrentThreadTaskDriverRegistry,
2922  metrics: Arc<RuntimeMetrics>,
2923  // Deadlock detection. `threadless`: no second thread can EVER deliver a
2924  // wake to a parked `block_on`, so a park decision with an
2925  // empty queue and no pending wake token is a PROVABLE deadlock (panic
2926  // immediately, no timing involved). `park_deadline`: on threaded builds,
2927  // the optional bound for a park with zero runtime progress -- off unless
2928  // configured, because a legitimately long park must never panic a
2929  // production build.
2930  threadless: bool,
2931  park_deadline: Option<Duration>,
2932  // HOST-driver timers currently armed on this runtime.
2933  // On the THREADED CT flavor a pending entry is a future wake token -- the
2934  // host event loop's timer callback -- so the deadline verdict treats a LIVE
2935  // wait (deadline still in the future) as a legitimate park. On a THREADLESS
2936  // build it is NOT a wake token (the host relay shares the parked thread and
2937  // can never run); there the CERTAIN check only consults it to pick the
2938  // timer-backed variant of the typed diagnostic. Shared with the `Sleep`
2939  // futures minted for this executor (they insert at first poll and remove on
2940  // completion/drop).
2941  host_timers: Arc<HostTimerRegistry>,
2942  // The generation this executor serves, attached by its `RuntimeBackend`.
2943  // A runnable rejected after the queue closed still owns its
2944  // `GenerationWorkGuard`, so `schedule` must wake the generation's idle
2945  // waiter, which retires such runnables on its own stack.
2946  generation_work: ArcSwapOption<GenerationWork>,
2947}
2948
2949impl Drop for CurrentThreadExecutor {
2950  fn drop(&mut self) {
2951    // Last drain point for rejected work (each runnable still owns its
2952    // `GenerationWorkGuard`) when no idle wait ran after the queue closed.
2953    self.retire_rejected_work();
2954  }
2955}
2956
2957struct CurrentThreadQueue {
2958  closed: bool,
2959  runnables: VecDeque<Runnable>,
2960  /// Runnables the executor owes a cancellation: the work that was queued
2961  /// when a terminal dispatch failure happened (moved here by
2962  /// `cancel_work_after_host_dispatch_failure`), a schedule that arrived
2963  /// while that transition held the executor closed, or a schedule that
2964  /// arrived after shutdown closed the queue. Their `queued_runnables` debit
2965  /// has already been taken. They are dropped only on clean stacks: by the
2966  /// transition itself when it is not on a waker's stack, and otherwise by
2967  /// `retire_rejected_work` at host entry points (a host turn, delivery
2968  /// completion, driver unregistration, a drain request) outside `schedule`
2969  /// and on the shutdown side (`begin_shutdown`, the generation's idle wait,
2970  /// `wait_until_scheduler_idle`, the executor's destructor). Never on the
2971  /// waker's stack: that waker may run while its producer holds a mutex
2972  /// (`futures::Shared` wakes awaiters under its `wakers` lock) that the
2973  /// rejected future's destructor re-locks. See `CurrentThreadScheduleScope`.
2974  rejected: Vec<Runnable>,
2975  /// Blocking jobs queued when a terminal dispatch failure happened. Same
2976  /// contract as `rejected`: dropping a job destroys its closure and its
2977  /// result sender, which wakes the awaiting task.
2978  #[cfg(not(target_family = "wasm"))]
2979  rejected_blocking: Vec<Box<dyn FnOnce() + Send + 'static>>,
2980}
2981
2982#[derive(Default)]
2983struct CurrentThreadSchedulerState {
2984  active_host_turns: usize,
2985  active_dispatch_calls: usize,
2986  dispatch_publications: Vec<CurrentThreadDispatchPublication>,
2987}
2988
2989struct CurrentThreadDispatchPublication {
2990  dispatch: u64,
2991  republish_requested: bool,
2992}
2993
2994#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2995enum CurrentThreadDispatchRequest {
2996  Accepted,
2997  Coalesced,
2998  Unavailable,
2999}
3000
3001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3002struct CurrentThreadTaskDispatchFailure {
3003  dispatch: u64,
3004  epoch: u64,
3005}
3006
3007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3008enum CurrentThreadHostDispatchResult {
3009  Accepted,
3010  Unavailable,
3011  Failed(CurrentThreadTaskDispatchFailure),
3012}
3013
3014enum CurrentThreadDispatchPublicationStep {
3015  Republish,
3016  Complete(Option<CurrentThreadHostDispatchResult>),
3017}
3018
3019struct CurrentThreadHostTurn {
3020  executor: Arc<CurrentThreadExecutor>,
3021  draining: bool,
3022  _generation: RuntimeGenerationGuard,
3023}
3024
3025impl CurrentThreadHostTurn {
3026  fn drive(&mut self) {
3027    let executor = Arc::clone(&self.executor);
3028    executor.drive_admitted_host_turn(self);
3029  }
3030
3031  fn release_draining(&mut self) {
3032    if !self.draining {
3033      return;
3034    }
3035    let _scheduler = self
3036      .executor
3037      .scheduler_idle_lock
3038      .lock()
3039      .unwrap_or_else(std::sync::PoisonError::into_inner);
3040    self.executor.draining.store(false, Ordering::Release);
3041    self.draining = false;
3042  }
3043}
3044
3045impl Drop for CurrentThreadHostTurn {
3046  fn drop(&mut self) {
3047    let mut scheduler = self
3048      .executor
3049      .scheduler_idle_lock
3050      .lock()
3051      .unwrap_or_else(std::sync::PoisonError::into_inner);
3052    if self.draining {
3053      self.executor.draining.store(false, Ordering::Release);
3054      self.draining = false;
3055    }
3056    scheduler.active_host_turns = scheduler
3057      .active_host_turns
3058      .checked_sub(1)
3059      .expect("CurrentThread active host-turn count underflow");
3060    self.executor.scheduler_idle.notify_all();
3061  }
3062}
3063
3064/// Retains one admitted CurrentThread host callback in its runtime generation.
3065///
3066/// The host must keep this opaque lease alive until its native callback has
3067/// acknowledged or failed the delivery and destroyed every callback-owned
3068/// payload. The `Rc` marker prevents moving a TLS generation guard to another
3069/// thread before it restores the callback thread's prior runtime identity.
3070#[must_use = "the host callback lease must outlive delivery completion and callback payloads"]
3071pub struct CurrentThreadTaskCallbackLease {
3072  _host_turn: CurrentThreadHostTurn,
3073  _not_send: std::marker::PhantomData<std::rc::Rc<()>>,
3074}
3075
3076impl CurrentThreadTaskCallbackLease {
3077  fn new(host_turn: CurrentThreadHostTurn) -> Self {
3078    Self {
3079      _host_turn: host_turn,
3080      _not_send: std::marker::PhantomData,
3081    }
3082  }
3083}
3084
3085struct CurrentThreadDispatchCall<'a> {
3086  executor: &'a CurrentThreadExecutor,
3087  publication: Option<u64>,
3088  _generation: RuntimeGenerationGuard,
3089}
3090
3091impl CurrentThreadDispatchCall<'_> {
3092  fn finish_publication(&mut self, scheduler: &mut CurrentThreadSchedulerState) {
3093    let dispatch = self
3094      .publication
3095      .expect("the CurrentThread dispatch call must own a publication");
3096    let index = scheduler
3097      .dispatch_publications
3098      .iter()
3099      .position(|publication| publication.dispatch == dispatch)
3100      .expect("the CurrentThread dispatch publication must remain registered");
3101    scheduler.dispatch_publications.swap_remove(index);
3102    self.publication = None;
3103  }
3104}
3105
3106impl Drop for CurrentThreadDispatchCall<'_> {
3107  fn drop(&mut self) {
3108    let mut scheduler = self
3109      .executor
3110      .scheduler_idle_lock
3111      .lock()
3112      .unwrap_or_else(std::sync::PoisonError::into_inner);
3113    if let Some(dispatch) = self.publication.take()
3114      && let Some(index) = scheduler
3115        .dispatch_publications
3116        .iter()
3117        .position(|publication| publication.dispatch == dispatch)
3118    {
3119      scheduler.dispatch_publications.swap_remove(index);
3120    }
3121    scheduler.active_dispatch_calls = scheduler
3122      .active_dispatch_calls
3123      .checked_sub(1)
3124      .expect("CurrentThread active dispatch-call count underflow");
3125    self.executor.scheduler_idle.notify_all();
3126  }
3127}
3128
3129thread_local! {
3130  /// Depth of the `CurrentThreadExecutor::schedule` frames on this thread that
3131  /// a waker could have entered. `async-task` runs its scheduler callback for
3132  /// two different reasons: a wake, which any `Waker` can raise from any stack,
3133  /// and the very first schedule of a freshly submitted task, which `spawn`
3134  /// makes on its caller's own stack. Only the wake can arrive while a foreign
3135  /// mutex is held (`futures::Shared` wakes its awaiters under its `wakers`
3136  /// lock), so only `schedule` enters this scope; `schedule_submission` does
3137  /// not. A nonzero depth therefore marks a stack that may be a waker's, and
3138  /// nothing that destroys user futures inline may run on it: their destructors
3139  /// can re-lock that same mutex.
3140  static CURRENT_THREAD_SCHEDULE_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
3141}
3142
3143/// RAII marker for one waker-reachable `CurrentThreadExecutor::schedule` frame;
3144/// see `CURRENT_THREAD_SCHEDULE_DEPTH`.
3145struct CurrentThreadScheduleScope;
3146
3147impl CurrentThreadScheduleScope {
3148  fn enter() -> Self {
3149    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3150      depth.set(
3151        depth
3152          .get()
3153          .checked_add(1)
3154          .expect("CurrentThread schedule depth exhausted"),
3155      );
3156    });
3157    Self
3158  }
3159
3160  /// True while this thread is inside `schedule`: the stack may be a waker's.
3161  fn is_active() -> bool {
3162    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| depth.get() != 0)
3163  }
3164}
3165
3166impl Drop for CurrentThreadScheduleScope {
3167  fn drop(&mut self) {
3168    CURRENT_THREAD_SCHEDULE_DEPTH.with(|depth| {
3169      depth.set(
3170        depth
3171          .get()
3172          .checked_sub(1)
3173          .expect("CurrentThread schedule depth underflow"),
3174      );
3175    });
3176  }
3177}
3178
3179thread_local! {
3180  /// Set while this thread is inside the retirement publication a waker-stack
3181  /// terminal failure issues for itself; see
3182  /// `CurrentThreadExecutor::request_rejected_work_retirement`. That
3183  /// publication calls the host driver again, so a runnable another thread
3184  /// queues meanwhile can drive a nested terminal transition on this same
3185  /// stack. Without this flag the nested transition would publish again, and a
3186  /// producer that keeps queueing work could recurse the stack without bound.
3187  ///
3188  /// It is thread-local, not per-executor: the bound it enforces is a stack
3189  /// bound. A terminal transition on ANOTHER thread finds this flag clear and
3190  /// keeps publishing its own continuation, which is what it should do -- its
3191  /// stack is its own.
3192  ///
3193  /// A nested transition records nothing when it returns. What it owes is
3194  /// executor state, not a message: it moved work into `queue.rejected` and
3195  /// consumed the dispatch that was outstanding, and the owning frame re-reads
3196  /// exactly that (`needs_rejected_work_retirement_dispatch`) before each of
3197  /// its turns. A separate "requested" flag used to carry it and could be
3198  /// discarded -- by a nested call whose request the owner never took, and
3199  /// then by the owner itself when the attempt budget ran out under it.
3200  static CURRENT_THREAD_RETIRING_REJECTED_WORK: std::cell::Cell<bool> =
3201    const { std::cell::Cell::new(false) };
3202}
3203
3204/// RAII marker for the frame that owns this thread's retirement publication;
3205/// see `CURRENT_THREAD_RETIRING_REJECTED_WORK`.
3206struct CurrentThreadRejectedWorkRetirementScope;
3207
3208impl CurrentThreadRejectedWorkRetirementScope {
3209  /// `None` when this thread is already publishing a retirement turn. The
3210  /// caller returns without publishing, and loses nothing by it: what it owes
3211  /// is the executor state the owning frame re-reads before its next turn.
3212  fn enter() -> Option<Self> {
3213    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| {
3214      if active.replace(true) {
3215        // The owning frame keeps the flag. Do not write this as `then_some`:
3216        // its argument is eager, so the guard would be constructed here and
3217        // its destructor would clear the owner's flag on the way out.
3218        return None;
3219      }
3220      Some(Self)
3221    })
3222  }
3223}
3224
3225impl Drop for CurrentThreadRejectedWorkRetirementScope {
3226  fn drop(&mut self) {
3227    CURRENT_THREAD_RETIRING_REJECTED_WORK.with(|active| active.set(false));
3228  }
3229}
3230
3231struct CurrentThreadDispatchCancellationGuard<'a>(&'a CurrentThreadExecutor);
3232
3233impl Drop for CurrentThreadDispatchCancellationGuard<'_> {
3234  fn drop(&mut self) {
3235    self
3236      .0
3237      .cancelling_failed_dispatch
3238      .store(false, Ordering::Release);
3239  }
3240}
3241
3242enum CurrentThreadDispatchFailureAction<'a> {
3243  None,
3244  Cancel(CurrentThreadDispatchCancellationGuard<'a>),
3245  Publish {
3246    dispatch: u64,
3247    call: CurrentThreadDispatchCall<'a>,
3248  },
3249}
3250
3251struct CurrentThreadDispatchFailureRecovery<'a> {
3252  action: CurrentThreadDispatchFailureAction<'a>,
3253  _registry_cancellation: CurrentThreadRegistryCancellationGuard<'a>,
3254}
3255
3256struct CurrentThreadRegistryCancellationGuard<'a> {
3257  registry: &'a CurrentThreadTaskDriverRegistry,
3258  failure: Option<CurrentThreadTaskDispatchFailure>,
3259}
3260
3261impl CurrentThreadRegistryCancellationGuard<'_> {
3262  fn new(
3263    registry: &CurrentThreadTaskDriverRegistry,
3264    failure: CurrentThreadTaskDispatchFailure,
3265  ) -> CurrentThreadRegistryCancellationGuard<'_> {
3266    CurrentThreadRegistryCancellationGuard {
3267      registry,
3268      failure: Some(failure),
3269    }
3270  }
3271}
3272
3273impl Drop for CurrentThreadRegistryCancellationGuard<'_> {
3274  fn drop(&mut self) {
3275    if let Some(failure) = self.failure.take() {
3276      let _ = catch_unwind_contained(|| self.registry.finish_cancellation(failure));
3277    }
3278  }
3279}
3280
3281struct CurrentThreadBlockingGuard<'a> {
3282  executor: &'a CurrentThreadExecutor,
3283  owner: BlockingOwnerToken,
3284  previous_owner: Option<BlockingOwnerToken>,
3285  owns_lane: bool,
3286}
3287
3288impl CurrentThreadBlockingGuard<'_> {
3289  fn counts_active_lane(&self) -> bool {
3290    self.owns_lane
3291  }
3292}
3293
3294impl Drop for CurrentThreadBlockingGuard<'_> {
3295  fn drop(&mut self) {
3296    BLOCKING_OWNER.with(|current| current.set(self.previous_owner));
3297    if !self.owns_lane {
3298      return;
3299    }
3300    let mut state = self
3301      .executor
3302      .blocking_admission
3303      .state
3304      .lock()
3305      .unwrap_or_else(std::sync::PoisonError::into_inner);
3306    debug_assert_eq!(state.owner, Some(self.owner));
3307    state.owner = None;
3308    drop(state);
3309    self.executor.stop.wake_all();
3310  }
3311}
3312
3313impl CurrentThreadExecutor {
3314  /// Bound one host callback so a self-waking task cannot monopolize the
3315  /// JavaScript event loop. Remaining work is continued in a fresh host turn.
3316  const HOST_TURN_RUNNABLE_BUDGET: usize = 64;
3317  /// Force one blocking turn after this many consecutive runnable polls.
3318  #[cfg(not(target_family = "wasm"))]
3319  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
3320  /// How many retirement turns one waker-stack terminal failure may take --
3321  /// the turns another thread's submissions can inflate. The one offer nothing
3322  /// can inflate follows them; see `request_rejected_work_retirement`.
3323  const REJECTED_WORK_RETIREMENT_ATTEMPTS: usize = 8;
3324
3325  /// Build-default construction, kept for the pre-existing unit tests
3326  /// (production goes through [`Self::with_detection`] in
3327  /// `RuntimeBackend::new`, which also resolves the env deadline).
3328  #[cfg(test)]
3329  fn new(metrics: Arc<RuntimeMetrics>) -> Self {
3330    Self::with_detection(metrics, THREADLESS_BUILD, None)
3331  }
3332
3333  /// Full constructor for the deadlock-detection knobs: `new` fixes
3334  /// `threadless` to the build's [`THREADLESS_BUILD`] with the deadline off;
3335  /// `RuntimeBackend::new` resolves the deadline from
3336  /// `RuntimeOptions::park_deadline` / [`PARK_DEADLINE_ENV`]; tests inject a
3337  /// native `threadless` stand-in and short deadlines through it.
3338  #[cfg(test)]
3339  fn with_detection(
3340    metrics: Arc<RuntimeMetrics>,
3341    threadless: bool,
3342    park_deadline: Option<Duration>,
3343  ) -> Self {
3344    Self::with_generation(
3345      metrics,
3346      threadless,
3347      park_deadline,
3348      u64::MAX,
3349      Arc::new(GenerationStop::default()),
3350    )
3351  }
3352
3353  fn with_generation(
3354    metrics: Arc<RuntimeMetrics>,
3355    threadless: bool,
3356    park_deadline: Option<Duration>,
3357    generation: u64,
3358    stop: Arc<GenerationStop>,
3359  ) -> Self {
3360    Self {
3361      id: next_unique_id(
3362        &NEXT_RUNTIME_EXECUTOR_ID,
3363        "async runtime executor id space exhausted",
3364      ),
3365      generation,
3366      stop,
3367      queue: Mutex::new(CurrentThreadQueue {
3368        closed: false,
3369        runnables: VecDeque::new(),
3370        rejected: Vec::new(),
3371        #[cfg(not(target_family = "wasm"))]
3372        rejected_blocking: Vec::new(),
3373      }),
3374      blocking_admission: CurrentThreadBlockingAdmission {
3375        state: Mutex::new(CurrentThreadBlockingAdmissionState {
3376          owner: None,
3377          closed: false,
3378          #[cfg(not(target_family = "wasm"))]
3379          queue: BlockingQueue {
3380            closed: false,
3381            head: None,
3382            tail: None,
3383            jobs: FxHashMap::default(),
3384          },
3385        }),
3386      },
3387      draining: AtomicBool::new(false),
3388      scheduler_idle_lock: Mutex::new(CurrentThreadSchedulerState::default()),
3389      scheduler_idle: Condvar::new(),
3390      dispatch_pending: AtomicU64::new(0),
3391      recovery_dispatch: AtomicU64::new(0),
3392      cancelling_failed_dispatch: AtomicBool::new(false),
3393      task_dispatch: dispatch_current_thread_tasks,
3394      task_drivers: &CURRENT_THREAD_TASK_DRIVERS,
3395      metrics,
3396      threadless,
3397      park_deadline,
3398      host_timers: Arc::new(HostTimerRegistry::default()),
3399      generation_work: ArcSwapOption::empty(),
3400    }
3401  }
3402
3403  fn attach_generation_work(&self, work: &Arc<GenerationWork>) {
3404    self.generation_work.store(Some(Arc::clone(work)));
3405  }
3406
3407  /// Whether `queue.rejected` (or `queue.rejected_blocking`) holds work the
3408  /// executor still owes a cancellation.
3409  fn has_rejected_work(&self) -> bool {
3410    let queue = self
3411      .queue
3412      .lock()
3413      .unwrap_or_else(std::sync::PoisonError::into_inner);
3414    #[cfg(not(target_family = "wasm"))]
3415    let has_rejected_blocking = !queue.rejected_blocking.is_empty();
3416    #[cfg(target_family = "wasm")]
3417    let has_rejected_blocking = false;
3418    !queue.rejected.is_empty() || has_rejected_blocking
3419  }
3420
3421  /// Cancel every rejected runnable and blocking job. Runs on the caller's
3422  /// stack with no executor lock held; the caller must not be inside a waker
3423  /// invocation (see `CurrentThreadQueue::rejected`). Dropping a runnable can
3424  /// wake a parked task whose schedule is rejected again (the queue closed,
3425  /// or a nested terminal transition moved it here), so drain until nothing
3426  /// remains. This terminates: every drop closes one task for good, so the
3427  /// closed-queue rejections are bounded by the live tasks, and a nested
3428  /// transition needs two more host-failed deliveries of its own.
3429  fn retire_rejected_work(&self) {
3430    if !self.has_rejected_work() {
3431      return;
3432    }
3433    let _generation = RuntimeGenerationGuard::enter(self.generation);
3434    loop {
3435      #[cfg(not(target_family = "wasm"))]
3436      let (rejected, rejected_blocking) = {
3437        let mut queue = self
3438          .queue
3439          .lock()
3440          .unwrap_or_else(std::sync::PoisonError::into_inner);
3441        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
3442          return;
3443        }
3444        (
3445          std::mem::take(&mut queue.rejected),
3446          std::mem::take(&mut queue.rejected_blocking),
3447        )
3448      };
3449      #[cfg(target_family = "wasm")]
3450      let rejected = {
3451        let mut queue = self
3452          .queue
3453          .lock()
3454          .unwrap_or_else(std::sync::PoisonError::into_inner);
3455        if queue.rejected.is_empty() {
3456          return;
3457        }
3458        std::mem::take(&mut queue.rejected)
3459      };
3460      for runnable in rejected {
3461        let _ = catch_unwind_contained(|| drop(runnable));
3462      }
3463      #[cfg(not(target_family = "wasm"))]
3464      for job in rejected_blocking {
3465        let _ = catch_unwind_contained(|| drop(job));
3466      }
3467    }
3468  }
3469
3470  /// Host-side entry points (a host turn, delivery completion, driver
3471  /// registration or unregistration, drain requests) retire rejected work
3472  /// here. They are clean stacks unless the host called back synchronously
3473  /// from inside a publication that a waker's `schedule` started; that case
3474  /// leaves the work for the next entry.
3475  fn retire_rejected_work_off_waker_stack(&self) {
3476    if CurrentThreadScheduleScope::is_active() {
3477      return;
3478    }
3479    self.retire_rejected_work();
3480  }
3481
3482  /// Whether the executor still owes its rejected work a continuation: work is
3483  /// parked in `queue.rejected` (or `queue.rejected_blocking`) and no host
3484  /// dispatch is outstanding to bring the clean stack that retires it.
3485  ///
3486  /// This is the whole state a retirement turn acts on. Nothing else needs to
3487  /// be remembered across a turn, on this thread or on the executor: a nested
3488  /// terminal transition that ran inside a turn's publication changed exactly
3489  /// these two things (it moved more work aside and consumed the dispatch),
3490  /// so re-reading them is the same as being told.
3491  fn needs_rejected_work_retirement_dispatch(&self) -> bool {
3492    self.dispatch_pending.load(Ordering::Acquire) == 0 && self.has_rejected_work()
3493  }
3494
3495  /// The continuation a terminal failure reached on a waker's stack owes
3496  /// itself.
3497  ///
3498  /// That branch of `cancel_work_after_host_dispatch_failure` may not destroy
3499  /// anything where it stands, so it parks the work in `queue.rejected` and
3500  /// reopens. Reopening alone is not a continuation: the transition consumed
3501  /// the executor's one outstanding host request (`dispatch_pending` is 0) and
3502  /// left the queue empty, so `has_serviceable_work` is false and nothing --
3503  /// not `request_drain_if_work_remains`, not a wake of an already-parked task
3504  /// -- publishes again. A wake that loses both of its publications therefore
3505  /// produced no host turn at all, and the parked runnables kept their
3506  /// `GenerationWorkGuard`s and their `JoinHandle`s forever.
3507  ///
3508  /// A host turn is the clean stack that retires them: `drive_admitted_host_turn`
3509  /// calls `retire_rejected_work_off_waker_stack` before it polls anything. So
3510  /// ask for one, and keep asking while the host refuses and the work is still
3511  /// owed a stack -- the host is racing its own teardown, not necessarily dead,
3512  /// and a refusal says nothing about the driver that answers next (a second
3513  /// registered host, or the same one once its delivery backlog clears).
3514  ///
3515  /// What may be published on this stack is bounded, in two different ways,
3516  /// because there are two different things to bound.
3517  ///
3518  /// * `REJECTED_WORK_RETIREMENT_ATTEMPTS` bounds the turns a PRODUCER can
3519  ///   inflate. Each turn calls the driver again, so another thread can queue
3520  ///   a runnable while the publication is in flight; the host can then fail
3521  ///   that delivery and the replacement it reserves for the new work, and a
3522  ///   second terminal transition runs nested on this stack. It may not
3523  ///   publish where it stands -- that is what would recurse without bound --
3524  ///   so `CURRENT_THREAD_RETIRING_REJECTED_WORK` sends it back, and this
3525  ///   frame takes its next turn instead. Every such turn is two more host
3526  ///   callbacks and two more producer submissions, and the producer's own
3527  ///   drain request coalesces behind the publication already in flight, so it
3528  ///   never runs dry: measured without a budget, 32768 submissions produced
3529  ///   32769 deliveries inside one wake, and a producer left running for 400ms
3530  ///   held this single `schedule` callback -- and the `futures::Shared`
3531  ///   `wakers` mutex under it -- for 432ms. Eight turns cap that at 16 host
3532  ///   callbacks, a small constant to run under a foreign lock; two turns are
3533  ///   all the known interleaving needs.
3534  /// * `publish_final_rejected_work_retirement` bounds nothing, because
3535  ///   nothing can inflate it. It is the one delivery this stack offers with
3536  ///   the terminal-cancellation claim held, so no producer can interleave
3537  ///   with it and no nested transition can consume it. It is what the budget
3538  ///   may not be allowed to swallow: a producer that queues its last runnable
3539  ///   inside the eighth turn spends the budget and then goes quiescent, and
3540  ///   without this offer a host that would have accepted the very next
3541  ///   delivery is never asked, leaving the `JoinHandle`s pending and their
3542  ///   `GenerationWorkGuard`s held until unrelated work or shutdown arrives.
3543  ///
3544  /// * One ordinary publication follows an ACCEPTED final offer whose dispatch
3545  ///   the host consumed on this stack. A driver may claim, drive and
3546  ///   acknowledge the delivery inside `dispatch`, and that turn can do
3547  ///   nothing: it is on this waker's stack, so
3548  ///   `retire_rejected_work_off_waker_stack` returns at nonzero depth, and the
3549  ///   claim is still held, so `take_runnable` returns `None`. All it did was
3550  ///   take the continuation. The state it leaves -- work parked, no dispatch
3551  ///   outstanding -- is the state a REFUSED offer leaves, so it is owed the
3552  ///   same thing, and the same condition
3553  ///   (`needs_rejected_work_retirement_dispatch`) says so. The executor is
3554  ///   open again by then, which is what `5857092f` reopened it for, so this
3555  ///   takes the ordinary path. It is one publication and not a loop: a
3556  ///   terminal transition nested inside it finds
3557  ///   `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and publishes
3558  ///   nothing, and this frame is returning.
3559  ///
3560  /// So the turns stop at the first of: a dispatch outstanding (a delivery the
3561  /// host accepted, or one it failed and replaced, is the continuation --
3562  /// `drive_admitted_host_turn` retires on it), nothing left owed, no live
3563  /// host, and the budget -- and the budget is followed by the final offer,
3564  /// which is followed by one ordinary publication if the host took it without
3565  /// leaving the work a stack.
3566  ///
3567  /// Nothing is destroyed here: this adds no `retire_rejected_work` call. A
3568  /// nested transition inside any of these publications still sees the outer
3569  /// `CurrentThreadScheduleScope` and takes the waker branch, and the
3570  /// `complete_current_thread_task_delivery` the driver runs there reaches only
3571  /// `retire_rejected_work_off_waker_stack`, which returns at nonzero depth.
3572  ///
3573  /// When the host refuses the final offer too -- or consumes it and then the
3574  /// publication that follows it, never leaving this waker's stack -- that is
3575  /// terminal by construction and honest: the work waits for the next host that
3576  /// registers
3577  /// (`request_drain_if_queued` retires before it publishes) or for shutdown
3578  /// (`begin_shutdown`, `wait_until_idle_retiring`, `wait_until_scheduler_idle`,
3579  /// `Drop`). With no host that accepts there is no stack on which user futures
3580  /// may be destroyed. Handing them to a helper thread was rejected --
3581  /// threadless wasm has none, and host-affine futures must be destroyed on a
3582  /// host or shutdown thread.
3583  ///
3584  /// Rejected alternatives: a time budget -- it leaves the number of host
3585  /// callbacks open, pins nothing an assertion can check, and reads a clock on
3586  /// a stack meant to do as little as possible; yielding the thread -- it does
3587  /// not release the producer's mutex, which is the thing being held; parking
3588  /// the leftover need on the executor for the next depth-0 entry -- those
3589  /// entries already retire `queue.rejected` unconditionally, and in the
3590  /// interleaving that needs it there is no next entry at all, which is why
3591  /// the continuation has to be a delivery; and making EVERY turn a claimed
3592  /// one like the final offer -- it is uninflatable for the same reason it
3593  /// cancels a runnable that races it, and paying that for the ordinary turns
3594  /// would undo what `5857092f` reopened the executor for.
3595  fn request_rejected_work_retirement(self: &Arc<Self>) {
3596    if !self.has_rejected_work() {
3597      return;
3598    }
3599    let Some(_retirement) = CurrentThreadRejectedWorkRetirementScope::enter() else {
3600      // Nested inside this thread's own retirement publication. Publishing
3601      // here is what would recurse the stack; the frame that owns the
3602      // publication takes the next turn, and reads what this one moved aside
3603      // off the executor rather than off a flag.
3604      return;
3605    };
3606    for _ in 0..Self::REJECTED_WORK_RETIREMENT_ATTEMPTS {
3607      if !self.needs_rejected_work_retirement_dispatch() {
3608        return;
3609      }
3610      if matches!(
3611        self.request_drain_with(next_current_thread_dispatch_id),
3612        CurrentThreadDispatchRequest::Unavailable
3613      ) {
3614        // Stopping, or no live driver: there is no host to offer anything to.
3615        return;
3616      }
3617    }
3618    if self.publish_final_rejected_work_retirement()
3619      && self.needs_rejected_work_retirement_dispatch()
3620    {
3621      // The host ACCEPTED the offer and consumed its dispatch without leaving
3622      // the work a stack: a turn it claims and drives inside `dispatch` runs
3623      // on this same waker's stack, where `retire_rejected_work_off_waker_stack`
3624      // returns at nonzero depth, and it runs with the claim held, where
3625      // `take_runnable` returns `None`. Such a turn can do nothing at all; all
3626      // it did was take the continuation. The state it left is the state a
3627      // refused offer leaves, and the invariant is the same, so it is owed the
3628      // same thing: work is parked and no dispatch is outstanding, so publish
3629      // one.
3630      //
3631      // This is the ordinary publication path -- the executor is open again,
3632      // which is what `5857092f` reopened it for -- and it is made once, not
3633      // looped. It cannot recurse: a terminal transition nested inside it
3634      // finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held by this frame and
3635      // publishes nothing, and this frame has already returned its budget. A
3636      // host that consumes this one on the waker's stack too has never left
3637      // that stack, which is the limit already documented above: with no host
3638      // turn on a clean stack there is nowhere to destroy a user future.
3639      let _ = self.request_drain_with(next_current_thread_dispatch_id);
3640    }
3641  }
3642
3643  /// The one delivery this stack offers that a producer cannot inflate, made
3644  /// after `REJECTED_WORK_RETIREMENT_ATTEMPTS` turns were spent without
3645  /// leaving a dispatch outstanding.
3646  ///
3647  /// It holds the terminal-cancellation claim across the host callback. That
3648  /// claim is what makes it terminal rather than the ninth of an unbounded
3649  /// series: `schedule` consults it under the queue lock and parks a racing
3650  /// runnable in `queue.rejected` instead of queueing it, so no serviceable
3651  /// work can appear during the publication, and
3652  /// `begin_host_dispatch_failure_transition_locked` returns `None` while it is
3653  /// held, so a failure of this delivery reserves no replacement and reaches no
3654  /// terminal transition. One host callback, no recursion, no budget needed.
3655  ///
3656  /// A runnable that does race it is cancelled with the work already parked
3657  /// rather than queued. That is the same outcome the nested terminal
3658  /// transitions in the turns above give it, and this is reached only after
3659  /// the host has refused every one of them.
3660  ///
3661  /// Returns whether the host ACCEPTED the offer. An accepted offer is not by
3662  /// itself a continuation: a driver may claim, drive and acknowledge the
3663  /// delivery inside `dispatch`, and that turn runs on this waker's stack with
3664  /// the claim held, so it can neither retire (`retire_rejected_work_off_waker_stack`
3665  /// returns at nonzero depth) nor drain (`take_runnable` returns `None` while
3666  /// the claim is held) -- it only consumes `dispatch_pending`. The caller
3667  /// re-reads `needs_rejected_work_retirement_dispatch` and publishes once more
3668  /// when that happened.
3669  fn publish_final_rejected_work_retirement(self: &Arc<Self>) -> bool {
3670    if !self.needs_rejected_work_retirement_dispatch() {
3671      return false;
3672    }
3673    let Some(cancelling) = self.begin_failed_dispatch_cancellation() else {
3674      // Another terminal transition owns the executor; it carries its own
3675      // continuation.
3676      return false;
3677    };
3678    let dispatch = next_current_thread_dispatch_id();
3679    let dispatch_call = {
3680      let mut scheduler = self.lock_scheduler_for_publication();
3681      self.dispatch_pending.store(dispatch, Ordering::Release);
3682      self
3683        .begin_host_dispatch_publication(&mut scheduler, dispatch)
3684        .expect("a fresh CurrentThread retirement dispatch must own its publication")
3685    };
3686    let result = self.publish_host_dispatch(dispatch, dispatch_call);
3687    {
3688      // Reopen while holding the queue lock, the way the transition does:
3689      // `schedule` consults the flag under it, so every runnable parked during
3690      // the publication is already in `queue.rejected` when the executor opens.
3691      let _queue = self
3692        .queue
3693        .lock()
3694        .unwrap_or_else(std::sync::PoisonError::into_inner);
3695      drop(cancelling);
3696    }
3697    // Those parked runnables carry their `GenerationWorkGuard`s and were
3698    // pushed without notifying; a waiting shutdown retires them on its stack.
3699    if let Some(work) = self.generation_work.load_full() {
3700      work.notify_idle_waiters();
3701    }
3702    result == CurrentThreadHostDispatchResult::Accepted
3703  }
3704
3705  fn fresh_owner_token(&self) -> BlockingOwnerToken {
3706    BlockingOwnerToken {
3707      executor_id: self.id,
3708      frame: next_unique_id(
3709        &NEXT_BLOCKING_FRAME,
3710        "blocking owner frame id space exhausted",
3711      ),
3712    }
3713  }
3714
3715  fn enter_blocking_owner(
3716    &self,
3717    owner: BlockingOwnerToken,
3718    owns_lane: bool,
3719  ) -> CurrentThreadBlockingGuard<'_> {
3720    let previous_owner = BLOCKING_OWNER.with(|current| current.replace(Some(owner)));
3721    CurrentThreadBlockingGuard {
3722      executor: self,
3723      owner,
3724      previous_owner,
3725      owns_lane,
3726    }
3727  }
3728
3729  fn schedule_blocking<F, T>(
3730    self: &Arc<Self>,
3731    function: F,
3732    registration: GenerationWorkGuard,
3733  ) -> JoinHandle<T>
3734  where
3735    F: FnOnce() -> T + Send + 'static,
3736    T: Send + 'static,
3737  {
3738    // See the implementation notes in this crate's README.
3739    self.metrics.blocking_scheduled();
3740    let mut function = Some(function);
3741    let mut registration = Some(registration);
3742    let ambient_owner = BLOCKING_OWNER
3743      .with(std::cell::Cell::get)
3744      .filter(|owner| owner.executor_id == self.id);
3745    let mut run_owner = None;
3746    let mut owns_lane = false;
3747    let mut rejected = false;
3748
3749    #[cfg(not(target_family = "wasm"))]
3750    let (sender, receiver) = oneshot::channel();
3751    #[cfg(not(target_family = "wasm"))]
3752    let dependency = BlockingDependency {
3753      job: BlockingJobId(next_unique_id(
3754        &NEXT_BLOCKING_JOB_ID,
3755        "blocking job id space exhausted",
3756      )),
3757      owner: ambient_owner,
3758    };
3759    #[cfg(not(target_family = "wasm"))]
3760    let mut queued = false;
3761
3762    {
3763      let mut state = self
3764        .blocking_admission
3765        .state
3766        .lock()
3767        .unwrap_or_else(std::sync::PoisonError::into_inner);
3768      #[cfg(all(test, not(target_family = "wasm")))]
3769      run_current_thread_blocking_claim_test_hook();
3770      // Shutdown publishes generation stop before taking this mutex. Recheck
3771      // it while admission is serialized so work cannot claim or queue for the
3772      // CurrentThread lane after stop publication.
3773      if self.stop.is_stopping()
3774        || state.closed
3775        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3776      {
3777        rejected = true;
3778      } else if ambient_owner.is_some() && state.owner == ambient_owner {
3779        run_owner = ambient_owner;
3780      } else {
3781        #[cfg(not(target_family = "wasm"))]
3782        let queue_empty = state.queue.is_empty();
3783        #[cfg(target_family = "wasm")]
3784        let queue_empty = true;
3785
3786        if state.owner.is_none() && queue_empty {
3787          let owner = self.fresh_owner_token();
3788          state.owner = Some(owner);
3789          run_owner = Some(owner);
3790          owns_lane = true;
3791        } else {
3792          #[cfg(not(target_family = "wasm"))]
3793          {
3794            let function = function
3795              .take()
3796              .expect("blocking function must be available");
3797            let registration = registration
3798              .take()
3799              .expect("blocking registration must be available");
3800            let generation = self.generation;
3801            let function = RegisteredBlockingFunction::new(function, registration, generation);
3802            state.queue.push(QueuedBlocking {
3803              id: dependency.job,
3804              run: Box::new(move || {
3805                let _generation = RuntimeGenerationGuard::enter(generation);
3806                let result = function
3807                  .run()
3808                  .map(|output| ContainedTaskOutput::new(output, generation));
3809                let _ = sender.send(result);
3810              }),
3811            });
3812            queued = true;
3813          }
3814          #[cfg(target_family = "wasm")]
3815          {
3816            // A threadless CurrentThread executor cannot reach unrelated
3817            // concurrent admission. Same-stack nesting carries `ambient_owner`
3818            // and takes the borrowing branch above.
3819            rejected = true;
3820          }
3821        }
3822      }
3823    }
3824
3825    #[cfg(not(target_family = "wasm"))]
3826    if queued {
3827      self.stop.wake_all();
3828      if self.has_serviceable_blocking_work() && !self.draining.load(Ordering::Acquire) {
3829        self.request_drain();
3830      }
3831      return JoinHandle(JoinHandleInner::Blocking {
3832        receiver,
3833        dependency,
3834        published: PublishedBlockingDependencies::default(),
3835      });
3836    }
3837
3838    if rejected {
3839      let _generation = RuntimeGenerationGuard::enter(self.generation);
3840      let _ = catch_unwind_contained(|| drop(function.take()));
3841      drop(registration.take());
3842      return JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
3843        message: "the async runtime stopped before the blocking task could start".to_string(),
3844      }))));
3845    }
3846
3847    let owner = run_owner.expect("accepted CurrentThread blocking work must own or borrow a lane");
3848    let blocking = self.enter_blocking_owner(owner, owns_lane);
3849    let _registration = registration
3850      .take()
3851      .expect("blocking registration must be available");
3852    let _generation = RuntimeGenerationGuard::enter(self.generation);
3853    let result = {
3854      let _task = self.metrics.blocking_started(blocking.counts_active_lane());
3855      catch_unwind_join_error(
3856        function
3857          .take()
3858          .expect("blocking function must be available"),
3859      )
3860    }
3861    .map(|output| ContainedTaskOutput::new(output, self.generation));
3862    drop(blocking);
3863    #[cfg(not(target_family = "wasm"))]
3864    if owns_lane && !self.draining.load(Ordering::Acquire) {
3865      // An admitted host turn already owns a bounded queue driver. Let its
3866      // outer loop service the released FIFO so blocking work cannot bypass
3867      // the host-turn budget and monopolize the JavaScript event loop.
3868      self.service_queued_blocking_work();
3869    }
3870    JoinHandle(JoinHandleInner::Ready(Some(result)))
3871  }
3872
3873  #[cfg(not(target_family = "wasm"))]
3874  fn take_queued_blocking(
3875    &self,
3876  ) -> Option<(
3877    Box<dyn FnOnce() + Send + 'static>,
3878    CurrentThreadBlockingGuard<'_>,
3879  )> {
3880    let (job, owner) = {
3881      let mut state = self
3882        .blocking_admission
3883        .state
3884        .lock()
3885        .unwrap_or_else(std::sync::PoisonError::into_inner);
3886      #[cfg(all(test, not(target_family = "wasm")))]
3887      run_current_thread_blocking_claim_test_hook();
3888      if self.stop.is_stopping()
3889        || state.closed
3890        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3891        || state.owner.is_some()
3892      {
3893        return None;
3894      }
3895      let job = state.queue.pop_front()?;
3896      let owner = self.fresh_owner_token();
3897      state.owner = Some(owner);
3898      (job, owner)
3899    };
3900    Some((job, self.enter_blocking_owner(owner, true)))
3901  }
3902
3903  #[cfg(not(target_family = "wasm"))]
3904  fn run_one_blocking(&self) -> bool {
3905    let Some((job, blocking)) = self.take_queued_blocking() else {
3906      return false;
3907    };
3908    let _generation = RuntimeGenerationGuard::enter(self.generation);
3909    {
3910      let _task = self.metrics.blocking_started(true);
3911      let _ = catch_unwind_contained(job);
3912    }
3913    drop(blocking);
3914    true
3915  }
3916
3917  #[cfg(not(target_family = "wasm"))]
3918  fn service_queued_blocking_work(&self) {
3919    while self.run_one_blocking() {}
3920  }
3921
3922  #[cfg(not(target_family = "wasm"))]
3923  fn has_serviceable_blocking_work(&self) -> bool {
3924    let state = self
3925      .blocking_admission
3926      .state
3927      .lock()
3928      .unwrap_or_else(std::sync::PoisonError::into_inner);
3929    !state.closed && state.owner.is_none() && !state.queue.is_empty()
3930  }
3931
3932  #[cfg(not(target_family = "wasm"))]
3933  fn try_run_owned_blocking(&self, dependency: &TaskDependency) -> bool {
3934    let Some(ambient_owner) = BLOCKING_OWNER
3935      .with(std::cell::Cell::get)
3936      .filter(|owner| owner.executor_id == self.id)
3937    else {
3938      return false;
3939    };
3940    let Some(current) = dependency.select_for_owner(ambient_owner) else {
3941      return false;
3942    };
3943    let job = {
3944      let mut state = self
3945        .blocking_admission
3946        .state
3947        .lock()
3948        .unwrap_or_else(std::sync::PoisonError::into_inner);
3949      #[cfg(all(test, not(target_family = "wasm")))]
3950      run_current_thread_blocking_claim_test_hook();
3951      if self.stop.is_stopping()
3952        || state.closed
3953        || self.cancelling_failed_dispatch.load(Ordering::Acquire)
3954        || state.owner != Some(ambient_owner)
3955      {
3956        return false;
3957      }
3958      if dependency.try_claim(&current) {
3959        state.queue.remove(current.publication.dependency.job)
3960      } else {
3961        None
3962      }
3963    };
3964    let Some(job) = job else {
3965      return false;
3966    };
3967    let _generation = RuntimeGenerationGuard::enter(self.generation);
3968    let _task = self.metrics.blocking_started(false);
3969    let _ = catch_unwind_contained(job);
3970    true
3971  }
3972
3973  fn close_blocking_admission(&self) {
3974    #[cfg(not(target_family = "wasm"))]
3975    let queued = {
3976      let mut state = self
3977        .blocking_admission
3978        .state
3979        .lock()
3980        .unwrap_or_else(std::sync::PoisonError::into_inner);
3981      state.closed = true;
3982      state.queue.closed = true;
3983      state.queue.take_all()
3984    };
3985    #[cfg(target_family = "wasm")]
3986    {
3987      let mut state = self
3988        .blocking_admission
3989        .state
3990        .lock()
3991        .unwrap_or_else(std::sync::PoisonError::into_inner);
3992      state.closed = true;
3993    }
3994    #[cfg(not(target_family = "wasm"))]
3995    for job in queued {
3996      let _ = catch_unwind_contained(|| drop(job));
3997    }
3998  }
3999
4000  #[cfg(test)]
4001  fn with_task_dispatch(
4002    metrics: Arc<RuntimeMetrics>,
4003    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4004  ) -> Self {
4005    Self::with_task_dispatch_registry(metrics, task_dispatch, &CURRENT_THREAD_TASK_DRIVERS)
4006  }
4007
4008  #[cfg(test)]
4009  fn with_task_dispatch_registry(
4010    metrics: Arc<RuntimeMetrics>,
4011    task_dispatch: fn(u64) -> CurrentThreadHostDispatchResult,
4012    task_drivers: &'static CurrentThreadTaskDriverRegistry,
4013  ) -> Self {
4014    let mut executor = Self::new(metrics);
4015    executor.task_dispatch = task_dispatch;
4016    executor.task_drivers = task_drivers;
4017    executor
4018  }
4019
4020  /// A wake, or any other schedule whose stack the executor does not own.
4021  /// `async-task` hands every `Waker::wake` to this callback, so the frame may
4022  /// belong to a producer that is holding its own mutex; see
4023  /// `CURRENT_THREAD_SCHEDULE_DEPTH`.
4024  fn schedule(self: &Arc<Self>, runnable: Runnable) {
4025    let _scope = CurrentThreadScheduleScope::enter();
4026    self.schedule_runnable(runnable);
4027  }
4028
4029  /// The first schedule of a freshly submitted task, made by the caller of
4030  /// `spawn` on its own stack. No waker is involved, so a terminal dispatch
4031  /// failure reached from here may cancel the rejected work inline -- and it
4032  /// must, because a submission that publishes no host delivery has no other
4033  /// continuation: nothing would ever reach `retire_rejected_work`, and the
4034  /// returned `JoinHandle` would stay pending with its generation guard held.
4035  fn schedule_submission(self: &Arc<Self>, runnable: Runnable) {
4036    self.schedule_runnable(runnable);
4037  }
4038
4039  fn schedule_runnable(self: &Arc<Self>, runnable: Runnable) {
4040    self.metrics.runnable_scheduled();
4041    {
4042      let mut queue = self
4043        .queue
4044        .lock()
4045        .unwrap_or_else(std::sync::PoisonError::into_inner);
4046      // Never drop a rejected runnable here: that destroys the future inline
4047      // on the waker's thread, and a waker can be invoked while its producer
4048      // holds a mutex that this future's destructor needs (`futures::Shared`
4049      // wakes under its `wakers` lock and its clone re-locks it on drop).
4050      if queue.closed {
4051        // A wake can be in flight when shutdown starts: `async-task` marks
4052        // the task SCHEDULED before it invokes this closure, so the abort
4053        // that `close_and_abort` issues coalesces with the in-flight wake and
4054        // `begin_shutdown` closes the queue without waiting for it to land.
4055        // The runnable still owns its `GenerationWorkGuard`; hand it to the
4056        // shutdown side and wake the generation's idle waiter to retire it.
4057        self
4058          .metrics
4059          .queued_runnables
4060          .fetch_sub(1, Ordering::Relaxed);
4061        queue.rejected.push(runnable);
4062        drop(queue);
4063        if let Some(work) = self.generation_work.load_full() {
4064          work.notify_idle_waiters();
4065        }
4066        return;
4067      }
4068      if self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4069        // A terminal failure transition is claimed but has not yet moved the
4070        // queue and reopened (it does both under this lock): the runnable
4071        // joins the work that transition cancels.
4072        self
4073          .metrics
4074          .queued_runnables
4075          .fetch_sub(1, Ordering::Relaxed);
4076        queue.rejected.push(runnable);
4077        return;
4078      }
4079      queue.runnables.push_back(runnable);
4080    }
4081    // Queue work and only then wake an explicit block_on driver. This is still
4082    // enqueue-only with respect to the wake caller: polling happens on the
4083    // driver's thread, never inline from an arbitrary waker invocation.
4084    self.stop.wake_all();
4085    if !self.draining.load(Ordering::Acquire) {
4086      self.request_drain();
4087    }
4088  }
4089
4090  /// Ask the host to enter a fresh JavaScript turn before polling queued work.
4091  ///
4092  /// A `Waker` may be invoked while its producer holds an internal mutex
4093  /// (`futures::Shared` does this). Polling inline from the scheduler callback
4094  /// can then re-enter that future and self-deadlock on the same mutex. Once a
4095  /// host dispatcher has accepted work, this executor stays host-driven for
4096  /// the rest of its generation; a temporarily missing env leaves work queued
4097  /// for the next registration or for shutdown cancellation.
4098  fn request_drain(self: &Arc<Self>) {
4099    let _ = self.request_drain_with(next_current_thread_dispatch_id);
4100  }
4101
4102  fn lock_scheduler_for_publication(
4103    &self,
4104  ) -> std::sync::MutexGuard<'_, CurrentThreadSchedulerState> {
4105    #[cfg(all(test, not(target_family = "wasm")))]
4106    match self.scheduler_idle_lock.try_lock() {
4107      Ok(scheduler) => scheduler,
4108      Err(std::sync::TryLockError::WouldBlock) => {
4109        run_current_thread_dispatch_publication_blocked_test_hook();
4110        self
4111          .scheduler_idle_lock
4112          .lock()
4113          .unwrap_or_else(std::sync::PoisonError::into_inner)
4114      }
4115      Err(std::sync::TryLockError::Poisoned(error)) => error.into_inner(),
4116    }
4117    #[cfg(any(not(test), target_family = "wasm"))]
4118    self
4119      .scheduler_idle_lock
4120      .lock()
4121      .unwrap_or_else(std::sync::PoisonError::into_inner)
4122  }
4123
4124  fn request_drain_with(
4125    self: &Arc<Self>,
4126    next_dispatch: impl FnOnce() -> u64,
4127  ) -> CurrentThreadDispatchRequest {
4128    let mut scheduler = self.lock_scheduler_for_publication();
4129    if self.stop.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4130      return CurrentThreadDispatchRequest::Unavailable;
4131    }
4132    if self.draining.load(Ordering::Acquire) || self.dispatch_pending.load(Ordering::Acquire) != 0 {
4133      return CurrentThreadDispatchRequest::Coalesced;
4134    }
4135    let dispatch = next_dispatch();
4136    if self
4137      .dispatch_pending
4138      .compare_exchange(0, dispatch, Ordering::AcqRel, Ordering::Acquire)
4139      .is_err()
4140    {
4141      return CurrentThreadDispatchRequest::Coalesced;
4142    }
4143    let dispatch_call = self
4144      .begin_host_dispatch_publication(&mut scheduler, dispatch)
4145      .expect("a fresh CurrentThread dispatch must own its publication");
4146    drop(scheduler);
4147    match self.publish_host_dispatch(dispatch, dispatch_call) {
4148      CurrentThreadHostDispatchResult::Accepted | CurrentThreadHostDispatchResult::Failed(_) => {
4149        CurrentThreadDispatchRequest::Accepted
4150      }
4151      CurrentThreadHostDispatchResult::Unavailable => CurrentThreadDispatchRequest::Unavailable,
4152    }
4153  }
4154
4155  fn claim_dispatch_call<'a>(
4156    &'a self,
4157    scheduler: &mut CurrentThreadSchedulerState,
4158  ) -> CurrentThreadDispatchCall<'a> {
4159    let generation = RuntimeGenerationGuard::enter(self.generation);
4160    let active_dispatch_calls = scheduler
4161      .active_dispatch_calls
4162      .checked_add(1)
4163      .expect("CurrentThread active dispatch-call count exhausted");
4164    scheduler.active_dispatch_calls = active_dispatch_calls;
4165    CurrentThreadDispatchCall {
4166      executor: self,
4167      publication: None,
4168      _generation: generation,
4169    }
4170  }
4171
4172  fn begin_host_dispatch_publication<'a>(
4173    &'a self,
4174    scheduler: &mut CurrentThreadSchedulerState,
4175    dispatch: u64,
4176  ) -> Option<CurrentThreadDispatchCall<'a>> {
4177    // See the implementation notes in this crate's README.
4178    if let Some(publication) = scheduler
4179      .dispatch_publications
4180      .iter_mut()
4181      .find(|publication| publication.dispatch == dispatch)
4182    {
4183      publication.republish_requested = true;
4184      return None;
4185    }
4186    let mut dispatch_call = self.claim_dispatch_call(scheduler);
4187    scheduler
4188      .dispatch_publications
4189      .push(CurrentThreadDispatchPublication {
4190        dispatch,
4191        republish_requested: false,
4192      });
4193    dispatch_call.publication = Some(dispatch);
4194    Some(dispatch_call)
4195  }
4196
4197  fn publish_host_dispatch(
4198    self: &Arc<Self>,
4199    dispatch: u64,
4200    mut dispatch_call: CurrentThreadDispatchCall<'_>,
4201  ) -> CurrentThreadHostDispatchResult {
4202    let mut panic_payload = RetainedPanicPayload::default();
4203    loop {
4204      let result = match catch_unwind(AssertUnwindSafe(|| {
4205        #[cfg(all(test, not(target_family = "wasm")))]
4206        run_current_thread_dispatch_call_test_hook();
4207        let result = (self.task_dispatch)(dispatch);
4208        #[cfg(all(test, not(target_family = "wasm")))]
4209        run_current_thread_dispatch_returned_test_hook();
4210        result
4211      })) {
4212        Ok(result) => Some(result),
4213        Err(payload) => {
4214          panic_payload.capture(payload);
4215          None
4216        }
4217      };
4218
4219      let step = catch_unwind(AssertUnwindSafe(|| {
4220        let (republish, cancelling, failure, result) = {
4221          let mut scheduler = self
4222            .scheduler_idle_lock
4223            .lock()
4224            .unwrap_or_else(std::sync::PoisonError::into_inner);
4225          let index = scheduler
4226            .dispatch_publications
4227            .iter()
4228            .position(|publication| publication.dispatch == dispatch)
4229            .expect("the CurrentThread dispatch publication must remain registered");
4230          let dispatch_remains_pending = self.dispatch_pending.load(Ordering::Acquire) == dispatch;
4231          let republish = dispatch_remains_pending
4232            && !self.stop.is_stopping()
4233            && !self.cancelling_failed_dispatch.load(Ordering::Acquire)
4234            && scheduler.dispatch_publications[index].republish_requested;
4235          if republish {
4236            if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4237              let _ = self.task_drivers.rearm_failed_publication(failure);
4238            }
4239            scheduler.dispatch_publications[index].republish_requested = false;
4240            (true, None, None, result)
4241          } else {
4242            dispatch_call.finish_publication(&mut scheduler);
4243            let failure = if let Some(CurrentThreadHostDispatchResult::Failed(failure)) = result {
4244              #[cfg(all(test, not(target_family = "wasm")))]
4245              run_current_thread_failed_publication_retired_test_hook();
4246              self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4247            } else {
4248              None
4249            };
4250            let cancelling = (result == Some(CurrentThreadHostDispatchResult::Unavailable)
4251              && dispatch_remains_pending)
4252              .then(|| self.retire_unavailable_host_dispatch_locked(dispatch))
4253              .flatten();
4254            let result = if result == Some(CurrentThreadHostDispatchResult::Unavailable)
4255              && !dispatch_remains_pending
4256            {
4257              // A synchronous callback may already have consumed, cancelled, or
4258              // superseded this exact capability.
4259              Some(CurrentThreadHostDispatchResult::Accepted)
4260            } else {
4261              result
4262            };
4263            (false, cancelling, failure, result)
4264          }
4265        };
4266
4267        if republish {
4268          return CurrentThreadDispatchPublicationStep::Republish;
4269        }
4270        if let Some(cancelling) = cancelling {
4271          self.cancel_work_after_host_dispatch_failure(cancelling);
4272        }
4273        if let Some(failure) = failure {
4274          self.run_host_dispatch_failure(failure.action);
4275        }
4276        CurrentThreadDispatchPublicationStep::Complete(result)
4277      }));
4278
4279      match step {
4280        Ok(CurrentThreadDispatchPublicationStep::Republish) => {}
4281        Ok(CurrentThreadDispatchPublicationStep::Complete(result)) => {
4282          if panic_payload.is_some() {
4283            panic_payload.resume();
4284          }
4285          return result
4286            .expect("a CurrentThread host dispatch without a result must resume its panic");
4287        }
4288        Err(payload) => {
4289          panic_payload.capture(payload);
4290          panic_payload.resume();
4291        }
4292      }
4293    }
4294  }
4295
4296  fn claim_host_turn(
4297    self: &Arc<Self>,
4298    scheduler: &mut CurrentThreadSchedulerState,
4299  ) -> CurrentThreadHostTurn {
4300    scheduler.active_host_turns = scheduler
4301      .active_host_turns
4302      .checked_add(1)
4303      .expect("CurrentThread active host-turn count exhausted");
4304    self.draining.store(true, Ordering::Release);
4305    CurrentThreadHostTurn {
4306      executor: Arc::clone(self),
4307      draining: true,
4308      _generation: RuntimeGenerationGuard::enter(self.generation),
4309    }
4310  }
4311
4312  fn try_admit_host_turn(self: &Arc<Self>, dispatch: u64) -> Option<CurrentThreadHostTurn> {
4313    let mut scheduler = self
4314      .scheduler_idle_lock
4315      .lock()
4316      .unwrap_or_else(std::sync::PoisonError::into_inner);
4317    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4318      return None;
4319    }
4320    if dispatch == 0
4321      || self
4322        .dispatch_pending
4323        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4324        .is_err()
4325    {
4326      return None;
4327    }
4328    #[cfg(all(test, not(target_family = "wasm")))]
4329    run_current_thread_dispatch_claim_test_hook();
4330    let _ =
4331      self
4332        .recovery_dispatch
4333        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire);
4334    Some(self.claim_host_turn(&mut scheduler))
4335  }
4336
4337  #[cfg(test)]
4338  fn try_admit_host_turn_without_dispatch(self: &Arc<Self>) -> Option<CurrentThreadHostTurn> {
4339    let mut scheduler = self
4340      .scheduler_idle_lock
4341      .lock()
4342      .unwrap_or_else(std::sync::PoisonError::into_inner);
4343    if self.stop.is_stopping() || self.draining.load(Ordering::Acquire) {
4344      return None;
4345    }
4346    Some(self.claim_host_turn(&mut scheduler))
4347  }
4348
4349  #[cfg(test)]
4350  fn drive_host_turn(self: &Arc<Self>) {
4351    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4352    let host_turn = if dispatch == 0 {
4353      self.try_admit_host_turn_without_dispatch()
4354    } else {
4355      self.try_admit_host_turn(dispatch)
4356    };
4357    if let Some(mut host_turn) = host_turn {
4358      host_turn.drive();
4359    }
4360  }
4361
4362  #[cfg(test)]
4363  fn drive_host_turn_for_dispatch(self: &Arc<Self>, dispatch: u64) {
4364    if let Some(mut host_turn) = self.try_admit_host_turn(dispatch) {
4365      host_turn.drive();
4366    }
4367  }
4368
4369  /// Drive a host turn whose scheduler role was claimed while the controller
4370  /// still held the lifecycle mutex. See
4371  /// the implementation notes in this crate's README.
4372  fn drive_admitted_host_turn(self: &Arc<Self>, host_turn: &mut CurrentThreadHostTurn) {
4373    // A host turn is the continuation a terminal failure on a waker's stack
4374    // relies on: cancel the work it moved aside before polling anything new.
4375    self.retire_rejected_work_off_waker_stack();
4376    #[cfg(not(target_family = "wasm"))]
4377    let mut runnable_streak = 0usize;
4378    for _ in 0..Self::HOST_TURN_RUNNABLE_BUDGET {
4379      #[cfg(not(target_family = "wasm"))]
4380      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4381        runnable_streak = 0;
4382        if self.run_one_blocking() {
4383          continue;
4384        }
4385      }
4386      if self.drain_one() {
4387        #[cfg(not(target_family = "wasm"))]
4388        {
4389          runnable_streak += 1;
4390        }
4391        continue;
4392      }
4393      #[cfg(not(target_family = "wasm"))]
4394      if self.run_one_blocking() {
4395        runnable_streak = 0;
4396        continue;
4397      }
4398      break;
4399    }
4400    // A bounded turn releases queue-drain exclusivity before publishing its
4401    // continuation, but remains an active host turn until that publication
4402    // returns. Shutdown waits for the latter lifecycle role, not merely the
4403    // former exclusivity bit.
4404    host_turn.release_draining();
4405    self.request_drain_if_work_remains();
4406  }
4407
4408  fn request_drain_if_queued(self: &Arc<Self>) {
4409    self.retire_rejected_work_off_waker_stack();
4410    if !self.has_serviceable_work() {
4411      return;
4412    }
4413    let mut scheduler = self.lock_scheduler_for_publication();
4414    if self.stop.is_stopping()
4415      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4416      || self.draining.load(Ordering::Acquire)
4417    {
4418      return;
4419    }
4420    let dispatch = self.dispatch_pending.load(Ordering::Acquire);
4421    if dispatch == 0 {
4422      drop(scheduler);
4423      self.request_drain_if_work_remains();
4424      return;
4425    }
4426
4427    // A newly registered host joins the existing all-host race through its own
4428    // delivery token. Reusing the executor capability preserves every other
4429    // host's accepted attempt and lets old registration tokens fail closed.
4430    let Some(dispatch_call) = self.begin_host_dispatch_publication(&mut scheduler, dispatch) else {
4431      return;
4432    };
4433    drop(scheduler);
4434    let _ = self.publish_host_dispatch(dispatch, dispatch_call);
4435  }
4436
4437  fn retire_unavailable_host_dispatch_locked(
4438    &self,
4439    dispatch: u64,
4440  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4441    if self
4442      .dispatch_pending
4443      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4444      .is_err()
4445    {
4446      return None;
4447    }
4448    if self
4449      .recovery_dispatch
4450      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4451      .is_ok()
4452    {
4453      self.begin_failed_dispatch_cancellation()
4454    } else {
4455      None
4456    }
4457  }
4458
4459  fn cancel_host_dispatch(self: &Arc<Self>, failure: CurrentThreadTaskDispatchFailure) {
4460    // See the implementation notes in this crate's README.
4461    let failure = {
4462      let mut scheduler = self
4463        .scheduler_idle_lock
4464        .lock()
4465        .unwrap_or_else(std::sync::PoisonError::into_inner);
4466      self.begin_host_dispatch_failure_locked(&mut scheduler, failure)
4467    };
4468    if let Some(failure) = failure {
4469      self.run_host_dispatch_failure(failure.action);
4470    }
4471    self.retire_rejected_work_off_waker_stack();
4472  }
4473
4474  fn begin_host_dispatch_failure_locked<'a>(
4475    &'a self,
4476    scheduler: &mut CurrentThreadSchedulerState,
4477    failure: CurrentThreadTaskDispatchFailure,
4478  ) -> Option<CurrentThreadDispatchFailureRecovery<'a>> {
4479    if !self.task_drivers.is_current_failure(failure) {
4480      return None;
4481    }
4482    let registry_cancellation =
4483      CurrentThreadRegistryCancellationGuard::new(self.task_drivers, failure);
4484    let action = self.begin_host_dispatch_failure_transition_locked(scheduler, failure.dispatch);
4485    Some(CurrentThreadDispatchFailureRecovery {
4486      action,
4487      _registry_cancellation: registry_cancellation,
4488    })
4489  }
4490
4491  fn begin_host_dispatch_failure_transition_locked<'a>(
4492    &'a self,
4493    scheduler: &mut CurrentThreadSchedulerState,
4494    dispatch: u64,
4495  ) -> CurrentThreadDispatchFailureAction<'a> {
4496    if dispatch == 0
4497      || self
4498        .dispatch_pending
4499        .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4500        .is_err()
4501    {
4502      return CurrentThreadDispatchFailureAction::None;
4503    }
4504    #[cfg(all(test, not(target_family = "wasm")))]
4505    run_current_thread_dispatch_recovery_test_hook();
4506
4507    if self
4508      .recovery_dispatch
4509      .compare_exchange(dispatch, 0, Ordering::AcqRel, Ordering::Acquire)
4510      .is_ok()
4511    {
4512      // The one fresh-turn replacement failed too. Polling inline here would
4513      // re-enter the host callback, while another retry could spin forever.
4514      // Claim terminal cancellation before allowing another publication.
4515      return self.begin_failed_dispatch_cancellation().map_or(
4516        CurrentThreadDispatchFailureAction::None,
4517        CurrentThreadDispatchFailureAction::Cancel,
4518      );
4519    }
4520
4521    if self.stop.is_stopping()
4522      || self.cancelling_failed_dispatch.load(Ordering::Acquire)
4523      || self.draining.load(Ordering::Acquire)
4524      || !self.has_serviceable_work()
4525    {
4526      return CurrentThreadDispatchFailureAction::None;
4527    }
4528
4529    // Consume the failed capability and reserve its sole replacement while
4530    // publication is excluded. A newly registered host can only join this
4531    // tagged replacement, never publish an untagged dispatch in between.
4532    let replacement = next_current_thread_dispatch_id();
4533    self.recovery_dispatch.store(replacement, Ordering::Release);
4534    self.dispatch_pending.store(replacement, Ordering::Release);
4535    let dispatch_call = self
4536      .begin_host_dispatch_publication(scheduler, replacement)
4537      .expect("a reserved CurrentThread replacement must own its publication");
4538    CurrentThreadDispatchFailureAction::Publish {
4539      dispatch: replacement,
4540      call: dispatch_call,
4541    }
4542  }
4543
4544  #[cfg(test)]
4545  fn cancel_host_dispatch_for_test(self: &Arc<Self>, dispatch: u64) {
4546    let failure = {
4547      let mut scheduler = self
4548        .scheduler_idle_lock
4549        .lock()
4550        .unwrap_or_else(std::sync::PoisonError::into_inner);
4551      self.begin_host_dispatch_failure_transition_locked(&mut scheduler, dispatch)
4552    };
4553    self.run_host_dispatch_failure(failure);
4554  }
4555
4556  fn run_host_dispatch_failure(self: &Arc<Self>, failure: CurrentThreadDispatchFailureAction<'_>) {
4557    match failure {
4558      CurrentThreadDispatchFailureAction::None => {}
4559      CurrentThreadDispatchFailureAction::Cancel(cancelling) => {
4560        self.cancel_work_after_host_dispatch_failure(cancelling);
4561      }
4562      CurrentThreadDispatchFailureAction::Publish { dispatch, call } => {
4563        let _ = self.publish_host_dispatch(dispatch, call);
4564      }
4565    }
4566  }
4567
4568  fn has_serviceable_work(&self) -> bool {
4569    let has_queued_runnable = {
4570      let queue = self
4571        .queue
4572        .lock()
4573        .unwrap_or_else(std::sync::PoisonError::into_inner);
4574      !queue.closed && !queue.runnables.is_empty()
4575    };
4576    #[cfg(not(target_family = "wasm"))]
4577    let has_queued_blocking = self.has_serviceable_blocking_work();
4578    #[cfg(target_family = "wasm")]
4579    let has_queued_blocking = false;
4580    has_queued_runnable || has_queued_blocking
4581  }
4582
4583  fn request_drain_if_work_remains(self: &Arc<Self>) {
4584    if self.has_serviceable_work() && !self.draining.load(Ordering::Acquire) {
4585      self.request_drain();
4586    }
4587  }
4588
4589  fn begin_failed_dispatch_cancellation(
4590    &self,
4591  ) -> Option<CurrentThreadDispatchCancellationGuard<'_>> {
4592    if self
4593      .cancelling_failed_dispatch
4594      .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
4595      .is_err()
4596    {
4597      return None;
4598    }
4599    self.dispatch_pending.store(0, Ordering::Release);
4600    self.recovery_dispatch.store(0, Ordering::Release);
4601    Some(CurrentThreadDispatchCancellationGuard(self))
4602  }
4603
4604  /// Terminal failure transition. The work queued at this point (runnables
4605  /// and blocking jobs) is what the failure cancels. It is MOVED into the
4606  /// rejected lists under the queue lock, with its `queued_runnables` debit,
4607  /// and what happens next depends on the stack:
4608  ///
4609  /// * On a clean stack -- a host entry point, or the submission `spawn` made
4610  ///   on its caller's own stack -- the rejected work is dropped right here,
4611  ///   with the executor still closed so that the tasks those drops wake are
4612  ///   rejected and cancelled with it, and the executor reopens under the
4613  ///   queue lock once nothing remains. This is the observable behaviour
4614  ///   terminal cancellation always had: queued work and everything it wakes
4615  ///   settle at once, and no later host work is published. A submission whose
4616  ///   two publications both failed has no other continuation -- it produced no
4617  ///   host turn -- so cancelling here is what keeps its `JoinHandle` from
4618  ///   staying pending forever with its generation guard held.
4619  /// * On a waker's stack (reached through `schedule`, whose publication the
4620  ///   host failed synchronously) nothing may be dropped: the waker's
4621  ///   producer may hold a lock the destructors re-lock. The executor reopens
4622  ///   at once instead -- staying closed would leave it without a continuation
4623  ///   while the driver stays registered -- and then publishes one fresh host
4624  ///   dispatch of its own (`request_rejected_work_retirement`). That turn is
4625  ///   the clean stack on which `retire_rejected_work` cancels the moved work;
4626  ///   without it a wake that lost both of its publications left no request
4627  ///   behind at all, and the moved runnables kept their `JoinHandle`s and
4628  ///   `GenerationWorkGuard`s until an unrelated entry happened to arrive.
4629  ///
4630  /// This cannot repeat unboundedly inside one `schedule`. Reaching here
4631  /// again needs a fresh publication and two more host-failed deliveries, and
4632  /// the queue is already empty once the move is done; the publications
4633  /// `request_rejected_work_retirement` makes are the only way such a fresh
4634  /// publication can start on this stack, and
4635  /// `CURRENT_THREAD_RETIRING_REJECTED_WORK` keeps a nested transition from
4636  /// publishing there -- it returns, and the frame that owns the publication
4637  /// reads what it moved aside off the executor and takes the next turn. Those
4638  /// turns stop at `REJECTED_WORK_RETIREMENT_ATTEMPTS`, so a sustained
4639  /// producer cannot turn this stack into a service loop, and are followed by
4640  /// the one offer a producer cannot inflate; see
4641  /// `request_rejected_work_retirement`.
4642  fn cancel_work_after_host_dispatch_failure(
4643    self: &Arc<Self>,
4644    cancelling: CurrentThreadDispatchCancellationGuard<'_>,
4645  ) {
4646    let mut cancelling = Some(cancelling);
4647    // Blocking work first: the claimed flag blocks admission and claims, so
4648    // nothing can join or leave the FIFO between this take and the reopen.
4649    #[cfg(not(target_family = "wasm"))]
4650    let queued_blocking = {
4651      let mut state = self
4652        .blocking_admission
4653        .state
4654        .lock()
4655        .unwrap_or_else(std::sync::PoisonError::into_inner);
4656      state.queue.take_all()
4657    };
4658    {
4659      let mut queue = self
4660        .queue
4661        .lock()
4662        .unwrap_or_else(std::sync::PoisonError::into_inner);
4663      let moved = u64::try_from(queue.runnables.len()).expect("queued runnable count overflow");
4664      // Rejected runnables carry their `queued_runnables` debit already.
4665      self
4666        .metrics
4667        .queued_runnables
4668        .fetch_sub(moved, Ordering::Relaxed);
4669      let runnables = std::mem::take(&mut queue.runnables);
4670      queue.rejected.extend(runnables);
4671      #[cfg(not(target_family = "wasm"))]
4672      queue.rejected_blocking.extend(queued_blocking);
4673      if CurrentThreadScheduleScope::is_active() {
4674        // Reopen while holding the queue lock: `schedule` consults the flag
4675        // under this same lock, so every runnable this transition rejects is
4676        // already in the list, and every later schedule is queued and
4677        // published.
4678        drop(cancelling.take());
4679        drop(queue);
4680        // The moved runnables still own their `GenerationWorkGuard`s; a
4681        // waiting shutdown retires them on its own stack.
4682        if let Some(work) = self.generation_work.load_full() {
4683          work.notify_idle_waiters();
4684        }
4685        // During normal operation there is no such waiter, and this branch
4686        // published no host delivery. Ask for the host turn that retires the
4687        // moved work: it is the only continuation this stack may leave behind.
4688        self.request_rejected_work_retirement();
4689        return;
4690      }
4691    }
4692
4693    // Clean stack: cancel now. Dropping a runnable can wake a parked task
4694    // whose schedule `schedule` then rejects into `queue.rejected` (the flag
4695    // is still set); drop those too, outside any lock, until none remain.
4696    // Reopen while holding the queue lock, so no runnable can be rejected
4697    // after the final pass.
4698    let _generation = RuntimeGenerationGuard::enter(self.generation);
4699    loop {
4700      #[cfg(not(target_family = "wasm"))]
4701      let (rejected, rejected_blocking) = {
4702        let mut queue = self
4703          .queue
4704          .lock()
4705          .unwrap_or_else(std::sync::PoisonError::into_inner);
4706        if queue.rejected.is_empty() && queue.rejected_blocking.is_empty() {
4707          drop(cancelling.take());
4708          break;
4709        }
4710        (
4711          std::mem::take(&mut queue.rejected),
4712          std::mem::take(&mut queue.rejected_blocking),
4713        )
4714      };
4715      #[cfg(target_family = "wasm")]
4716      let rejected = {
4717        let mut queue = self
4718          .queue
4719          .lock()
4720          .unwrap_or_else(std::sync::PoisonError::into_inner);
4721        if queue.rejected.is_empty() {
4722          drop(cancelling.take());
4723          break;
4724        }
4725        std::mem::take(&mut queue.rejected)
4726      };
4727      for runnable in rejected {
4728        let _ = catch_unwind_contained(|| drop(runnable));
4729      }
4730      #[cfg(not(target_family = "wasm"))]
4731      for job in rejected_blocking {
4732        let _ = catch_unwind_contained(|| drop(job));
4733      }
4734    }
4735  }
4736
4737  fn take_runnable(&self) -> Option<Runnable> {
4738    // Terminal dispatch cancellation publishes while holding `scheduler_idle_lock`;
4739    // generation shutdown publishes while holding `stop.publication`. Claiming
4740    // under both locks gives each publication one exact ordering edge with the
4741    // runnable queue. The guards are released before polling user code.
4742    let scheduler = self
4743      .scheduler_idle_lock
4744      .lock()
4745      .unwrap_or_else(std::sync::PoisonError::into_inner);
4746    #[cfg(all(test, not(target_family = "wasm")))]
4747    run_current_thread_runnable_claim_test_hook();
4748    let stop_publication = self.stop.publication_guard();
4749    if stop_publication.is_stopping() || self.cancelling_failed_dispatch.load(Ordering::Acquire) {
4750      return None;
4751    }
4752    let runnable = {
4753      let mut queue = self
4754        .queue
4755        .lock()
4756        .unwrap_or_else(std::sync::PoisonError::into_inner);
4757      if queue.closed {
4758        None
4759      } else {
4760        queue.runnables.pop_front()
4761      }
4762    };
4763    drop(stop_publication);
4764    drop(scheduler);
4765    runnable
4766  }
4767
4768  fn drain_one(&self) -> bool {
4769    let runnable = self.take_runnable();
4770    if let Some(runnable) = runnable {
4771      #[cfg(not(target_family = "wasm"))]
4772      let _non_owner = BlockingOwnerGuard::enter(None);
4773      run_runnable(&self.metrics, runnable);
4774      true
4775    } else {
4776      false
4777    }
4778  }
4779
4780  /// Drive `future` to completion on this thread, draining queued runnables
4781  /// between polls. This loop OWNS its park site: the future is polled with
4782  /// this frame's own [`DriverParker`] as its waker, so the park DECISION can
4783  /// inspect the wake token directly instead of handing it to
4784  /// `futures::executor::block_on`'s opaque parker:
4785  ///
4786  ///   * `Pending` + queue empty + wake token pending: a self-waking future
4787  ///     (the `PendThenReady` shape) -- consume the token and re-poll. NOT a
4788  ///     park, NOT a deadlock; panic only at the park decision, never at
4789  ///     Pending-return.
4790  ///   * `Pending` + queue empty + NO token, threadless build: PROVABLE
4791  ///     deadlock -- no other thread exists that could ever deliver the wake
4792  ///     (the block_on-awaiting-JS class). Panic immediately with the typed
4793  ///     [`BlockOnDeadlock`] diagnostic. A pending HOST timer is no
4794  ///     rescue here: its `setTimeout` relay shares this very thread, so it
4795  ///     can never fire while the park holds it -- it only selects the
4796  ///     timer-backed variant of the same certain diagnostic.
4797  ///   * `Pending` + queue empty + NO token, threaded build: park. When a
4798  ///     `park_deadline` is armed, the park is bounded with progress-based
4799  ///     reset (see [`park_with_deadline`]) and panics typed if a full
4800  ///     window passes with zero runtime progress.
4801  fn block_on(self: &Arc<Self>, mut future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
4802    let parker = Arc::new(DriverParker::default());
4803    let _stop_registration = self.stop.register(&parker);
4804    #[cfg(not(target_family = "wasm"))]
4805    let dependency = BlockOnDependencyGuard::enter(self.generation);
4806    let waker = std::task::Waker::from(Arc::clone(&parker));
4807    let mut cx = Context::from_waker(&waker);
4808    #[cfg(not(target_family = "wasm"))]
4809    let mut runnable_streak = 0usize;
4810    loop {
4811      if self.stop.is_stopping() {
4812        return BlockOnOutcome::Stopped;
4813      }
4814      #[cfg(not(target_family = "wasm"))]
4815      dependency.clear();
4816      if future.as_mut().poll(&mut cx).is_ready() {
4817        return BlockOnOutcome::Completed;
4818      }
4819      // Shutdown must outrank queue work and self-wake permits. Otherwise an
4820      // always-self-waking future can continue before reaching the park-site
4821      // check forever while its generation work guard blocks quiescence.
4822      if self.stop.is_stopping() {
4823        return BlockOnOutcome::Stopped;
4824      }
4825      #[cfg(not(target_family = "wasm"))]
4826      if runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
4827        runnable_streak = 0;
4828        if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4829          continue;
4830        }
4831      }
4832      if self.drain_one() {
4833        #[cfg(not(target_family = "wasm"))]
4834        {
4835          runnable_streak += 1;
4836        }
4837        continue;
4838      }
4839      #[cfg(not(target_family = "wasm"))]
4840      if self.try_run_owned_blocking(&dependency.dependency) || self.run_one_blocking() {
4841        runnable_streak = 0;
4842        continue;
4843      }
4844      #[cfg(not(target_family = "wasm"))]
4845      {
4846        runnable_streak = 0;
4847      }
4848      // PARK DECISION: the future is Pending and the queue is empty. A token
4849      // stored during the poll (or by a racing thread) means a wake is
4850      // already pending: consume it and re-poll instead of parking.
4851      if parker.consume_permit() {
4852        continue;
4853      }
4854      // Threadless timer invariant: on a THREADLESS build even a pending
4855      // HOST timer is NOT a wake-token source -- the host event loop's
4856      // `setTimeout` relay can only run on the very thread that is about to
4857      // park, so it can never fire while `block_on` holds it. EVERY threadless
4858      // park at this decision is therefore provably dead; a pending timer only
4859      // selects the more precise diagnostic. Falling through instead would hit
4860      // `parker.park()`, which on the real target (wasm32-wasip1 no_threads
4861      // std) is an untyped "condvar wait not supported" abort -- a diagnostics
4862      // downgrade, never a rescue. (The deadline verdict's LIVE-wait veto
4863      // below stays: it serves the THREADED CT flavor, where the host loop
4864      // genuinely runs concurrently.)
4865      if self.threadless {
4866        if self.host_timers.has_pending() {
4867          std::panic::panic_any(BlockOnDeadlock::current_thread_certain_timer_backed());
4868        }
4869        std::panic::panic_any(BlockOnDeadlock::current_thread_certain());
4870      }
4871      match self.park_deadline {
4872        // A wake landing between `consume_permit` above and the park is
4873        // stored as the parker's permit, so `park` returns immediately --
4874        // never lost.
4875        None => parker.park(),
4876        Some(deadline) => match park_with_deadline(&parker, deadline, &self.metrics) {
4877          DeadlineParkOutcome::Woken => {}
4878          DeadlineParkOutcome::Expired(armed) => {
4879            // EXPIRY DECISION (CurrentThread has no parked-driver registry):
4880            // a wake can land in the instructions after the window
4881            // closed, and the fingerprint read inside `park_with_deadline`
4882            // is stale by now. Re-check both before declaring death; a hit
4883            // means the park was healthy -- continue the loop (re-poll,
4884            // drain, fresh full window on the next park). A LIVE host-timer
4885            // wait also vetoes, re-checked LAST -- arming a host timer bumps
4886            // no progress
4887            // counter, so it is invisible to the fingerprint read: waiting
4888            // out a host timer with zero progress is legitimate, its wake is
4889            // scheduled on the host loop. `deadline` doubles as the grace
4890            // for host firing lag; a timer past due by more than one full
4891            // window without firing is the frozen-JS-loop signature this
4892            // detection exists for, so it stops vetoing.
4893            if parker.consume_permit()
4894              || self.metrics.progress_fingerprint() != armed
4895              || self.host_timers.has_live_wait(deadline)
4896            {
4897              continue;
4898            }
4899            std::panic::panic_any(BlockOnDeadlock::current_thread_deadline(deadline));
4900          }
4901        },
4902      }
4903    }
4904  }
4905
4906  fn begin_shutdown(&self) {
4907    let _generation = RuntimeGenerationGuard::enter(self.generation);
4908    self.stop.begin_shutdown();
4909    self.close_blocking_admission();
4910    let (queued, rejected) = {
4911      let mut queue = self
4912        .queue
4913        .lock()
4914        .unwrap_or_else(std::sync::PoisonError::into_inner);
4915      queue.closed = true;
4916      #[cfg(not(target_family = "wasm"))]
4917      let rejected_blocking = std::mem::take(&mut queue.rejected_blocking);
4918      let rejected = std::mem::take(&mut queue.rejected);
4919      #[cfg(not(target_family = "wasm"))]
4920      let rejected = (rejected, rejected_blocking);
4921      (std::mem::take(&mut queue.runnables), rejected)
4922    };
4923    self.dispatch_pending.store(0, Ordering::Release);
4924    self.recovery_dispatch.store(0, Ordering::Release);
4925    self.task_drivers.retire_for_shutdown();
4926    for runnable in queued {
4927      self
4928        .metrics
4929        .queued_runnables
4930        .fetch_sub(1, Ordering::Relaxed);
4931      // Dropping the last runnable cancels its detached async-task and retires
4932      // the generation guard. Isolate user future destructors from shutdown.
4933      let _ = catch_unwind_contained(|| drop(runnable));
4934    }
4935    // Rejected work (already unaccounted above) is cancelled by whichever
4936    // side takes it first: this one, or a host entry that got there earlier.
4937    #[cfg(not(target_family = "wasm"))]
4938    let (rejected, rejected_blocking) = rejected;
4939    for runnable in rejected {
4940      let _ = catch_unwind_contained(|| drop(runnable));
4941    }
4942    #[cfg(not(target_family = "wasm"))]
4943    for job in rejected_blocking {
4944      let _ = catch_unwind_contained(|| drop(job));
4945    }
4946    self.host_timers.shutdown();
4947  }
4948
4949  fn wait_until_scheduler_idle(&self) {
4950    // Registered work has retired by now on the backend path; this catches
4951    // rejections from unregistered tasks (tests) before the executor goes.
4952    self.retire_rejected_work();
4953    let mut idle = self
4954      .scheduler_idle_lock
4955      .lock()
4956      .unwrap_or_else(std::sync::PoisonError::into_inner);
4957    while idle.active_host_turns != 0 || idle.active_dispatch_calls != 0 {
4958      idle = self
4959        .scheduler_idle
4960        .wait(idle)
4961        .unwrap_or_else(std::sync::PoisonError::into_inner);
4962    }
4963    debug_assert!(
4964      idle.dispatch_publications.is_empty(),
4965      "an idle CurrentThread scheduler must not retain dispatch publications"
4966    );
4967  }
4968}
4969
4970// Identifies the executor whose Rayon pool owns THIS worker thread (or `None`
4971// when not on a pool worker). Used by `MultiThreadExecutor::block_on` to detect
4972// a re-entrant (on-pool) call OF THE SAME EXECUTOR and drive the queue
4973// cooperatively instead of parking the worker. Carrying the executor id (not a
4974// bare bool) keeps this marker scoped exactly like
4975// `BlockingOwnerToken.executor_id`: a worker of executor A that re-enters
4976// `block_on` on executor B must NOT be treated as B's on-pool driver -- it owns
4977// none of B's accounting, so it parks like any foreign-thread caller rather
4978// than driving B's queues as an unaccounted within-cap driver. Under the single
4979// global executor used in production these ids always match, so this is a
4980// defensive scoping gate (consistent with the blocking-owner machinery), not a
4981// reachable production change.
4982#[cfg(not(target_family = "wasm"))]
4983thread_local! {
4984  static ON_POOL_WORKER: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4985  /// Identifies the dedicated timer thread for lifecycle self-wait
4986  /// prevention. It is deliberately separate from `ON_POOL_WORKER`: the
4987  /// timer thread must never become a cooperative Rayon scheduler driver.
4988  static ON_TIMER_THREAD: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
4989}
4990
4991// Identifies scheduler-driver frames on a pool worker. Unlike
4992// `ON_POOL_WORKER`, this is not installed by Rayon's worker start hook:
4993// arbitrary nested Rayon jobs need cooperative `block_on` classification, but
4994// must not place work in the per-thread LIFO slot because no drain frame may
4995// remain to consume it after the Rayon job returns.
4996#[cfg(not(target_family = "wasm"))]
4997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4998struct SchedulerDriverRole {
4999  executor_id: u64,
5000  can_run_blocking: bool,
5001}
5002
5003#[cfg(not(target_family = "wasm"))]
5004thread_local! {
5005  static IN_SCHEDULER_DRIVER: std::cell::Cell<Option<SchedulerDriverRole>> =
5006    const { std::cell::Cell::new(None) };
5007}
5008
5009// Per-worker LIFO slot (tokio-style): holds at most ONE
5010// runnable that was scheduled FROM this pool worker (typically a task the
5011// currently running runnable just woke). It is popped by this same thread's
5012// next drain/run_one iteration BEFORE the shared FIFO, so the hot
5013// wake-then-await ping-pong pattern stays on one core with no queue mutex, no
5014// wake and no drainer spawn. Tagged with the executor id -- the same scoping
5015// pattern as `ON_POOL_WORKER` and `BlockingOwnerToken.executor_id` -- so only
5016// drain/run_one/flush of the SAME executor may pop it.
5017//
5018// STRANDING INVARIANT: slot work is invisible to every other
5019// thread (`finish_draining`'s re-check and `wake_one` cannot see it), so the
5020// owning thread MUST either run its slot entry or flush it to the shared FIFO
5021// before it stops draining -- see the flush calls at `drain`'s returns, the
5022// pre-park flush in `cooperative_block_on`, and `LifoSlotFlushGuard` for
5023// unwinds. A runnable left here while the thread returns to rayon would be a
5024// silently lost task and a `queued_runnables` leak.
5025#[cfg(not(target_family = "wasm"))]
5026thread_local! {
5027  static LIFO_SLOT: std::cell::Cell<Option<(u64, Runnable)>> =
5028    const { std::cell::Cell::new(None) };
5029}
5030
5031#[cfg(not(target_family = "wasm"))]
5032struct OnPoolWorkerGuard {
5033  previous_worker: Option<u64>,
5034  previous_driver: Option<SchedulerDriverRole>,
5035}
5036
5037#[cfg(not(target_family = "wasm"))]
5038impl OnPoolWorkerGuard {
5039  // Save the previous marker and install `id`, so nested/re-entrant drains
5040  // (possibly from different executors on the same thread) restore the exact
5041  // prior executor id on drop instead of unconditionally clearing it.
5042  fn enter(id: u64) -> Self {
5043    Self::enter_with_role(id, true)
5044  }
5045
5046  fn enter_with_role(id: u64, can_run_blocking: bool) -> Self {
5047    Self {
5048      previous_worker: ON_POOL_WORKER.with(|flag| flag.replace(Some(id))),
5049      previous_driver: IN_SCHEDULER_DRIVER.with(|flag| {
5050        flag.replace(Some(SchedulerDriverRole {
5051          executor_id: id,
5052          can_run_blocking,
5053        }))
5054      }),
5055    }
5056  }
5057}
5058
5059#[cfg(not(target_family = "wasm"))]
5060impl Drop for OnPoolWorkerGuard {
5061  fn drop(&mut self) {
5062    IN_SCHEDULER_DRIVER.with(|flag| flag.set(self.previous_driver));
5063    ON_POOL_WORKER.with(|flag| flag.set(self.previous_worker));
5064  }
5065}
5066
5067// Identifies one specific counted-blocking owner frame on one specific executor.
5068//
5069// `executor_id` is assigned once per executor (so a stale token left on a
5070// thread by a shut-down executor can never authorize work on a replacement);
5071// `frame` is unique per blocking-lane entry. An owner frame is the eligibility
5072// half of dependency lending: the exact job half comes from the pending
5073// `JoinHandle` dependency recorded by the owning `block_on` frame. A plain
5074// runnable driver owns no frame, so it must respect normal blocking admission.
5075//
5076// CurrentThread uses the same identity to distinguish same-stack lane borrowing
5077// from cross-driver contention. MultiThread additionally reserves active owner
5078// frames while an otherwise-idle cooperative driver reuses their admitted lane.
5079#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
5080struct BlockingOwnerToken {
5081  executor_id: u64,
5082  frame: u64,
5083}
5084
5085#[cfg(not(target_family = "wasm"))]
5086enum OwnedBlockingOutcome {
5087  Ineligible,
5088  OwnerBusy(BlockingOwnerToken),
5089  ReservationReleased {
5090    owner: BlockingOwnerToken,
5091    ran: bool,
5092  },
5093}
5094
5095// Process-global owner-frame source for per-frame dependency-lending
5096// isolation. Executor ids use `NEXT_RUNTIME_EXECUTOR_ID` above on every target.
5097static NEXT_BLOCKING_FRAME: AtomicU64 = AtomicU64::new(1);
5098#[cfg(not(target_family = "wasm"))]
5099static NEXT_OWNER_RESERVATION: AtomicU64 = AtomicU64::new(1);
5100
5101// The owner frame (if any) currently running a counted blocking closure on THIS
5102// thread. The exact frame identity scopes any over-cap dependency lending.
5103thread_local! {
5104  static BLOCKING_OWNER: std::cell::Cell<Option<BlockingOwnerToken>> =
5105    const { std::cell::Cell::new(None) };
5106}
5107
5108// Save/replace/restore guard for `BLOCKING_OWNER`. Enter with `Some(token)` while
5109// running a counted blocking closure, or with `None` while a cooperative driver
5110// runs a plain queued runnable (ownership is LEXICAL to the blocking closure: a
5111// runnable an owner happens to drive via `run_one` is a logical non-owner and
5112// must not inherit the owner's over-cap privilege). Restored on drop, so the
5113// pattern is panic-safe even though `run_runnable` already catches panics.
5114#[cfg(not(target_family = "wasm"))]
5115struct BlockingOwnerGuard(Option<BlockingOwnerToken>);
5116
5117#[cfg(not(target_family = "wasm"))]
5118impl BlockingOwnerGuard {
5119  fn enter(token: Option<BlockingOwnerToken>) -> Self {
5120    Self(BLOCKING_OWNER.with(|cell| cell.replace(token)))
5121  }
5122}
5123
5124#[cfg(not(target_family = "wasm"))]
5125impl Drop for BlockingOwnerGuard {
5126  fn drop(&mut self) {
5127    BLOCKING_OWNER.with(|cell| cell.set(self.0));
5128  }
5129}
5130
5131#[cfg(not(target_family = "wasm"))]
5132#[derive(Default)]
5133struct ActiveBlockingOwners {
5134  // `None` is available; `Some(id)` is reserved by exactly that lending
5135  // transfer. The identity prevents a delayed stale drop from releasing a
5136  // newer reservation of the same owner frame.
5137  owners: Mutex<FxHashMap<BlockingOwnerToken, Option<u64>>>,
5138}
5139
5140#[cfg(not(target_family = "wasm"))]
5141impl ActiveBlockingOwners {
5142  fn register(&self, owner: BlockingOwnerToken) {
5143    let previous = self
5144      .owners
5145      .lock()
5146      .unwrap_or_else(std::sync::PoisonError::into_inner)
5147      .insert(owner, None);
5148    debug_assert!(previous.is_none());
5149  }
5150
5151  fn unregister(&self, owner: BlockingOwnerToken) {
5152    let removed = self
5153      .owners
5154      .lock()
5155      .unwrap_or_else(std::sync::PoisonError::into_inner)
5156      .remove(&owner);
5157    debug_assert!(removed.is_some());
5158  }
5159
5160  fn reserve(&self, owner: BlockingOwnerToken) -> Option<u64> {
5161    let mut owners = self
5162      .owners
5163      .lock()
5164      .unwrap_or_else(std::sync::PoisonError::into_inner);
5165    let reservation = owners.get_mut(&owner)?;
5166    if reservation.is_some() {
5167      return None;
5168    }
5169    let id = next_unique_id(
5170      &NEXT_OWNER_RESERVATION,
5171      "blocking owner reservation id space exhausted",
5172    );
5173    *reservation = Some(id);
5174    Some(id)
5175  }
5176
5177  fn release(&self, owner: BlockingOwnerToken, reservation: u64) {
5178    let mut owners = self
5179      .owners
5180      .lock()
5181      .unwrap_or_else(std::sync::PoisonError::into_inner);
5182    if let Some(current) = owners.get_mut(&owner)
5183      && *current == Some(reservation)
5184    {
5185      *current = None;
5186    }
5187  }
5188
5189  fn is_available(&self, owner: BlockingOwnerToken) -> bool {
5190    self
5191      .owners
5192      .lock()
5193      .unwrap_or_else(std::sync::PoisonError::into_inner)
5194      .get(&owner)
5195      .is_some_and(Option::is_none)
5196  }
5197}
5198
5199#[cfg(not(target_family = "wasm"))]
5200struct ActiveBlockingOwnerGuard<'a> {
5201  owners: &'a ActiveBlockingOwners,
5202  owner: BlockingOwnerToken,
5203}
5204
5205#[cfg(not(target_family = "wasm"))]
5206impl<'a> ActiveBlockingOwnerGuard<'a> {
5207  fn enter(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Self {
5208    owners.register(owner);
5209    Self { owners, owner }
5210  }
5211}
5212
5213#[cfg(not(target_family = "wasm"))]
5214impl Drop for ActiveBlockingOwnerGuard<'_> {
5215  fn drop(&mut self) {
5216    self.owners.unregister(self.owner);
5217  }
5218}
5219
5220#[cfg(not(target_family = "wasm"))]
5221struct OwnerLaneReservation<'a> {
5222  owners: &'a ActiveBlockingOwners,
5223  owner: BlockingOwnerToken,
5224  reservation: u64,
5225}
5226
5227#[cfg(not(target_family = "wasm"))]
5228impl<'a> OwnerLaneReservation<'a> {
5229  fn try_acquire(owners: &'a ActiveBlockingOwners, owner: BlockingOwnerToken) -> Option<Self> {
5230    let reservation = owners.reserve(owner)?;
5231    Some(Self {
5232      owners,
5233      owner,
5234      reservation,
5235    })
5236  }
5237}
5238
5239#[cfg(not(target_family = "wasm"))]
5240impl Drop for OwnerLaneReservation<'_> {
5241  fn drop(&mut self) {
5242    self.owners.release(self.owner, self.reservation);
5243  }
5244}
5245
5246// A blocking job queued for the pool. Its stable id is copied into the returned
5247// JoinHandle and propagated through async JoinHandle dependencies, allowing a
5248// saturated owner to run exactly the job its nested block_on awaits.
5249#[cfg(not(target_family = "wasm"))]
5250type BlockingClosure = Box<dyn FnOnce() + Send + 'static>;
5251
5252#[cfg(not(target_family = "wasm"))]
5253struct QueuedBlocking {
5254  id: BlockingJobId,
5255  run: BlockingClosure,
5256}
5257
5258#[cfg(not(target_family = "wasm"))]
5259struct BlockingQueueNode {
5260  previous: Option<BlockingJobId>,
5261  next: Option<BlockingJobId>,
5262  run: Box<dyn FnOnce() + Send + 'static>,
5263}
5264
5265#[cfg(not(target_family = "wasm"))]
5266struct BlockingQueue {
5267  closed: bool,
5268  head: Option<BlockingJobId>,
5269  tail: Option<BlockingJobId>,
5270  jobs: FxHashMap<BlockingJobId, BlockingQueueNode>,
5271}
5272
5273#[cfg(not(target_family = "wasm"))]
5274impl BlockingQueue {
5275  fn push(&mut self, job: QueuedBlocking) {
5276    let previous = self.tail;
5277    let replaced = self.jobs.insert(
5278      job.id,
5279      BlockingQueueNode {
5280        previous,
5281        next: None,
5282        run: job.run,
5283      },
5284    );
5285    debug_assert!(replaced.is_none());
5286    if let Some(previous) = previous {
5287      self
5288        .jobs
5289        .get_mut(&previous)
5290        .expect("blocking queue tail must be indexed")
5291        .next = Some(job.id);
5292    } else {
5293      self.head = Some(job.id);
5294    }
5295    self.tail = Some(job.id);
5296  }
5297
5298  fn pop_front(&mut self) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5299    self.head.and_then(|id| self.remove(id))
5300  }
5301
5302  fn remove(&mut self, id: BlockingJobId) -> Option<Box<dyn FnOnce() + Send + 'static>> {
5303    let node = self.jobs.remove(&id)?;
5304    if let Some(previous) = node.previous {
5305      self
5306        .jobs
5307        .get_mut(&previous)
5308        .expect("blocking queue predecessor must be indexed")
5309        .next = node.next;
5310    } else {
5311      self.head = node.next;
5312    }
5313    if let Some(next) = node.next {
5314      self
5315        .jobs
5316        .get_mut(&next)
5317        .expect("blocking queue successor must be indexed")
5318        .previous = node.previous;
5319    } else {
5320      self.tail = node.previous;
5321    }
5322    Some(node.run)
5323  }
5324
5325  fn is_empty(&self) -> bool {
5326    debug_assert_eq!(self.head.is_none(), self.jobs.is_empty());
5327    debug_assert_eq!(self.tail.is_none(), self.jobs.is_empty());
5328    self.jobs.is_empty()
5329  }
5330
5331  fn take_all(&mut self) -> Vec<Box<dyn FnOnce() + Send + 'static>> {
5332    self.head = None;
5333    self.tail = None;
5334    std::mem::take(&mut self.jobs)
5335      .into_values()
5336      .map(|node| node.run)
5337      .collect()
5338  }
5339}
5340
5341#[cfg(not(target_family = "wasm"))]
5342struct WorkerLifecycle {
5343  remaining: Mutex<usize>,
5344  handles: Mutex<Option<Vec<std::thread::JoinHandle<()>>>>,
5345}
5346
5347#[cfg(not(target_family = "wasm"))]
5348impl WorkerLifecycle {
5349  fn new(worker_threads: usize) -> Arc<Self> {
5350    Arc::new(Self {
5351      remaining: Mutex::new(worker_threads),
5352      handles: Mutex::new(Some(Vec::with_capacity(worker_threads))),
5353    })
5354  }
5355
5356  fn retain(&self, handle: std::thread::JoinHandle<()>) {
5357    self
5358      .handles
5359      .lock()
5360      .unwrap_or_else(std::sync::PoisonError::into_inner)
5361      .as_mut()
5362      .expect("runtime worker handles already joined")
5363      .push(handle);
5364  }
5365
5366  fn worker_exited(&self) {
5367    let mut remaining = self
5368      .remaining
5369      .lock()
5370      .unwrap_or_else(std::sync::PoisonError::into_inner);
5371    *remaining = remaining
5372      .checked_sub(1)
5373      .expect("runtime worker exit count underflow");
5374  }
5375
5376  fn wait_for_all_workers(&self) {
5377    let handles = self
5378      .handles
5379      .lock()
5380      .unwrap_or_else(std::sync::PoisonError::into_inner)
5381      .take()
5382      .expect("runtime worker handles already joined");
5383    for handle in handles {
5384      if let Err(payload) = handle.join() {
5385        discard_panic_payload(payload);
5386      }
5387    }
5388  }
5389
5390  #[cfg(test)]
5391  fn remaining(&self) -> usize {
5392    *self
5393      .remaining
5394      .lock()
5395      .unwrap_or_else(std::sync::PoisonError::into_inner)
5396  }
5397}
5398
5399#[cfg(not(target_family = "wasm"))]
5400struct MultiThreadExecutor {
5401  generation: u64,
5402  stop: Arc<GenerationStop>,
5403  // Stable per-executor id; tags owner tokens so a stale token from a shut-down
5404  // executor can never authorize an over-cap escape on a replacement.
5405  id: u64,
5406  pool: ThreadPool,
5407  // Lock-free MPMC injector replacing the former `Mutex<VecDeque<Runnable>>`.
5408  // Invariant: every push goes through `push_to_queue_and_wake` and every
5409  // emptiness check through `has_queued_runnable` -- their paired SeqCst fences
5410  // restore the removed mutex's synchronizes-with edge (full lost-wakeup proof
5411  // at `push_to_queue_and_wake`). A raw `injector.push`/`is_empty` at a new site
5412  // silently breaks the pairing.
5413  injector: Injector<Runnable>,
5414  blocking_queue: Mutex<BlockingQueue>,
5415  active_drainers: AtomicUsize,
5416  active_blocking: AtomicUsize,
5417  // Guards the idle-waiter count. `finish_draining` runs once per drain batch
5418  // -- i.e. per task whenever the drainer catches the producer -- and used to
5419  // call `notify_all` unconditionally. On Linux `Condvar::notify_all` issues a
5420  // `futex_wake_all` SYSCALL even with an empty waiter set, and the only waiter
5421  // site in this crate (`wait_until_scheduler_idle`) is reachable only from
5422  // shutdown/restart. Counting waiters under this same mutex lets the steady
5423  // state skip the syscall entirely.
5424  scheduler_idle_lock: Mutex<usize>,
5425  scheduler_idle: Condvar,
5426  worker_lifecycle: Arc<WorkerLifecycle>,
5427  // Registry of pool workers currently parked inside a re-entrant cooperative
5428  // `block_on`, woken ONE at a time when new queue work is scheduled. The pool
5429  // has a fixed number of OS threads, so a parked worker cannot rely on
5430  // `ensure_drainer` spawning a replacement (there is no free thread to run
5431  // it) -- it must wake and run the work itself. Future-wakes do NOT go
5432  // through this registry: each driver's own `DriverParker` is its awaited
5433  // future's waker (see the wake-domain note at `DriverParker`).
5434  parked_drivers: ParkedDrivers,
5435  active_blocking_owners: ActiveBlockingOwners,
5436  #[cfg(test)]
5437  owner_lending_attempts: AtomicUsize,
5438  // Linger-lifecycle observability for tests (see the idle-linger block in
5439  // `drain`): counts every linger-parker REGISTRATION into `parked_drivers`
5440  // and every idle-budget EXPIRY exit (the deadline-elapsed `break` paths,
5441  // counted AFTER the parker is deregistered, so `expiries` rising implies
5442  // the linger parker has permanently left the registry for that episode).
5443  // Tests that must observe a lingering drainer's full register->expire
5444  // lifecycle wait on these instead of sampling the non-monotonic registry
5445  // count, which is transiently at its settled value BEFORE the linger
5446  // parker registers.
5447  #[cfg(test)]
5448  drain_linger_registrations: AtomicUsize,
5449  #[cfg(test)]
5450  drain_linger_expiries: AtomicUsize,
5451  max_drainers: usize,
5452  max_blocking: usize,
5453  // Deadline-based deadlock detection for COOPERATIVE driver parks ONLY. The
5454  // foreign/napi whole-build park in `block_on`'s else-branch is exempt by
5455  // design because it parks for entire builds and a deadline there would
5456  // panic healthy production runs. Off unless
5457  // configured via `RuntimeOptions::park_deadline` (the embedder resolves
5458  // `PARK_DEADLINE_ENV` into it).
5459  park_deadline: Option<Duration>,
5460  // Idle-linger budget for pool drainers (`None` = disabled); resolved from
5461  // `RuntimeOptions::drain_linger` (`ZERO` = disabled, embedder-resolved from
5462  // `DRAIN_LINGER_ENV` at addon load) at construction and ceiling-clamped
5463  // there so even direct unvalidated test constructions stay bounded.
5464  drain_linger: Option<Duration>,
5465  // Shared with the timer heap because firing removes the timer before invoking
5466  // its waker. The admission remains active until that wake has published any
5467  // resulting runnable, so a final verdict cannot observe both sources empty.
5468  deadlock_state: Arc<MultiThreadDeadlockState>,
5469  // Runtime-owned timer heap plus its dedicated non-Rayon service thread.
5470  // Cooperative parks also fire due timers after timer-bounded waits.
5471  timers: TimerHeap,
5472  metrics: Arc<RuntimeMetrics>,
5473}
5474
5475#[cfg(not(target_family = "wasm"))]
5476impl MultiThreadExecutor {
5477  /// Max work units before a drainer yields. Cooperative drivers use the same
5478  /// value as the LIFO streak cap.
5479  const RUNNABLE_BUDGET: usize = 64;
5480  /// Force one blocking admission after this many consecutive runnable polls
5481  /// when blocking capacity and queued work are both available.
5482  const RUNNABLE_FAIRNESS_QUANTUM: usize = 16;
5483
5484  #[cfg(test)]
5485  fn new(
5486    options: &RuntimeOptions,
5487    metrics: Arc<RuntimeMetrics>,
5488  ) -> Result<Self, RuntimeConfigError> {
5489    Self::new_for_generation(
5490      options,
5491      metrics,
5492      u64::MAX,
5493      Arc::new(GenerationStop::default()),
5494    )
5495  }
5496
5497  fn new_for_generation(
5498    options: &RuntimeOptions,
5499    metrics: Arc<RuntimeMetrics>,
5500    generation: u64,
5501    stop: Arc<GenerationStop>,
5502  ) -> Result<Self, RuntimeConfigError> {
5503    let id = next_unique_id(
5504      &NEXT_RUNTIME_EXECUTOR_ID,
5505      "async runtime executor id space exhausted",
5506    );
5507    let thread_name_prefix = options.thread_name_prefix.clone();
5508    let deadlock_state = Arc::new(MultiThreadDeadlockState::new(
5509      options.park_deadline.is_some(),
5510    ));
5511    let timers = TimerHeap::new(
5512      generation,
5513      id,
5514      format!("{thread_name_prefix}-timer"),
5515      Arc::clone(&deadlock_state),
5516    );
5517    // Production options are validated before construction, so this exact
5518    // physical count is also the configured/reported count. Direct executor
5519    // unit tests may still construct a one-thread topology intentionally.
5520    let pool_threads = options.worker_threads;
5521    let worker_lifecycle = WorkerLifecycle::new(pool_threads);
5522    let exit_lifecycle = Arc::clone(&worker_lifecycle);
5523    let spawn_lifecycle = Arc::clone(&worker_lifecycle);
5524    let pool = ThreadPoolBuilder::new()
5525      .num_threads(pool_threads)
5526      .thread_name(move |index| format!("{thread_name_prefix}-{index}"))
5527      .start_handler(move |_| {
5528        ON_POOL_WORKER.with(|worker| worker.set(Some(id)));
5529      })
5530      .exit_handler(move |_| {
5531        IN_SCHEDULER_DRIVER.with(|driver| {
5532          if driver.get().is_some_and(|role| role.executor_id == id) {
5533            driver.set(None);
5534          }
5535        });
5536        // Keep ON_POOL_WORKER set until the OS thread exits. TLS destructors
5537        // run after this Rayon hook and may re-enter lifecycle APIs while the
5538        // external shutdown caller is joining this worker; retaining the
5539        // executor identity makes that re-entry fail fast instead of waiting
5540        // in Stopping and deadlocking the join.
5541        exit_lifecycle.worker_exited();
5542      })
5543      .spawn_handler(move |thread| {
5544        let mut builder = std::thread::Builder::new();
5545        if let Some(name) = thread.name() {
5546          builder = builder.name(name.to_string());
5547        }
5548        if let Some(stack_size) = thread.stack_size() {
5549          builder = builder.stack_size(stack_size);
5550        }
5551        let handle = builder.spawn(move || thread.run())?;
5552        spawn_lifecycle.retain(handle);
5553        Ok(())
5554      })
5555      .build()
5556      .map_err(|error| {
5557        worker_lifecycle.wait_for_all_workers();
5558        RuntimeConfigError(format!("failed to create runtime workers: {error}"))
5559      })?;
5560    Ok(Self {
5561      generation,
5562      stop,
5563      id,
5564      pool,
5565      injector: Injector::new(),
5566      blocking_queue: Mutex::new(BlockingQueue {
5567        closed: false,
5568        head: None,
5569        tail: None,
5570        jobs: FxHashMap::default(),
5571      }),
5572      active_drainers: AtomicUsize::new(0),
5573      active_blocking: AtomicUsize::new(0),
5574      scheduler_idle_lock: Mutex::new(0),
5575      scheduler_idle: Condvar::new(),
5576      worker_lifecycle,
5577      parked_drivers: ParkedDrivers::default(),
5578      active_blocking_owners: ActiveBlockingOwners::default(),
5579      #[cfg(test)]
5580      owner_lending_attempts: AtomicUsize::new(0),
5581      #[cfg(test)]
5582      drain_linger_registrations: AtomicUsize::new(0),
5583      #[cfg(test)]
5584      drain_linger_expiries: AtomicUsize::new(0),
5585      max_drainers: pool_threads,
5586      max_blocking: options.max_blocking_tasks,
5587      park_deadline: options.park_deadline,
5588      drain_linger: {
5589        // Defensive re-clamp: production options were already clamped by
5590        // `RuntimeOptions::validate`, but direct executor unit tests construct
5591        // from unvalidated options.
5592        let budget = options.drain_linger.min(MAX_DRAIN_LINGER);
5593        (!budget.is_zero()).then_some(budget)
5594      },
5595      deadlock_state,
5596      timers,
5597      metrics,
5598    })
5599  }
5600
5601  fn begin_deadlock_admission(&self) -> Option<MultiThreadDeadlockAdmissionGuard> {
5602    self.deadlock_state.begin_admission()
5603  }
5604
5605  fn try_begin_deadlock_verdict(&self) -> Option<MultiThreadDeadlockVerdictGuard> {
5606    self.deadlock_state.try_begin_verdict()
5607  }
5608
5609  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
5610    // See the implementation notes in this crate's README.
5611    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5612    self.parked_drivers.wake_one_owner_dependency(owner)
5613  }
5614
5615  fn release_owner_lane_and_wake(&self, reservation: OwnerLaneReservation<'_>) -> bool {
5616    let owner = reservation.owner;
5617    // Keep release and its targeted handoff in one detector-visible critical
5618    // section. Unwind drops the mutex after the lane is already available, so
5619    // a later verdict rechecks that availability even if selection panicked.
5620    let _publication = self.deadlock_state.owner_handoff_publication_guard();
5621    drop(reservation);
5622    self.parked_drivers.wake_one_owner_dependency(owner)
5623  }
5624
5625  fn schedule(self: &Arc<Self>, runnable: Runnable) {
5626    let mut admission = self.begin_deadlock_admission();
5627    // Fires for BOTH the slot and the FIFO path: a slot-resident runnable is
5628    // still "queued" until popped (`runnable_started` balances it either way).
5629    self.metrics.runnable_scheduled();
5630    // LIFO fast path: a runnable scheduled from a pool
5631    // worker OF THIS EXECUTOR goes into that worker's slot instead of the
5632    // shared FIFO -- this thread pops it before its next FIFO look, so no
5633    // queue mutex, no wake, no drainer spawn.
5634    //
5635    // "Pops it before its next FIFO look" is only true while RUNNABLE code is
5636    // on the stack (drain/run_one loop iterations, cooperative polls). It is
5637    // FALSE inside a blocking closure: `ON_POOL_WORKER` spans the whole drain
5638    // frame including `blocking()` calls, but the slot is only popped after
5639    // the closure returns -- so a closure that spawns and then synchronously
5640    // waits on the child would deadlock with the child slotted (no wake, no
5641    // drainer, even with idle workers). `BLOCKING_OWNER` is `Some` exactly
5642    // while straight-line blocking-closure code runs (drain/run_one blocking
5643    // branches, the over-cap escape) and `None` while runnables run (the
5644    // slot/FIFO owner-clear guards), so it is the discriminator: bypass the
5645    // slot and take the FIFO+wake path whenever an owner frame is ambient.
5646    // (A cooperative poll entered FROM a blocking closure also carries the
5647    // closure's token and loses the fast path -- a rare, conservative,
5648    // correctness-first trade.)
5649    if IN_SCHEDULER_DRIVER
5650      .with(std::cell::Cell::get)
5651      .is_some_and(|role| role.executor_id == self.id)
5652      && BLOCKING_OWNER.with(std::cell::Cell::get).is_none()
5653    {
5654      match LIFO_SLOT.with(std::cell::Cell::take) {
5655        None => {
5656          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5657          if let Some(admission) = admission.as_mut() {
5658            admission.mark_published();
5659          }
5660          return;
5661        }
5662        // Same-executor occupant: the NEWEST runnable takes the slot (it is
5663        // the hottest); the displaced occupant falls back to the shared FIFO
5664        // with a normal wake so other workers can pick it up.
5665        Some((id, displaced)) if id == self.id => {
5666          LIFO_SLOT.with(|slot| slot.set(Some((self.id, runnable))));
5667          self.push_to_queue_and_wake(displaced);
5668          if let Some(admission) = admission.as_mut() {
5669            admission.mark_published();
5670          }
5671          return;
5672        }
5673        // Foreign-executor occupant (defensive: requires a thread acting as a
5674        // worker of two executors, e.g. under a test-installed marker; the
5675        // production process has one global executor). We must not flush
5676        // another executor's runnable onto OUR queue, so leave it in place --
5677        // its own executor's drain on this thread pops or flushes it -- and
5678        // route the new runnable to the FIFO.
5679        Some(foreign) => {
5680          LIFO_SLOT.with(|slot| slot.set(Some(foreign)));
5681        }
5682      }
5683    }
5684    self.push_to_queue_and_wake(runnable);
5685    if let Some(admission) = admission.as_mut() {
5686      admission.mark_published();
5687    }
5688  }
5689
5690  /// Shared-FIFO tail of [`Self::schedule`]: push, then wake (also used for
5691  /// displaced/flushed slot runnables, whose `runnable_scheduled` accounting
5692  /// already happened at their original schedule).
5693  fn push_to_queue_and_wake(self: &Arc<Self>, runnable: Runnable) {
5694    self.injector.push(runnable);
5695    // The former queue mutex provided the synchronizes-with edge the
5696    // lost-wakeup proof depends on (see `wake_one`). With a lock-free injector
5697    // that edge is gone, so restore it explicitly: a SeqCst fence between the
5698    // push and BOTH wake mechanisms (`wake_one`'s SeqCst `count` load and
5699    // `ensure_drainer`'s `active_drainers` load), mirrored by the SeqCst fence
5700    // in `has_queued_runnable` before the waiter/drainer re-check. This is the
5701    // Dekker ordering rayon-core's injector+sleep protocol uses; without both
5702    // fences a worker can park next to stealable work.
5703    std::sync::atomic::fence(Ordering::SeqCst);
5704    self.wake_for_new_work();
5705  }
5706
5707  /// Queue-wake tail shared by `schedule` and `schedule_blocking`: wake ONE
5708  /// parked cooperative driver; when none is parked, fall back to spawning a
5709  /// drainer. A single wake suffices because the woken driver re-checks
5710  /// every work source in its loop before re-parking, and a wake it absorbs
5711  /// without consuming is handed on at `cooperative_block_on` exit (miss
5712  /// compensation). MUST be called AFTER the work was pushed
5713  /// under its queue mutex -- see the lost-wakeup argument at
5714  /// [`ParkedDrivers::wake_one`].
5715  fn wake_for_new_work(self: &Arc<Self>) {
5716    if !self.parked_drivers.wake_one() {
5717      self.ensure_drainer();
5718    }
5719  }
5720
5721  fn wake_for_blocking_work(self: &Arc<Self>) {
5722    // Prefer a cooperative driver that can consume the blocking FIFO;
5723    // otherwise arm a normal Rayon drainer.
5724    if !self.parked_drivers.wake_one_blocking() {
5725      self.ensure_drainer();
5726    }
5727  }
5728
5729  #[cfg(test)]
5730  fn schedule_blocking<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5731  where
5732    F: FnOnce() -> T + Send + 'static,
5733    T: Send + 'static,
5734  {
5735    self.schedule_blocking_result(move || catch_unwind_join_error(function))
5736  }
5737
5738  fn schedule_blocking_result<F, T>(self: &Arc<Self>, function: F) -> JoinHandle<T>
5739  where
5740    F: FnOnce() -> Result<T, JoinError> + Send + 'static,
5741    T: Send + 'static,
5742  {
5743    let mut admission = self.begin_deadlock_admission();
5744    let (sender, receiver) = oneshot::channel();
5745    let generation = self.generation;
5746    let dependency = BlockingDependency {
5747      job: BlockingJobId(next_unique_id(
5748        &NEXT_BLOCKING_JOB_ID,
5749        "blocking job id space exhausted",
5750      )),
5751      owner: BLOCKING_OWNER
5752        .with(std::cell::Cell::get)
5753        .filter(|owner| owner.executor_id == self.id),
5754    };
5755    // Enqueue-time progress signal, BEFORE the queue push/wake: the deadlock
5756    // detector's fingerprint must be able to observe
5757    // this submission even while the job merely sits queued -- symmetric with
5758    // `runnable_scheduled` on the runnable path.
5759    self.metrics.blocking_scheduled();
5760    let queued = QueuedBlocking {
5761      id: dependency.job,
5762      run: Box::new(move || {
5763        let result = function().map(|output| ContainedTaskOutput::new(output, generation));
5764        let _ = sender.send(result);
5765      }),
5766    };
5767    let rejected = {
5768      let mut queue = self
5769        .blocking_queue
5770        .lock()
5771        .unwrap_or_else(std::sync::PoisonError::into_inner);
5772      if queue.closed {
5773        Some(queued)
5774      } else {
5775        queue.push(queued);
5776        None
5777      }
5778    };
5779    if let Some(rejected) = rejected {
5780      // Shutdown may close the queue after the controller registered this
5781      // generation work but before it reaches the executor. Drop captured
5782      // user values outside the queue lock and contain hostile destructors,
5783      // matching the queued-job shutdown path.
5784      let _generation = RuntimeGenerationGuard::enter(self.generation);
5785      let _ = catch_unwind_contained(|| drop(rejected));
5786    } else {
5787      // Wake a blocking-capable cooperative driver, or arm a normal drainer.
5788      self.wake_for_blocking_work();
5789      if let Some(admission) = admission.as_mut() {
5790        admission.mark_published();
5791      }
5792    }
5793    JoinHandle(JoinHandleInner::Blocking {
5794      receiver,
5795      dependency,
5796      published: PublishedBlockingDependencies::default(),
5797    })
5798  }
5799
5800  fn ensure_drainer(self: &Arc<Self>) {
5801    loop {
5802      let active = self.active_drainers.load(Ordering::Acquire);
5803      if active >= self.max_drainers {
5804        return;
5805      }
5806      if self
5807        .active_drainers
5808        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
5809        .is_ok()
5810      {
5811        let executor = Arc::clone(self);
5812        self.pool.spawn_fifo(move || executor.drain());
5813        return;
5814      }
5815    }
5816  }
5817
5818  fn drain(self: Arc<Self>) {
5819    // LINGER GATE: captured BEFORE the guard installs the marker below. In
5820    // production `drain` only ever runs via `pool.spawn_fifo`, i.e. on a pool
5821    // worker whose start handler already set `ON_POOL_WORKER`, so lingering is
5822    // active there; a direct test call from a foreign thread keeps the legacy
5823    // return-when-empty shape.
5824    let on_pool_worker = ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id);
5825    // Mark this worker so a re-entrant `block_on` (reached from a polled task)
5826    // drives the queue cooperatively instead of parking the worker.
5827    let _on_pool = OnPoolWorkerGuard::enter(self.id);
5828    // Unwind backstop: never leave a runnable stranded in
5829    // the LIFO slot. Normal exits flush explicitly BEFORE `finish_draining`
5830    // below (so its re-check can see the flushed runnable); this guard only
5831    // covers unwinds, which should be impossible (runnables and blocking
5832    // wrappers are catch_unwind'd) but must not lose a task if they happen.
5833    let _slot_backstop = LifoSlotFlushGuard(&self);
5834
5835    let linger_budget = self.drain_linger.filter(|_| on_pool_worker);
5836    // Linger state (parker + generation-stop registration), created lazily on
5837    // this frame's first idle episode and reused for the rest of the frame.
5838    let mut linger: Option<DrainLinger> = None;
5839    // Absolute exit deadline of the CURRENT idle episode, armed on the first
5840    // empty observation after work ran. Spurious wakes (timer churn, a stolen
5841    // queue wake) re-park with the REMAINING time rather than a fresh budget,
5842    // so a lingering drainer occupies its pool slot for at most
5843    // `linger_budget` past the last work unit it executed.
5844    let mut idle_deadline: Option<Instant> = None;
5845    // Absolute residence deadline of the WHOLE frame, armed on the frame's
5846    // FIRST idle observation and -- unlike `idle_deadline` -- never reset by
5847    // executed work. Without it, periodic sub-budget work re-arms the episode
5848    // deadline forever and this frame never returns its rayon worker; see
5849    // [`DRAIN_LINGER_FRAME_FACTOR`].
5850    let mut frame_deadline: Option<Instant> = None;
5851
5852    'frame: loop {
5853      let mut runnable_streak = 0usize;
5854      for _ in 0..Self::RUNNABLE_BUDGET {
5855        if self.run_one_fair(&mut runnable_streak) {
5856          idle_deadline = None;
5857          continue;
5858        }
5859        // Queue observed empty. MANDATORY flush before the shared exit AND
5860        // before any park: slot work is invisible to other threads and to
5861        // `finish_draining`'s re-check, so no path may leave this loop (or
5862        // sleep) with an occupied slot. On this path the slot was observed
5863        // empty at the top of the iteration and no user code ran since, so
5864        // the flush is defensive -- kept unconditional so every exit upholds
5865        // the stranding invariant by construction.
5866        self.flush_lifo_slot();
5867        // IDLE LINGER: instead of exiting and paying a full
5868        // spawn_fifo/rayon-idle-protocol round trip on the NEXT wave, park on
5869        // our own parker, registered in `parked_drivers` so
5870        // `wake_for_new_work`/`wake_for_blocking_work` deliver ONE targeted
5871        // wake. `active_drainers` stays counted for the whole linger: this
5872        // frame is still a live drainer, so `ensure_drainer`'s cap and the
5873        // idle-wait predicate keep their meaning.
5874        //
5875        // LOST-WAKEUP ARGUMENT (identical protocol to a cooperative driver's
5876        // park): register FIRST (the registry `count` store), THEN re-check
5877        // the shared queues (`has_queued_work_for_role`'s SeqCst fence pairs
5878        // with `push_to_queue_and_wake`'s post-push fence), THEN park -- see
5879        // [`ParkedDrivers::wake_one`]. On the timeout path, `wake_one` may
5880        // have popped this parker (counting the wake as DELIVERED, so the
5881        // producer spawned no drainer) while its `unpark` is still in flight:
5882        // the pop precedes our `deregister` on the registry mutex, so the
5883        // producer's push (made BEFORE its wake) is visible to the
5884        // post-deregister permit/queue re-checks below, which then RESUME
5885        // draining instead of exiting. Only a truly empty re-check falls
5886        // through to `finish_draining`, whose own post-decrement re-check
5887        // remains the existing final net for pushes racing the exit.
5888        let Some(budget) = linger_budget else {
5889          break 'frame;
5890        };
5891        if self.stop.is_stopping() {
5892          break 'frame;
5893        }
5894        let now = Instant::now();
5895        // FRAME RESIDENCE BOUND: exiting here (or via the min-capped park
5896        // below) funnels through the UNCHANGED finish_draining path, whose
5897        // re-check re-arms a FIFO drain via ensure_drainer/spawn_fifo when
5898        // work remains -- that re-entry through rayon's queue is exactly the
5899        // interleaving point that lets pending raw rayon work run.
5900        let frame = *frame_deadline.get_or_insert_with(|| {
5901          now
5902            + budget
5903              .saturating_mul(DRAIN_LINGER_FRAME_FACTOR)
5904              .min(MAX_DRAIN_LINGER_FRAME_RESIDENCE)
5905        });
5906        let deadline = (*idle_deadline.get_or_insert_with(|| now + budget)).min(frame);
5907        if now >= deadline {
5908          // Deadline EXPIRY observed at the top: either a wake consumed the
5909          // rest of the episode's idle budget, or the frame residence bound
5910          // elapsed while work kept re-arming the episode deadline. Either
5911          // way this frame's lingering is over, and the linger parker of the
5912          // previous episode -- if one registered -- was already deregistered
5913          // after its park.
5914          #[cfg(test)]
5915          self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5916          break 'frame;
5917        }
5918        let state = match &linger {
5919          Some(state) => state,
5920          None => linger.insert(DrainLinger::new(&self)),
5921        };
5922        self
5923          .parked_drivers
5924          .register_with_role(&state.parker, true, None);
5925        #[cfg(test)]
5926        self
5927          .drain_linger_registrations
5928          .fetch_add(1, Ordering::SeqCst);
5929        if self.has_queued_work_for_role(true) {
5930          self.parked_drivers.deregister(&state.parker);
5931          continue 'frame;
5932        }
5933        // Stop re-check AFTER registering: `begin_shutdown` unparks every
5934        // generation-stop-registered parker (ours included, via
5935        // `DrainLinger`'s registration), so a stop publishing after this
5936        // check still cuts the park short instead of waiting out the budget.
5937        if self.stop.is_stopping() {
5938          self.parked_drivers.deregister(&state.parker);
5939          break 'frame;
5940        }
5941        let woken = state.parker.park_timeout(deadline - now);
5942        self.parked_drivers.deregister(&state.parker);
5943        if woken || state.parker.consume_permit() || self.has_queued_work_for_role(true) {
5944          continue 'frame;
5945        }
5946        // Idle-budget EXPIRY: the park timed out with no wake, no permit and
5947        // no queued work. Counted AFTER the deregister above, so a test
5948        // observing `drain_linger_expiries` rise knows the linger parker has
5949        // permanently left the registry for this idle episode.
5950        #[cfg(test)]
5951        self.drain_linger_expiries.fetch_add(1, Ordering::SeqCst);
5952        break 'frame;
5953      }
5954      // Budget exhausted: yield this worker back to rayon exactly as before
5955      // the linger existed -- `finish_draining` re-arms a FIFO drain job
5956      // BEHIND any pending rayon work (par_iter splits, nested jobs), which
5957      // is the pool-fairness property the budget exists for.
5958      break 'frame;
5959    }
5960
5961    // MANDATORY flush before `finish_draining` so its
5962    // re-check sees a slot runnable on the shared FIFO and re-arms a drainer.
5963    self.flush_lifo_slot();
5964    self.finish_draining();
5965  }
5966
5967  /// Pop this worker's LIFO slot if it holds a runnable of THIS executor.
5968  /// A foreign executor's entry is left untouched (see the scoping note at
5969  /// `LIFO_SLOT`).
5970  fn pop_lifo_slot(&self) -> Option<Runnable> {
5971    LIFO_SLOT.with(|slot| match slot.take() {
5972      Some((id, runnable)) if id == self.id => Some(runnable),
5973      other => {
5974        slot.set(other);
5975        None
5976      }
5977    })
5978  }
5979
5980  /// Move this worker's slot entry (same-executor only) to the shared FIFO,
5981  /// with a wake so any worker can pick it up. MANDATORY on every path where
5982  /// this thread stops draining this executor: `drain`'s
5983  /// returns (BEFORE `finish_draining`), before a cooperative park, and on
5984  /// unwind via `LifoSlotFlushGuard` -- a runnable stranded in a thread-local
5985  /// slot while its thread leaves executor code is a silently lost task.
5986  fn flush_lifo_slot(self: &Arc<Self>) {
5987    let mut admission = self.begin_deadlock_admission();
5988    if let Some(runnable) = self.pop_lifo_slot() {
5989      self.push_to_queue_and_wake(runnable);
5990      if let Some(admission) = admission.as_mut() {
5991        admission.mark_published();
5992      }
5993    }
5994  }
5995
5996  /// Mint a fresh owner token for a counted blocking closure entering on this
5997  /// executor. Each entry gets a unique `frame` so nested owners on the same
5998  /// thread can never run each other's queued jobs over the cap.
5999  fn fresh_owner_token(&self) -> BlockingOwnerToken {
6000    BlockingOwnerToken {
6001      executor_id: self.id,
6002      frame: next_unique_id(
6003        &NEXT_BLOCKING_FRAME,
6004        "blocking owner frame id space exhausted",
6005      ),
6006    }
6007  }
6008
6009  fn finish_draining(self: &Arc<Self>) {
6010    // LIFO-slot invariant: this re-check reads only the
6011    // SHARED queues -- per-worker slots are invisible to it, and that is
6012    // sound because slot work never needs a foreign observer. A slot entry is
6013    // always either popped and run by its owning thread (drain / run_one) or
6014    // flushed to the shared FIFO by that same thread BEFORE it stops draining
6015    // (both `drain` returns flush ahead of this call; `LifoSlotFlushGuard`
6016    // covers unwinds; cooperative parks flush defensively). No thread leaves
6017    // executor code with its slot occupied, so the exit-then-respawn window
6018    // below cannot hide slot work from `ensure_drainer`.
6019    self.active_drainers.fetch_sub(1, Ordering::AcqRel);
6020    let has_runnable = self.has_queued_runnable();
6021    let has_blocking = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6022      && !self
6023        .blocking_queue
6024        .lock()
6025        .unwrap_or_else(std::sync::PoisonError::into_inner)
6026        .is_empty();
6027    if has_runnable || has_blocking {
6028      self.ensure_drainer();
6029    }
6030    // The lock is still taken unconditionally: it is the release edge that
6031    // publishes the `active_drainers` decrement above to a waiter that is about
6032    // to evaluate the predicate. Only the wake is now conditional, and the
6033    // waiter count is read under the very mutex the waiter must hold to
6034    // register, so a waiter is never missed. See `wait_until_scheduler_idle`.
6035    let idle = self
6036      .scheduler_idle_lock
6037      .lock()
6038      .unwrap_or_else(std::sync::PoisonError::into_inner);
6039    if *idle > 0 {
6040      self.scheduler_idle.notify_all();
6041    }
6042  }
6043
6044  fn block_on(self: &Arc<Self>, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
6045    if ON_POOL_WORKER.with(std::cell::Cell::get) == Some(self.id) {
6046      // Re-entrant call from a pool worker OF THIS EXECUTOR: parking here would
6047      // freeze a worker that the awaited future may itself need to make
6048      // progress. Drive the queue cooperatively instead (mirrors the
6049      // CurrentThread drive loop). The id check mirrors the
6050      // `BlockingOwnerToken.executor_id` gate: a worker of a DIFFERENT executor
6051      // owns none of this executor's accounting, so it must take the parking
6052      // path below rather than drive these queues as an unaccounted driver.
6053      self.cooperative_block_on(future)
6054    } else {
6055      // Non-pool (e.g. napi caller) thread, or a pool worker of another
6056      // executor: park without driving queues. The runtime owns this park so
6057      // generation shutdown can wake and cancel it.
6058      self.parking_block_on(future)
6059    }
6060  }
6061
6062  fn parking_block_on(
6063    self: &Arc<Self>,
6064    mut future: Pin<&mut dyn Future<Output = ()>>,
6065  ) -> BlockOnOutcome {
6066    let parker = Arc::new(DriverParker::default());
6067    let _stop_registration = self.stop.register(&parker);
6068    let waker = Waker::from(Arc::clone(&parker));
6069    let mut cx = Context::from_waker(&waker);
6070    loop {
6071      if self.stop.is_stopping() {
6072        return BlockOnOutcome::Stopped;
6073      }
6074      if future.as_mut().poll(&mut cx).is_ready() {
6075        return BlockOnOutcome::Completed;
6076      }
6077      if self.stop.is_stopping() {
6078        return BlockOnOutcome::Stopped;
6079      }
6080      parker.park();
6081    }
6082  }
6083
6084  fn run_runnable(
6085    self: &Arc<Self>,
6086    runnable: Runnable,
6087    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6088  ) {
6089    // Ownership is lexical to a blocking closure. A runnable driven from that
6090    // closure must not borrow its over-cap privilege.
6091    let _non_owner = BlockingOwnerGuard::enter(None);
6092    let _active = self.metrics.runnable_started();
6093    #[cfg(test)]
6094    run_multi_thread_work_started_test_hook();
6095    // The queue/slot removal is invisible to the deadline verdict until start
6096    // accounting advances. Keep the detector gate open across exactly that gap,
6097    // but never across a user poll.
6098    drop(admission);
6099    let _ = catch_unwind_contained(|| runnable.run());
6100  }
6101
6102  fn cancel_runnable(
6103    &self,
6104    runnable: Runnable,
6105    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6106  ) {
6107    self.metrics.runnable_cancelled();
6108    // Cancellation accounting closes the dequeue gap just as start accounting
6109    // does for a runnable that will be polled.
6110    drop(admission);
6111    let _generation = RuntimeGenerationGuard::enter(self.generation);
6112    let _ = catch_unwind_contained(|| drop(runnable));
6113  }
6114
6115  fn finish_runnable_claim(
6116    self: &Arc<Self>,
6117    claim: RunnableClaim,
6118    admission: Option<MultiThreadDeadlockAdmissionGuard>,
6119  ) -> bool {
6120    match claim {
6121      RunnableClaim::Empty => {
6122        drop(admission);
6123        false
6124      }
6125      RunnableClaim::Run(runnable) => {
6126        #[cfg(test)]
6127        run_multi_thread_work_claimed_test_hook();
6128        self.run_runnable(runnable, admission);
6129        true
6130      }
6131      RunnableClaim::Cancel(runnable) => {
6132        #[cfg(test)]
6133        run_multi_thread_work_claimed_test_hook();
6134        self.cancel_runnable(runnable, admission);
6135        true
6136      }
6137    }
6138  }
6139
6140  fn runnable_claim(stopping: bool, runnable: Option<Runnable>) -> RunnableClaim {
6141    match runnable {
6142      Some(runnable) if stopping => RunnableClaim::Cancel(runnable),
6143      Some(runnable) => RunnableClaim::Run(runnable),
6144      None => RunnableClaim::Empty,
6145    }
6146  }
6147
6148  /// Lock-free single-item steal from the shared injector, retrying the
6149  /// transient `Steal::Retry` contention state. Replaces the former
6150  /// `queue.lock().pop_front()`; `Injector` preserves FIFO order.
6151  fn steal_one(&self) -> Option<Runnable> {
6152    loop {
6153      match self.injector.steal() {
6154        Steal::Success(runnable) => return Some(runnable),
6155        Steal::Empty => return None,
6156        Steal::Retry => continue,
6157      }
6158    }
6159  }
6160
6161  /// Test-only raw FIFO enqueue: the former `queue.lock().push_back()` staging
6162  /// that tests use to seed queued work WITHOUT the accompanying wake (so no
6163  /// drainer races the manual `run_one`/`drain` calls). Keeps the post-push
6164  /// SeqCst fence from `push_to_queue_and_wake` so any wake/park interaction a
6165  /// test performs afterwards still sees the Dekker pairing documented on
6166  /// `injector`.
6167  #[cfg(test)]
6168  fn push_fifo_raw(&self, runnable: Runnable) {
6169    self.injector.push(runnable);
6170    std::sync::atomic::fence(Ordering::SeqCst);
6171  }
6172
6173  fn claim_fifo_runnable(&self) -> RunnableClaim {
6174    let stop_publication = self.stop.runnable_claim_guard();
6175    let runnable = self.steal_one();
6176    let stopping = stop_publication.is_stopping();
6177    drop(stop_publication);
6178    Self::runnable_claim(stopping, runnable)
6179  }
6180
6181  fn claim_runnable(&self) -> RunnableClaim {
6182    let stop_publication = self.stop.runnable_claim_guard();
6183    let runnable = self.pop_lifo_slot().or_else(|| self.steal_one());
6184    let stopping = stop_publication.is_stopping();
6185    drop(stop_publication);
6186    Self::runnable_claim(stopping, runnable)
6187  }
6188
6189  fn run_one_fifo_runnable(self: &Arc<Self>) -> bool {
6190    let admission = self.begin_deadlock_admission();
6191    let claim = self.claim_fifo_runnable();
6192    self.finish_runnable_claim(claim, admission)
6193  }
6194
6195  /// Run one runnable, preferring this worker's LIFO slot over the shared FIFO.
6196  fn run_one_runnable(self: &Arc<Self>) -> bool {
6197    let admission = self.begin_deadlock_admission();
6198    // Same owner-clear as the FIFO-only path: a runnable from the slot must
6199    // not smuggle an owner frame's over-cap privilege.
6200    let claim = self.claim_runnable();
6201    self.finish_runnable_claim(claim, admission)
6202  }
6203
6204  fn run_one_blocking(self: &Arc<Self>) -> bool {
6205    if let Some((blocking, admission)) = self.take_blocking() {
6206      let _generation = RuntimeGenerationGuard::enter(self.generation);
6207      {
6208        let _task = self.metrics.blocking_started(true);
6209        #[cfg(test)]
6210        run_multi_thread_work_started_test_hook();
6211        // Publish start before the dequeue admission retires. The detector can
6212        // then observe either the queued job, the active admission, or progress.
6213        drop(admission);
6214        let owner = self.fresh_owner_token();
6215        let _active_owner = ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, owner);
6216        let _owner = BlockingOwnerGuard::enter(Some(owner));
6217        // The queued wrapper contains panics from the user function, but
6218        // delivering its result can still drop user-owned state when the
6219        // receiver is gone. Contain that destructor boundary as well so an
6220        // unwind cannot bypass blocking-slot and drainer retirement.
6221        let _ = catch_unwind_contained(blocking);
6222      }
6223      self.active_blocking.fetch_sub(1, Ordering::Release);
6224      return true;
6225    }
6226    false
6227  }
6228
6229  /// Run one fair scheduler unit. Runnable locality remains the normal
6230  /// priority, but a continuously hot runnable stream must yield one quantum
6231  /// to the blocking FIFO.
6232  fn run_one_fair(self: &Arc<Self>, runnable_streak: &mut usize) -> bool {
6233    self.run_one_fair_with_exact(runnable_streak, None)
6234  }
6235
6236  /// Cooperative variant of [`Self::run_one_fair`]. Once runnable priority has
6237  /// been satisfied, a live exact dependency must claim its owner's lane before
6238  /// unrelated FIFO blocking work can consume the last ordinary slot.
6239  fn run_one_fair_with_exact(
6240    self: &Arc<Self>,
6241    runnable_streak: &mut usize,
6242    exact_dependency: Option<&TaskDependency>,
6243  ) -> bool {
6244    let mut exact_attempted = false;
6245    if *runnable_streak >= Self::RUNNABLE_FAIRNESS_QUANTUM {
6246      *runnable_streak = 0;
6247      if let Some(dependency) = exact_dependency {
6248        exact_attempted = true;
6249        if self.try_owned_blocking_over_cap(dependency) {
6250          return true;
6251        }
6252      }
6253      if self.run_one_blocking() {
6254        return true;
6255      }
6256    }
6257    if self.run_one_runnable() {
6258      *runnable_streak += 1;
6259      return true;
6260    }
6261    if !exact_attempted
6262      && exact_dependency.is_some_and(|dependency| self.try_owned_blocking_over_cap(dependency))
6263    {
6264      *runnable_streak = 0;
6265      return true;
6266    }
6267    if self.run_one_blocking() {
6268      *runnable_streak = 0;
6269      return true;
6270    }
6271    false
6272  }
6273
6274  #[cfg(test)]
6275  fn run_one(self: &Arc<Self>) -> bool {
6276    let mut runnable_streak = 0;
6277    self.run_one_fair(&mut runnable_streak)
6278  }
6279
6280  /// Exact-dependency fallback for a re-entrant `block_on` that would otherwise
6281  /// park while the blocking cap is saturated. The dependency carries its
6282  /// exact owner frame through JoinHandle lineage. Reserving that frame
6283  /// transfers its counted lane to this otherwise-idle cooperative driver.
6284  ///
6285  /// Owner lending is lexical: the cooperative driver must carry the exact
6286  /// blocking frame whose counted lane it reuses. Rayon does not propagate
6287  /// thread-local ancestry to stolen jobs, and registry cardinality cannot
6288  /// prove that an untagged worker belongs to the sole active owner.
6289  fn run_owned_blocking_over_cap(
6290    self: &Arc<Self>,
6291    dependency: &TaskDependency,
6292  ) -> OwnedBlockingOutcome {
6293    #[cfg(test)]
6294    self.owner_lending_attempts.fetch_add(1, Ordering::Relaxed);
6295
6296    let Some(ambient_owner) = BLOCKING_OWNER
6297      .with(std::cell::Cell::get)
6298      .filter(|owner| owner.executor_id == self.id)
6299    else {
6300      return OwnedBlockingOutcome::Ineligible;
6301    };
6302    let Some(current) = dependency.select_for_owner(ambient_owner) else {
6303      return OwnedBlockingOutcome::Ineligible;
6304    };
6305    let admission = self.begin_deadlock_admission();
6306    let owner = current
6307      .publication
6308      .dependency
6309      .owner
6310      .expect("selected dependency must carry its owner");
6311    let Some(reservation) = OwnerLaneReservation::try_acquire(&self.active_blocking_owners, owner)
6312    else {
6313      return OwnedBlockingOutcome::OwnerBusy(owner);
6314    };
6315    #[cfg(test)]
6316    run_before_owned_blocking_queue_claim_test_hook();
6317    let job = {
6318      let mut queue = self
6319        .blocking_queue
6320        .lock()
6321        .unwrap_or_else(std::sync::PoisonError::into_inner);
6322      // Shutdown publishes stop before waiting on this queue mutex. Recheck both
6323      // admission states before consuming the dependency so a shutdown that
6324      // reached this claim first leaves the job queued for cancellation.
6325      if queue.closed || self.stop.is_stopping() {
6326        None
6327      // Claim while holding the queue lock. A concurrent clear/switch that
6328      // wins first makes this fail; once the claim succeeds, no stale snapshot
6329      // can authorize removal of the old job.
6330      } else if dependency.try_claim(&current) {
6331        queue.remove(current.publication.dependency.job)
6332      } else {
6333        None
6334      }
6335    };
6336    let ran = if let Some(job) = job {
6337      #[cfg(test)]
6338      run_multi_thread_work_claimed_test_hook();
6339      let _generation = RuntimeGenerationGuard::enter(self.generation);
6340      {
6341        let _task = self.metrics.blocking_started(false);
6342        #[cfg(test)]
6343        run_multi_thread_work_started_test_hook();
6344        // The exact job is no longer visible in the indexed FIFO. Publish its
6345        // start before allowing a deadline verdict to close the admission gate.
6346        drop(admission);
6347        let nested_owner = self.fresh_owner_token();
6348        let _active_owner =
6349          ActiveBlockingOwnerGuard::enter(&self.active_blocking_owners, nested_owner);
6350        let _owner = BlockingOwnerGuard::enter(Some(nested_owner));
6351        let _ = catch_unwind_contained(job);
6352      }
6353      true
6354    } else {
6355      drop(admission);
6356      false
6357    };
6358    // Every reservation release can make another exact dependency serviceable,
6359    // including failed-claim and already-removed-job exits.
6360    self.release_owner_lane_and_wake(reservation);
6361    OwnedBlockingOutcome::ReservationReleased { owner, ran }
6362  }
6363
6364  fn try_owned_blocking_over_cap(self: &Arc<Self>, dependency: &TaskDependency) -> bool {
6365    matches!(
6366      self.run_owned_blocking_over_cap(dependency),
6367      OwnedBlockingOutcome::ReservationReleased { ran: true, .. }
6368    )
6369  }
6370
6371  fn service_owner_handoff(
6372    self: &Arc<Self>,
6373    parker: &DriverParker,
6374    dependency: &TaskDependency,
6375    can_run_blocking: bool,
6376  ) -> Option<bool> {
6377    let handoff_owner = parker.take_owner_handoff()?;
6378    if !can_run_blocking {
6379      self.wake_one_owner_dependency(handoff_owner);
6380      return Some(false);
6381    }
6382    match self.run_owned_blocking_over_cap(dependency) {
6383      OwnedBlockingOutcome::Ineligible => {
6384        self.wake_one_owner_dependency(handoff_owner);
6385        Some(false)
6386      }
6387      OwnedBlockingOutcome::OwnerBusy(owner) => {
6388        if owner != handoff_owner {
6389          self.wake_one_owner_dependency(handoff_owner);
6390        }
6391        Some(false)
6392      }
6393      OwnedBlockingOutcome::ReservationReleased { owner, ran } => {
6394        if owner != handoff_owner {
6395          self.wake_one_owner_dependency(handoff_owner);
6396        }
6397        Some(ran)
6398      }
6399    }
6400  }
6401
6402  fn forward_owner_handoff(&self, parker: &DriverParker) {
6403    if let Some(owner) = parker.take_owner_handoff() {
6404      self.wake_one_owner_dependency(owner);
6405    }
6406  }
6407
6408  fn owner_dependency_lane_available(&self, dependency: &TaskDependency) -> bool {
6409    let Some(ambient_owner) = BLOCKING_OWNER
6410      .with(std::cell::Cell::get)
6411      .filter(|owner| owner.executor_id == self.id)
6412    else {
6413      return false;
6414    };
6415    self.active_blocking_owners.is_available(ambient_owner)
6416      && dependency.has_live_owner_dependency(ambient_owner, OwnerDependencyMode::Available)
6417  }
6418
6419  fn has_exact_owner_work_for_role(
6420    &self,
6421    parker: &DriverParker,
6422    dependency: &TaskDependency,
6423    can_run_blocking: bool,
6424  ) -> bool {
6425    parker.has_owner_handoff()
6426      || (can_run_blocking && self.owner_dependency_lane_available(dependency))
6427  }
6428
6429  /// Cooperative `block_on` for a re-entrant call from a pool worker. Instead
6430  /// of parking the worker (which would freeze one of the pool's fixed OS
6431  /// threads), it drives the awaited future while servicing queued work. When
6432  /// nothing is immediately runnable it parks on its own [`DriverParker`],
6433  /// which is woken directly by the awaited future's waker and through the
6434  /// `parked_drivers` registry by `schedule`/`schedule_blocking`, so a later
6435  /// wake reaches this worker even when every worker is parked and
6436  /// `ensure_drainer` has no free thread to spawn a replacement.
6437  fn cooperative_block_on(
6438    self: &Arc<Self>,
6439    mut future: Pin<&mut dyn Future<Output = ()>>,
6440  ) -> BlockOnOutcome {
6441    // One parker per `block_on` frame. Nested re-entrant frames each own one:
6442    // only the innermost frame runs or parks at any moment, and an outer
6443    // frame's future-wake is stored as its parker's permit, consumed when
6444    // control returns to that frame's loop.
6445    let parker = Arc::new(DriverParker::default());
6446    let _stop_registration = self.stop.register(&parker);
6447    let dependency = BlockOnDependencyGuard::enter(self.generation);
6448    let driver_role = IN_SCHEDULER_DRIVER.with(std::cell::Cell::get);
6449    let can_run_blocking =
6450      driver_role.is_none_or(|role| role.executor_id != self.id || role.can_run_blocking);
6451    let _exit = CooperativeDriverExitGuard {
6452      executor: self,
6453      parker: &parker,
6454      can_run_blocking,
6455    };
6456    // WAKE-DOMAIN SPLIT: the awaited future is polled with
6457    // THIS driver's own parker as its waker, so a future-wake targets exactly
6458    // the driver that awaits it -- delivered as a stored permit (skip next
6459    // park) when this driver is currently running, so it is never lost.
6460    let waker = std::task::Waker::from(Arc::clone(&parker));
6461    let mut cx = Context::from_waker(&waker);
6462    // Consecutive `run_one` successes since the last yield point: the
6463    // cooperative loop mirrors `drain`'s RUNNABLE_BUDGET streak cap. Without
6464    // it, a hot slot chain (each runnable schedules a successor into this
6465    // worker's slot, which `run_one` pops BEFORE the FIFO) would keep this
6466    // loop inside `run_one` forever and the shared FIFO would never be
6467    // reached from this thread -- a deadlock on a 1-worker pool when the
6468    // awaited future depends on a FIFO task.
6469    let mut runnable_streak = 0usize;
6470    let mut work_streak = 0usize;
6471    let mut force_fifo = false;
6472    loop {
6473      if self.stop.is_stopping() {
6474        return BlockOnOutcome::Stopped;
6475      }
6476      dependency.clear();
6477      if future.as_mut().poll(&mut cx).is_ready() {
6478        return BlockOnOutcome::Completed;
6479      }
6480      if self.stop.is_stopping() {
6481        return BlockOnOutcome::Stopped;
6482      }
6483      if self
6484        .service_owner_handoff(&parker, &dependency.dependency, can_run_blocking)
6485        .is_some_and(|ran| ran)
6486      {
6487        continue;
6488      }
6489      if force_fifo {
6490        force_fifo = false;
6491        if self.run_one_fifo_runnable() {
6492          runnable_streak += 1;
6493          work_streak += 1;
6494          continue;
6495        }
6496      }
6497      let ran_work = if can_run_blocking {
6498        self.run_one_fair_with_exact(&mut runnable_streak, Some(&dependency.dependency))
6499      } else if self.run_one_runnable() {
6500        runnable_streak += 1;
6501        true
6502      } else {
6503        false
6504      };
6505      if ran_work {
6506        work_streak += 1;
6507        if work_streak >= Self::RUNNABLE_BUDGET {
6508          // Budget yield: move a hot slot occupant to the shared FIFO (with
6509          // a wake), so the next `run_one` pops the FIFO front (the oldest
6510          // work) and other workers can steal the chain. The awaited future
6511          // is polled every iteration regardless, so it is never starved.
6512          self.flush_lifo_slot();
6513          work_streak = 0;
6514          force_fifo = true;
6515        }
6516        continue;
6517      }
6518      runnable_streak = 0;
6519      work_streak = 0;
6520      // MANDATORY pre-park flush. Defensive on every
6521      // currently reachable path: `run_one` above popped the slot and no user
6522      // code ran since -- but parking with a stranded slot runnable would
6523      // silently lose that task, so the invariant is upheld unconditionally.
6524      self.flush_lifo_slot();
6525      // Park protocol: register FIRST, then re-check, then park -- the
6526      // waiter-side mirror of the push-then-wake schedule side. See the
6527      // lost-wakeup argument at [`ParkedDrivers::wake_one`].
6528      self.parked_drivers.register_with_role(
6529        &parker,
6530        can_run_blocking,
6531        Some(Arc::clone(&dependency.dependency)),
6532      );
6533      if self.has_queued_work_for_role(can_run_blocking)
6534        || (can_run_blocking && self.owner_dependency_lane_available(&dependency.dependency))
6535      {
6536        self.parked_drivers.deregister(&parker);
6537        continue;
6538      }
6539      if self.stop.is_stopping() {
6540        return BlockOnOutcome::Stopped;
6541      }
6542      // TIMER-BOUNDED PARK: while any heap
6543      // timer is pending, this park is a TIMER WAIT, not a deadlock
6544      // candidate -- the wall clock is a guaranteed wake source -- so it uses
6545      // its own bounded primitive instead of `park`/`park_with_deadline` and
6546      // fires whatever came due on the way out. An armed park deadline is
6547      // deliberately NOT evaluated during a timer wait: the fire schedules
6548      // the woken task (fingerprint progress), and the next timerless park
6549      // arms a fresh full window, so genuine-deadlock detection is delayed
6550      // past the timer, never lost. This is also load-bearing for liveness:
6551      // on a 1-worker pool whose only thread is parked HERE, the timekeeper
6552      // job has no free thread to run on, so this driver must fire due
6553      // timers itself. (A timer registered between the deadline read and the
6554      // park re-arms this parker through `note_earlier_timer`'s
6555      // `wake_one` -- we are registered -- or wakes the timekeeper.)
6556      if let Some(next) = self.next_timer_deadline() {
6557        let now = Instant::now();
6558        if next > now {
6559          parker.park_timeout(next - now);
6560        }
6561        self.parked_drivers.deregister(&parker);
6562        self.fire_due_timers();
6563        continue;
6564      }
6565      match self.park_deadline {
6566        None => parker.park(),
6567        Some(deadline) => {
6568          // Deadline-bounded cooperative park with PROGRESS-BASED reset: a
6569          // candidate deadlock only when a
6570          // FULL deadline window passes with zero executor progress. The
6571          // foreign-thread whole-build park in `block_on`'s else-branch is
6572          // exempt by design.
6573          let deadlock_publications = self.deadlock_state.publications();
6574          match park_with_deadline(&parker, deadline, &self.metrics) {
6575            DeadlineParkOutcome::Woken => {}
6576            DeadlineParkOutcome::Expired(armed) => {
6577              #[cfg(test)]
6578              run_deadline_expiry_test_hook();
6579              // EXPIRY DECISION. While this parker was
6580              // registered, a racing `wake_one` may have popped it, stored
6581              // its permit and reported the wake as DELIVERED (so the
6582              // scheduler spawned no drainer) -- panicking now would swallow
6583              // a healthy wake and kill a live build. Deregister FIRST so no
6584              // FURTHER wake can be counted against this parker, then
6585              // re-check every way a wake could have landed around the
6586              // expiry:
6587              //   * permit: a `wake_one` (or direct future-wake) that landed
6588              //     while still registered MUST be acted on;
6589              //   * queued work: the work behind such a wake was pushed
6590              //     under the queue mutex BEFORE `wake_one` popped us, so
6591              //     even if its `unpark` is still in flight the queue
6592              //     re-check observes it;
6593              //   * pending timers: arming a timer bumps NO progress counter
6594              //     by design, so a registration racing this already-
6595              //     committed park (it read an empty heap before parking) is
6596              //     invisible to every other re-check -- yet its wake (the
6597              //     fire) is wall-clock-guaranteed, so this park is healthy.
6598              //     The loop then re-parks TIMER-BOUNDED (see above) and
6599              //     fires the timer itself if needed;
6600              //   * fingerprint: the verdict requires ZERO progress, and the
6601              //     read inside `park_with_deadline` is stale by the time we
6602              //     get here -- re-read as the LAST step. Both ENQUEUE
6603              //     counters are in the fingerprint (`runnable_schedules`
6604              //     and `blocking_tasks_scheduled`, both bumped before the
6605              //     push), so a submission of EITHER kind
6606              //     landing after the queue re-check above is still caught
6607              //     here.
6608              // Any hit => not a deadlock: continue the cooperative loop
6609              // (fresh future poll, `run_one` picks up the work, and the
6610              // next park arms a fresh FULL deadline window). The residue is
6611              // exactly a BARE direct future-wake -- no queue push, no
6612              // counter movement -- landing in the few instructions after
6613              // these re-checks: the inherent, unclosable residue of any
6614              // timing-based detector, one reason the deadline is opt-in.
6615              self.parked_drivers.deregister(&parker);
6616              if parker.consume_permit() || self.has_queued_work_for_role(can_run_blocking) {
6617                continue;
6618              }
6619              // Test-only seam between the permit/queued-work re-checks and
6620              // the fingerprint verdict.
6621              #[cfg(test)]
6622              run_deadline_verdict_test_hook();
6623              if self.metrics.progress_fingerprint() != armed {
6624                continue;
6625              }
6626              // Pending-timer re-check LAST, after even the fingerprint:
6627              // arming a timer bumps no counter, so it is the one healthy
6628              // wake source that can land after every earlier re-check and
6629              // still be invisible to the fingerprint read above. (A
6630              // registration racing the instructions after this line is the
6631              // same inherent residue class as the bare future-wake -- and
6632              // its timer is still served: `note_earlier_timer` claims the
6633              // timekeeper independently of this driver.)
6634              if self.next_timer_deadline().is_some() {
6635                continue;
6636              }
6637              // Test-only seam for a cross-thread scheduler admission after
6638              // every legacy final check and before the synchronized verdict.
6639              #[cfg(test)]
6640              run_deadline_final_verdict_test_hook();
6641              let Some(verdict) = self.try_begin_deadlock_verdict() else {
6642                continue;
6643              };
6644              #[cfg(test)]
6645              run_deadline_gated_verdict_test_hook();
6646              // Lock order is verdict gate -> stop publication -> exact-owner
6647              // publication. The controller takes lifecycle -> stop
6648              // publication and never acquires the verdict gate or exact-owner
6649              // publication while retaining either lock.
6650              let stop_publication = self.stop.publication_guard();
6651              #[cfg(test)]
6652              run_deadline_synchronized_stop_test_hook();
6653              if stop_publication.is_stopping() {
6654                return BlockOnOutcome::Stopped;
6655              }
6656              let owner_handoff_publication = self
6657                .deadlock_state
6658                .owner_handoff_publication_guard()
6659                .expect("a deadline verdict requires exact-owner publication");
6660              #[cfg(test)]
6661              run_deadline_exact_owner_verdict_test_hook(&parker);
6662              // The closed gate excludes later publications. Any admission
6663              // that began earlier either remains counted (so gate acquisition
6664              // failed above) or completed before this recheck and advanced
6665              // the publication epoch. The stop-publication guard also keeps
6666              // the lifecycle in Running if this verdict linearized first. The
6667              // exact-owner publication guard similarly orders reservation
6668              // release and targeted handoff with the final predicates below.
6669              if self.has_exact_owner_work_for_role(
6670                &parker,
6671                &dependency.dependency,
6672                can_run_blocking,
6673              ) || parker.consume_permit()
6674                || self.has_queued_work_for_role(can_run_blocking)
6675                || self.deadlock_state.publications() != deadlock_publications
6676                || self.metrics.progress_fingerprint() != armed
6677                || self.next_timer_deadline().is_some()
6678              {
6679                continue;
6680              }
6681              let diagnostic = BlockOnDeadlock::multi_thread_cooperative(deadline);
6682              // Panic hooks run before unwind. Release every synchronization
6683              // guard first so a hook that submits work, releases an owner
6684              // lane, or starts shutdown cannot wait for the panic that invokes
6685              // it to unwind.
6686              drop(owner_handoff_publication);
6687              drop(stop_publication);
6688              drop(verdict);
6689              std::panic::panic_any(diagnostic);
6690            }
6691          }
6692        }
6693      }
6694      // `wake_one` removes the parkers it pops; a direct future-wake does
6695      // not. Deregister-if-present covers both.
6696      self.parked_drivers.deregister(&parker);
6697    }
6698  }
6699
6700  /// Is there work a cooperative driver could pick up right now? Used by the
6701  /// pre-park re-check and the exit compensation in `cooperative_block_on`;
6702  /// mirrors `finish_draining`'s re-check (a queued runnable, or a queued
6703  /// blocking job within the cap). The exact over-cap dependency is handled
6704  /// separately before registration.
6705  fn has_queued_work_for_role(&self, can_run_blocking: bool) -> bool {
6706    if self.has_queued_runnable() {
6707      return true;
6708    }
6709    can_run_blocking
6710      && self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6711      && !self
6712        .blocking_queue
6713        .lock()
6714        .unwrap_or_else(std::sync::PoisonError::into_inner)
6715        .is_empty()
6716  }
6717
6718  fn compensate_queued_work_wake(self: &Arc<Self>, can_run_blocking: bool) {
6719    if self.has_queued_runnable() {
6720      self.wake_for_new_work();
6721    }
6722    // Shutdown-aborted runnables still need a driver so their generators can
6723    // retire, but queued blocking closures are cancelled by begin_shutdown and
6724    // must never be admitted by exit compensation after stop is observable.
6725    if self.stop.is_stopping() {
6726      return;
6727    }
6728    if self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6729      && !self
6730        .blocking_queue
6731        .lock()
6732        .unwrap_or_else(std::sync::PoisonError::into_inner)
6733        .is_empty()
6734    {
6735      // This driver may have absorbed the queue wake that made its awaited
6736      // future ready. Runnable and blocking residue are independent. If no
6737      // other blocking-capable driver is available, merely spawning a Rayon
6738      // job is insufficient: this worker may return into arbitrary
6739      // synchronous user code while every other Rayon lane is busy.
6740      // Consume one admitted job before exit, then hand any residue to another
6741      // blocking-capable driver.
6742      let consumed = can_run_blocking && self.run_one_blocking();
6743      let has_residue = self.active_blocking.load(Ordering::Acquire) < self.max_blocking
6744        && !self
6745          .blocking_queue
6746          .lock()
6747          .unwrap_or_else(std::sync::PoisonError::into_inner)
6748          .is_empty();
6749      if !self.stop.is_stopping() && (!consumed || has_residue) {
6750        self.wake_for_blocking_work();
6751      }
6752    }
6753  }
6754
6755  fn has_queued_runnable(&self) -> bool {
6756    // Waiter-side mirror of the `push_to_queue_and_wake` fence: a parking
6757    // driver (pre-park re-check) and the drainer-exit re-check in
6758    // `finish_draining` must not observe the injector empty when a racing push
6759    // is already visible to a wake. This SeqCst fence pairs with the post-push
6760    // fence so the Dekker ordering holds without the former queue mutex. Only
6761    // on park/quiesce decision paths, never the per-task hot path.
6762    std::sync::atomic::fence(Ordering::SeqCst);
6763    !self.injector.is_empty()
6764  }
6765
6766  /// Pop the next queued blocking job FIFO within the cap. Any blocking-capable
6767  /// driver may run any job within `max_blocking`; only the over-cap escape is
6768  /// restricted to an exact dependency id.
6769  fn take_blocking(&self) -> Option<(BlockingClosure, Option<MultiThreadDeadlockAdmissionGuard>)> {
6770    let admission = self.begin_deadlock_admission();
6771    loop {
6772      let active = self.active_blocking.load(Ordering::Acquire);
6773      if active >= self.max_blocking {
6774        return None;
6775      }
6776      let mut queue = self
6777        .blocking_queue
6778        .lock()
6779        .unwrap_or_else(std::sync::PoisonError::into_inner);
6780      if queue.closed || self.stop.is_stopping() || queue.is_empty() {
6781        return None;
6782      }
6783      if self
6784        .active_blocking
6785        .compare_exchange_weak(active, active + 1, Ordering::AcqRel, Ordering::Relaxed)
6786        .is_ok()
6787      {
6788        let blocking = queue
6789          .pop_front()
6790          .expect("a non-empty blocking queue must have a FIFO head");
6791        drop(queue);
6792        #[cfg(test)]
6793        run_multi_thread_work_claimed_test_hook();
6794        return Some((blocking, admission));
6795      }
6796    }
6797  }
6798}
6799
6800#[cfg(not(target_family = "wasm"))]
6801struct MultiThreadDeadlockState {
6802  enabled: bool,
6803  verdict_closed: AtomicBool,
6804  admissions: AtomicUsize,
6805  publications: AtomicU64,
6806  owner_handoff_publication: Mutex<()>,
6807}
6808
6809#[cfg(not(target_family = "wasm"))]
6810impl MultiThreadDeadlockState {
6811  fn new(enabled: bool) -> Self {
6812    Self {
6813      enabled,
6814      verdict_closed: AtomicBool::new(false),
6815      admissions: AtomicUsize::new(0),
6816      publications: AtomicU64::new(0),
6817      owner_handoff_publication: Mutex::new(()),
6818    }
6819  }
6820
6821  fn begin_admission(self: &Arc<Self>) -> Option<MultiThreadDeadlockAdmissionGuard> {
6822    if !self.enabled {
6823      return None;
6824    }
6825    loop {
6826      while self.verdict_closed.load(Ordering::SeqCst) {
6827        #[cfg(test)]
6828        run_deadlock_closed_gate_admission_test_hook();
6829        std::hint::spin_loop();
6830      }
6831      self.admissions.fetch_add(1, Ordering::SeqCst);
6832      if !self.verdict_closed.load(Ordering::SeqCst) {
6833        return Some(MultiThreadDeadlockAdmissionGuard {
6834          state: Arc::clone(self),
6835          published: false,
6836        });
6837      }
6838      self.admissions.fetch_sub(1, Ordering::SeqCst);
6839    }
6840  }
6841
6842  fn try_begin_verdict(self: &Arc<Self>) -> Option<MultiThreadDeadlockVerdictGuard> {
6843    debug_assert!(
6844      self.enabled,
6845      "a deadline verdict requires an enabled admission state"
6846    );
6847    if self
6848      .verdict_closed
6849      .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
6850      .is_err()
6851    {
6852      return None;
6853    }
6854    if self.admissions.load(Ordering::SeqCst) != 0 {
6855      self.verdict_closed.store(false, Ordering::SeqCst);
6856      return None;
6857    }
6858    Some(MultiThreadDeadlockVerdictGuard(Arc::clone(self)))
6859  }
6860
6861  fn publications(&self) -> u64 {
6862    self.publications.load(Ordering::SeqCst)
6863  }
6864
6865  fn owner_handoff_publication_guard(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
6866    self.enabled.then(|| {
6867      self
6868        .owner_handoff_publication
6869        .lock()
6870        .unwrap_or_else(std::sync::PoisonError::into_inner)
6871    })
6872  }
6873}
6874
6875#[cfg(not(target_family = "wasm"))]
6876struct MultiThreadDeadlockAdmissionGuard {
6877  state: Arc<MultiThreadDeadlockState>,
6878  published: bool,
6879}
6880
6881#[cfg(not(target_family = "wasm"))]
6882impl MultiThreadDeadlockAdmissionGuard {
6883  fn mark_published(&mut self) {
6884    self.published = true;
6885  }
6886}
6887
6888#[cfg(not(target_family = "wasm"))]
6889impl Drop for MultiThreadDeadlockAdmissionGuard {
6890  fn drop(&mut self) {
6891    if self.published {
6892      self.state.publications.fetch_add(1, Ordering::SeqCst);
6893    }
6894    // Publication and every scheduler-state write above must precede the final
6895    // admission decrement observed by a closed verdict gate.
6896    self.state.admissions.fetch_sub(1, Ordering::SeqCst);
6897  }
6898}
6899
6900#[cfg(not(target_family = "wasm"))]
6901struct MultiThreadDeadlockVerdictGuard(Arc<MultiThreadDeadlockState>);
6902
6903#[cfg(not(target_family = "wasm"))]
6904impl Drop for MultiThreadDeadlockVerdictGuard {
6905  fn drop(&mut self) {
6906    self.0.verdict_closed.store(false, Ordering::SeqCst);
6907  }
6908}
6909
6910/// Cooperative-driver exit backstop. A user-future panic can unwind from any
6911/// poll, including after this driver absorbed a queue wake, received an exact
6912/// owner handoff, or filled its thread-local LIFO slot. Retire each obligation
6913/// independently so one cleanup failure cannot suppress the others or
6914/// double-panic the process.
6915#[cfg(not(target_family = "wasm"))]
6916struct CooperativeDriverExitGuard<'a> {
6917  executor: &'a Arc<MultiThreadExecutor>,
6918  parker: &'a Arc<DriverParker>,
6919  can_run_blocking: bool,
6920}
6921
6922#[cfg(not(target_family = "wasm"))]
6923impl Drop for CooperativeDriverExitGuard<'_> {
6924  fn drop(&mut self) {
6925    let _ = catch_unwind_contained(|| self.executor.parked_drivers.deregister(self.parker));
6926    let _ = catch_unwind_contained(|| self.executor.forward_owner_handoff(self.parker));
6927    let _ = catch_unwind_contained(|| self.executor.flush_lifo_slot());
6928    let _ = catch_unwind_contained(|| {
6929      self
6930        .executor
6931        .compensate_queued_work_wake(self.can_run_blocking);
6932    });
6933  }
6934}
6935
6936/// Unwind backstop for `drain`: flushes the worker's LIFO slot on drop so an
6937/// unwinding drain can never strand a runnable in the
6938/// thread-local slot of a thread that is about to return to rayon. Normal
6939/// drain exits flush explicitly BEFORE `finish_draining`; this drop is then a
6940/// no-op (the slot is already empty).
6941#[cfg(not(target_family = "wasm"))]
6942struct LifoSlotFlushGuard<'a>(&'a Arc<MultiThreadExecutor>);
6943
6944#[cfg(not(target_family = "wasm"))]
6945impl Drop for LifoSlotFlushGuard<'_> {
6946  fn drop(&mut self) {
6947    self.0.flush_lifo_slot();
6948  }
6949}
6950
6951/// Per-drain-frame idle-linger state, created lazily on the frame's first
6952/// idle episode (see the linger block in `MultiThreadExecutor::drain`): the
6953/// parker queue wakes are delivered to while this drainer lingers, plus its
6954/// generation-stop registration so `begin_shutdown` cuts a linger short
6955/// instead of `wait_until_scheduler_idle` waiting out the idle budget. The
6956/// registration guard deregisters on drop when the drain frame exits.
6957#[cfg(not(target_family = "wasm"))]
6958struct DrainLinger {
6959  parker: Arc<DriverParker>,
6960  _stop_registration: GenerationStopGuard,
6961}
6962
6963#[cfg(not(target_family = "wasm"))]
6964impl DrainLinger {
6965  fn new(executor: &MultiThreadExecutor) -> Self {
6966    let parker = Arc::new(DriverParker::default());
6967    let stop_registration = executor.stop.register(&parker);
6968    Self {
6969      parker,
6970      _stop_registration: stop_registration,
6971    }
6972  }
6973}
6974
6975// ---------------------------------------------------------------------------
6976// Targeted wake machinery for cooperative re-entrant `block_on` drivers.
6977//
6978// WAKE-DOMAIN SPLIT: future wakes and queue wakes have different targets:
6979//   * FUTURE-WAKES: `cooperative_block_on` polls its future with the driver's
6980//     OWN `DriverParker` as the waker, so a future-wake reaches exactly the
6981//     driver that awaits it. The permit bit makes a wake delivered while that
6982//     driver is RUNNING (mid-poll / mid-run_one) stick: the driver skips its
6983//     next park instead of losing the wake.
6984//   * QUEUE-WAKES (`schedule` / `schedule_blocking`): any parked driver can
6985//     serve queue work, so these go through the `ParkedDrivers` registry and
6986//     wake ONE parked driver; `ensure_drainer` remains the fallback when none
6987//     is parked, and an absorbed wake is handed on at `cooperative_block_on`
6988//     exit (miss compensation).
6989// Foreign-thread (napi caller) parks in `block_on` use
6990// `futures::executor::block_on`'s own parker and are OUTSIDE this mechanism.
6991// The CurrentThread flavor's `block_on` reuses `DriverParker` (one per
6992// frame, no registry) so it too OWNS its park site for deadlock detection
6993// while `ParkedDrivers` remains MultiThread-only.
6994
6995/// No permit; the driver is running (or consumed its last permit).
6996const PARKER_EMPTY: usize = 0;
6997/// A wake permit is stored; the next `park` consumes it without sleeping.
6998const PARKER_NOTIFIED: usize = 1;
6999/// The driver is sleeping on the condvar (or committing to sleep under `lock`).
7000const PARKER_SLEEPING: usize = 2;
7001
7002/// One driver's private parker: a saturating one-permit token plus a condvar
7003/// to sleep on when no permit is stored. Also the `Waker` for the future that
7004/// driver's `cooperative_block_on` / CurrentThread `block_on` frame awaits.
7005#[derive(Default)]
7006struct DriverParker {
7007  state: AtomicUsize,
7008  lock: Mutex<()>,
7009  condvar: std::sync::Condvar,
7010  #[cfg(not(target_family = "wasm"))]
7011  owner_handoff_pending: AtomicBool,
7012  #[cfg(not(target_family = "wasm"))]
7013  owner_handoff: Mutex<Option<BlockingOwnerToken>>,
7014}
7015
7016impl DriverParker {
7017  #[cfg(not(target_family = "wasm"))]
7018  fn grant_owner_handoff(&self, owner: BlockingOwnerToken) {
7019    let mut pending = self
7020      .owner_handoff
7021      .lock()
7022      .unwrap_or_else(std::sync::PoisonError::into_inner);
7023    debug_assert!(pending.is_none());
7024    *pending = Some(owner);
7025    self.owner_handoff_pending.store(true, Ordering::Release);
7026  }
7027
7028  #[cfg(not(target_family = "wasm"))]
7029  fn take_owner_handoff(&self) -> Option<BlockingOwnerToken> {
7030    if !self.owner_handoff_pending.swap(false, Ordering::AcqRel) {
7031      return None;
7032    }
7033    self
7034      .owner_handoff
7035      .lock()
7036      .unwrap_or_else(std::sync::PoisonError::into_inner)
7037      .take()
7038  }
7039
7040  #[cfg(not(target_family = "wasm"))]
7041  fn has_owner_handoff(&self) -> bool {
7042    self.owner_handoff_pending.load(Ordering::Acquire)
7043  }
7044
7045  /// Grant the wake permit. Wakes the driver if it is sleeping; otherwise the
7046  /// permit is stored and the driver's next [`Self::park`] returns
7047  /// immediately -- a wake delivered while the driver is running is never
7048  /// lost. Multiple grants coalesce into one permit, which is safe because a
7049  /// woken driver re-checks every work source in its loop before it can park
7050  /// again.
7051  fn unpark(&self) {
7052    if self.state.swap(PARKER_NOTIFIED, Ordering::SeqCst) == PARKER_SLEEPING {
7053      // The sleeper publishes PARKER_SLEEPING while holding `lock` and
7054      // releases the lock only inside `condvar.wait`; the empty critical
7055      // section here fences the notify so it cannot fire in the window
7056      // between the sleeper's state store and its wait.
7057      drop(
7058        self
7059          .lock
7060          .lock()
7061          .unwrap_or_else(std::sync::PoisonError::into_inner),
7062      );
7063      self.condvar.notify_one();
7064    }
7065  }
7066
7067  /// Consume a stored wake permit if one is present, without ever sleeping.
7068  /// This is the park DECISION's token check: a `true` return means a wake is
7069  /// already pending, so the caller must re-poll instead of parking (or
7070  /// panicking).
7071  fn consume_permit(&self) -> bool {
7072    self
7073      .state
7074      .compare_exchange(
7075        PARKER_NOTIFIED,
7076        PARKER_EMPTY,
7077        Ordering::SeqCst,
7078        Ordering::SeqCst,
7079      )
7080      .is_ok()
7081  }
7082
7083  /// Block until the permit is granted, consuming it. Returns immediately if
7084  /// a permit is already stored. Only the owning driver calls `park`, so at
7085  /// most one thread ever sleeps here.
7086  fn park(&self) {
7087    // Fast path: consume a stored permit without touching the lock.
7088    if self.consume_permit() {
7089      return;
7090    }
7091    let mut guard = self
7092      .lock
7093      .lock()
7094      .unwrap_or_else(std::sync::PoisonError::into_inner);
7095    match self.state.compare_exchange(
7096      PARKER_EMPTY,
7097      PARKER_SLEEPING,
7098      Ordering::SeqCst,
7099      Ordering::SeqCst,
7100    ) {
7101      Ok(_) => {}
7102      // The permit arrived between the fast path and here: consume it. (Only
7103      // this thread stores PARKER_SLEEPING, so the observed value can only be
7104      // PARKER_NOTIFIED.)
7105      Err(_) => {
7106        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7107        return;
7108      }
7109    }
7110    loop {
7111      guard = self
7112        .condvar
7113        .wait(guard)
7114        .unwrap_or_else(std::sync::PoisonError::into_inner);
7115      if self
7116        .state
7117        .compare_exchange(
7118          PARKER_NOTIFIED,
7119          PARKER_EMPTY,
7120          Ordering::SeqCst,
7121          Ordering::SeqCst,
7122        )
7123        .is_ok()
7124      {
7125        return;
7126      }
7127      // Spurious wakeup (state still PARKER_SLEEPING): keep waiting.
7128    }
7129  }
7130
7131  /// Like [`Self::park`], but bounded: returns `true` when woken by a permit
7132  /// (consumed) and `false` when `timeout` elapsed with no permit granted.
7133  /// A `false` return has retracted the SLEEPING claim and left the parker
7134  /// EMPTY, so the caller may park again. Used by the deadline-based deadlock
7135  /// detection.
7136  fn park_timeout(&self, timeout: Duration) -> bool {
7137    if self.consume_permit() {
7138      return true;
7139    }
7140    let mut guard = self
7141      .lock
7142      .lock()
7143      .unwrap_or_else(std::sync::PoisonError::into_inner);
7144    match self.state.compare_exchange(
7145      PARKER_EMPTY,
7146      PARKER_SLEEPING,
7147      Ordering::SeqCst,
7148      Ordering::SeqCst,
7149    ) {
7150      Ok(_) => {}
7151      // The permit arrived between the fast path and here: consume it. (Only
7152      // this thread stores PARKER_SLEEPING, so the observed value can only be
7153      // PARKER_NOTIFIED.)
7154      Err(_) => {
7155        self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7156        return true;
7157      }
7158    }
7159    let Some(deadline) = Instant::now().checked_add(timeout) else {
7160      // An unrepresentable deadline is farther away than this platform can
7161      // observe. Treat it as an unbounded wait instead of panicking on a
7162      // user-provided deadlock-detection duration.
7163      loop {
7164        guard = self
7165          .condvar
7166          .wait(guard)
7167          .unwrap_or_else(std::sync::PoisonError::into_inner);
7168        if self
7169          .state
7170          .compare_exchange(
7171            PARKER_NOTIFIED,
7172            PARKER_EMPTY,
7173            Ordering::SeqCst,
7174            Ordering::SeqCst,
7175          )
7176          .is_ok()
7177        {
7178          return true;
7179        }
7180      }
7181    };
7182    loop {
7183      if self
7184        .state
7185        .compare_exchange(
7186          PARKER_NOTIFIED,
7187          PARKER_EMPTY,
7188          Ordering::SeqCst,
7189          Ordering::SeqCst,
7190        )
7191        .is_ok()
7192      {
7193        return true;
7194      }
7195      let now = Instant::now();
7196      if now >= deadline {
7197        // Timed out: retract the SLEEPING claim -- unless a permit landed in
7198        // the meantime (an `unpark` that already saw SLEEPING may be waiting
7199        // on `lock` to notify; consuming its permit here IS the wake it
7200        // intended to deliver, so report "woken", not "timed out").
7201        return match self.state.compare_exchange(
7202          PARKER_SLEEPING,
7203          PARKER_EMPTY,
7204          Ordering::SeqCst,
7205          Ordering::SeqCst,
7206        ) {
7207          Ok(_) => false,
7208          Err(_) => {
7209            self.state.store(PARKER_EMPTY, Ordering::SeqCst);
7210            true
7211          }
7212        };
7213      }
7214      let (returned_guard, _timed_out) = self
7215        .condvar
7216        .wait_timeout(guard, deadline - now)
7217        .unwrap_or_else(std::sync::PoisonError::into_inner);
7218      guard = returned_guard;
7219      // Loop re-checks the permit first, then the deadline (spurious wakeups
7220      // simply wait out the remaining time).
7221    }
7222  }
7223}
7224
7225impl std::task::Wake for DriverParker {
7226  fn wake(self: Arc<Self>) {
7227    self.unpark();
7228  }
7229
7230  fn wake_by_ref(self: &Arc<Self>) {
7231    self.unpark();
7232  }
7233}
7234
7235/// Outcome of [`park_with_deadline`].
7236enum DeadlineParkOutcome {
7237  /// Woken by a permit (consumed); the park was healthy.
7238  Woken,
7239  /// A full deadline window elapsed with zero observed progress. Carries the
7240  /// fingerprint captured when that window was ARMED so the caller can make
7241  /// the final expiry DECISION against a fresh read -- expiry alone is NOT a
7242  /// verdict: a wake can land in the instructions after the window closes,
7243  /// and for MultiThread a `wake_one` can still pop the
7244  /// still-registered parker and count its wake as DELIVERED. Callers must
7245  /// deregister (MT), then re-check permit / queued work / fingerprint before
7246  /// panicking.
7247  Expired(ProgressFingerprint),
7248}
7249
7250/// Deadline-bounded park with PROGRESS-BASED reset.
7251/// Every time `deadline` elapses without a wake, the runtime's progress
7252/// fingerprint is compared against the value captured when the deadline was
7253/// ARMED: any advance re-arms the deadline instead of firing, because a
7254/// legitimately long park -- e.g. awaiting a slow JS plugin while other work
7255/// proceeds -- must never be declared dead. `Expired` is a *candidate*
7256/// deadlock only; the panic decision lives with the caller (see
7257/// [`DeadlineParkOutcome`]).
7258fn park_with_deadline(
7259  parker: &DriverParker,
7260  deadline: Duration,
7261  metrics: &RuntimeMetrics,
7262) -> DeadlineParkOutcome {
7263  let mut armed = metrics.progress_fingerprint();
7264  loop {
7265    if parker.park_timeout(deadline) {
7266      return DeadlineParkOutcome::Woken;
7267    }
7268    let current = metrics.progress_fingerprint();
7269    if current != armed {
7270      armed = current;
7271      continue;
7272    }
7273    return DeadlineParkOutcome::Expired(armed);
7274  }
7275}
7276
7277// Test-only injection seam for the deadline-expiry race: fired on the driver
7278// thread immediately after
7279// `park_with_deadline` reports `Expired`, while the parker is STILL
7280// REGISTERED in `parked_drivers` and before the expiry decision runs -- the
7281// exact window in which a racing `wake_one` can pop the parker, store its
7282// permit and report the wake as delivered. Lets a test interleave that race
7283// deterministically instead of wall-clock lottery.
7284#[cfg(all(test, not(target_family = "wasm")))]
7285thread_local! {
7286  static DEADLINE_EXPIRY_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7287    const { std::cell::RefCell::new(None) };
7288}
7289
7290#[cfg(all(test, not(target_family = "wasm")))]
7291fn run_deadline_expiry_test_hook() {
7292  if let Some(hook) = DEADLINE_EXPIRY_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7293    hook();
7294  }
7295}
7296
7297// Second test-only injection seam for the enqueue/verdict race: fired AFTER
7298// the expiry decision's permit and queued-work re-checks both came up
7299// empty (parker already deregistered) and BEFORE the final fingerprint
7300// verdict -- the window in which an enqueue is invisible to every earlier
7301// re-check and only its ENQUEUE counter in the fingerprint can prevent a
7302// false BlockOnDeadlock.
7303#[cfg(all(test, not(target_family = "wasm")))]
7304thread_local! {
7305  static DEADLINE_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7306    const { std::cell::RefCell::new(None) };
7307}
7308
7309#[cfg(all(test, not(target_family = "wasm")))]
7310fn run_deadline_verdict_test_hook() {
7311  if let Some(hook) = DEADLINE_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7312    hook();
7313  }
7314}
7315
7316// Final test-only injection seam: fired after every permit/queue/fingerprint/
7317// timer check from the legacy verdict and immediately before the synchronized
7318// admission gate closes. A separate OS thread can complete a publication here
7319// to prove the final verdict recheck has a real cross-thread ordering edge.
7320#[cfg(all(test, not(target_family = "wasm")))]
7321thread_local! {
7322  static DEADLINE_FINAL_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7323    const { std::cell::RefCell::new(None) };
7324}
7325
7326#[cfg(all(test, not(target_family = "wasm")))]
7327fn run_deadline_final_verdict_test_hook() {
7328  if let Some(hook) = DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7329    hook();
7330  }
7331}
7332
7333// Fired after the synchronized verdict gate has closed and before the verdict
7334// takes the stop-publication mutex for its final rechecks. A test can pause
7335// here while shutdown publishes generation stop and its abort wake reaches the
7336// closed admission gate.
7337#[cfg(all(test, not(target_family = "wasm")))]
7338thread_local! {
7339  static DEADLINE_GATED_VERDICT_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7340    const { std::cell::RefCell::new(None) };
7341}
7342
7343#[cfg(all(test, not(target_family = "wasm")))]
7344fn run_deadline_gated_verdict_test_hook() {
7345  if let Some(hook) = DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7346    hook();
7347  }
7348}
7349
7350// Fired after the final verdict has acquired the stop-publication mutex. A
7351// shutdown test can retain that guard while another thread reaches the
7352// lifecycle -> stop-publication lock edge.
7353#[cfg(all(test, not(target_family = "wasm")))]
7354thread_local! {
7355  static DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7356    const { std::cell::RefCell::new(None) };
7357}
7358
7359#[cfg(all(test, not(target_family = "wasm")))]
7360fn run_deadline_synchronized_stop_test_hook() {
7361  if let Some(hook) = DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7362    hook();
7363  }
7364}
7365
7366// Fired after the final verdict owns exact-owner publication and before it
7367// checks handoff/lane predicates. Tests can remove unrelated saving predicates
7368// while retaining the synchronized owner state under examination.
7369#[cfg(all(test, not(target_family = "wasm")))]
7370type DeadlineExactOwnerVerdictTestHook = Box<dyn FnOnce(&DriverParker)>;
7371
7372#[cfg(all(test, not(target_family = "wasm")))]
7373thread_local! {
7374  static DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK:
7375    std::cell::RefCell<Option<DeadlineExactOwnerVerdictTestHook>> =
7376      const { std::cell::RefCell::new(None) };
7377}
7378
7379#[cfg(all(test, not(target_family = "wasm")))]
7380fn run_deadline_exact_owner_verdict_test_hook(parker: &DriverParker) {
7381  if let Some(hook) = DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7382    hook(parker);
7383  }
7384}
7385
7386// Fired immediately before a test thread takes the stop-publication mutex.
7387// Installed only on the shutdown thread, this proves the controller still
7388// holds the lifecycle in Running while waiting for a verdict-owned guard.
7389#[cfg(all(test, not(target_family = "wasm")))]
7390thread_local! {
7391  static BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK:
7392    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7393      const { std::cell::RefCell::new(None) };
7394}
7395
7396#[cfg(all(test, not(target_family = "wasm")))]
7397fn run_before_generation_stop_publication_lock_test_hook() {
7398  if let Some(hook) =
7399    BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7400  {
7401    hook();
7402  }
7403}
7404
7405// Fired on an admission thread after it observes a closed verdict gate. The
7406// hook is thread-local so a shutdown test can prove that an abort wake reached
7407// this exact wait without affecting unrelated scheduler threads.
7408#[cfg(all(test, not(target_family = "wasm")))]
7409thread_local! {
7410  static DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK:
7411    std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7412      const { std::cell::RefCell::new(None) };
7413}
7414
7415#[cfg(all(test, not(target_family = "wasm")))]
7416fn run_deadlock_closed_gate_admission_test_hook() {
7417  if let Some(hook) = DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
7418  {
7419    hook();
7420  }
7421}
7422
7423// Timer registration enters the shared verdict admission before this seam.
7424// Tests can pause there to prove the timer remains an announced publication
7425// until its heap entry becomes visible.
7426#[cfg(all(test, not(target_family = "wasm")))]
7427thread_local! {
7428  static TIMER_REGISTRATION_ADMISSION_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7429    const { std::cell::RefCell::new(None) };
7430}
7431
7432#[cfg(all(test, not(target_family = "wasm")))]
7433fn run_timer_registration_admission_test_hook() {
7434  if let Some(hook) = TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7435    hook();
7436  }
7437}
7438
7439#[cfg(all(test, not(target_family = "wasm")))]
7440thread_local! {
7441  static OWNER_HANDOFF_SELECTED_TEST_HOOK: std::cell::RefCell<Option<Box<dyn FnOnce()>>> =
7442    const { std::cell::RefCell::new(None) };
7443}
7444
7445#[cfg(all(test, not(target_family = "wasm")))]
7446fn run_owner_handoff_selected_test_hook() {
7447  if let Some(hook) = OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
7448    hook();
7449  }
7450}
7451
7452/// Registry of parked (or committed-to-parking) cooperative drivers, used by
7453/// queue-wakes to wake exactly one of them.
7454#[cfg(not(target_family = "wasm"))]
7455#[derive(Default)]
7456struct ParkedDrivers {
7457  parked: Mutex<Vec<ParkedDriver>>,
7458  // Mirror of `parked.len()`, maintained under the mutex and read lock-free
7459  // by `wake_one`'s no-waiter fast path.
7460  count: AtomicUsize,
7461}
7462
7463#[cfg(not(target_family = "wasm"))]
7464struct ParkedDriver {
7465  parker: Arc<DriverParker>,
7466  can_run_blocking: bool,
7467  dependency: Option<Arc<TaskDependency>>,
7468}
7469
7470#[cfg(not(target_family = "wasm"))]
7471impl ParkedDrivers {
7472  /// Register `parker` as parked-or-parking. MUST precede the caller's final
7473  /// work re-check -- see the lost-wakeup argument at [`Self::wake_one`].
7474  #[cfg(test)]
7475  fn register(&self, parker: &Arc<DriverParker>) {
7476    self.register_with_role(parker, true, None);
7477  }
7478
7479  fn register_with_role(
7480    &self,
7481    parker: &Arc<DriverParker>,
7482    can_run_blocking: bool,
7483    dependency: Option<Arc<TaskDependency>>,
7484  ) {
7485    let mut parked = self
7486      .parked
7487      .lock()
7488      .unwrap_or_else(std::sync::PoisonError::into_inner);
7489    parked.push(ParkedDriver {
7490      parker: Arc::clone(parker),
7491      can_run_blocking,
7492      dependency,
7493    });
7494    self.count.store(parked.len(), Ordering::SeqCst);
7495  }
7496
7497  /// Remove `parker` if still registered. `wake_one` removes the parkers it
7498  /// pops, while a direct future-wake does not, so drivers call this after
7499  /// every park (and after a re-check aborts one).
7500  fn deregister(&self, parker: &Arc<DriverParker>) {
7501    let removed = {
7502      let mut parked = self
7503        .parked
7504        .lock()
7505        .unwrap_or_else(std::sync::PoisonError::into_inner);
7506      let removed = parked
7507        .iter()
7508        .position(|candidate| Arc::ptr_eq(&candidate.parker, parker))
7509        .map(|index| parked.swap_remove(index));
7510      self.count.store(parked.len(), Ordering::SeqCst);
7511      removed
7512    };
7513    drop(removed);
7514  }
7515
7516  /// Wake one parked driver -- most recently parked first, its stack and
7517  /// caches are the warmest -- returning whether anyone was woken.
7518  ///
7519  /// LOST-WAKEUP ARGUMENT. A scheduler pushes
7520  /// work under a work-queue mutex Q, THEN calls `wake_one`; a parking driver
7521  /// registers itself (the `count` store above, under the registry mutex),
7522  /// THEN re-checks the work queues under Q, and only then parks. Order the
7523  /// two Q critical sections:
7524  ///   * If the driver's re-check locks Q after the scheduler's push unlocked
7525  ///     it, the re-check observes the work and the driver does not park --
7526  ///     skipping the wake (fast path below) is safe.
7527  ///   * Otherwise the driver's re-check unlocked Q before the scheduler's
7528  ///     push locked it. The driver's `count` store is sequenced before its
7529  ///     re-check, the re-check's unlock of Q synchronizes-with the push's
7530  ///     lock of Q, and the push is sequenced before the fast-path load
7531  ///     below; the load therefore happens-after the `count` store and must
7532  ///     observe `count >= 1`: the slow path pops a registered parker and
7533  ///     wakes it.
7534  ///
7535  /// A wake delivered to a driver that raced out of its park (or aborted it)
7536  /// is stored as that driver's permit; the driver re-checks every work
7537  /// source before it can sleep on the next park, so the wake still lands.
7538  fn wake_one(&self) -> bool {
7539    if self.count.load(Ordering::SeqCst) == 0 {
7540      // No-waiter fast path: nobody is parked or committed to parking (any
7541      // in-flight parker that missed this check re-checks the queues after
7542      // registering, per the argument above).
7543      return false;
7544    }
7545    let driver = {
7546      let mut parked = self
7547        .parked
7548        .lock()
7549        .unwrap_or_else(std::sync::PoisonError::into_inner);
7550      let driver = parked.pop();
7551      self.count.store(parked.len(), Ordering::SeqCst);
7552      driver
7553    };
7554    match driver {
7555      Some(driver) => {
7556        driver.parker.unpark();
7557        true
7558      }
7559      None => false,
7560    }
7561  }
7562
7563  /// Wake the most recently parked cooperative driver that is eligible to
7564  /// consume blocking work.
7565  fn wake_one_blocking(&self) -> bool {
7566    if self.count.load(Ordering::SeqCst) == 0 {
7567      return false;
7568    }
7569    let driver = {
7570      let mut parked = self
7571        .parked
7572        .lock()
7573        .unwrap_or_else(std::sync::PoisonError::into_inner);
7574      let driver = parked
7575        .iter()
7576        .rposition(|driver| driver.can_run_blocking)
7577        .map(|index| parked.swap_remove(index));
7578      self.count.store(parked.len(), Ordering::SeqCst);
7579      driver
7580    };
7581    match driver {
7582      Some(driver) => {
7583        driver.parker.unpark();
7584        true
7585      }
7586      None => false,
7587    }
7588  }
7589
7590  /// Wake one parked driver whose live dependency belongs to `owner`.
7591  ///
7592  /// The dependency is published with the parker, so an unrelated newer
7593  /// driver cannot consume the bounded owner-lane handoff.
7594  fn wake_one_owner_dependency(&self, owner: BlockingOwnerToken) -> bool {
7595    loop {
7596      if self.count.load(Ordering::SeqCst) == 0 {
7597        return false;
7598      }
7599      let candidates = {
7600        let parked = self
7601          .parked
7602          .lock()
7603          .unwrap_or_else(std::sync::PoisonError::into_inner);
7604        parked
7605          .iter()
7606          .rev()
7607          .filter_map(|driver| {
7608            if !driver.can_run_blocking {
7609              return None;
7610            }
7611            driver
7612              .dependency
7613              .as_ref()
7614              .map(|dependency| (Arc::clone(&driver.parker), Arc::clone(dependency)))
7615          })
7616          .collect::<Vec<_>>()
7617      };
7618      let selected = candidates.into_iter().find(|(_, dependency)| {
7619        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact)
7620      });
7621      let Some((selected_parker, selected_dependency)) = selected else {
7622        return false;
7623      };
7624      let driver = {
7625        let mut parked = self
7626          .parked
7627          .lock()
7628          .unwrap_or_else(std::sync::PoisonError::into_inner);
7629        let driver = parked
7630          .iter()
7631          .rposition(|driver| {
7632            Arc::ptr_eq(&driver.parker, &selected_parker)
7633              && driver
7634                .dependency
7635                .as_ref()
7636                .is_some_and(|dependency| Arc::ptr_eq(dependency, &selected_dependency))
7637          })
7638          .map(|index| parked.swap_remove(index));
7639        // Publish the exact-owner token before releasing the registry lock. A
7640        // direct future wake can race this selection, but the driver must
7641        // deregister under this same lock before it can poll Ready and forward
7642        // an unconsumed handoff on exit.
7643        if let Some(driver) = &driver {
7644          driver.parker.grant_owner_handoff(owner);
7645        }
7646        self.count.store(parked.len(), Ordering::SeqCst);
7647        driver
7648      };
7649      if let Some(driver) = driver {
7650        #[cfg(test)]
7651        run_owner_handoff_selected_test_hook();
7652        driver.parker.unpark();
7653        return true;
7654      }
7655    }
7656  }
7657
7658  /// Wake every timer-bounded driver so cancellation of the earliest timer
7659  /// cannot leave pool workers parked until a stale deadline.
7660  fn wake_all(&self) {
7661    if self.count.load(Ordering::SeqCst) == 0 {
7662      return;
7663    }
7664    let drivers = {
7665      let mut parked = self
7666        .parked
7667        .lock()
7668        .unwrap_or_else(std::sync::PoisonError::into_inner);
7669      let drivers = parked.drain(..).collect::<Vec<_>>();
7670      self.count.store(0, Ordering::SeqCst);
7671      drivers
7672    };
7673    for driver in drivers {
7674      driver.parker.unpark();
7675    }
7676  }
7677}
7678
7679// ---------------------------------------------------------------------------
7680// Runtime timer facility: `sleep_until` without a tokio
7681// reactor. Two drivers, selected by runtime flavor:
7682//
7683//   * MultiThread: a runtime-owned binary heap plus one dedicated non-Rayon
7684//     timekeeper thread. Timer waits never consume the configured CPU workers.
7685//     Cooperative re-entrant `block_on` parks are ALSO bounded by the earliest
7686//     deadline, so the parked driver can fire due timers without depending on
7687//     any particular inter-thread wake ordering.
7688//   * CurrentThread: timers are DELEGATED TO THE HOST event loop through an
7689//     injected [`TimerDriver`] (the Node binding registers a setTimeout-based
7690//     driver at import; wasi-single uses the same path). A CT runtime without
7691//     a registered driver fails LOUD at `sleep_until` -- never a silent hang.
7692//
7693// DEADLOCK-DETECTION INTERACTIONS:
7694//   * MT: a wait on a pending timer is never a deadlock candidate -- the wall
7695//     clock is a guaranteed wake source. Timer waits therefore use their own
7696//     primitive (a plain `park_timeout` bounded by the earliest deadline),
7697//     NEVER `park_with_deadline`, so an armed park deadline
7698//     shorter than the next timer cannot fire on a legitimate timer wait; the
7699//     fire itself schedules the woken task, which IS fingerprint progress for
7700//     every other parked driver. The expiry VERDICT additionally re-checks
7701//     the heap (see `cooperative_block_on`): arming a timer bumps no progress
7702//     counter by design, so a registration racing an already-committed
7703//     deadline park must veto the panic there.
7704//   * CT certain check (threadless): a pending HOST timer is NOT a wake
7705//     token -- the JS setTimeout relay can only run on the very thread that
7706//     is parking, so it can never fire while `block_on` holds it. The check
7707//     panics unconditionally on a threadless build; a pending timer merely
7708//     selects the timer-backed variant of the typed diagnostic. The CT
7709//     deadline verdict (threaded flavor, where the host loop genuinely runs
7710//     concurrently) counts only LIVE waits (deadline still in the future): a
7711//     pending timer whose deadline passed without firing is the
7712//     frozen-JS-event-loop signature that the deadline-based detection
7713//     exists to catch, so it must NOT suppress the panic.
7714
7715/// Host-turn dispatcher for the CurrentThread runnable queue.
7716///
7717/// The NAPI binding registers one weak native threadsafe-function driver per
7718/// importing environment. Its custom Node-API callback owns the fresh host turn
7719/// and keeps delivery capabilities out of JavaScript. Scheduling through a
7720/// fresh host turn is required for soundness: arbitrary futures may invoke
7721/// their waker while holding internal locks, so polling the runnable inline
7722/// from that wake can self-deadlock.
7723pub trait CurrentThreadTaskDriver: Send + Sync + 'static {
7724  /// Queue one host turn that will call [`drive_current_thread_tasks`] with
7725  /// [`CurrentThreadTaskDelivery::capability`].
7726  ///
7727  /// The delivery capability is scoped to this exact host registration and
7728  /// attempt. The registry maps it back to the executor's one internal
7729  /// dispatch capability only while this attempt remains current.
7730  /// `false` means this driver can no longer dispatch and must be swept. After
7731  /// returning `true`, the driver must eventually call either
7732  /// [`acknowledge_current_thread_task_delivery`] or
7733  /// [`fail_current_thread_task_delivery`] with this exact value. A successful
7734  /// acknowledgement is accepted only after
7735  /// [`drive_current_thread_tasks`] claimed this exact delivery.
7736  ///
7737  /// `dispatch` must return promptly and must not wait on another thread. In
7738  /// particular it must never block on the thread that will acknowledge or
7739  /// fail this delivery (for instance by spawning a helper that calls
7740  /// [`fail_current_thread_task_delivery`] and joining it). The executor
7741  /// calls `dispatch` from `schedule`, which may be a waker's stack: a waker
7742  /// can be invoked while its producer holds a mutex (`futures::Shared` wakes
7743  /// its awaiters under its `wakers` lock). When a delivery failure turns
7744  /// terminal, the rejected work is cancelled on the stack of the thread that
7745  /// failed it, and a rejected future's destructor may re-lock the very mutex
7746  /// the blocked waker still holds. A `dispatch` that waits on that thread
7747  /// therefore deadlocks with it.
7748  ///
7749  /// Failing the delivery synchronously from inside `dispatch`, on the
7750  /// calling thread, is handled: the executor recognises that stack as a
7751  /// possible waker's, moves the rejected work aside and cancels it on the
7752  /// next clean stack (see `CurrentThreadQueue::rejected`). Failing or
7753  /// acknowledging it later, from any thread, is handled too. The Node-API
7754  /// driver satisfies this contract: it makes one nonblocking
7755  /// threadsafe-function call and returns `false` at once when the
7756  /// environment is closing.
7757  fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool;
7758
7759  fn is_live(&self) -> bool {
7760    true
7761  }
7762
7763  /// Called after this driver has been removed from the registry, with no
7764  /// registry lock held. Must be idempotent with explicit host cleanup.
7765  fn on_swept(&self) {}
7766}
7767
7768#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7769pub struct CurrentThreadTaskDriverId(u64);
7770
7771type CurrentThreadTaskDriverSnapshot =
7772  ContainedArcSnapshot<CurrentThreadTaskDriverId, dyn CurrentThreadTaskDriver>;
7773
7774#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7775pub struct CurrentThreadTaskDelivery {
7776  driver: CurrentThreadTaskDriverId,
7777  capability: u64,
7778  dispatch: u64,
7779}
7780
7781impl CurrentThreadTaskDelivery {
7782  /// Opaque driver capability for this exact registration attempt.
7783  pub fn capability(self) -> u64 {
7784    self.capability
7785  }
7786}
7787
7788struct CurrentThreadTaskDeliveryState {
7789  delivery: CurrentThreadTaskDelivery,
7790  claimed: bool,
7791  accepted: bool,
7792}
7793
7794struct CurrentThreadTaskDriverEntry {
7795  driver: Arc<dyn CurrentThreadTaskDriver>,
7796  delivery: Option<CurrentThreadTaskDeliveryState>,
7797  pending: Option<u64>,
7798  previous: Option<CurrentThreadTaskDriverId>,
7799  next: Option<CurrentThreadTaskDriverId>,
7800}
7801
7802#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
7803enum CurrentThreadDispatchPhase {
7804  #[default]
7805  Pending,
7806  Serviced,
7807  Cancelling,
7808}
7809
7810#[derive(Default)]
7811struct CurrentThreadDispatchState {
7812  phase: CurrentThreadDispatchPhase,
7813  epoch: u64,
7814  publications: usize,
7815  references: usize,
7816  accepted: bool,
7817  accepted_failure: bool,
7818}
7819
7820impl CurrentThreadDispatchState {
7821  fn is_serviced(&self) -> bool {
7822    self.phase == CurrentThreadDispatchPhase::Serviced
7823  }
7824
7825  fn is_cancelling(&self) -> bool {
7826    self.phase == CurrentThreadDispatchPhase::Cancelling
7827  }
7828
7829  fn failure(&self, dispatch: u64) -> CurrentThreadTaskDispatchFailure {
7830    CurrentThreadTaskDispatchFailure {
7831      dispatch,
7832      epoch: self.epoch,
7833    }
7834  }
7835}
7836
7837#[derive(Default)]
7838struct CurrentThreadTaskDriverRegistryState {
7839  entries: FxHashMap<CurrentThreadTaskDriverId, CurrentThreadTaskDriverEntry>,
7840  oldest: Option<CurrentThreadTaskDriverId>,
7841  newest: Option<CurrentThreadTaskDriverId>,
7842  delivery_drivers: FxHashMap<u64, CurrentThreadTaskDriverId>,
7843  dispatches: FxHashMap<u64, CurrentThreadDispatchState>,
7844}
7845
7846impl CurrentThreadTaskDriverRegistryState {
7847  fn insert_driver(
7848    &mut self,
7849    id: CurrentThreadTaskDriverId,
7850    driver: Arc<dyn CurrentThreadTaskDriver>,
7851  ) {
7852    let previous = self.newest;
7853    let replaced = self.entries.insert(
7854      id,
7855      CurrentThreadTaskDriverEntry {
7856        driver,
7857        delivery: None,
7858        pending: None,
7859        previous,
7860        next: None,
7861      },
7862    );
7863    assert!(
7864      replaced.is_none(),
7865      "CurrentThread task driver id must be unique"
7866    );
7867    if let Some(previous) = previous {
7868      self
7869        .entries
7870        .get_mut(&previous)
7871        .expect("the newest CurrentThread task driver must remain registered")
7872        .next = Some(id);
7873    } else {
7874      self.oldest = Some(id);
7875    }
7876    self.newest = Some(id);
7877  }
7878
7879  fn remove_driver(
7880    &mut self,
7881    id: CurrentThreadTaskDriverId,
7882  ) -> Option<CurrentThreadTaskDriverEntry> {
7883    let entry = self.entries.remove(&id)?;
7884    if let Some(previous) = entry.previous {
7885      self
7886        .entries
7887        .get_mut(&previous)
7888        .expect("the previous CurrentThread task driver must remain registered")
7889        .next = entry.next;
7890    } else {
7891      self.oldest = entry.next;
7892    }
7893    if let Some(next) = entry.next {
7894      self
7895        .entries
7896        .get_mut(&next)
7897        .expect("the next CurrentThread task driver must remain registered")
7898        .previous = entry.previous;
7899    } else {
7900      self.newest = entry.previous;
7901    }
7902    if let Some(delivery) = &entry.delivery {
7903      let indexed = self.delivery_drivers.remove(&delivery.delivery.capability);
7904      debug_assert_eq!(
7905        indexed,
7906        Some(id),
7907        "delivery capability index must match its driver"
7908      );
7909    }
7910    Some(entry)
7911  }
7912
7913  fn ordered_snapshot(&self) -> Vec<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
7914    let mut snapshot = Vec::with_capacity(self.entries.len());
7915    let mut current = self.oldest;
7916    while let Some(id) = current {
7917      let entry = self
7918        .entries
7919        .get(&id)
7920        .expect("CurrentThread task driver order must reference a live entry");
7921      snapshot.push((id, Arc::clone(&entry.driver)));
7922      current = entry.next;
7923    }
7924    debug_assert_eq!(
7925      snapshot.len(),
7926      self.entries.len(),
7927      "CurrentThread task driver order must contain every entry exactly once"
7928    );
7929    snapshot
7930  }
7931
7932  fn ordered_ids_match(&self, expected: &[CurrentThreadTaskDriverId]) -> bool {
7933    if self.entries.len() != expected.len() {
7934      return false;
7935    }
7936    let mut current = self.oldest;
7937    for expected in expected {
7938      if current != Some(*expected) {
7939        return false;
7940      }
7941      current = self
7942        .entries
7943        .get(expected)
7944        .expect("CurrentThread task driver order must reference a live entry")
7945        .next;
7946    }
7947    current.is_none()
7948  }
7949}
7950
7951struct CurrentThreadTaskDriverSelection {
7952  live: CurrentThreadTaskDriverSnapshot,
7953  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7954}
7955
7956#[derive(Default)]
7957struct CurrentThreadTaskDeliveryCompletion {
7958  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7959  redispatch: bool,
7960}
7961
7962struct CurrentThreadTaskDispatchCompletion {
7963  result: CurrentThreadHostDispatchResult,
7964  failed_dispatches: Vec<CurrentThreadTaskDispatchFailure>,
7965}
7966
7967struct CurrentThreadTaskDispatchPublicationGuard<'a> {
7968  registry: &'a CurrentThreadTaskDriverRegistry,
7969  dispatch: Option<u64>,
7970}
7971
7972impl CurrentThreadTaskDispatchPublicationGuard<'_> {
7973  fn finish(mut self) -> CurrentThreadHostDispatchResult {
7974    let dispatch = self
7975      .dispatch
7976      .expect("the CurrentThread task dispatch publication must remain armed");
7977    let result = self.registry.finish_publication(dispatch);
7978    self.dispatch = None;
7979    result
7980  }
7981}
7982
7983impl Drop for CurrentThreadTaskDispatchPublicationGuard<'_> {
7984  fn drop(&mut self) {
7985    if let Some(dispatch) = self.dispatch.take() {
7986      self.registry.abort_publication(dispatch);
7987    }
7988  }
7989}
7990
7991#[derive(Default)]
7992struct CurrentThreadTaskDriverRegistry {
7993  state: Mutex<CurrentThreadTaskDriverRegistryState>,
7994  next_id: AtomicU64,
7995  next_delivery: AtomicU64,
7996}
7997
7998impl CurrentThreadTaskDriverRegistry {
7999  fn register(&self, driver: Arc<dyn CurrentThreadTaskDriver>) -> CurrentThreadTaskDriverId {
8000    let id = next_unique_id(
8001      &self.next_id,
8002      "CurrentThread task driver id space exhausted",
8003    );
8004    let id = CurrentThreadTaskDriverId(id);
8005    self
8006      .state
8007      .lock()
8008      .unwrap_or_else(std::sync::PoisonError::into_inner)
8009      .insert_driver(id, driver);
8010    id
8011  }
8012
8013  fn unregister(&self, id: CurrentThreadTaskDriverId) -> CurrentThreadTaskDeliveryCompletion {
8014    let (removed, failed_dispatches) = {
8015      let mut state = self
8016        .state
8017        .lock()
8018        .unwrap_or_else(std::sync::PoisonError::into_inner);
8019      let removed = state.remove_driver(id);
8020      let mut failed_dispatches = Vec::new();
8021      if let Some(entry) = &removed {
8022        Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8023      }
8024      (removed, failed_dispatches)
8025    };
8026    let _ = catch_unwind_contained(|| drop(removed));
8027    CurrentThreadTaskDeliveryCompletion {
8028      failed_dispatches,
8029      redispatch: false,
8030    }
8031  }
8032
8033  fn live(&self) -> CurrentThreadTaskDriverSelection {
8034    let mut failed_dispatches = Vec::new();
8035    loop {
8036      let snapshot = CurrentThreadTaskDriverSnapshot::new({
8037        let state = self
8038          .state
8039          .lock()
8040          .unwrap_or_else(std::sync::PoisonError::into_inner);
8041        state.ordered_snapshot()
8042      });
8043      let mut dead_ids = FxHashSet::default();
8044      let mut live_ids = Vec::new();
8045      for (id, driver) in snapshot.as_slice() {
8046        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
8047          live_ids.push(*id);
8048        } else {
8049          dead_ids.insert(*id);
8050        }
8051      }
8052
8053      let (live, swept, retry) = {
8054        let mut state = self
8055          .state
8056          .lock()
8057          .unwrap_or_else(std::sync::PoisonError::into_inner);
8058        let mut swept = Vec::new();
8059        let mut current = state.oldest;
8060        while let Some(id) = current {
8061          let next = state
8062            .entries
8063            .get(&id)
8064            .expect("CurrentThread task driver order must reference a live entry")
8065            .next;
8066          if dead_ids.contains(&id)
8067            && let Some(entry) = state.remove_driver(id)
8068          {
8069            swept.push(entry);
8070          }
8071          current = next;
8072        }
8073        for entry in &swept {
8074          Self::finish_entry_references(&mut state, entry, &mut failed_dispatches);
8075        }
8076        let retry = !state.ordered_ids_match(&live_ids);
8077        let live = if retry {
8078          Vec::new()
8079        } else {
8080          state.ordered_snapshot()
8081        };
8082        (CurrentThreadTaskDriverSnapshot::new(live), swept, retry)
8083      };
8084
8085      // A reentrant probe can unregister multiple snapshot-only drivers.
8086      // See the implementation notes in this crate's README.
8087      drop(snapshot);
8088      for entry in swept {
8089        let _ = catch_unwind_contained(|| entry.driver.on_swept());
8090        let _ = catch_unwind_contained(|| drop(entry));
8091      }
8092      if !retry {
8093        return CurrentThreadTaskDriverSelection {
8094          live,
8095          failed_dispatches,
8096        };
8097      }
8098    }
8099  }
8100
8101  #[cfg(test)]
8102  fn current(&self) -> Option<(CurrentThreadTaskDriverId, Arc<dyn CurrentThreadTaskDriver>)> {
8103    let mut live = self.live().live;
8104    live.pop().map(|(id, driver)| (id, driver.into_inner()))
8105  }
8106
8107  fn next_delivery(
8108    &self,
8109    driver: CurrentThreadTaskDriverId,
8110    dispatch: u64,
8111  ) -> CurrentThreadTaskDelivery {
8112    let capability = next_unique_id(
8113      &self.next_delivery,
8114      "CurrentThread task delivery id space exhausted",
8115    )
8116    .checked_add(1)
8117    .expect("CurrentThread task delivery id space exhausted");
8118    CurrentThreadTaskDelivery {
8119      driver,
8120      capability,
8121      dispatch,
8122    }
8123  }
8124
8125  fn add_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8126    let dispatch_state = state
8127      .dispatches
8128      .get_mut(&dispatch)
8129      .expect("a referenced CurrentThread dispatch must remain registered");
8130    dispatch_state.references = dispatch_state
8131      .references
8132      .checked_add(1)
8133      .expect("CurrentThread dispatch reference count exhausted");
8134  }
8135
8136  fn remove_dispatch_reference(state: &mut CurrentThreadTaskDriverRegistryState, dispatch: u64) {
8137    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8138      return;
8139    };
8140    dispatch_state.references = dispatch_state
8141      .references
8142      .checked_sub(1)
8143      .expect("CurrentThread dispatch reference count underflow");
8144  }
8145
8146  fn dispatch_with_completion(&self, dispatch: u64) -> CurrentThreadTaskDispatchCompletion {
8147    let publication = {
8148      let mut state = self
8149        .state
8150        .lock()
8151        .unwrap_or_else(std::sync::PoisonError::into_inner);
8152      let dispatch_state = state.dispatches.entry(dispatch).or_default();
8153      if dispatch_state.is_serviced() {
8154        return CurrentThreadTaskDispatchCompletion {
8155          result: CurrentThreadHostDispatchResult::Accepted,
8156          failed_dispatches: Vec::new(),
8157        };
8158      }
8159      if dispatch_state.is_cancelling() {
8160        return CurrentThreadTaskDispatchCompletion {
8161          result: CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch)),
8162          failed_dispatches: Vec::new(),
8163        };
8164      }
8165      dispatch_state.publications = dispatch_state
8166        .publications
8167        .checked_add(1)
8168        .expect("CurrentThread dispatch publication count exhausted");
8169      CurrentThreadTaskDispatchPublicationGuard {
8170        registry: self,
8171        dispatch: Some(dispatch),
8172      }
8173    };
8174
8175    let CurrentThreadTaskDriverSelection {
8176      mut live,
8177      mut failed_dispatches,
8178    } = self.live();
8179    let planned_deliveries = {
8180      let mut state = self
8181        .state
8182        .lock()
8183        .unwrap_or_else(std::sync::PoisonError::into_inner);
8184      let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8185        return CurrentThreadTaskDispatchCompletion {
8186          result: CurrentThreadHostDispatchResult::Unavailable,
8187          failed_dispatches,
8188        };
8189      };
8190      if dispatch_state.is_serviced() || dispatch_state.is_cancelling() {
8191        Vec::new()
8192      } else {
8193        // Reserve every capability and all output storage before mutating an
8194        // entry. If ID allocation unwinds, the publication guard can retire
8195        // the barrier without leaving an undispatched physical slot occupied.
8196        let mut reserved_deliveries = Vec::with_capacity(live.len());
8197        for (id, _) in live.as_slice().iter().rev() {
8198          let Some(entry) = state.entries.get(id) else {
8199            continue;
8200          };
8201          if entry.delivery.is_none() {
8202            reserved_deliveries.push(self.next_delivery(*id, dispatch));
8203          }
8204        }
8205        state.delivery_drivers.reserve(reserved_deliveries.len());
8206        let mut reserved_deliveries = reserved_deliveries.into_iter();
8207        let mut planned_deliveries = Vec::with_capacity(live.len());
8208        let mut retired = Vec::with_capacity(live.len());
8209        let mut accepted_reservation = false;
8210        for (id, _) in live.as_slice().iter().rev() {
8211          let Some(entry) = state.entries.get(id) else {
8212            continue;
8213          };
8214          if let Some(delivery) = &entry.delivery
8215            && delivery.delivery.dispatch == dispatch
8216          {
8217            accepted_reservation |= delivery.accepted;
8218            continue;
8219          }
8220          if entry.delivery.is_some() {
8221            let previous = state
8222              .entries
8223              .get_mut(id)
8224              .expect("the CurrentThread task driver must remain registered")
8225              .pending
8226              .replace(dispatch);
8227            if previous != Some(dispatch) {
8228              if let Some(previous) = previous {
8229                Self::remove_dispatch_reference(&mut state, previous);
8230              }
8231              Self::add_dispatch_reference(&mut state, dispatch);
8232            }
8233            retired.push(previous);
8234            accepted_reservation = true;
8235            continue;
8236          }
8237          let delivery = reserved_deliveries
8238            .next()
8239            .expect("every available CurrentThread host must have a reserved delivery");
8240          let pending = state
8241            .entries
8242            .get_mut(id)
8243            .expect("the CurrentThread task driver must remain registered")
8244            .pending
8245            .take();
8246          let accepted = pending == Some(dispatch);
8247          if !accepted && pending.is_some() {
8248            retired.push(pending);
8249          }
8250          match pending {
8251            Some(pending) if pending == dispatch => {}
8252            Some(pending) => {
8253              Self::remove_dispatch_reference(&mut state, pending);
8254              Self::add_dispatch_reference(&mut state, dispatch);
8255            }
8256            None => Self::add_dispatch_reference(&mut state, dispatch),
8257          }
8258          let indexed = state.delivery_drivers.insert(delivery.capability, *id);
8259          assert!(
8260            indexed.is_none(),
8261            "CurrentThread delivery capability must be unique"
8262          );
8263          state
8264            .entries
8265            .get_mut(id)
8266            .expect("the CurrentThread task driver must remain registered")
8267            .delivery = Some(CurrentThreadTaskDeliveryState {
8268            delivery,
8269            claimed: false,
8270            accepted,
8271          });
8272          accepted_reservation |= accepted;
8273          planned_deliveries.push((*id, delivery));
8274        }
8275        let all_reserved_deliveries_installed = reserved_deliveries.next().is_none();
8276        debug_assert!(
8277          all_reserved_deliveries_installed,
8278          "every reserved CurrentThread delivery must be installed"
8279        );
8280        if accepted_reservation {
8281          state
8282            .dispatches
8283            .get_mut(&dispatch)
8284            .expect("the publishing dispatch state must remain registered")
8285            .accepted = true;
8286        }
8287        for dispatch in retired.into_iter().flatten() {
8288          Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8289        }
8290        planned_deliveries
8291      }
8292    };
8293
8294    // Pair newest-first delivery plans with contained driver owners after
8295    // releasing the registry lock. Normal, rejection, and unwind cleanup can
8296    // then destroy every selected driver independently without re-entering the
8297    // registry under its mutex.
8298    let mut deliveries = Vec::with_capacity(planned_deliveries.len());
8299    let mut planned_deliveries = planned_deliveries.into_iter();
8300    let mut next_delivery = planned_deliveries.next();
8301    while let Some((id, driver)) = live.pop() {
8302      let Some((planned_id, delivery)) = next_delivery else {
8303        break;
8304      };
8305      if planned_id == id {
8306        deliveries.push((id, driver, delivery));
8307        next_delivery = planned_deliveries.next();
8308      } else {
8309        next_delivery = Some((planned_id, delivery));
8310      }
8311    }
8312    debug_assert!(
8313      next_delivery.is_none() && planned_deliveries.len() == 0,
8314      "every planned CurrentThread delivery must retain its selected driver"
8315    );
8316
8317    for (id, driver, delivery) in deliveries {
8318      if catch_unwind_contained(|| driver.get().dispatch(delivery)).unwrap_or(false) {
8319        self.accept_delivery(id, delivery);
8320      } else {
8321        let (entry, failures) = self.reject_delivery(id, delivery);
8322        failed_dispatches.extend(failures);
8323        if let Some(entry) = entry {
8324          let _ = catch_unwind_contained(|| entry.driver.on_swept());
8325          let _ = catch_unwind_contained(|| drop(entry));
8326        }
8327      }
8328    }
8329
8330    let result = publication.finish();
8331    debug_assert!(
8332      !failed_dispatches
8333        .iter()
8334        .any(|failure| failure.dispatch == dispatch),
8335      "the active publication must report its own terminal outcome directly"
8336    );
8337    CurrentThreadTaskDispatchCompletion {
8338      result,
8339      failed_dispatches,
8340    }
8341  }
8342
8343  #[cfg(test)]
8344  fn dispatch(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8345    self.dispatch_with_completion(dispatch).result
8346  }
8347
8348  fn accept_delivery(&self, id: CurrentThreadTaskDriverId, delivery: CurrentThreadTaskDelivery) {
8349    let mut state = self
8350      .state
8351      .lock()
8352      .unwrap_or_else(std::sync::PoisonError::into_inner);
8353    let delivery_remains = if state.entries.get(&id).is_some_and(|entry| {
8354      entry
8355        .delivery
8356        .as_ref()
8357        .is_some_and(|current| current.delivery == delivery)
8358    }) {
8359      state
8360        .entries
8361        .get_mut(&id)
8362        .expect("the matching CurrentThread task driver must remain registered")
8363        .delivery
8364        .as_mut()
8365        .expect("the matching delivery must remain installed")
8366        .accepted = true;
8367      true
8368    } else {
8369      // A callback may complete or unregister synchronously before
8370      // `CurrentThreadTaskDriver::dispatch` returns. The true return still
8371      // proves acceptance; if no host serviced it, losing the reference is an
8372      // accepted delivery failure retained behind this publication barrier.
8373      false
8374    };
8375    if let Some(dispatch_state) = state.dispatches.get_mut(&delivery.dispatch) {
8376      dispatch_state.accepted = true;
8377      dispatch_state.accepted_failure |= !delivery_remains && !dispatch_state.is_serviced();
8378    }
8379  }
8380
8381  fn reject_delivery(
8382    &self,
8383    id: CurrentThreadTaskDriverId,
8384    delivery: CurrentThreadTaskDelivery,
8385  ) -> (
8386    Option<CurrentThreadTaskDriverEntry>,
8387    Vec<CurrentThreadTaskDispatchFailure>,
8388  ) {
8389    let mut state = self
8390      .state
8391      .lock()
8392      .unwrap_or_else(std::sync::PoisonError::into_inner);
8393    if !state.entries.get(&id).is_some_and(|entry| {
8394      entry
8395        .delivery
8396        .as_ref()
8397        .is_some_and(|current| current.delivery == delivery)
8398    }) {
8399      return (None, Vec::new());
8400    }
8401    let entry = state
8402      .remove_driver(id)
8403      .expect("the matching CurrentThread task driver must remain registered");
8404    let mut failures = Vec::new();
8405    Self::finish_entry_references(&mut state, &entry, &mut failures);
8406    (Some(entry), failures)
8407  }
8408
8409  fn finish_delivery(
8410    &self,
8411    delivery: CurrentThreadTaskDelivery,
8412    failed: bool,
8413  ) -> CurrentThreadTaskDeliveryCompletion {
8414    let mut state = self
8415      .state
8416      .lock()
8417      .unwrap_or_else(std::sync::PoisonError::into_inner);
8418    if !state.entries.get(&delivery.driver).is_some_and(|entry| {
8419      entry
8420        .delivery
8421        .as_ref()
8422        .is_some_and(|current| current.delivery == delivery)
8423    }) {
8424      return CurrentThreadTaskDeliveryCompletion::default();
8425    }
8426    let current = state
8427      .entries
8428      .get_mut(&delivery.driver)
8429      .expect("the matching CurrentThread task driver must remain registered")
8430      .delivery
8431      .take()
8432      .expect("the matching delivery must remain installed");
8433    let indexed = state.delivery_drivers.remove(&delivery.capability);
8434    debug_assert_eq!(
8435      indexed,
8436      Some(delivery.driver),
8437      "delivery capability index must match its driver"
8438    );
8439    let redispatch = state
8440      .entries
8441      .get(&delivery.driver)
8442      .expect("the matching CurrentThread task driver must remain registered")
8443      .pending
8444      .is_some_and(|dispatch| {
8445        state
8446          .dispatches
8447          .get(&dispatch)
8448          .is_some_and(|state| !state.is_serviced() && !state.is_cancelling())
8449      });
8450    let mut failed_dispatches = Vec::new();
8451    // Callback completion proves only that JavaScript ran. A success return is
8452    // authoritative only after `drive_current_thread_tasks` claimed this exact
8453    // host attempt; otherwise a no-op or malicious callback could retire the
8454    // registry reference while leaving the executor dispatch pending forever.
8455    Self::finish_dispatch_reference(
8456      &mut state,
8457      delivery.dispatch,
8458      true,
8459      failed || !current.claimed,
8460      &mut failed_dispatches,
8461    );
8462    CurrentThreadTaskDeliveryCompletion {
8463      failed_dispatches,
8464      redispatch,
8465    }
8466  }
8467
8468  fn claim_delivery(&self, capability: u64) -> Option<u64> {
8469    let mut state = self
8470      .state
8471      .lock()
8472      .unwrap_or_else(std::sync::PoisonError::into_inner);
8473    let driver = *state.delivery_drivers.get(&capability)?;
8474    let delivery = state.entries.get(&driver)?.delivery.as_ref()?.delivery;
8475    if delivery.capability != capability {
8476      return None;
8477    }
8478    let dispatch_state = state.dispatches.get(&delivery.dispatch)?;
8479    if dispatch_state.is_serviced()
8480      || dispatch_state.is_cancelling()
8481      || state.entries.get(&driver)?.delivery.as_ref()?.claimed
8482    {
8483      return None;
8484    }
8485    let current = state.entries.get_mut(&driver)?.delivery.as_mut()?;
8486    current.claimed = true;
8487    current.accepted = true;
8488    state.dispatches.get_mut(&delivery.dispatch)?.accepted = true;
8489    Some(delivery.dispatch)
8490  }
8491
8492  fn mark_serviced(&self, dispatch: u64) {
8493    let mut state = self
8494      .state
8495      .lock()
8496      .unwrap_or_else(std::sync::PoisonError::into_inner);
8497    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8498      return;
8499    };
8500    dispatch_state.phase = CurrentThreadDispatchPhase::Serviced;
8501    let mut removed_references = 0usize;
8502    for entry in state.entries.values_mut() {
8503      if entry.pending == Some(dispatch) {
8504        entry.pending = None;
8505        removed_references = removed_references
8506          .checked_add(1)
8507          .expect("CurrentThread dispatch reference count exhausted");
8508      }
8509    }
8510    let dispatch_state = state
8511      .dispatches
8512      .get_mut(&dispatch)
8513      .expect("the serviced CurrentThread dispatch must remain registered");
8514    dispatch_state.references = dispatch_state
8515      .references
8516      .checked_sub(removed_references)
8517      .expect("CurrentThread dispatch reference count underflow");
8518    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8519  }
8520
8521  fn fail_delivery(&self, capability: u64) -> CurrentThreadTaskDeliveryCompletion {
8522    let delivery = {
8523      let state = self
8524        .state
8525        .lock()
8526        .unwrap_or_else(std::sync::PoisonError::into_inner);
8527      state
8528        .delivery_drivers
8529        .get(&capability)
8530        .and_then(|driver| state.entries.get(driver))
8531        .and_then(|entry| entry.delivery.as_ref())
8532        .map(|delivery| delivery.delivery)
8533        .filter(|delivery| delivery.capability == capability)
8534    };
8535    delivery.map_or_else(CurrentThreadTaskDeliveryCompletion::default, |delivery| {
8536      self.finish_delivery(delivery, true)
8537    })
8538  }
8539
8540  fn finish_dispatch_reference(
8541    state: &mut CurrentThreadTaskDriverRegistryState,
8542    dispatch: u64,
8543    accepted: bool,
8544    failed: bool,
8545    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8546  ) {
8547    let (publications, references) = {
8548      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8549        return;
8550      };
8551      dispatch_state.references = dispatch_state
8552        .references
8553        .checked_sub(1)
8554        .expect("CurrentThread dispatch reference count underflow");
8555      dispatch_state.accepted |= accepted;
8556      dispatch_state.accepted_failure |= accepted && failed;
8557      (dispatch_state.publications, dispatch_state.references)
8558    };
8559    if references != 0 || publications != 0 {
8560      return;
8561    }
8562    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8563      return;
8564    };
8565    if dispatch_state.accepted_failure
8566      && !dispatch_state.is_serviced()
8567      && !dispatch_state.is_cancelling()
8568    {
8569      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8570      failures.push(dispatch_state.failure(dispatch));
8571    } else if !dispatch_state.is_cancelling() {
8572      state.dispatches.remove(&dispatch);
8573    }
8574  }
8575
8576  fn finish_entry_references(
8577    state: &mut CurrentThreadTaskDriverRegistryState,
8578    entry: &CurrentThreadTaskDriverEntry,
8579    failures: &mut Vec<CurrentThreadTaskDispatchFailure>,
8580  ) {
8581    if let Some(delivery) = &entry.delivery {
8582      Self::finish_dispatch_reference(
8583        state,
8584        delivery.delivery.dispatch,
8585        delivery.accepted,
8586        delivery.accepted,
8587        failures,
8588      );
8589    }
8590    if let Some(pending) = entry.pending {
8591      Self::finish_dispatch_reference(state, pending, true, true, failures);
8592    }
8593  }
8594
8595  fn finish_publication(&self, dispatch: u64) -> CurrentThreadHostDispatchResult {
8596    let mut state = self
8597      .state
8598      .lock()
8599      .unwrap_or_else(std::sync::PoisonError::into_inner);
8600    let publications = {
8601      let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8602        return CurrentThreadHostDispatchResult::Unavailable;
8603      };
8604      dispatch_state.publications = dispatch_state
8605        .publications
8606        .checked_sub(1)
8607        .expect("CurrentThread dispatch publication count underflow");
8608      dispatch_state.publications
8609    };
8610    if Self::has_dispatch_reference(&state, dispatch) || publications != 0 {
8611      return CurrentThreadHostDispatchResult::Accepted;
8612    }
8613    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8614      return CurrentThreadHostDispatchResult::Unavailable;
8615    };
8616    if dispatch_state.is_cancelling() {
8617      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8618    }
8619    if dispatch_state.accepted_failure && !dispatch_state.is_serviced() {
8620      dispatch_state.phase = CurrentThreadDispatchPhase::Cancelling;
8621      return CurrentThreadHostDispatchResult::Failed(dispatch_state.failure(dispatch));
8622    }
8623    let accepted = dispatch_state.accepted || dispatch_state.is_serviced();
8624    state.dispatches.remove(&dispatch);
8625    if accepted {
8626      CurrentThreadHostDispatchResult::Accepted
8627    } else {
8628      CurrentThreadHostDispatchResult::Unavailable
8629    }
8630  }
8631
8632  fn abort_publication(&self, dispatch: u64) {
8633    let mut state = self
8634      .state
8635      .lock()
8636      .unwrap_or_else(std::sync::PoisonError::into_inner);
8637    let Some(dispatch_state) = state.dispatches.get_mut(&dispatch) else {
8638      return;
8639    };
8640    let Some(publications) = dispatch_state.publications.checked_sub(1) else {
8641      return;
8642    };
8643    dispatch_state.publications = publications;
8644    Self::retire_unreferenced_dispatch(&mut state, Some(dispatch));
8645  }
8646
8647  fn rearm_failed_publication(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8648    let mut state = self
8649      .state
8650      .lock()
8651      .unwrap_or_else(std::sync::PoisonError::into_inner);
8652    let can_rearm = state
8653      .dispatches
8654      .get(&failure.dispatch)
8655      .is_some_and(|dispatch_state| {
8656        dispatch_state.is_cancelling()
8657          && dispatch_state.epoch == failure.epoch
8658          && dispatch_state.publications == 0
8659      })
8660      && !Self::has_dispatch_reference(&state, failure.dispatch);
8661    if !can_rearm {
8662      return false;
8663    }
8664    let dispatch_state = state
8665      .dispatches
8666      .get_mut(&failure.dispatch)
8667      .expect("the exact failed CurrentThread dispatch must remain registered");
8668    let epoch = dispatch_state
8669      .epoch
8670      .checked_add(1)
8671      .expect("CurrentThread dispatch failure epoch space exhausted");
8672    *dispatch_state = CurrentThreadDispatchState {
8673      epoch,
8674      ..CurrentThreadDispatchState::default()
8675    };
8676    true
8677  }
8678
8679  fn is_current_failure(&self, failure: CurrentThreadTaskDispatchFailure) -> bool {
8680    self
8681      .state
8682      .lock()
8683      .unwrap_or_else(std::sync::PoisonError::into_inner)
8684      .dispatches
8685      .get(&failure.dispatch)
8686      .is_some_and(|dispatch_state| {
8687        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8688      })
8689  }
8690
8691  fn retire_unreferenced_dispatch(
8692    state: &mut CurrentThreadTaskDriverRegistryState,
8693    dispatch: Option<u64>,
8694  ) {
8695    let Some(dispatch) = dispatch else {
8696      return;
8697    };
8698    if Self::has_dispatch_reference(state, dispatch) {
8699      return;
8700    }
8701    let Some(dispatch_state) = state.dispatches.get(&dispatch) else {
8702      return;
8703    };
8704    if dispatch_state.publications == 0
8705      && !dispatch_state.is_cancelling()
8706      && (dispatch_state.is_serviced() || !dispatch_state.accepted_failure)
8707    {
8708      state.dispatches.remove(&dispatch);
8709    }
8710  }
8711
8712  fn has_dispatch_reference(state: &CurrentThreadTaskDriverRegistryState, dispatch: u64) -> bool {
8713    state
8714      .dispatches
8715      .get(&dispatch)
8716      .is_some_and(|dispatch_state| dispatch_state.references != 0)
8717  }
8718
8719  fn finish_cancellation(&self, failure: CurrentThreadTaskDispatchFailure) {
8720    let mut state = self
8721      .state
8722      .lock()
8723      .unwrap_or_else(std::sync::PoisonError::into_inner);
8724    if state
8725      .dispatches
8726      .get(&failure.dispatch)
8727      .is_some_and(|dispatch_state| {
8728        dispatch_state.is_cancelling() && dispatch_state.epoch == failure.epoch
8729      })
8730    {
8731      state.dispatches.remove(&failure.dispatch);
8732    }
8733  }
8734
8735  fn retire_for_shutdown(&self) {
8736    let mut state = self
8737      .state
8738      .lock()
8739      .unwrap_or_else(std::sync::PoisonError::into_inner);
8740    state.dispatches.clear();
8741    for entry in state.entries.values_mut() {
8742      entry.pending = None;
8743    }
8744  }
8745}
8746
8747static CURRENT_THREAD_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
8748  LazyLock::new(CurrentThreadTaskDriverRegistry::default);
8749
8750pub fn register_current_thread_task_driver(
8751  driver: Arc<dyn CurrentThreadTaskDriver>,
8752) -> CurrentThreadTaskDriverId {
8753  CURRENT_THREAD_TASK_DRIVERS.register(driver)
8754}
8755
8756/// Request service for work that may have accumulated while no live host
8757/// dispatcher was registered. A new host receives the existing internal
8758/// dispatch capability through a fresh registration-scoped delivery token;
8759/// already accepting hosts keep their attempts.
8760pub fn request_current_thread_task_drain() {
8761  RUNTIME.request_current_thread_drain();
8762}
8763
8764pub fn unregister_current_thread_task_driver(id: CurrentThreadTaskDriverId) {
8765  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.unregister(id));
8766}
8767
8768fn dispatch_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
8769  let CurrentThreadTaskDispatchCompletion {
8770    result,
8771    failed_dispatches,
8772  } = CURRENT_THREAD_TASK_DRIVERS.dispatch_with_completion(dispatch);
8773  complete_current_thread_task_delivery(CurrentThreadTaskDeliveryCompletion {
8774    failed_dispatches,
8775    redispatch: false,
8776  });
8777  result
8778}
8779
8780pub type TimerId = u64;
8781
8782/// Process-global id source for [`Sleep`] futures (both drivers).
8783static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(0);
8784
8785/// Seam for host-delegated timers: the CurrentThread flavor cannot park a
8786/// helper thread on a threadless build, so it delegates
8787/// `sleep_until` to the host event loop through this trait. The Node binding
8788/// installs a `setTimeout`-based implementation via [`register_timer_driver`]
8789/// at import -- one per importing napi env (main thread AND workers).
8790pub trait TimerDriver: Send + Sync + 'static {
8791  /// Arm (or re-arm on re-poll) `waker` to fire at/after `deadline`. Called
8792  /// once per [`Sleep`] poll: implementations must treat a repeated `id` as a
8793  /// waker refresh, not a second timer.
8794  fn register(&self, id: TimerId, deadline: Instant, waker: Waker);
8795  /// Best-effort cancel (Drop of the [`Sleep`] future). A fire that already
8796  /// raced ahead is acceptable (the woken task observes the sleep completed).
8797  fn cancel(&self, id: TimerId);
8798  /// Whether this driver can still deliver wakes. `false` once the driver's
8799  /// host is gone (the Node binding's driver dies with its owning napi env:
8800  /// worker exit aborts the weak threadsafe function). Selection skips and
8801  /// evicts dead drivers -- a timer armed on one would only ever busy-fail.
8802  fn is_live(&self) -> bool {
8803    true
8804  }
8805  /// Called by [`TimerDriverRegistry`] AFTER it swept this driver out of the
8806  /// entries (its `is_live` turned false), with NO registry lock held.
8807  /// Implementations with pending-waker bookkeeping must wake everything
8808  /// armed on them here, so those sleeps re-poll onto the next live
8809  /// registrant: the `is_live` probe can be the FIRST layer to notice a dying
8810  /// host (before its env-cleanup hook or any call failure runs), and a
8811  /// silent `retain` would strand sleeps whose only wake source was the swept
8812  /// driver. Idempotent with the owner's other eviction paths. Default no-op
8813  /// for drivers without waker state.
8814  fn on_swept(&self) {}
8815}
8816
8817/// Handle to one registration in a [`TimerDriverRegistry`], returned by
8818/// [`TimerDriverRegistry::register`] and consumed by
8819/// [`TimerDriverRegistry::unregister`].
8820#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8821pub struct TimerDriverId(u64);
8822
8823type HostTimerDriverSnapshot = ContainedArcSnapshot<TimerDriverId, dyn TimerDriver>;
8824
8825struct TimerDriverEntry {
8826  driver: Arc<dyn TimerDriver>,
8827  previous: Option<TimerDriverId>,
8828  next: Option<TimerDriverId>,
8829}
8830
8831#[derive(Default)]
8832struct TimerDriverRegistryState {
8833  entries: FxHashMap<TimerDriverId, TimerDriverEntry>,
8834  oldest: Option<TimerDriverId>,
8835  newest: Option<TimerDriverId>,
8836}
8837
8838impl TimerDriverRegistryState {
8839  fn insert_driver(&mut self, id: TimerDriverId, driver: Arc<dyn TimerDriver>) {
8840    let previous = self.newest;
8841    let replaced = self.entries.insert(
8842      id,
8843      TimerDriverEntry {
8844        driver,
8845        previous,
8846        next: None,
8847      },
8848    );
8849    assert!(replaced.is_none(), "timer driver id must be unique");
8850    if let Some(previous) = previous {
8851      self
8852        .entries
8853        .get_mut(&previous)
8854        .expect("the newest timer driver must remain registered")
8855        .next = Some(id);
8856    } else {
8857      self.oldest = Some(id);
8858    }
8859    self.newest = Some(id);
8860  }
8861
8862  fn remove_driver(&mut self, id: TimerDriverId) -> Option<TimerDriverEntry> {
8863    let entry = self.entries.remove(&id)?;
8864    if let Some(previous) = entry.previous {
8865      self
8866        .entries
8867        .get_mut(&previous)
8868        .expect("the previous timer driver must remain registered")
8869        .next = entry.next;
8870    } else {
8871      self.oldest = entry.next;
8872    }
8873    if let Some(next) = entry.next {
8874      self
8875        .entries
8876        .get_mut(&next)
8877        .expect("the next timer driver must remain registered")
8878        .previous = entry.previous;
8879    } else {
8880      self.newest = entry.previous;
8881    }
8882    Some(entry)
8883  }
8884
8885  fn ordered_snapshot(&self) -> Vec<(TimerDriverId, Arc<dyn TimerDriver>)> {
8886    let mut snapshot = Vec::with_capacity(self.entries.len());
8887    let mut current = self.oldest;
8888    while let Some(id) = current {
8889      let entry = self
8890        .entries
8891        .get(&id)
8892        .expect("timer driver order must reference a live entry");
8893      snapshot.push((id, Arc::clone(&entry.driver)));
8894      current = entry.next;
8895    }
8896    debug_assert_eq!(
8897      snapshot.len(),
8898      self.entries.len(),
8899      "timer driver order must contain every entry exactly once"
8900    );
8901    snapshot
8902  }
8903
8904  fn ordered_ids_match(&self, expected: &[TimerDriverId]) -> bool {
8905    if self.entries.len() != expected.len() {
8906      return false;
8907    }
8908    let mut current = self.oldest;
8909    for expected in expected {
8910      if current != Some(*expected) {
8911        return false;
8912      }
8913      current = self
8914        .entries
8915        .get(expected)
8916        .expect("timer driver order must reference a live entry")
8917        .next;
8918    }
8919    current.is_none()
8920  }
8921}
8922
8923/// Host timer drivers keyed by registration order. A driver is owned by a host
8924/// context that can die independently
8925/// of the process -- the Node binding registers one driver per importing napi
8926/// env, and a worker's env teardown kills its driver's threadsafe function. A
8927/// single first-wins slot is therefore unsound: a worker that imports the
8928/// binding first and then exits would permanently shadow the slot with a dead
8929/// driver, and every CT timer armed afterwards would busy-fail against it.
8930/// Selecting only the newest live driver is also
8931/// insufficient: a live worker can be CPU-blocked indefinitely while another
8932/// environment remains responsive. The registry therefore keeps all
8933/// registrants and returns a stable snapshot of every live driver at each
8934/// [`Sleep`] poll, sweeping dead entries out as it goes.
8935///
8936/// Instance-based (not a hidden global) so tests exercise
8937/// registration/eviction/selection against local registries; production uses
8938/// the process-global one behind
8939/// [`register_timer_driver`]/[`unregister_timer_driver`]/[`has_live_timer_driver`].
8940#[derive(Default)]
8941pub struct TimerDriverRegistry {
8942  /// Keyed lookup with a linked registration order. Exact unregister and a
8943  /// swept binding host's idempotent unregister are expected O(1), while
8944  /// snapshots keep deterministic oldest-to-newest fallback order.
8945  state: Mutex<TimerDriverRegistryState>,
8946  /// Stable repoll signals for every currently polled host sleep. A newly
8947  /// registered driver snapshots these after publishing itself, then wakes
8948  /// them outside both registry locks so each sleep re-arms the same timer id
8949  /// on the expanded all-live driver set.
8950  pending: Mutex<FxHashMap<TimerId, Weak<HostTimerRepoll>>>,
8951  next_id: AtomicU64,
8952}
8953
8954impl Drop for TimerDriverRegistry {
8955  fn drop(&mut self) {
8956    let state = self
8957      .state
8958      .get_mut()
8959      .unwrap_or_else(std::sync::PoisonError::into_inner);
8960    state.oldest = None;
8961    state.newest = None;
8962    let entries = std::mem::take(&mut state.entries);
8963    for (_, entry) in entries {
8964      let _ = catch_unwind_contained(|| drop(entry));
8965    }
8966  }
8967}
8968
8969impl TimerDriverRegistry {
8970  /// Add `driver` as the newest registrant and return its handle.
8971  pub fn register(&self, driver: Arc<dyn TimerDriver>) -> TimerDriverId {
8972    let id = next_unique_id(&self.next_id, "timer driver id space exhausted");
8973    let id = TimerDriverId(id);
8974    self
8975      .state
8976      .lock()
8977      .unwrap_or_else(std::sync::PoisonError::into_inner)
8978      .insert_driver(id, driver);
8979    // Publish the driver before snapshotting pending sleeps. A sleep publishes
8980    // its repoll signal before selecting live drivers. Therefore either this
8981    // snapshot sees the sleep, or that sleep's subsequent selection sees this
8982    // driver; no registration/insertion interleaving can miss both sides.
8983    let pending = {
8984      let mut entries = self
8985        .pending
8986        .lock()
8987        .unwrap_or_else(std::sync::PoisonError::into_inner);
8988      let mut pending = Vec::with_capacity(entries.len());
8989      entries.retain(|_, repoll| {
8990        if let Some(repoll) = repoll.upgrade() {
8991          pending.push(repoll);
8992          true
8993        } else {
8994          false
8995        }
8996      });
8997      pending
8998    };
8999    for repoll in pending {
9000      repoll.wake();
9001    }
9002    id
9003  }
9004
9005  /// Drop the registration behind `id` (no-op when already swept/removed).
9006  pub fn unregister(&self, id: TimerDriverId) {
9007    let removed = self
9008      .state
9009      .lock()
9010      .unwrap_or_else(std::sync::PoisonError::into_inner)
9011      .remove_driver(id);
9012    // A driver's destructor may re-enter this registry. Keep it outside the
9013    // entries lock just like is_live/on_swept callbacks.
9014    let _ = catch_unwind_contained(|| drop(removed));
9015  }
9016
9017  /// Every LIVE driver in registration order, sweeping dead entries out as a
9018  /// side effect.
9019  ///
9020  /// LOCK DISCIPLINE: no driver callback or destructor runs with the entries
9021  /// lock held. `is_live` is externally implemented and may re-enter this
9022  /// registry; `on_swept` does so in the binding by calling `unregister` and
9023  /// waking pending sleeps whose re-polls call `current()` again. Selection
9024  /// therefore probes a snapshot, removes dead entries under the lock, then
9025  /// runs hooks after release. A concurrent registration/unregistration can
9026  /// stale the snapshot, in which case selection retries.
9027  fn live(&self) -> HostTimerDriverSnapshot {
9028    loop {
9029      let snapshot = HostTimerDriverSnapshot::new({
9030        let state = self
9031          .state
9032          .lock()
9033          .unwrap_or_else(std::sync::PoisonError::into_inner);
9034        state.ordered_snapshot()
9035      });
9036      let mut dead_ids = FxHashSet::default();
9037      let mut live_ids = Vec::new();
9038      for (id, driver) in snapshot.as_slice() {
9039        if catch_unwind_contained(|| driver.is_live()).unwrap_or(false) {
9040          live_ids.push(*id);
9041        } else {
9042          dead_ids.insert(*id);
9043        }
9044      }
9045
9046      let (live, swept, retry) = {
9047        let mut state = self
9048          .state
9049          .lock()
9050          .unwrap_or_else(std::sync::PoisonError::into_inner);
9051        let mut swept = Vec::new();
9052        let mut current = state.oldest;
9053        while let Some(id) = current {
9054          let next = state
9055            .entries
9056            .get(&id)
9057            .expect("timer driver order must reference a live entry")
9058            .next;
9059          if dead_ids.contains(&id)
9060            && let Some(entry) = state.remove_driver(id)
9061          {
9062            swept.push(entry);
9063          }
9064          current = next;
9065        }
9066        let retry = !state.ordered_ids_match(&live_ids);
9067        let live = if retry {
9068          Vec::new()
9069        } else {
9070          state.ordered_snapshot()
9071        };
9072        (HostTimerDriverSnapshot::new(live), swept, retry)
9073      };
9074
9075      // A reentrant probe can unregister multiple snapshot-only drivers.
9076      // See the implementation notes in this crate's README.
9077      drop(snapshot);
9078      for entry in swept {
9079        let _ = catch_unwind_contained(|| entry.driver.on_swept());
9080        let _ = catch_unwind_contained(|| drop(entry));
9081      }
9082      if !retry {
9083        return live;
9084      }
9085    }
9086  }
9087
9088  #[cfg(test)]
9089  fn current(&self) -> Option<(TimerDriverId, Arc<dyn TimerDriver>)> {
9090    let mut live = self.live();
9091    live.pop().map(|(id, driver)| (id, driver.into_inner()))
9092  }
9093
9094  /// Whether a LIVE driver is currently registered (dead-only counts as no).
9095  pub fn has_live_driver(&self) -> bool {
9096    !self.live().is_empty()
9097  }
9098
9099  fn register_pending(&self, id: TimerId, repoll: &Arc<HostTimerRepoll>) {
9100    let replaced = self
9101      .pending
9102      .lock()
9103      .unwrap_or_else(std::sync::PoisonError::into_inner)
9104      .insert(id, Arc::downgrade(repoll));
9105    assert!(
9106      replaced.is_none(),
9107      "host timer {id} registered its repoll signal twice"
9108    );
9109    // Shutdown can close and try to remove the signal immediately before this
9110    // insertion. The post-insert check is the other side of that race.
9111    if repoll.is_closed() {
9112      self.remove_pending(id);
9113    }
9114  }
9115
9116  fn remove_pending(&self, id: TimerId) {
9117    let removed = self
9118      .pending
9119      .lock()
9120      .unwrap_or_else(std::sync::PoisonError::into_inner)
9121      .remove(&id);
9122    let _ = catch_unwind_contained(|| drop(removed));
9123  }
9124
9125  #[cfg(test)]
9126  fn pending_len(&self) -> usize {
9127    self
9128      .pending
9129      .lock()
9130      .unwrap_or_else(std::sync::PoisonError::into_inner)
9131      .len()
9132  }
9133}
9134
9135/// The process-global registry behind [`sleep_until`] (see
9136/// [`TimerDriverRegistry`] for the lifetime story).
9137static TIMER_DRIVERS: LazyLock<Arc<TimerDriverRegistry>> =
9138  LazyLock::new(|| Arc::new(TimerDriverRegistry::default()));
9139
9140/// Register a host timer driver for the CurrentThread flavor's `sleep_until`.
9141/// Every host context that can serve timers registers its own driver (the
9142/// Node binding: one per importing napi env); every LIVE registrant receives
9143/// each timer so a starved environment cannot own its only wake source.
9144/// Returns the handle for [`unregister_timer_driver`].
9145pub fn register_timer_driver(driver: Arc<dyn TimerDriver>) -> TimerDriverId {
9146  TIMER_DRIVERS.register(driver)
9147}
9148
9149/// Remove a driver registered via [`register_timer_driver`] -- called when
9150/// its host context dies (the Node binding evicts on env teardown and on
9151/// callback failure). Idempotent.
9152pub fn unregister_timer_driver(id: TimerDriverId) {
9153  TIMER_DRIVERS.unregister(id);
9154}
9155
9156/// Whether a LIVE host timer driver is currently registered. Lets the
9157/// embedder report CurrentThread timer availability honestly (e.g. through a
9158/// capabilities export) instead of guessing: with no
9159/// live driver a CurrentThread `sleep_until` would panic, so the capability
9160/// must read `false` -- including when the only registrants are DEAD (their
9161/// owning envs torn down), not merely when none ever registered.
9162pub fn has_live_timer_driver() -> bool {
9163  TIMER_DRIVERS.has_live_driver()
9164}
9165
9166/// Coalescing wake signal used only to make an already-pending [`Sleep`]
9167/// re-select host drivers after a new registration. The task's arbitrary
9168/// waker is never cloned, invoked, or dropped while this mutex is held.
9169#[derive(Default)]
9170struct HostTimerRepoll {
9171  state: Mutex<HostTimerRepollState>,
9172}
9173
9174#[derive(Default)]
9175struct HostTimerRepollState {
9176  closed: bool,
9177  notified: bool,
9178  waker: Option<Waker>,
9179}
9180
9181impl HostTimerRepoll {
9182  fn register(&self, waker: Waker) {
9183    let (replaced, wake) = {
9184      let mut state = self
9185        .state
9186        .lock()
9187        .unwrap_or_else(std::sync::PoisonError::into_inner);
9188      if state.closed {
9189        (Some(waker), None)
9190      } else if state.notified {
9191        state.notified = false;
9192        (state.waker.take(), Some(waker))
9193      } else {
9194        (state.waker.replace(waker), None)
9195      }
9196    };
9197    let _ = catch_unwind_contained(|| drop(replaced));
9198    if let Some(waker) = wake {
9199      wake_waker_contained(waker);
9200    }
9201  }
9202
9203  fn wake(&self) {
9204    let waker = {
9205      let mut state = self
9206        .state
9207        .lock()
9208        .unwrap_or_else(std::sync::PoisonError::into_inner);
9209      if state.closed {
9210        return;
9211      }
9212      if let Some(waker) = state.waker.take() {
9213        Some(waker)
9214      } else {
9215        state.notified = true;
9216        None
9217      }
9218    };
9219    if let Some(waker) = waker {
9220      wake_waker_contained(waker);
9221    }
9222  }
9223
9224  fn close(&self) {
9225    let waker = {
9226      let mut state = self
9227        .state
9228        .lock()
9229        .unwrap_or_else(std::sync::PoisonError::into_inner);
9230      state.closed = true;
9231      state.notified = false;
9232      state.waker.take()
9233    };
9234    let _ = catch_unwind_contained(|| drop(waker));
9235  }
9236
9237  fn is_closed(&self) -> bool {
9238    self
9239      .state
9240      .lock()
9241      .unwrap_or_else(std::sync::PoisonError::into_inner)
9242      .closed
9243  }
9244}
9245
9246/// Pending HOST-driver timers armed on one CurrentThread executor, keyed by
9247/// timer id with the armed deadline as the value. Maintained by the [`Sleep`]
9248/// futures themselves (insert at FIRST POLL, exactly when `driver.register`
9249/// arms the host timer -- an unpolled Sleep holds no wake-token; remove on
9250/// completion/drop via [`HostTimerPendingGuard`]) and read by the CT deadlock
9251/// detection -- see the interaction notes at the top of this section.
9252#[derive(Default)]
9253struct HostTimerRegistry {
9254  state: Mutex<HostTimerRegistryState>,
9255}
9256
9257#[derive(Default)]
9258struct HostTimerRegistryState {
9259  closed: bool,
9260  timers: FxHashMap<TimerId, HostTimerEntry>,
9261}
9262
9263struct HostTimerEntry {
9264  deadline: Instant,
9265  waker: Waker,
9266  fired: Arc<AtomicBool>,
9267  drivers: Weak<TimerDriverRegistry>,
9268  repoll: Arc<HostTimerRepoll>,
9269}
9270
9271impl HostTimerRegistry {
9272  fn register(
9273    &self,
9274    id: TimerId,
9275    deadline: Instant,
9276    waker: Waker,
9277    fired: &Arc<AtomicBool>,
9278    drivers: &Arc<TimerDriverRegistry>,
9279    repoll: &Arc<HostTimerRepoll>,
9280  ) -> bool {
9281    let replaced = {
9282      let mut state = self
9283        .state
9284        .lock()
9285        .unwrap_or_else(std::sync::PoisonError::into_inner);
9286      if state.closed || fired.load(Ordering::SeqCst) {
9287        fired.store(true, Ordering::SeqCst);
9288        Err(waker)
9289      } else {
9290        Ok(state.timers.insert(
9291          id,
9292          HostTimerEntry {
9293            deadline,
9294            waker,
9295            fired: Arc::clone(fired),
9296            drivers: Arc::downgrade(drivers),
9297            repoll: Arc::clone(repoll),
9298          },
9299        ))
9300      }
9301    };
9302    match replaced {
9303      Ok(Some(entry)) => {
9304        let _ = catch_unwind_contained(|| drop(entry));
9305        true
9306      }
9307      Ok(None) => true,
9308      Err(waker) => {
9309        let _ = catch_unwind_contained(|| drop(waker));
9310        false
9311      }
9312    }
9313  }
9314
9315  fn remove(&self, id: TimerId) {
9316    let removed = self
9317      .state
9318      .lock()
9319      .unwrap_or_else(std::sync::PoisonError::into_inner)
9320      .timers
9321      .remove(&id);
9322    if let Some(entry) = removed {
9323      let _ = catch_unwind_contained(|| drop(entry));
9324    }
9325  }
9326
9327  fn shutdown(&self) {
9328    let timers = {
9329      let mut state = self
9330        .state
9331        .lock()
9332        .unwrap_or_else(std::sync::PoisonError::into_inner);
9333      state.closed = true;
9334      state
9335        .timers
9336        .drain()
9337        .map(|(id, entry)| {
9338          entry.fired.store(true, Ordering::SeqCst);
9339          (id, entry)
9340        })
9341        .collect::<Vec<_>>()
9342    };
9343    for (id, entry) in timers {
9344      entry.repoll.close();
9345      if let Some(drivers) = entry.drivers.upgrade() {
9346        drivers.remove_pending(id);
9347      }
9348      wake_waker_contained(entry.waker);
9349    }
9350  }
9351
9352  /// Any host timer armed at all? Consulted by the threadless CERTAIN check
9353  /// only to pick the diagnostic variant: on a threadless build the park is
9354  /// provably dead either way (the host relay shares the parked thread), but
9355  /// a pending timer means the timer-backed message applies.
9356  fn has_pending(&self) -> bool {
9357    !self
9358      .state
9359      .lock()
9360      .unwrap_or_else(std::sync::PoisonError::into_inner)
9361      .timers
9362      .is_empty()
9363  }
9364
9365  /// Any host timer whose deadline (plus `grace` for host firing lag) is
9366  /// still in the future? Used by the CT deadline VERDICT: waiting out such a
9367  /// timer with zero progress is legitimate. A timer past due by more than
9368  /// `grace` that never fired is not -- that is the frozen-JS-event-loop
9369  /// signature the deadline detection exists to catch, so it must not keep
9370  /// suppressing the panic. Callers pass the armed park deadline as `grace`
9371  /// (the detector's own granularity: firing lag within one window is
9372  /// indistinguishable from an expiry racing the fire).
9373  fn has_live_wait(&self, grace: Duration) -> bool {
9374    let now = Instant::now();
9375    self
9376      .state
9377      .lock()
9378      .unwrap_or_else(std::sync::PoisonError::into_inner)
9379      .timers
9380      .values()
9381      .any(|entry| {
9382        entry
9383          .deadline
9384          .checked_add(grace)
9385          .is_none_or(|deadline| deadline > now)
9386      })
9387  }
9388}
9389
9390/// Removes this sleep's entry from its executor's [`HostTimerRegistry`] on
9391/// drop, so completion AND cancellation (Sleep dropped by a `select!` losing
9392/// arm) both retire the wake-token exactly once.
9393struct HostTimerPendingGuard {
9394  registry: Arc<HostTimerRegistry>,
9395  drivers: Arc<TimerDriverRegistry>,
9396  repoll: Arc<HostTimerRepoll>,
9397  id: TimerId,
9398}
9399
9400impl Drop for HostTimerPendingGuard {
9401  fn drop(&mut self) {
9402    self.repoll.close();
9403    self.registry.remove(self.id);
9404    self.drivers.remove_pending(self.id);
9405  }
9406}
9407
9408/// Runtime-owned timer heap for the MultiThread executor.
9409///
9410/// The timekeeper is a dedicated lifecycle-managed OS thread. It never enters
9411/// Rayon and therefore cannot consume one of the configured CPU workers while
9412/// a long timer is armed.
9413#[cfg(not(target_family = "wasm"))]
9414struct TimerHeap {
9415  inner: Arc<Mutex<TimerHeapInner>>,
9416  parker: Arc<DriverParker>,
9417  thread: Mutex<Option<std::thread::JoinHandle<()>>>,
9418  deadlock_state: Arc<MultiThreadDeadlockState>,
9419  #[cfg(test)]
9420  fail_next_start: AtomicBool,
9421  generation: u64,
9422  executor_id: u64,
9423  thread_name: String,
9424}
9425
9426#[cfg(not(target_family = "wasm"))]
9427#[derive(Default)]
9428struct TimerHeapInner {
9429  /// Indexed min-heap of `(deadline, id)`. Cancellation removes its node in
9430  /// `O(log n)`, so non-earliest timer churn cannot accumulate tombstones for
9431  /// one later unbounded cleanup pass.
9432  queue: TimerDeadlineQueue,
9433  entries: FxHashMap<TimerId, HeapTimerEntry>,
9434  /// The parker of the dedicated timekeeper while it is waiting on a timer.
9435  /// `register_timer` unparks it when the earliest deadline moves up, so the
9436  /// timekeeper re-arms (task requirement: re-arm-to-earlier re-wakes).
9437  timekeeper_parker: Option<Arc<DriverParker>>,
9438  /// Set by `shutdown_timers`: every pending entry has been drain-fired and
9439  /// later registrations must fire immediately (a `Sleep` polled after
9440  /// shutdown resolves early instead of parking a closing runtime forever).
9441  closed: bool,
9442}
9443
9444#[cfg(not(target_family = "wasm"))]
9445struct HeapTimerEntry {
9446  waker: Waker,
9447  /// Shared with the owning [`Sleep`]: set (under the heap lock) when this
9448  /// timer fires or the heap shuts down, so the woken poll returns `Ready`
9449  /// even when the wall clock has not reached `deadline` (shutdown
9450  /// drain-fire).
9451  fired: Arc<AtomicBool>,
9452}
9453
9454#[cfg(not(target_family = "wasm"))]
9455#[derive(Default)]
9456struct TimerDeadlineQueue {
9457  nodes: Vec<(Instant, TimerId)>,
9458  positions: FxHashMap<TimerId, usize>,
9459}
9460
9461#[cfg(not(target_family = "wasm"))]
9462impl TimerDeadlineQueue {
9463  #[cfg(test)]
9464  fn len(&self) -> usize {
9465    self.nodes.len()
9466  }
9467
9468  #[cfg(test)]
9469  fn is_empty(&self) -> bool {
9470    self.nodes.is_empty()
9471  }
9472
9473  fn clear(&mut self) {
9474    self.nodes.clear();
9475    self.positions.clear();
9476  }
9477
9478  fn peek(&self) -> Option<(Instant, TimerId)> {
9479    self.nodes.first().copied()
9480  }
9481
9482  fn push(&mut self, deadline: Instant, id: TimerId) {
9483    let index = self.nodes.len();
9484    assert!(
9485      self.positions.insert(id, index).is_none(),
9486      "timer deadline queue received duplicate id {id}"
9487    );
9488    self.nodes.push((deadline, id));
9489    self.sift_up(index);
9490  }
9491
9492  fn pop(&mut self) -> Option<(Instant, TimerId)> {
9493    let first = self.nodes.first().copied()?;
9494    self.positions.remove(&first.1);
9495    let last = self
9496      .nodes
9497      .pop()
9498      .expect("the non-empty timer heap must have a final node");
9499    if !self.nodes.is_empty() {
9500      self.nodes[0] = last;
9501      self.positions.insert(last.1, 0);
9502      self.sift_down(0);
9503    }
9504    Some(first)
9505  }
9506
9507  fn remove(&mut self, id: TimerId) -> Option<(Instant, TimerId)> {
9508    let index = self.positions.remove(&id)?;
9509    let removed = self.nodes[index];
9510    let last = self.nodes.pop().expect("an indexed timer node must exist");
9511    if index < self.nodes.len() {
9512      self.nodes[index] = last;
9513      self.positions.insert(last.1, index);
9514      if index > 0 && self.nodes[index] < self.nodes[(index - 1) / 2] {
9515        self.sift_up(index);
9516      } else {
9517        self.sift_down(index);
9518      }
9519    }
9520    Some(removed)
9521  }
9522
9523  fn sift_up(&mut self, mut index: usize) {
9524    while index > 0 {
9525      let parent = (index - 1) / 2;
9526      if self.nodes[parent] <= self.nodes[index] {
9527        break;
9528      }
9529      self.swap(parent, index);
9530      index = parent;
9531    }
9532  }
9533
9534  fn sift_down(&mut self, mut index: usize) {
9535    loop {
9536      let left = index * 2 + 1;
9537      if left >= self.nodes.len() {
9538        return;
9539      }
9540      let right = left + 1;
9541      let child = if right < self.nodes.len() && self.nodes[right] < self.nodes[left] {
9542        right
9543      } else {
9544        left
9545      };
9546      if self.nodes[index] <= self.nodes[child] {
9547        return;
9548      }
9549      self.swap(index, child);
9550      index = child;
9551    }
9552  }
9553
9554  fn swap(&mut self, left: usize, right: usize) {
9555    self.nodes.swap(left, right);
9556    self.positions.insert(self.nodes[left].1, left);
9557    self.positions.insert(self.nodes[right].1, right);
9558  }
9559}
9560
9561#[cfg(not(target_family = "wasm"))]
9562impl TimerHeapInner {
9563  fn next_deadline(&self) -> Option<Instant> {
9564    self.queue.peek().map(|(deadline, _)| deadline)
9565  }
9566}
9567
9568#[cfg(not(target_family = "wasm"))]
9569impl TimerHeap {
9570  fn new(
9571    generation: u64,
9572    executor_id: u64,
9573    thread_name: String,
9574    deadlock_state: Arc<MultiThreadDeadlockState>,
9575  ) -> Self {
9576    Self {
9577      inner: Arc::new(Mutex::new(TimerHeapInner::default())),
9578      parker: Arc::new(DriverParker::default()),
9579      thread: Mutex::new(None),
9580      deadlock_state,
9581      #[cfg(test)]
9582      fail_next_start: AtomicBool::new(false),
9583      generation,
9584      executor_id,
9585      thread_name,
9586    }
9587  }
9588
9589  fn ensure_started(&self) -> Result<(), RuntimeConfigError> {
9590    if self
9591      .inner
9592      .lock()
9593      .unwrap_or_else(std::sync::PoisonError::into_inner)
9594      .closed
9595    {
9596      return Ok(());
9597    }
9598    let current_thread = std::thread::current().id();
9599    loop {
9600      let finished = {
9601        let mut thread = self
9602          .thread
9603          .lock()
9604          .unwrap_or_else(std::sync::PoisonError::into_inner);
9605        match thread.as_ref() {
9606          Some(handle) if handle.thread().id() == current_thread => return Ok(()),
9607          Some(handle) if !handle.is_finished() => return Ok(()),
9608          Some(_) => thread.take(),
9609          None => {
9610            // Shutdown can close the heap after the optimistic check above.
9611            // Recheck before publishing another service thread.
9612            if self
9613              .inner
9614              .lock()
9615              .unwrap_or_else(std::sync::PoisonError::into_inner)
9616              .closed
9617            {
9618              return Ok(());
9619            }
9620            #[cfg(test)]
9621            if self.fail_next_start.swap(false, Ordering::SeqCst) {
9622              return Err(RuntimeConfigError(
9623                "injected runtime timer thread creation failure".to_string(),
9624              ));
9625            }
9626
9627            let inner = Arc::clone(&self.inner);
9628            let parker = Arc::clone(&self.parker);
9629            let deadlock_state = Arc::clone(&self.deadlock_state);
9630            let generation = self.generation;
9631            let executor_id = self.executor_id;
9632            let handle = std::thread::Builder::new()
9633              .name(self.thread_name.clone())
9634              .spawn(move || {
9635                // Keep the identity through OS-thread TLS teardown. Unlike
9636                // `ON_POOL_WORKER`, this marker never authorizes scheduler driving.
9637                ON_TIMER_THREAD.with(|current| current.set(Some(executor_id)));
9638                let _generation = RuntimeGenerationGuard::enter(generation);
9639                timer_timekeeper_main(&inner, &parker, &deadlock_state);
9640              })
9641              .map_err(|error| {
9642                RuntimeConfigError(format!("failed to create runtime timer thread: {error}"))
9643              })?;
9644            *thread = Some(handle);
9645            return Ok(());
9646          }
9647        }
9648      };
9649
9650      // A finished timer thread may still be running TLS destructors that
9651      // re-enter timer code. Never hold the handle mutex while waiting for
9652      // those destructors, and never attempt to join this thread from itself.
9653      if let Some(finished) = finished
9654        && finished.thread().id() != current_thread
9655        && let Err(payload) = finished.join()
9656      {
9657        discard_panic_payload(payload);
9658      }
9659      if self
9660        .inner
9661        .lock()
9662        .unwrap_or_else(std::sync::PoisonError::into_inner)
9663        .closed
9664      {
9665        return Ok(());
9666      }
9667    }
9668  }
9669
9670  fn close(&self) -> Vec<Waker> {
9671    let mut inner = self
9672      .inner
9673      .lock()
9674      .unwrap_or_else(std::sync::PoisonError::into_inner);
9675    inner.closed = true;
9676    inner.queue.clear();
9677    inner.timekeeper_parker = None;
9678    inner
9679      .entries
9680      .drain()
9681      .map(|(_, entry)| {
9682        entry.fired.store(true, Ordering::SeqCst);
9683        entry.waker
9684      })
9685      .collect()
9686  }
9687
9688  fn join(&self) {
9689    let handle = self
9690      .thread
9691      .lock()
9692      .unwrap_or_else(std::sync::PoisonError::into_inner)
9693      .take();
9694    if let Some(handle) = handle {
9695      if handle.thread().id() == std::thread::current().id() {
9696        // A custom RawWaker may own the final direct executor Arc and release
9697        // it from this thread. Closing the heap makes the loop exit after the
9698        // callback returns; joining self would deadlock that exit.
9699        drop(handle);
9700        return;
9701      }
9702      if let Err(payload) = handle.join() {
9703        discard_panic_payload(payload);
9704      }
9705    }
9706  }
9707}
9708
9709#[cfg(not(target_family = "wasm"))]
9710impl Drop for TimerHeap {
9711  fn drop(&mut self) {
9712    let mut admission = self.deadlock_state.begin_admission();
9713    let wakers = self.close();
9714    if !wakers.is_empty()
9715      && let Some(admission) = admission.as_mut()
9716    {
9717      admission.mark_published();
9718    }
9719    self.parker.unpark();
9720    for waker in wakers {
9721      wake_waker_contained(waker);
9722    }
9723    drop(admission);
9724    self.join();
9725  }
9726}
9727
9728#[cfg(not(target_family = "wasm"))]
9729fn timer_timekeeper_main(
9730  inner: &Mutex<TimerHeapInner>,
9731  parker: &Arc<DriverParker>,
9732  deadlock_state: &Arc<MultiThreadDeadlockState>,
9733) {
9734  loop {
9735    // Acquire before touching the heap. If a verdict has already closed the
9736    // gate, a due timer remains visible until the verdict's final timer recheck
9737    // causes that guard to reopen.
9738    let mut admission = deadlock_state.begin_admission();
9739    let (due, deadline) = {
9740      let mut inner = inner
9741        .lock()
9742        .unwrap_or_else(std::sync::PoisonError::into_inner);
9743      if inner.closed {
9744        inner.timekeeper_parker = None;
9745        return;
9746      }
9747      let now = Instant::now();
9748      let mut due = Vec::new();
9749      while let Some((deadline, id)) = inner.queue.peek() {
9750        if deadline > now {
9751          break;
9752        }
9753        inner.queue.pop();
9754        if let Some(entry) = inner.entries.remove(&id) {
9755          entry.fired.store(true, Ordering::SeqCst);
9756          due.push(entry.waker);
9757        }
9758      }
9759      let deadline = inner.next_deadline();
9760      inner.timekeeper_parker = deadline.map(|_| Arc::clone(parker));
9761      (due, deadline)
9762    };
9763
9764    if !due.is_empty() {
9765      if let Some(admission) = admission.as_mut() {
9766        admission.mark_published();
9767      }
9768      for waker in due {
9769        wake_waker_contained(waker);
9770      }
9771      continue;
9772    }
9773    // Never retain an admission while sleeping: it protects only the
9774    // remove-then-wake publication window.
9775    drop(admission);
9776
9777    match deadline {
9778      Some(deadline) => {
9779        let now = Instant::now();
9780        if deadline > now {
9781          parker.park_timeout(deadline - now);
9782        }
9783      }
9784      None => parker.park(),
9785    }
9786  }
9787}
9788
9789#[cfg(not(target_family = "wasm"))]
9790impl MultiThreadExecutor {
9791  /// Arm (or waker-refresh) heap timer `id` at `deadline`. `fired` is the
9792  /// owning `Sleep`'s completion flag: set under the heap lock on fire and on
9793  /// shutdown, and set here immediately when the heap is already closed (the
9794  /// caller re-checks it after registering, so a post-shutdown poll resolves
9795  /// instead of parking forever).
9796  fn register_timer(
9797    self: &Arc<Self>,
9798    id: TimerId,
9799    deadline: Instant,
9800    waker: Waker,
9801    fired: &Arc<AtomicBool>,
9802  ) {
9803    let mut admission = self.begin_deadlock_admission();
9804    #[cfg(test)]
9805    run_timer_registration_admission_test_hook();
9806    // Establish the service thread before publishing the entry. If thread
9807    // creation fails, `Sleep::poll` unwinds with `registered == false` and no
9808    // heap state exists for Drop to miss.
9809    if let Err(error) = self.timers.ensure_started() {
9810      let _ = catch_unwind_contained(|| drop(waker));
9811      panic!("{error}");
9812    }
9813    let (became_earliest, published, discarded_waker) = {
9814      let mut inner = self
9815        .timers
9816        .inner
9817        .lock()
9818        .unwrap_or_else(std::sync::PoisonError::into_inner);
9819      if inner.closed || fired.load(Ordering::SeqCst) {
9820        fired.store(true, Ordering::SeqCst);
9821        (false, false, Some(waker))
9822      } else if let Some(entry) = inner.entries.get_mut(&id) {
9823        // Re-poll of a still-armed sleep: refresh the waker, nothing else.
9824        (
9825          false,
9826          false,
9827          Some(std::mem::replace(&mut entry.waker, waker)),
9828        )
9829      } else {
9830        let previous = inner.next_deadline();
9831        inner.entries.insert(
9832          id,
9833          HeapTimerEntry {
9834            waker,
9835            fired: Arc::clone(fired),
9836          },
9837        );
9838        inner.queue.push(deadline, id);
9839        (
9840          previous.is_none_or(|previous| deadline < previous),
9841          true,
9842          None,
9843        )
9844      }
9845    };
9846    if published && let Some(admission) = admission.as_mut() {
9847      admission.mark_published();
9848    }
9849    if let Some(waker) = discarded_waker {
9850      // RawWaker destruction is user code and may re-enter timer cancellation.
9851      // Keep it outside the heap mutex and contain hostile destructors.
9852      let _ = catch_unwind_contained(|| drop(waker));
9853    }
9854    if became_earliest {
9855      // Re-arm the lazy service thread and any cooperative timer wait.
9856      self.note_earlier_timer();
9857    }
9858  }
9859
9860  /// Best-effort cancel: remove the entry and its indexed heap node.
9861  /// If cancellation changes the earliest deadline, wake every timer-bounded
9862  /// driver so no pool worker remains parked on the stale deadline.
9863  fn cancel_timer(&self, id: TimerId) {
9864    let (removed, earliest_changed) = {
9865      let mut inner = self
9866        .timers
9867        .inner
9868        .lock()
9869        .unwrap_or_else(std::sync::PoisonError::into_inner);
9870      let previous = inner.next_deadline();
9871      let removed = inner.entries.remove(&id);
9872      if removed.is_some() {
9873        let queued = inner.queue.remove(id);
9874        debug_assert!(
9875          queued.is_some(),
9876          "a live timer entry must have one indexed deadline node"
9877        );
9878      }
9879      let current = inner.next_deadline();
9880      (removed, previous != current)
9881    };
9882    if let Some(entry) = removed {
9883      // A RawWaker destructor may cancel another timer. Never run it while
9884      // holding the heap lock.
9885      let _ = catch_unwind_contained(|| drop(entry));
9886    }
9887    if earliest_changed {
9888      self.timers.parker.unpark();
9889      self.parked_drivers.wake_all();
9890    }
9891  }
9892
9893  /// Earliest pending deadline (None when the heap is empty or closed).
9894  fn next_timer_deadline(&self) -> Option<Instant> {
9895    self
9896      .timers
9897      .inner
9898      .lock()
9899      .unwrap_or_else(std::sync::PoisonError::into_inner)
9900      .next_deadline()
9901  }
9902
9903  /// Pop and wake everything due at/before now. Wakers are collected under
9904  /// the lock but woken OUTSIDE it (a wake runs `schedule`, which must not
9905  /// nest inside the heap lock). Firing sets each sleep's `fired` flag under
9906  /// the lock, before the wake, so the woken poll observes it.
9907  fn fire_due_timers(&self) {
9908    let mut admission = self.begin_deadlock_admission();
9909    let due: Vec<Waker> = {
9910      let mut inner = self
9911        .timers
9912        .inner
9913        .lock()
9914        .unwrap_or_else(std::sync::PoisonError::into_inner);
9915      let now = Instant::now();
9916      let mut due = Vec::new();
9917      while let Some((deadline, id)) = inner.queue.peek() {
9918        if deadline > now {
9919          break;
9920        }
9921        inner.queue.pop();
9922        if let Some(entry) = inner.entries.remove(&id) {
9923          entry.fired.store(true, Ordering::SeqCst);
9924          due.push(entry.waker);
9925        }
9926      }
9927      due
9928    };
9929    if !due.is_empty()
9930      && let Some(admission) = admission.as_mut()
9931    {
9932      admission.mark_published();
9933    }
9934    for waker in due {
9935      // Wakers may originate outside this executor. Isolate user RawWaker
9936      // implementations so a panic cannot unwind the dedicated timekeeper.
9937      wake_waker_contained(waker);
9938    }
9939  }
9940
9941  /// The earliest pending deadline moved up (or the heap went non-empty).
9942  /// Re-arm the dedicated timekeeper and one timer-bounded cooperative driver
9943  /// whose current park may still reflect the previous deadline.
9944  fn note_earlier_timer(self: &Arc<Self>) {
9945    self.timers.parker.unpark();
9946    self.parked_drivers.wake_one();
9947  }
9948
9949  /// Drain-fire every pending heap timer and poison later registrations.
9950  /// `shutdown()` with armed timers must not hang a pending `close()`. Fired
9951  /// sleeps resolve on their next poll; the woken tasks are
9952  /// scheduled through the normal path, so a coordinator awaiting a debounce
9953  /// completes its close sequence instead of parking forever.
9954  fn shutdown_timers(&self) {
9955    let mut admission = self.begin_deadlock_admission();
9956    let wakers = self.timers.close();
9957    if !wakers.is_empty()
9958      && let Some(admission) = admission.as_mut()
9959    {
9960      admission.mark_published();
9961    }
9962    for waker in wakers {
9963      // A user-provided RawWaker may panic. Shutdown is a lifecycle
9964      // transition, so one hostile wake must not leave the controller stuck
9965      // in `Stopping` and prevent every later restart.
9966      wake_waker_contained(waker);
9967    }
9968    self.timers.parker.unpark();
9969  }
9970
9971  fn begin_shutdown(&self) {
9972    let _generation = RuntimeGenerationGuard::enter(self.generation);
9973    // Publish stop before taking the blocking queue. The final deadlock verdict
9974    // holds stop publication while rechecking that queue, so queue -> stop
9975    // would invert its lock order. A concurrent taker either claims while stop
9976    // is still false or observes stop after this publication.
9977    self.stop.begin_shutdown();
9978    let queued = {
9979      let mut queue = self
9980        .blocking_queue
9981        .lock()
9982        .unwrap_or_else(std::sync::PoisonError::into_inner);
9983      // The blocking FIFO mutex closes queue admission. `take_blocking` either
9984      // claimed a job before stop publication or observes stop/closure.
9985      queue.closed = true;
9986      queue.take_all()
9987    };
9988    // Captured values belong to user code and may panic in Drop. Retire each
9989    // rejected closure independently so one destructor cannot strand the
9990    // runtime in `Stopping` or prevent the remaining queued jobs from being
9991    // cancelled.
9992    for queued in queued {
9993      let _ = catch_unwind_contained(|| drop(queued));
9994    }
9995    self.shutdown_timers();
9996  }
9997
9998  fn wait_until_scheduler_idle(&self) {
9999    self.timers.join();
10000    let mut idle = self
10001      .scheduler_idle_lock
10002      .lock()
10003      .unwrap_or_else(std::sync::PoisonError::into_inner);
10004    // Register BEFORE the first predicate evaluation and stay registered for
10005    // the whole wait. `finish_draining` reads this count under the same mutex,
10006    // so any drainer that retires after this increment is published either
10007    // sees `> 0` and wakes us, or had already decremented `active_drainers`
10008    // before we read the predicate -- in which case we never sleep.
10009    *idle += 1;
10010    while self.active_drainers.load(Ordering::Acquire) != 0 {
10011      idle = self
10012        .scheduler_idle
10013        .wait(idle)
10014        .unwrap_or_else(std::sync::PoisonError::into_inner);
10015    }
10016    *idle -= 1;
10017  }
10018}
10019
10020/// Timer future returned by [`sleep_until`]. Resolves at/after its deadline
10021/// (or early on runtime shutdown drain-fire). Dropping it cancels the
10022/// underlying registration -- the `tokio::select!` losing-arm semantics the
10023/// watch coordinator's debounce loop relies on.
10024pub struct Sleep {
10025  deadline: Instant,
10026  inner: SleepInner,
10027}
10028
10029enum SleepInner {
10030  #[cfg(not(target_family = "wasm"))]
10031  Heap(HeapSleep),
10032  Host(HostSleep),
10033}
10034
10035#[cfg(not(target_family = "wasm"))]
10036struct HeapSleep {
10037  id: TimerId,
10038  executor: Weak<MultiThreadExecutor>,
10039  fired: Arc<AtomicBool>,
10040  registered: bool,
10041}
10042
10043struct HostSleep {
10044  id: TimerId,
10045  /// Selection source, NOT a pinned driver: each poll snapshots every LIVE
10046  /// registrant. Arming redundant host timers prevents a live but starved
10047  /// environment from owning the sleep's only wake source.
10048  drivers: Arc<TimerDriverRegistry>,
10049  /// Drivers this sleep last armed on. The first responsive fire wakes the
10050  /// sleep; completion cancels every losing arm, including pre-delivery arms
10051  /// whose binding relay retains a cancellation tombstone.
10052  armed: FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10053  /// The executor's registry, held so the FIRST poll can arm the wake-token
10054  /// at the same moment `driver.register` arms the host timer.
10055  registry: Arc<HostTimerRegistry>,
10056  fired: Arc<AtomicBool>,
10057  /// Stable, coalescing signal used by [`TimerDriverRegistry::register`] to
10058  /// request a re-poll without retaining or invoking a task waker under the
10059  /// driver-registry locks.
10060  repoll: Arc<HostTimerRepoll>,
10061  /// Retires this sleep's wake-token in the executor's registry exactly once
10062  /// (created at first poll alongside the host registration, taken on
10063  /// completion, dropped with the Sleep on cancellation). `None` before the
10064  /// first poll: an unpolled Sleep holds no wake-token.
10065  pending: Option<HostTimerPendingGuard>,
10066}
10067
10068fn cancel_host_timer_driver(id: TimerId, driver: Arc<dyn TimerDriver>) {
10069  let _ = catch_unwind_contained(|| driver.cancel(id));
10070  let _ = catch_unwind_contained(|| drop(driver));
10071}
10072
10073fn cancel_host_timer_drivers(
10074  id: TimerId,
10075  armed: &mut FxHashMap<TimerDriverId, Arc<dyn TimerDriver>>,
10076) {
10077  for (_, driver) in armed.drain() {
10078    cancel_host_timer_driver(id, driver);
10079  }
10080}
10081
10082impl Future for Sleep {
10083  type Output = ();
10084
10085  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
10086    let deadline = self.deadline;
10087    let this = self.get_mut();
10088    match &mut this.inner {
10089      #[cfg(not(target_family = "wasm"))]
10090      SleepInner::Heap(heap) => {
10091        if heap.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10092          if heap.registered {
10093            // Wall-clock completion may beat the (busy) timekeeper's fire:
10094            // retire the registration so it cannot wake spuriously later.
10095            if let Some(executor) = heap.executor.upgrade() {
10096              executor.cancel_timer(heap.id);
10097            }
10098            heap.registered = false;
10099          }
10100          return Poll::Ready(());
10101        }
10102        let Some(executor) = heap.executor.upgrade() else {
10103          heap.fired.store(true, Ordering::SeqCst);
10104          heap.registered = false;
10105          return Poll::Ready(());
10106        };
10107        executor.register_timer(heap.id, deadline, cx.waker().clone(), &heap.fired);
10108        heap.registered = true;
10109        // Registration on a closed (shut-down) heap stores nothing and sets
10110        // `fired` instead: re-check so this poll resolves rather than parking
10111        // a closing runtime forever.
10112        if heap.fired.load(Ordering::SeqCst) {
10113          heap.registered = false;
10114          return Poll::Ready(());
10115        }
10116        Poll::Pending
10117      }
10118      SleepInner::Host(host) => {
10119        if host.fired.load(Ordering::SeqCst) || Instant::now() >= deadline {
10120          cancel_host_timer_drivers(host.id, &mut host.armed);
10121          host.pending.take();
10122          return Poll::Ready(());
10123        }
10124        // Clone both wakers before publishing either one. If the second clone
10125        // unwinds, destroy the first clone behind its own panic boundary before
10126        // resuming the original panic, so a hostile RawWaker destructor cannot
10127        // turn clone failure into a process-aborting double panic.
10128        let repoll_waker = cx.waker().clone();
10129        let registry_waker = match catch_unwind(AssertUnwindSafe(|| cx.waker().clone())) {
10130          Ok(waker) => waker,
10131          Err(payload) => {
10132            let _ = catch_unwind_contained(|| drop(repoll_waker));
10133            resume_unwind(payload);
10134          }
10135        };
10136        host.repoll.register(repoll_waker);
10137        if !host.registry.register(
10138          host.id,
10139          deadline,
10140          registry_waker,
10141          &host.fired,
10142          &host.drivers,
10143          &host.repoll,
10144        ) {
10145          cancel_host_timer_drivers(host.id, &mut host.armed);
10146          if host.pending.take().is_none() {
10147            host.repoll.close();
10148          }
10149          return Poll::Ready(());
10150        }
10151        if host.pending.is_none() {
10152          // FIRST poll: the registry entry appears at the same moment the
10153          // host timer is armed below -- and only then. An unpolled Sleep has
10154          // no host callback behind it, so counting it at creation would
10155          // wrongly veto the threaded deadline verdict (`has_live_wait`) and
10156          // mislabel the threadless certain diagnostic as timer-backed. The
10157          // certain panic itself no longer consults pending timers for its
10158          // verdict.
10159          host.drivers.register_pending(host.id, &host.repoll);
10160          host.pending = Some(HostTimerPendingGuard {
10161            registry: Arc::clone(&host.registry),
10162            drivers: Arc::clone(&host.drivers),
10163            repoll: Arc::clone(&host.repoll),
10164            id: host.id,
10165          });
10166        }
10167        // Re-select every LIVE driver on every poll. A live but starved
10168        // environment may accept a TSFN call and then stop servicing its
10169        // event loop, so no single host may own the only timer arm.
10170        let current = host.drivers.live();
10171        assert!(
10172          !current.as_slice().is_empty(),
10173          "CurrentThread runtime lost every live timer driver mid-sleep: the host contexts \
10174           that registered timer drivers (via \
10175           `napi_async_runtime::register_timer_driver`; the napi adapter registers \
10176           one per importing env through `registerTimerHost`) have all been torn down, so this \
10177           `sleep_until` can never be woken."
10178        );
10179        // Publish cancellation ownership for the complete live snapshot before
10180        // invoking any arbitrary callback. An earlier driver may unregister a
10181        // later one and then unwind; both the Sleep and the contained snapshot
10182        // must still own that later driver while the original panic propagates.
10183        // See the implementation notes in this crate's README.
10184        let missing = current
10185          .as_slice()
10186          .iter()
10187          .filter(|(current_id, _)| !host.armed.contains_key(current_id))
10188          .count();
10189        host.armed.reserve(missing);
10190        for (current_id, current_driver) in current.as_slice() {
10191          host
10192            .armed
10193            .entry(*current_id)
10194            .or_insert_with(|| Arc::clone(current_driver));
10195        }
10196        for (current_id, _) in current.as_slice() {
10197          let armed_driver = host
10198            .armed
10199            .get(current_id)
10200            .expect("every live timer driver must have cancellation ownership before callbacks");
10201          armed_driver.register(host.id, deadline, panic_contained_waker(cx.waker().clone()));
10202        }
10203        let current_ids = current
10204          .as_slice()
10205          .iter()
10206          .map(|(current_id, _)| *current_id)
10207          .collect::<FxHashSet<_>>();
10208        let retired = host
10209          .armed
10210          .keys()
10211          .filter(|armed_id| !current_ids.contains(armed_id))
10212          .copied()
10213          .collect::<Vec<_>>();
10214        for retired in retired {
10215          let old_driver = host
10216            .armed
10217            .remove(&retired)
10218            .expect("the retired timer driver must remain armed");
10219          cancel_host_timer_driver(host.id, old_driver);
10220        }
10221        Poll::Pending
10222      }
10223    }
10224  }
10225}
10226
10227impl Drop for Sleep {
10228  fn drop(&mut self) {
10229    match &mut self.inner {
10230      #[cfg(not(target_family = "wasm"))]
10231      SleepInner::Heap(heap) => {
10232        if heap.registered
10233          && let Some(executor) = heap.executor.upgrade()
10234        {
10235          executor.cancel_timer(heap.id);
10236        }
10237      }
10238      SleepInner::Host(host) => {
10239        // Close directly as an unwind backstop even when first-poll setup
10240        // failed before `pending` could install its normal cleanup guard.
10241        host.repoll.close();
10242        cancel_host_timer_drivers(host.id, &mut host.armed);
10243        // `pending` (if still held) drops here and retires the wake-token.
10244      }
10245    }
10246  }
10247}
10248
10249/// Sleep until `deadline` on the runtime's timer facility. MultiThread uses
10250/// the executor-owned heap; CurrentThread requires a LIVE host driver
10251/// registered via [`register_timer_driver`] and otherwise fails LOUD (a
10252/// missing driver must never become a silent never-firing debounce).
10253pub fn sleep_until(deadline: Instant) -> Sleep {
10254  make_sleep(&RUNTIME.backend(), &TIMER_DRIVERS, deadline)
10255}
10256
10257/// Flavor dispatch behind [`sleep_until`], parameterized for tests (which
10258/// build executors and driver registries locally and must not touch the
10259/// process-global ones).
10260fn make_sleep(
10261  backend: &RuntimeBackend,
10262  host_drivers: &Arc<TimerDriverRegistry>,
10263  deadline: Instant,
10264) -> Sleep {
10265  let id = next_unique_id(&NEXT_TIMER_ID, "timer id space exhausted");
10266  match &backend.executor {
10267    #[cfg(not(target_family = "wasm"))]
10268    RuntimeExecutor::MultiThread(executor) => Sleep {
10269      deadline,
10270      inner: SleepInner::Heap(HeapSleep {
10271        id,
10272        executor: Arc::downgrade(executor),
10273        fired: Arc::new(AtomicBool::new(false)),
10274        registered: false,
10275      }),
10276    },
10277    RuntimeExecutor::CurrentThread(executor) => {
10278      // Fail loud at CREATION when no live driver exists (the
10279      // never-registered case gets its diagnostic at the sleep_until call
10280      // site, not on some later poll). The driver is NOT pinned here: each
10281      // poll re-selects every live registrant, so a driver dying while this
10282      // sleep is in flight is cancelled while the remaining hosts stay armed
10283      // -- and if every driver dies mid-flight, the poll path has its own loud
10284      // panic.
10285      assert!(
10286        host_drivers.has_live_driver(),
10287        "CurrentThread runtime has no live timer driver registered: `sleep_until` on the \
10288         single-thread flavor delegates timers to the host event loop. Install one via \
10289         `napi_async_runtime::register_timer_driver` (the napi adapter registers \
10290         a setTimeout-based driver per importing env through `registerTimerHost` at import)."
10291      );
10292      // The registry entry is created at FIRST POLL (when `driver.register`
10293      // arms the host timer), not here: an unpolled Sleep cannot wake anyone
10294      // -- nothing was handed to the host loop -- so counting it at creation
10295      // would wrongly veto the threaded deadline verdict and mislabel the
10296      // threadless certain diagnostic as timer-backed. First-poll accounting
10297      // has no false-panic side.
10298      Sleep {
10299        deadline,
10300        inner: SleepInner::Host(HostSleep {
10301          id,
10302          drivers: Arc::clone(host_drivers),
10303          armed: FxHashMap::default(),
10304          registry: Arc::clone(&executor.host_timers),
10305          fired: Arc::new(AtomicBool::new(false)),
10306          repoll: Arc::new(HostTimerRepoll::default()),
10307          pending: None,
10308        }),
10309      }
10310    }
10311  }
10312}
10313
10314#[derive(Clone)]
10315enum RuntimeExecutor {
10316  CurrentThread(Arc<CurrentThreadExecutor>),
10317  #[cfg(not(target_family = "wasm"))]
10318  MultiThread(Arc<MultiThreadExecutor>),
10319}
10320
10321enum WeakRuntimeExecutor {
10322  CurrentThread(Weak<CurrentThreadExecutor>),
10323  #[cfg(not(target_family = "wasm"))]
10324  MultiThread(Weak<MultiThreadExecutor>),
10325}
10326
10327impl WeakRuntimeExecutor {
10328  fn schedule(&self, runnable: Runnable) {
10329    match self {
10330      Self::CurrentThread(executor) => {
10331        if let Some(executor) = executor.upgrade() {
10332          executor.schedule(runnable);
10333        }
10334      }
10335      #[cfg(not(target_family = "wasm"))]
10336      Self::MultiThread(executor) => {
10337        if let Some(executor) = executor.upgrade() {
10338          executor.schedule(runnable);
10339        }
10340      }
10341    }
10342  }
10343}
10344
10345#[derive(Clone)]
10346struct RuntimeBackend {
10347  work: Arc<GenerationWork>,
10348  executor: RuntimeExecutor,
10349}
10350
10351impl RuntimeBackend {
10352  fn new(
10353    options: &RuntimeOptions,
10354    metrics: Arc<RuntimeMetrics>,
10355  ) -> Result<Self, RuntimeConfigError> {
10356    #[cfg(test)]
10357    if FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.replace(false)) {
10358      return Err(RuntimeConfigError(
10359        "injected async runtime backend creation failure".to_string(),
10360      ));
10361    }
10362
10363    let work = GenerationWork::new();
10364    let stop = Arc::new(GenerationStop::default());
10365    let executor = match options.flavor {
10366      RuntimeFlavor::CurrentThread => {
10367        let executor = CurrentThreadExecutor::with_generation(
10368          metrics,
10369          THREADLESS_BUILD,
10370          options.park_deadline,
10371          work.id,
10372          Arc::clone(&stop),
10373        );
10374        executor.attach_generation_work(&work);
10375        RuntimeExecutor::CurrentThread(Arc::new(executor))
10376      }
10377      RuntimeFlavor::MultiThread => {
10378        #[cfg(not(target_family = "wasm"))]
10379        {
10380          RuntimeExecutor::MultiThread(Arc::new(MultiThreadExecutor::new_for_generation(
10381            options, metrics, work.id, stop,
10382          )?))
10383        }
10384        #[cfg(target_family = "wasm")]
10385        {
10386          let _ = metrics;
10387          return Err(RuntimeConfigError(
10388            "the multi-thread runtime is unavailable in this WebAssembly build".to_string(),
10389          ));
10390        }
10391      }
10392    };
10393    Ok(Self { work, executor })
10394  }
10395
10396  #[cfg(test)]
10397  fn from_executor(executor: RuntimeExecutor) -> Self {
10398    let work = GenerationWork::new();
10399    if let RuntimeExecutor::CurrentThread(executor) = &executor {
10400      executor.attach_generation_work(&work);
10401    }
10402    Self { work, executor }
10403  }
10404
10405  fn generation(&self) -> u64 {
10406    self.work.id
10407  }
10408
10409  fn downgrade_executor(&self) -> WeakRuntimeExecutor {
10410    match &self.executor {
10411      RuntimeExecutor::CurrentThread(executor) => {
10412        WeakRuntimeExecutor::CurrentThread(Arc::downgrade(executor))
10413      }
10414      #[cfg(not(target_family = "wasm"))]
10415      RuntimeExecutor::MultiThread(executor) => {
10416        WeakRuntimeExecutor::MultiThread(Arc::downgrade(executor))
10417      }
10418    }
10419  }
10420
10421  /// First schedule of a freshly submitted task. This runs on the stack of the
10422  /// caller of `spawn`, never on a waker's; see
10423  /// `CurrentThreadExecutor::schedule_submission`.
10424  fn schedule_submission(&self, runnable: Runnable) {
10425    match &self.executor {
10426      RuntimeExecutor::CurrentThread(executor) => executor.schedule_submission(runnable),
10427      #[cfg(not(target_family = "wasm"))]
10428      RuntimeExecutor::MultiThread(executor) => executor.schedule(runnable),
10429    }
10430  }
10431
10432  fn spawn_registered_blocking<F, T>(
10433    &self,
10434    function: F,
10435    registration: GenerationWorkGuard,
10436    metrics: &Arc<RuntimeMetrics>,
10437  ) -> JoinHandle<T>
10438  where
10439    F: FnOnce() -> T + Send + 'static,
10440    T: Send + 'static,
10441  {
10442    match &self.executor {
10443      RuntimeExecutor::CurrentThread(executor) => {
10444        let _ = metrics;
10445        executor.schedule_blocking(function, registration)
10446      }
10447      #[cfg(not(target_family = "wasm"))]
10448      RuntimeExecutor::MultiThread(executor) => {
10449        let generation = self.generation();
10450        let _ = metrics;
10451        let function = RegisteredBlockingFunction::new(function, registration, generation);
10452        executor.schedule_blocking_result(move || function.run())
10453      }
10454    }
10455  }
10456
10457  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
10458    match &self.executor {
10459      RuntimeExecutor::CurrentThread(executor) => executor.block_on(future),
10460      #[cfg(not(target_family = "wasm"))]
10461      RuntimeExecutor::MultiThread(executor) => executor.block_on(future),
10462    }
10463  }
10464
10465  fn generation_stop(&self) -> Arc<GenerationStop> {
10466    match &self.executor {
10467      RuntimeExecutor::CurrentThread(executor) => Arc::clone(&executor.stop),
10468      #[cfg(not(target_family = "wasm"))]
10469      RuntimeExecutor::MultiThread(executor) => Arc::clone(&executor.stop),
10470    }
10471  }
10472
10473  fn begin_shutdown(&self) {
10474    let _generation = RuntimeGenerationGuard::enter(self.generation());
10475    self.work.close_and_abort();
10476    match &self.executor {
10477      RuntimeExecutor::CurrentThread(executor) => executor.begin_shutdown(),
10478      #[cfg(not(target_family = "wasm"))]
10479      RuntimeExecutor::MultiThread(executor) => executor.begin_shutdown(),
10480    }
10481  }
10482
10483  fn wait_until_idle(&self) {
10484    match &self.executor {
10485      RuntimeExecutor::CurrentThread(executor) => {
10486        // A wake in flight across `begin_shutdown` is rejected by the closed
10487        // queue and parked in `queue.rejected` with its guard still held, and
10488        // a failure transition racing `begin_shutdown` from a waker's stack
10489        // moves the queued guards there too; retire them here, on this stack,
10490        // until the generation is idle.
10491        self.work.wait_until_idle_retiring(
10492          || executor.has_rejected_work(),
10493          || executor.retire_rejected_work(),
10494        );
10495        executor.wait_until_scheduler_idle();
10496      }
10497      #[cfg(not(target_family = "wasm"))]
10498      RuntimeExecutor::MultiThread(executor) => {
10499        self.work.wait_until_idle();
10500        executor.wait_until_scheduler_idle();
10501      }
10502    }
10503  }
10504
10505  #[cfg(not(target_family = "wasm"))]
10506  fn worker_lifecycle(&self) -> Option<Arc<WorkerLifecycle>> {
10507    match &self.executor {
10508      RuntimeExecutor::CurrentThread(_) => None,
10509      RuntimeExecutor::MultiThread(executor) => Some(Arc::clone(&executor.worker_lifecycle)),
10510    }
10511  }
10512
10513  fn stop_identity(&self) -> RuntimeStopIdentity {
10514    RuntimeStopIdentity {
10515      generation: self.generation(),
10516      #[cfg(not(target_family = "wasm"))]
10517      executor_id: match &self.executor {
10518        RuntimeExecutor::CurrentThread(_) => None,
10519        RuntimeExecutor::MultiThread(executor) => Some(executor.id),
10520      },
10521    }
10522  }
10523
10524  fn is_current(&self) -> bool {
10525    self.stop_identity().is_current()
10526  }
10527}
10528
10529#[derive(Clone, Copy)]
10530struct RuntimeStopIdentity {
10531  generation: u64,
10532  #[cfg(not(target_family = "wasm"))]
10533  executor_id: Option<u64>,
10534}
10535
10536impl RuntimeStopIdentity {
10537  fn is_current(self) -> bool {
10538    if ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get) == Some(self.generation) {
10539      return true;
10540    }
10541    #[cfg(not(target_family = "wasm"))]
10542    if let Some(executor_id) = self.executor_id
10543      && (ON_POOL_WORKER.with(std::cell::Cell::get) == Some(executor_id)
10544        || ON_TIMER_THREAD.with(std::cell::Cell::get) == Some(executor_id))
10545    {
10546      return true;
10547    }
10548    false
10549  }
10550}
10551
10552enum RuntimeLifecycle {
10553  Initial,
10554  StoppedBeforeFirstUse,
10555  Running(RuntimeBackend),
10556  Stopping(RuntimeStopIdentity),
10557  StoppingWithoutBackend(ZeroBackendShutdownTarget),
10558  Stopped,
10559}
10560
10561#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10562enum ZeroBackendShutdownTarget {
10563  StoppedBeforeFirstUse,
10564  Stopped,
10565}
10566
10567impl ZeroBackendShutdownTarget {
10568  fn into_lifecycle(self) -> RuntimeLifecycle {
10569    match self {
10570      Self::StoppedBeforeFirstUse => RuntimeLifecycle::StoppedBeforeFirstUse,
10571      Self::Stopped => RuntimeLifecycle::Stopped,
10572    }
10573  }
10574}
10575
10576struct RuntimeState {
10577  options: RuntimeOptions,
10578  lifecycle: RuntimeLifecycle,
10579  rejected_drops: usize,
10580}
10581
10582#[cfg(test)]
10583thread_local! {
10584  static BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK:
10585    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10586  static AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK:
10587    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10588  static AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK:
10589    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10590  static AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK:
10591    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10592  static BEFORE_INITIAL_START_WAIT_TEST_HOOK:
10593    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10594  static AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK:
10595    std::cell::RefCell<Option<Box<dyn FnOnce()>>> = const { std::cell::RefCell::new(None) };
10596  static FAIL_NEXT_RUNTIME_BACKEND_CREATION:
10597    std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
10598}
10599
10600#[cfg(test)]
10601fn run_before_runtime_submission_lock_test_hook() {
10602  if let Some(hook) = BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10603  {
10604    hook();
10605  }
10606}
10607
10608#[cfg(test)]
10609fn run_after_async_work_registration_test_hook() {
10610  if let Some(hook) = AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10611  {
10612    hook();
10613  }
10614}
10615
10616#[cfg(test)]
10617fn run_after_blocking_work_registration_test_hook() {
10618  if let Some(hook) =
10619    AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10620  {
10621    hook();
10622  }
10623}
10624
10625#[cfg(test)]
10626fn run_after_current_thread_host_admission_test_hook() {
10627  if let Some(hook) =
10628    AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10629  {
10630    hook();
10631  }
10632}
10633
10634#[cfg(test)]
10635fn run_before_initial_start_wait_test_hook() {
10636  if let Some(hook) = BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| slot.borrow_mut().take()) {
10637    hook();
10638  }
10639}
10640
10641#[cfg(test)]
10642fn run_after_generation_stop_publication_test_hook() {
10643  if let Some(hook) =
10644    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| slot.borrow_mut().take())
10645  {
10646    hook();
10647  }
10648}
10649
10650thread_local! {
10651  static IN_REJECTED_SUBMISSION_DROP: std::cell::Cell<usize> =
10652    const { std::cell::Cell::new(0) };
10653}
10654
10655struct RejectedSubmissionDropContext;
10656
10657impl RejectedSubmissionDropContext {
10658  fn enter() -> Self {
10659    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.set(depth.get() + 1));
10660    Self
10661  }
10662
10663  fn is_current() -> bool {
10664    IN_REJECTED_SUBMISSION_DROP.with(|depth| depth.get() != 0)
10665  }
10666}
10667
10668impl Drop for RejectedSubmissionDropContext {
10669  fn drop(&mut self) {
10670    IN_REJECTED_SUBMISSION_DROP.with(|depth| {
10671      depth.set(
10672        depth
10673          .get()
10674          .checked_sub(1)
10675          .expect("rejected submission drop depth underflow"),
10676      );
10677    });
10678  }
10679}
10680
10681struct RuntimeController {
10682  state: Mutex<RuntimeState>,
10683  lifecycle_changed: Condvar,
10684  metrics: Arc<RuntimeMetrics>,
10685  // Lock-free mirror of `state.lifecycle`'s backend, read by `fast_register`
10686  // without the `state` lock. Invariant: store `Some(b)` on every transition
10687  // INTO `Running(b)` and `None` on every transition OUT -- exactly 3 sites
10688  // today (lazy-create, start, shutdown). Any new Running-boundary write MUST
10689  // mirror here, or `fast_register` routes work onto a superseded backend.
10690  current_backend: ArcSwapOption<RuntimeBackend>,
10691}
10692
10693struct RejectedSubmissionGuard<'a> {
10694  controller: &'a RuntimeController,
10695}
10696
10697impl Drop for RejectedSubmissionGuard<'_> {
10698  fn drop(&mut self) {
10699    let mut state = self
10700      .controller
10701      .state
10702      .lock()
10703      .unwrap_or_else(std::sync::PoisonError::into_inner);
10704    state.rejected_drops = state
10705      .rejected_drops
10706      .checked_sub(1)
10707      .expect("rejected submission drop count underflow");
10708    self.controller.lifecycle_changed.notify_all();
10709  }
10710}
10711
10712impl RuntimeController {
10713  fn new() -> Self {
10714    let options = RuntimeOptions::default()
10715      .validate()
10716      .expect("default async runtime options must be valid");
10717    Self {
10718      state: Mutex::new(RuntimeState {
10719        options,
10720        lifecycle: RuntimeLifecycle::Initial,
10721        rejected_drops: 0,
10722      }),
10723      lifecycle_changed: Condvar::new(),
10724      metrics: Arc::new(RuntimeMetrics::default()),
10725      current_backend: ArcSwapOption::empty(),
10726    }
10727  }
10728
10729  fn configure(&self, options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
10730    let options = options.validate()?;
10731    let mut state = self
10732      .state
10733      .lock()
10734      .unwrap_or_else(std::sync::PoisonError::into_inner);
10735    Self::ensure_configuration_mutable(&state)?;
10736    state.options = options;
10737    Ok(())
10738  }
10739
10740  fn configure_partial(&self, patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
10741    self.configure_partial_inner(patch, |_| {})
10742  }
10743
10744  fn configure_partial_inner(
10745    &self,
10746    patch: RuntimeOptionsPatch,
10747    after_merge: impl FnOnce(&RuntimeOptions),
10748  ) -> Result<(), RuntimeConfigError> {
10749    let mut state = self
10750      .state
10751      .lock()
10752      .unwrap_or_else(std::sync::PoisonError::into_inner);
10753    Self::ensure_configuration_mutable(&state)?;
10754
10755    let mut options = state.options.clone();
10756    patch.apply_to(&mut options);
10757    after_merge(&options);
10758    let options = options.validate()?;
10759    state.options = options;
10760    Ok(())
10761  }
10762
10763  fn ensure_configuration_mutable(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10764    if matches!(
10765      &state.lifecycle,
10766      RuntimeLifecycle::Initial | RuntimeLifecycle::StoppedBeforeFirstUse
10767    ) {
10768      return Ok(());
10769    }
10770    Err(RuntimeConfigError(
10771      "the async runtime configuration is frozen; configure it before the first async call"
10772        .to_string(),
10773    ))
10774  }
10775
10776  fn ensure_active_generation_current(state: &RuntimeState) -> Result<(), RuntimeConfigError> {
10777    let Some(active_generation) = active_runtime_generation() else {
10778      return Ok(());
10779    };
10780    let lifecycle_generation = match &state.lifecycle {
10781      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10782      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10783      RuntimeLifecycle::Initial
10784      | RuntimeLifecycle::StoppedBeforeFirstUse
10785      | RuntimeLifecycle::StoppingWithoutBackend(_)
10786      | RuntimeLifecycle::Stopped => None,
10787    };
10788    if lifecycle_generation == Some(active_generation) {
10789      return Ok(());
10790    }
10791    Err(RuntimeConfigError(
10792      "cannot access or control the async runtime from work owned by a retired generation"
10793        .to_string(),
10794    ))
10795  }
10796
10797  fn backend_locked(&self, state: &mut RuntimeState) -> Result<RuntimeBackend, RuntimeConfigError> {
10798    Self::ensure_active_generation_current(state)?;
10799    match &state.lifecycle {
10800      RuntimeLifecycle::Running(backend) => return Ok(backend.clone()),
10801      RuntimeLifecycle::StoppedBeforeFirstUse
10802      | RuntimeLifecycle::Stopping(_)
10803      | RuntimeLifecycle::StoppingWithoutBackend(_)
10804      | RuntimeLifecycle::Stopped => {
10805        return Err(RuntimeConfigError(
10806          "the async runtime is stopped; call start before submitting work".to_string(),
10807        ));
10808      }
10809      RuntimeLifecycle::Initial => {}
10810    }
10811    if state.rejected_drops != 0 {
10812      return Err(RuntimeConfigError(
10813        "the async runtime cannot start while a rejected submission is being destroyed".to_string(),
10814      ));
10815    }
10816    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
10817    state.lifecycle = RuntimeLifecycle::Running(backend.clone());
10818    self.current_backend.store(Some(Arc::new(backend.clone())));
10819    Ok(backend)
10820  }
10821
10822  fn try_backend(&self) -> Result<RuntimeBackend, RuntimeConfigError> {
10823    let mut state = self
10824      .state
10825      .lock()
10826      .unwrap_or_else(std::sync::PoisonError::into_inner);
10827    self.backend_locked(&mut state)
10828  }
10829
10830  fn backend(&self) -> RuntimeBackend {
10831    self.try_backend().unwrap_or_else(|error| panic!("{error}"))
10832  }
10833
10834  fn begin_rejected_drop(state: &mut RuntimeState) -> Option<u64> {
10835    let generation = active_runtime_generation().or_else(|| match &state.lifecycle {
10836      RuntimeLifecycle::Running(backend) => Some(backend.generation()),
10837      RuntimeLifecycle::Stopping(identity) => Some(identity.generation),
10838      RuntimeLifecycle::Initial
10839      | RuntimeLifecycle::StoppedBeforeFirstUse
10840      | RuntimeLifecycle::StoppingWithoutBackend(_)
10841      | RuntimeLifecycle::Stopped => None,
10842    });
10843    state.rejected_drops += 1;
10844    generation
10845  }
10846
10847  /// Lock-free spawn fast path. Returns Some((backend, registration)) when the
10848  /// runtime is Running, the caller's generation is current, and registration
10849  /// succeeds -- all without taking the outer `state` lock. Returns None (fall
10850  /// to the locked slow path) for any miss: no current backend, retired
10851  /// generation, or a closed generation. Staleness is safe: see the safety
10852  /// invariant -- try_register_* re-checks `closed`, and wait_until_idle already
10853  /// gates on any registration that slips in during shutdown.
10854  #[inline]
10855  fn fast_register<R>(
10856    &self,
10857    register: impl FnOnce(&Arc<GenerationWork>) -> Option<R>,
10858  ) -> Option<(Arc<RuntimeBackend>, R)> {
10859    let guard = self.current_backend.load();
10860    let backend = guard.as_ref()?; // None => Initial/Stopping/Stopped => slow path
10861    if let Some(active) = active_runtime_generation() {
10862      if active != backend.generation() {
10863        return None; // retired generation => slow path yields exact error
10864      }
10865    }
10866    let registration = register(&backend.work)?; // closed => None => slow path rejects
10867    // Hand back the already-loaded Arc (one refcount bump) rather than deep-
10868    // cloning `RuntimeBackend`'s two inner Arcs; both callers only borrow it.
10869    Some((Arc::clone(backend), registration))
10870  }
10871
10872  fn try_spawn_tracked<F, T>(
10873    &self,
10874    future: F,
10875  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10876  where
10877    F: Future<Output = T> + Send + 'static,
10878    T: Send + 'static,
10879  {
10880    #[cfg(test)]
10881    run_before_runtime_submission_lock_test_hook();
10882
10883    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_async()) {
10884      #[cfg(test)]
10885      run_after_async_work_registration_test_hook();
10886      return Ok(spawn_registered(
10887        &backend,
10888        Arc::clone(&self.metrics),
10889        future,
10890        registration,
10891      ));
10892    }
10893
10894    let (backend, registration) = {
10895      let mut state = self
10896        .state
10897        .lock()
10898        .unwrap_or_else(std::sync::PoisonError::into_inner);
10899      let backend = match self.backend_locked(&mut state) {
10900        Ok(backend) => backend,
10901        Err(error) => {
10902          let generation = Self::begin_rejected_drop(&mut state);
10903          return Err((error, future, generation));
10904        }
10905      };
10906      let Some(registration) = backend.work.try_register_async() else {
10907        let generation = Self::begin_rejected_drop(&mut state);
10908        return Err((
10909          RuntimeConfigError(
10910            "the async runtime is stopped; call start before submitting work".to_string(),
10911          ),
10912          future,
10913          generation,
10914        ));
10915      };
10916      (backend, registration)
10917    };
10918    #[cfg(test)]
10919    run_after_async_work_registration_test_hook();
10920    Ok(spawn_registered(
10921      &backend,
10922      Arc::clone(&self.metrics),
10923      future,
10924      registration,
10925    ))
10926  }
10927
10928  fn try_spawn<F, T>(&self, future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
10929  where
10930    F: Future<Output = T> + Send + 'static,
10931    T: Send + 'static,
10932  {
10933    match self.try_spawn_tracked(future) {
10934      Ok(handle) => Ok(handle),
10935      Err((error, future, _generation)) => {
10936        drop(RejectedSubmissionGuard { controller: self });
10937        Err((error, future))
10938      }
10939    }
10940  }
10941
10942  fn spawn<F, T>(&self, future: F) -> JoinHandle<T>
10943  where
10944    F: Future<Output = T> + Send + 'static,
10945    T: Send + 'static,
10946  {
10947    match self.try_spawn_tracked(future) {
10948      Ok(handle) => handle,
10949      Err((error, future, generation)) => {
10950        let _rejection = RejectedSubmissionGuard { controller: self };
10951        let _drop_context = RejectedSubmissionDropContext::enter();
10952        let _generation = generation.map(RuntimeGenerationGuard::enter);
10953        let _ = catch_unwind_contained(|| drop(future));
10954        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
10955          message: error.to_string(),
10956        }))))
10957      }
10958    }
10959  }
10960
10961  fn try_spawn_detached<F>(&self, future: F) -> Result<(), F>
10962  where
10963    F: Future<Output = ()> + Send + 'static,
10964  {
10965    match self.try_spawn(future) {
10966      Ok(handle) => {
10967        handle.detach();
10968        Ok(())
10969      }
10970      Err((_, future)) => Err(future),
10971    }
10972  }
10973
10974  fn try_spawn_blocking_tracked<F, T>(
10975    &self,
10976    function: F,
10977  ) -> Result<JoinHandle<T>, (RuntimeConfigError, F, Option<u64>)>
10978  where
10979    F: FnOnce() -> T + Send + 'static,
10980    T: Send + 'static,
10981  {
10982    #[cfg(test)]
10983    run_before_runtime_submission_lock_test_hook();
10984
10985    if let Some((backend, registration)) = self.fast_register(|w| w.try_register_work()) {
10986      #[cfg(test)]
10987      run_after_blocking_work_registration_test_hook();
10988      return Ok(backend.spawn_registered_blocking(function, registration, &self.metrics));
10989    }
10990
10991    let (backend, registration) = {
10992      let mut state = self
10993        .state
10994        .lock()
10995        .unwrap_or_else(std::sync::PoisonError::into_inner);
10996      let backend = match self.backend_locked(&mut state) {
10997        Ok(backend) => backend,
10998        Err(error) => {
10999          let generation = Self::begin_rejected_drop(&mut state);
11000          return Err((error, function, generation));
11001        }
11002      };
11003      let Some(registration) = backend.work.try_register_work() else {
11004        let generation = Self::begin_rejected_drop(&mut state);
11005        return Err((
11006          RuntimeConfigError(
11007            "the async runtime is stopped; call start before submitting work".to_string(),
11008          ),
11009          function,
11010          generation,
11011        ));
11012      };
11013      (backend, registration)
11014    };
11015    #[cfg(test)]
11016    run_after_blocking_work_registration_test_hook();
11017    Ok(backend.spawn_registered_blocking(function, registration, &self.metrics))
11018  }
11019
11020  fn try_spawn_blocking<F, T>(&self, function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11021  where
11022    F: FnOnce() -> T + Send + 'static,
11023    T: Send + 'static,
11024  {
11025    match self.try_spawn_blocking_tracked(function) {
11026      Ok(handle) => Ok(handle),
11027      Err((error, function, _generation)) => {
11028        drop(RejectedSubmissionGuard { controller: self });
11029        Err((error, function))
11030      }
11031    }
11032  }
11033
11034  fn spawn_blocking<F, T>(&self, function: F) -> JoinHandle<T>
11035  where
11036    F: FnOnce() -> T + Send + 'static,
11037    T: Send + 'static,
11038  {
11039    match self.try_spawn_blocking_tracked(function) {
11040      Ok(handle) => handle,
11041      Err((error, function, generation)) => {
11042        let _rejection = RejectedSubmissionGuard { controller: self };
11043        let _drop_context = RejectedSubmissionDropContext::enter();
11044        let _generation = generation.map(RuntimeGenerationGuard::enter);
11045        let _ = catch_unwind_contained(|| drop(function));
11046        JoinHandle(JoinHandleInner::Ready(Some(Err(JoinError {
11047          message: error.to_string(),
11048        }))))
11049      }
11050    }
11051  }
11052
11053  #[cfg(test)]
11054  fn block_on(&self, future: Pin<&mut dyn Future<Output = ()>>) -> BlockOnOutcome {
11055    self
11056      .try_block_on(future)
11057      .unwrap_or_else(|error| panic!("{error}"))
11058  }
11059
11060  fn try_block_on_scope(
11061    &self,
11062  ) -> Result<(RuntimeBackend, GenerationWorkGuard), (RuntimeConfigError, Option<u64>)> {
11063    let mut state = self
11064      .state
11065      .lock()
11066      .unwrap_or_else(std::sync::PoisonError::into_inner);
11067    let backend = match self.backend_locked(&mut state) {
11068      Ok(backend) => backend,
11069      Err(error) => {
11070        let generation = Self::begin_rejected_drop(&mut state);
11071        return Err((error, generation));
11072      }
11073    };
11074    let Some(registration) = backend.work.try_register_work() else {
11075      let generation = Self::begin_rejected_drop(&mut state);
11076      return Err((
11077        RuntimeConfigError(
11078          "the async runtime is stopped; call start before submitting work".to_string(),
11079        ),
11080        generation,
11081      ));
11082    };
11083    Ok((backend, registration))
11084  }
11085
11086  fn try_block_on(
11087    &self,
11088    future: Pin<&mut dyn Future<Output = ()>>,
11089  ) -> Result<BlockOnOutcome, RuntimeConfigError> {
11090    let (backend, registration) = match self.try_block_on_scope() {
11091      Ok(scope) => scope,
11092      Err((error, _generation)) => {
11093        drop(RejectedSubmissionGuard { controller: self });
11094        return Err(error);
11095      }
11096    };
11097    let _registration = registration;
11098    let _generation = RuntimeGenerationGuard::enter(backend.generation());
11099    Ok(backend.block_on(future))
11100  }
11101
11102  fn start(&self) -> Result<(), RuntimeConfigError> {
11103    let mut state = self
11104      .state
11105      .lock()
11106      .unwrap_or_else(std::sync::PoisonError::into_inner);
11107    loop {
11108      Self::ensure_active_generation_current(&state)?;
11109      match &state.lifecycle {
11110        // napi starts the runtime while the addon is still being registered.
11111        // Keep the first backend lazy so configureAsyncRuntime remains usable
11112        // until the first async binding call.
11113        RuntimeLifecycle::Initial => {
11114          if state.rejected_drops == 0 {
11115            return Ok(());
11116          }
11117          if RejectedSubmissionDropContext::is_current() {
11118            return Err(RuntimeConfigError(
11119              "cannot start the async runtime while a rejected submission is being destroyed"
11120                .to_string(),
11121            ));
11122          }
11123          #[cfg(test)]
11124          run_before_initial_start_wait_test_hook();
11125          state = self
11126            .lifecycle_changed
11127            .wait(state)
11128            .unwrap_or_else(std::sync::PoisonError::into_inner);
11129        }
11130        RuntimeLifecycle::Running(_) => return Ok(()),
11131        RuntimeLifecycle::Stopping(identity) => {
11132          if identity.is_current() {
11133            return Err(RuntimeConfigError(
11134              "cannot restart the async runtime from work in the generation being stopped"
11135                .to_string(),
11136            ));
11137          }
11138          state = self
11139            .lifecycle_changed
11140            .wait(state)
11141            .unwrap_or_else(std::sync::PoisonError::into_inner);
11142        }
11143        RuntimeLifecycle::StoppingWithoutBackend(_) => {
11144          if RejectedSubmissionDropContext::is_current() {
11145            return Err(RuntimeConfigError(
11146              "cannot restart the async runtime while a rejected submission is being destroyed"
11147                .to_string(),
11148            ));
11149          }
11150          state = self
11151            .lifecycle_changed
11152            .wait(state)
11153            .unwrap_or_else(std::sync::PoisonError::into_inner);
11154        }
11155        RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11156          if state.rejected_drops == 0 {
11157            if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11158              state.lifecycle = RuntimeLifecycle::Initial;
11159              return Ok(());
11160            }
11161            break;
11162          }
11163          if RejectedSubmissionDropContext::is_current() {
11164            return Err(RuntimeConfigError(
11165              "cannot restart the async runtime while a rejected submission is being destroyed"
11166                .to_string(),
11167            ));
11168          }
11169          state = self
11170            .lifecycle_changed
11171            .wait(state)
11172            .unwrap_or_else(std::sync::PoisonError::into_inner);
11173        }
11174      }
11175    }
11176    let backend = RuntimeBackend::new(&state.options, Arc::clone(&self.metrics))?;
11177    self.current_backend.store(Some(Arc::new(backend.clone())));
11178    state.lifecycle = RuntimeLifecycle::Running(backend);
11179    Ok(())
11180  }
11181
11182  fn options(&self) -> RuntimeOptions {
11183    self
11184      .state
11185      .lock()
11186      .unwrap_or_else(std::sync::PoisonError::into_inner)
11187      .options
11188      .clone()
11189  }
11190
11191  fn running_current_thread_executor(&self) -> Option<Arc<CurrentThreadExecutor>> {
11192    let state = self
11193      .state
11194      .lock()
11195      .unwrap_or_else(std::sync::PoisonError::into_inner);
11196    let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11197      return None;
11198    };
11199    match &backend.executor {
11200      RuntimeExecutor::CurrentThread(executor) => Some(Arc::clone(executor)),
11201      #[cfg(not(target_family = "wasm"))]
11202      RuntimeExecutor::MultiThread(_) => None,
11203    }
11204  }
11205
11206  fn request_current_thread_drain(&self) {
11207    if let Some(executor) = self.running_current_thread_executor() {
11208      executor.request_drain_if_queued();
11209    }
11210  }
11211
11212  fn retire_rejected_current_thread_work(&self) {
11213    if let Some(executor) = self.running_current_thread_executor() {
11214      executor.retire_rejected_work_off_waker_stack();
11215    }
11216  }
11217
11218  fn cancel_current_thread_dispatch(&self, failure: CurrentThreadTaskDispatchFailure) {
11219    let executor = {
11220      let state = self
11221        .state
11222        .lock()
11223        .unwrap_or_else(std::sync::PoisonError::into_inner);
11224      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11225        return;
11226      };
11227      match &backend.executor {
11228        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11229        #[cfg(not(target_family = "wasm"))]
11230        RuntimeExecutor::MultiThread(_) => return,
11231      }
11232    };
11233    executor.cancel_host_dispatch(failure);
11234  }
11235
11236  #[cfg(test)]
11237  fn cancel_current_thread_dispatch_for_test(&self, dispatch: u64) {
11238    let executor = {
11239      let state = self
11240        .state
11241        .lock()
11242        .unwrap_or_else(std::sync::PoisonError::into_inner);
11243      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11244        return;
11245      };
11246      match &backend.executor {
11247        RuntimeExecutor::CurrentThread(executor) => Arc::clone(executor),
11248        #[cfg(not(target_family = "wasm"))]
11249        RuntimeExecutor::MultiThread(_) => return,
11250      }
11251    };
11252    executor.cancel_host_dispatch_for_test(dispatch);
11253  }
11254
11255  fn drive_current_thread_tasks(&self, delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11256    let dispatch = CURRENT_THREAD_TASK_DRIVERS.claim_delivery(delivery)?;
11257    self.drive_current_thread_dispatch_with_lease(dispatch, true)
11258  }
11259
11260  fn drive_current_thread_dispatch_with_lease(
11261    &self,
11262    dispatch: u64,
11263    mark_serviced: bool,
11264  ) -> Option<CurrentThreadTaskCallbackLease> {
11265    let mut host_turn = {
11266      let state = self
11267        .state
11268        .lock()
11269        .unwrap_or_else(std::sync::PoisonError::into_inner);
11270      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
11271        return None;
11272      };
11273      let executor = match &backend.executor {
11274        RuntimeExecutor::CurrentThread(executor) => executor,
11275        #[cfg(not(target_family = "wasm"))]
11276        RuntimeExecutor::MultiThread(_) => return None,
11277      };
11278      let host_turn = executor.try_admit_host_turn(dispatch)?;
11279      if mark_serviced {
11280        CURRENT_THREAD_TASK_DRIVERS.mark_serviced(dispatch);
11281      }
11282      host_turn
11283    };
11284    #[cfg(test)]
11285    run_after_current_thread_host_admission_test_hook();
11286    host_turn.drive();
11287    Some(CurrentThreadTaskCallbackLease::new(host_turn))
11288  }
11289
11290  #[cfg(test)]
11291  fn drive_current_thread_dispatch(&self, dispatch: u64, mark_serviced: bool) -> bool {
11292    self
11293      .drive_current_thread_dispatch_with_lease(dispatch, mark_serviced)
11294      .is_some()
11295  }
11296
11297  fn shutdown(&self) -> Result<(), RuntimeConfigError> {
11298    let backend = {
11299      let mut state = self
11300        .state
11301        .lock()
11302        .unwrap_or_else(std::sync::PoisonError::into_inner);
11303      loop {
11304        Self::ensure_active_generation_current(&state)?;
11305        match &state.lifecycle {
11306          RuntimeLifecycle::Initial => {
11307            if state.rejected_drops != 0 && RejectedSubmissionDropContext::is_current() {
11308              return Err(RuntimeConfigError(
11309                "cannot shut down the async runtime while a rejected submission is being destroyed"
11310                  .to_string(),
11311              ));
11312            }
11313            let target = ZeroBackendShutdownTarget::StoppedBeforeFirstUse;
11314            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11315            self.lifecycle_changed.notify_all();
11316            while state.rejected_drops != 0 {
11317              state = self
11318                .lifecycle_changed
11319                .wait(state)
11320                .unwrap_or_else(std::sync::PoisonError::into_inner);
11321            }
11322            debug_assert!(matches!(
11323              state.lifecycle,
11324              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
11325            ));
11326            state.lifecycle = target.into_lifecycle();
11327            self.lifecycle_changed.notify_all();
11328            return Ok(());
11329          }
11330          RuntimeLifecycle::Running(backend) => {
11331            if backend.is_current() {
11332              return Err(RuntimeConfigError(
11333                "cannot shut down the async runtime from work running on that runtime".to_string(),
11334              ));
11335            }
11336            let identity = backend.stop_identity();
11337            let stop = backend.generation_stop();
11338            // Lock order is lifecycle -> stop publication. Publish before
11339            // mutating the lifecycle so a gated verdict that acquired the
11340            // publication mutex first wins while this state remains Running;
11341            // otherwise the verdict must observe the published stop.
11342            let stop_publication = stop.publication_guard();
11343            stop_publication.publish_stopping();
11344            let RuntimeLifecycle::Running(backend) =
11345              std::mem::replace(&mut state.lifecycle, RuntimeLifecycle::Stopping(identity))
11346            else {
11347              unreachable!();
11348            };
11349            self.current_backend.store(None);
11350            // Release before aborting tasks or running any user destruction.
11351            // Neither path may retain the publication mutex across scheduler
11352            // admission, lifecycle reentry, or a panic boundary.
11353            drop(stop_publication);
11354            break backend;
11355          }
11356          RuntimeLifecycle::Stopping(identity) => {
11357            if identity.is_current() {
11358              return Err(RuntimeConfigError(
11359                "cannot wait for async runtime shutdown from work in the generation being stopped"
11360                  .to_string(),
11361              ));
11362            }
11363            state = self
11364              .lifecycle_changed
11365              .wait(state)
11366              .unwrap_or_else(std::sync::PoisonError::into_inner);
11367          }
11368          RuntimeLifecycle::StoppingWithoutBackend(_) => {
11369            if RejectedSubmissionDropContext::is_current() {
11370              return Err(RuntimeConfigError(
11371                "cannot shut down the async runtime while a rejected submission is being destroyed"
11372                  .to_string(),
11373              ));
11374            }
11375            state = self
11376              .lifecycle_changed
11377              .wait(state)
11378              .unwrap_or_else(std::sync::PoisonError::into_inner);
11379          }
11380          RuntimeLifecycle::StoppedBeforeFirstUse | RuntimeLifecycle::Stopped => {
11381            if state.rejected_drops == 0 {
11382              return Ok(());
11383            }
11384            if RejectedSubmissionDropContext::is_current() {
11385              return Err(RuntimeConfigError(
11386                "cannot shut down the async runtime while a rejected submission is being destroyed"
11387                  .to_string(),
11388              ));
11389            }
11390            let target = if matches!(&state.lifecycle, RuntimeLifecycle::StoppedBeforeFirstUse) {
11391              ZeroBackendShutdownTarget::StoppedBeforeFirstUse
11392            } else {
11393              ZeroBackendShutdownTarget::Stopped
11394            };
11395            state.lifecycle = RuntimeLifecycle::StoppingWithoutBackend(target);
11396            self.lifecycle_changed.notify_all();
11397            while state.rejected_drops != 0 {
11398              state = self
11399                .lifecycle_changed
11400                .wait(state)
11401                .unwrap_or_else(std::sync::PoisonError::into_inner);
11402            }
11403            debug_assert!(matches!(
11404              state.lifecycle,
11405              RuntimeLifecycle::StoppingWithoutBackend(current) if current == target
11406            ));
11407            state.lifecycle = target.into_lifecycle();
11408            self.lifecycle_changed.notify_all();
11409            return Ok(());
11410          }
11411        }
11412      }
11413    };
11414
11415    #[cfg(test)]
11416    run_after_generation_stop_publication_test_hook();
11417    backend.begin_shutdown();
11418    backend.wait_until_idle();
11419
11420    #[cfg(not(target_family = "wasm"))]
11421    let worker_lifecycle = backend.worker_lifecycle();
11422    drop(backend);
11423
11424    #[cfg(not(target_family = "wasm"))]
11425    if let Some(worker_lifecycle) = worker_lifecycle {
11426      worker_lifecycle.wait_for_all_workers();
11427    }
11428
11429    let mut state = self
11430      .state
11431      .lock()
11432      .unwrap_or_else(std::sync::PoisonError::into_inner);
11433    while state.rejected_drops != 0 {
11434      state = self
11435        .lifecycle_changed
11436        .wait(state)
11437        .unwrap_or_else(std::sync::PoisonError::into_inner);
11438    }
11439    debug_assert!(matches!(state.lifecycle, RuntimeLifecycle::Stopping(_)));
11440    state.lifecycle = RuntimeLifecycle::Stopped;
11441    self.lifecycle_changed.notify_all();
11442    Ok(())
11443  }
11444}
11445
11446static RUNTIME: LazyLock<RuntimeController> = LazyLock::new(RuntimeController::new);
11447
11448pub fn configure(options: RuntimeOptions) -> Result<(), RuntimeConfigError> {
11449  RUNTIME.configure(options)
11450}
11451
11452pub fn configure_partial(patch: RuntimeOptionsPatch) -> Result<(), RuntimeConfigError> {
11453  RUNTIME.configure_partial(patch)
11454}
11455
11456pub fn configured_options() -> RuntimeOptions {
11457  RUNTIME.options()
11458}
11459
11460pub fn is_multi_threaded() -> bool {
11461  RUNTIME.options().flavor == RuntimeFlavor::MultiThread
11462}
11463
11464fn spawn_registered<F, T>(
11465  backend: &RuntimeBackend,
11466  metrics: Arc<RuntimeMetrics>,
11467  future: F,
11468  registration: (AbortRegistration, GenerationWorkGuard),
11469) -> JoinHandle<T>
11470where
11471  F: Future<Output = T> + Send + 'static,
11472  T: Send + 'static,
11473{
11474  metrics.tasks_spawned.fetch_add(1, Ordering::Relaxed);
11475  let generation = backend.generation();
11476  let (abort_registration, work_registration) = registration;
11477  #[cfg(not(target_family = "wasm"))]
11478  let dependency = Arc::new(TaskDependency::new_task(generation));
11479  #[cfg(not(target_family = "wasm"))]
11480  let task_dependency = Arc::clone(&dependency);
11481  let wrapped = async move {
11482    let abortable = Abortable::new(ContainedFuture::new(future, generation), abort_registration);
11483    let mut abortable = std::pin::pin!(abortable);
11484    let polled = futures::future::poll_fn(|cx| {
11485      #[cfg(not(target_family = "wasm"))]
11486      task_dependency.begin_poll();
11487      #[cfg(not(target_family = "wasm"))]
11488      let _dependency = TaskDependencyGuard::enter(&task_dependency);
11489      abortable.as_mut().poll(cx)
11490    });
11491    match AssertUnwindSafe(polled).catch_unwind().await {
11492      Ok(Ok(output)) => {
11493        metrics.tasks_completed.fetch_add(1, Ordering::Relaxed);
11494        Ok(ContainedTaskOutput::new(output, generation))
11495      }
11496      Ok(Err(_)) => Err(JoinError {
11497        message: "async runtime stopped before the task completed".to_string(),
11498      }),
11499      Err(panic) => {
11500        metrics.tasks_panicked.fetch_add(1, Ordering::Relaxed);
11501        Err(panic_payload_to_join_error(panic))
11502      }
11503    }
11504  };
11505  let scheduler = backend.downgrade_executor();
11506  // `async-task` aborts the process if its future's destructor unwinds. Keep
11507  // the entire task generator behind our manually-dropped containment wrapper,
11508  // including the never-polled case where `future` is still a generator field.
11509  let (runnable, task) = async_task::spawn(
11510    RegisteredTaskFuture::new(wrapped, work_registration, generation),
11511    move |runnable| {
11512      scheduler.schedule(runnable);
11513    },
11514  );
11515  backend.schedule_submission(runnable);
11516  JoinHandle(JoinHandleInner::Task {
11517    task: task.fallible(),
11518    awaiter: None,
11519    #[cfg(not(target_family = "wasm"))]
11520    dependency,
11521  })
11522}
11523
11524pub fn spawn<F, T>(future: F) -> JoinHandle<T>
11525where
11526  F: Future<Output = T> + Send + 'static,
11527  T: Send + 'static,
11528{
11529  RUNTIME.spawn(future)
11530}
11531
11532/// Submit a future without consuming it when the runtime is not accepting work.
11533///
11534/// Lifecycle owners can retain the returned future and retry it after
11535/// [`start`] completes a runtime restart.
11536pub fn try_spawn<F, T>(future: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11537where
11538  F: Future<Output = T> + Send + 'static,
11539  T: Send + 'static,
11540{
11541  RUNTIME.try_spawn(future)
11542}
11543
11544pub fn try_spawn_detached<F>(future: F) -> Result<(), F>
11545where
11546  F: Future<Output = ()> + Send + 'static,
11547{
11548  RUNTIME.try_spawn_detached(future)
11549}
11550
11551pub fn spawn_detached<F>(future: F)
11552where
11553  F: Future<Output = ()> + Send + 'static,
11554{
11555  spawn(future).detach();
11556}
11557
11558pub fn spawn_blocking<F, T>(function: F) -> JoinHandle<T>
11559where
11560  F: FnOnce() -> T + Send + 'static,
11561  T: Send + 'static,
11562{
11563  RUNTIME.spawn_blocking(function)
11564}
11565
11566/// Submit blocking work without consuming it when the runtime is not accepting work.
11567///
11568/// Lifecycle owners can retain the returned closure and retry it after [`start`]
11569/// completes a runtime restart.
11570pub fn try_spawn_blocking<F, T>(function: F) -> Result<JoinHandle<T>, (RuntimeConfigError, F)>
11571where
11572  F: FnOnce() -> T + Send + 'static,
11573  T: Send + 'static,
11574{
11575  RUNTIME.try_spawn_blocking(function)
11576}
11577
11578fn block_on_with_controller<F: Future>(controller: &RuntimeController, future: F) -> F::Output {
11579  let (backend, registration) = match controller.try_block_on_scope() {
11580    Ok(scope) => scope,
11581    Err((error, generation)) => {
11582      let _rejection = RejectedSubmissionGuard { controller };
11583      let _drop_context = RejectedSubmissionDropContext::enter();
11584      let _generation = generation.map(RuntimeGenerationGuard::enter);
11585      let _ = catch_unwind_contained(|| drop(future));
11586      panic!("{error}");
11587    }
11588  };
11589  let generation = backend.generation();
11590  let _registration = registration;
11591  let _generation = RuntimeGenerationGuard::enter(generation);
11592  let mut output = None;
11593  {
11594    let future = ContainedFuture::new(future, generation);
11595    let mut erased = std::pin::pin!(async {
11596      output = Some(future.await);
11597    });
11598    let _ = backend.block_on(erased.as_mut());
11599    // `erased` is destroyed before this scope exits, while the controller's
11600    // generation work and identity guards are live. `ContainedFuture` keeps a
11601    // poll panic from becoming a process-aborting double panic if the owned
11602    // future's destructor also unwinds.
11603  }
11604  output.expect("async runtime returned before the future completed")
11605}
11606
11607pub fn block_on<F: Future>(future: F) -> F::Output {
11608  block_on_with_controller(&RUNTIME, future)
11609}
11610
11611pub fn block_on_dyn(future: Pin<&mut dyn Future<Output = ()>>) {
11612  try_block_on_dyn(future).unwrap_or_else(|error| panic!("{error}"));
11613}
11614
11615/// Drive a borrowed future without consuming it when admission or shutdown fails.
11616pub fn try_block_on_dyn(
11617  future: Pin<&mut dyn Future<Output = ()>>,
11618) -> Result<(), RuntimeConfigError> {
11619  match RUNTIME.try_block_on(future)? {
11620    BlockOnOutcome::Completed => Ok(()),
11621    BlockOnOutcome::Stopped => Err(RuntimeConfigError(
11622      "the async runtime stopped before block_on completed its future".to_string(),
11623    )),
11624  }
11625}
11626
11627pub fn start() -> Result<(), RuntimeConfigError> {
11628  RUNTIME.start()
11629}
11630
11631pub fn shutdown() -> Result<(), RuntimeConfigError> {
11632  RUNTIME.shutdown()
11633}
11634
11635fn complete_current_thread_task_delivery(completion: CurrentThreadTaskDeliveryCompletion) {
11636  for failure in completion.failed_dispatches {
11637    RUNTIME.cancel_current_thread_dispatch(failure);
11638  }
11639  if completion.redispatch {
11640    RUNTIME.request_current_thread_drain();
11641  }
11642  // Every host entry point lands here (delivery acknowledgement or failure,
11643  // driver unregistration, explicit dispatch cancellation). Work a
11644  // waker-stack failure transition moved aside is cancelled on this clean
11645  // stack even when the completion itself carried nothing, e.g. an env
11646  // cleanup hook unregistering a driver whose deliveries all failed already.
11647  RUNTIME.retire_rejected_current_thread_work();
11648}
11649
11650/// Acknowledge that one exact host delivery callback returned the successful
11651/// claim result from [`drive_current_thread_tasks`].
11652///
11653/// This releases the host's single-flight slot and submits at most its latest
11654/// still-needed pending delivery. The registry verifies that this exact
11655/// delivery was claimed; a forged success for an unclaimed delivery is treated
11656/// as failure. A stale acknowledgement from an unregistered host or retired
11657/// attempt is a no-op.
11658pub fn acknowledge_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
11659  complete_current_thread_task_delivery(
11660    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, false),
11661  );
11662}
11663
11664/// Report that one exact host delivery callback failed.
11665///
11666/// Recovery begins only after no other live host attempt can still service the
11667/// same executor dispatch. The registration and delivery identities prevent a
11668/// stale failure from affecting another host or a re-registered generation.
11669pub fn fail_current_thread_task_delivery(delivery: CurrentThreadTaskDelivery) {
11670  complete_current_thread_task_delivery(
11671    CURRENT_THREAD_TASK_DRIVERS.finish_delivery(delivery, true),
11672  );
11673}
11674
11675/// Cancel an accepted CurrentThread host delivery that failed before it could
11676/// schedule a fresh turn.
11677///
11678/// `delivery` is the opaque registration-scoped driver capability,
11679/// not the executor's shared dispatch capability. Repeated or stale failures
11680/// are no-ops.
11681pub fn cancel_current_thread_task_dispatch(delivery: u64) {
11682  complete_current_thread_task_delivery(CURRENT_THREAD_TASK_DRIVERS.fail_delivery(delivery));
11683}
11684
11685/// Poll the shared runtime's CurrentThread queue from a host-dispatched turn.
11686///
11687/// Embedders normally call this from their [`CurrentThreadTaskDriver`]
11688/// implementation with the opaque [`CurrentThreadTaskDelivery::capability`].
11689/// Returns a callback lease only when this exact delivery was claimed, admitted,
11690/// and driven. It returns `None` for stale/unregistered deliveries, before
11691/// backend creation, and on MultiThread. The host must retain the lease through
11692/// delivery acknowledgement/failure and callback payload destruction.
11693pub fn drive_current_thread_tasks(delivery: u64) -> Option<CurrentThreadTaskCallbackLease> {
11694  RUNTIME.drive_current_thread_tasks(delivery)
11695}
11696
11697pub fn reset_metrics() {
11698  RUNTIME.metrics.reset();
11699}
11700
11701pub fn metrics() -> RuntimeMetricsSnapshot {
11702  let options = RUNTIME.options();
11703  RUNTIME.metrics.snapshot(&options)
11704}
11705
11706#[cfg(test)]
11707mod tests {
11708  use super::*;
11709
11710  static CURRENT_THREAD_ADMISSION_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11711  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11712    LazyLock::new(Mutex::default);
11713  static CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11714  static CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11715  static CURRENT_THREAD_BUDGET_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11716  static CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11717    LazyLock::new(Mutex::default);
11718  static CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11719  static CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11720    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11721  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11722    LazyLock::new(Mutex::default);
11723  static CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS: AtomicUsize = AtomicUsize::new(0);
11724  static CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS: LazyLock<
11725    CurrentThreadTaskDriverRegistry,
11726  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11727  static CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11728  static CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS: AtomicUsize = AtomicUsize::new(0);
11729  static CURRENT_THREAD_NESTED_UNWIND_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11730    LazyLock::new(Mutex::default);
11731  static CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11732    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11733  static CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11734    LazyLock::new(Mutex::default);
11735  static CURRENT_THREAD_PANICKING_PUBLICATION_ONCE: AtomicBool = AtomicBool::new(false);
11736  static CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11737    LazyLock::new(Mutex::default);
11738  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11739    LazyLock::new(Mutex::default);
11740  static CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS: LazyLock<
11741    CurrentThreadTaskDriverRegistry,
11742  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11743  static CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES: LazyLock<Mutex<Vec<u64>>> =
11744    LazyLock::new(Mutex::default);
11745  static CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11746  static CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11747    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11748  static CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
11749    CurrentThreadTaskDriverRegistry,
11750  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11751  static CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS: LazyLock<
11752    CurrentThreadTaskDriverRegistry,
11753  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11754  static CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11755    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11756  static CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11757    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11758  static CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11759    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11760  static CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11761    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11762  static CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11763    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11764  static CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS: LazyLock<
11765    CurrentThreadTaskDriverRegistry,
11766  > = LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11767  static CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS: LazyLock<CurrentThreadTaskDriverRegistry> =
11768    LazyLock::new(CurrentThreadTaskDriverRegistry::default);
11769  static CURRENT_THREAD_STOPPED_HOST_DISPATCHES: AtomicUsize = AtomicUsize::new(0);
11770
11771  thread_local! {
11772    static CURRENT_THREAD_RECOVERY_HOST_DISPATCHES: std::cell::Cell<usize> =
11773      const { std::cell::Cell::new(0) };
11774    static CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES: std::cell::Cell<usize> =
11775      const { std::cell::Cell::new(0) };
11776  }
11777
11778  #[cfg(not(target_family = "wasm"))]
11779  struct StopPollProbe {
11780    polls: Arc<AtomicUsize>,
11781    first_poll: Option<std::sync::mpsc::Sender<()>>,
11782  }
11783
11784  #[cfg(not(target_family = "wasm"))]
11785  impl Future for StopPollProbe {
11786    type Output = ();
11787
11788    fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
11789      self.polls.fetch_add(1, Ordering::SeqCst);
11790      if let Some(first_poll) = self.first_poll.take() {
11791        first_poll.send(()).unwrap();
11792      }
11793      Poll::Pending
11794    }
11795  }
11796
11797  #[cfg(not(target_family = "wasm"))]
11798  struct PanickingJoinAwaiter {
11799    wakes: Arc<AtomicUsize>,
11800    drops: Arc<AtomicUsize>,
11801  }
11802
11803  #[cfg(not(target_family = "wasm"))]
11804  impl std::task::Wake for PanickingJoinAwaiter {
11805    fn wake(self: Arc<Self>) {
11806      self.wakes.fetch_add(1, Ordering::SeqCst);
11807      panic!("intentional JoinHandle awaiter wake panic");
11808    }
11809
11810    fn wake_by_ref(self: &Arc<Self>) {
11811      self.wakes.fetch_add(1, Ordering::SeqCst);
11812      panic!("intentional JoinHandle awaiter wake-by-ref panic");
11813    }
11814  }
11815
11816  #[cfg(not(target_family = "wasm"))]
11817  impl Drop for PanickingJoinAwaiter {
11818    fn drop(&mut self) {
11819      self.drops.fetch_add(1, Ordering::SeqCst);
11820      panic!("intentional JoinHandle awaiter destructor panic");
11821    }
11822  }
11823
11824  #[cfg(not(target_family = "wasm"))]
11825  fn panicking_join_awaiter_waker(wakes: Arc<AtomicUsize>, drops: Arc<AtomicUsize>) -> Waker {
11826    Waker::from(Arc::new(PanickingJoinAwaiter { wakes, drops }))
11827  }
11828
11829  #[cfg(not(target_family = "wasm"))]
11830  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
11831  enum JoinAwaiterWakerEventKind {
11832    Clone,
11833    Wake,
11834    WakeByRef,
11835    Drop,
11836  }
11837
11838  #[cfg(not(target_family = "wasm"))]
11839  struct JoinAwaiterWakerState {
11840    events: Mutex<Vec<(JoinAwaiterWakerEventKind, Option<u64>)>>,
11841  }
11842
11843  #[cfg(not(target_family = "wasm"))]
11844  impl JoinAwaiterWakerState {
11845    fn record(&self, kind: JoinAwaiterWakerEventKind) {
11846      self
11847        .events
11848        .lock()
11849        .unwrap_or_else(std::sync::PoisonError::into_inner)
11850        .push((kind, active_runtime_generation()));
11851    }
11852
11853    fn clear(&self) {
11854      self
11855        .events
11856        .lock()
11857        .unwrap_or_else(std::sync::PoisonError::into_inner)
11858        .clear();
11859    }
11860
11861    fn snapshot(&self) -> Vec<(JoinAwaiterWakerEventKind, Option<u64>)> {
11862      self
11863        .events
11864        .lock()
11865        .unwrap_or_else(std::sync::PoisonError::into_inner)
11866        .clone()
11867    }
11868  }
11869
11870  #[cfg(not(target_family = "wasm"))]
11871  struct JoinAwaiterWakerToken {
11872    state: Arc<JoinAwaiterWakerState>,
11873  }
11874
11875  #[cfg(not(target_family = "wasm"))]
11876  unsafe fn clone_join_awaiter_waker(data: *const ()) -> std::task::RawWaker {
11877    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11878    token.state.record(JoinAwaiterWakerEventKind::Clone);
11879    let clone = Box::new(JoinAwaiterWakerToken {
11880      state: Arc::clone(&token.state),
11881    });
11882    std::task::RawWaker::new(Box::into_raw(clone).cast(), &JOIN_AWAITER_WAKER_VTABLE)
11883  }
11884
11885  #[cfg(not(target_family = "wasm"))]
11886  unsafe fn wake_join_awaiter_waker(data: *const ()) {
11887    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11888    token.state.record(JoinAwaiterWakerEventKind::Wake);
11889  }
11890
11891  #[cfg(not(target_family = "wasm"))]
11892  unsafe fn wake_join_awaiter_waker_by_ref(data: *const ()) {
11893    let token = unsafe { &*data.cast::<JoinAwaiterWakerToken>() };
11894    token.state.record(JoinAwaiterWakerEventKind::WakeByRef);
11895  }
11896
11897  #[cfg(not(target_family = "wasm"))]
11898  unsafe fn drop_join_awaiter_waker(data: *const ()) {
11899    let token = unsafe { Box::from_raw(data.cast_mut().cast::<JoinAwaiterWakerToken>()) };
11900    token.state.record(JoinAwaiterWakerEventKind::Drop);
11901  }
11902
11903  #[cfg(not(target_family = "wasm"))]
11904  static JOIN_AWAITER_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
11905    clone_join_awaiter_waker,
11906    wake_join_awaiter_waker,
11907    wake_join_awaiter_waker_by_ref,
11908    drop_join_awaiter_waker,
11909  );
11910
11911  #[cfg(not(target_family = "wasm"))]
11912  fn join_awaiter_waker(state: Arc<JoinAwaiterWakerState>) -> Waker {
11913    let token = Box::new(JoinAwaiterWakerToken { state });
11914    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &JOIN_AWAITER_WAKER_VTABLE);
11915    unsafe { Waker::from_raw(raw) }
11916  }
11917
11918  #[cfg(not(target_family = "wasm"))]
11919  fn wait_for_generation_parker_to_sleep(stop: &GenerationStop) {
11920    wait_until("the block_on driver to sleep", || {
11921      stop
11922        .parkers
11923        .lock()
11924        .unwrap_or_else(std::sync::PoisonError::into_inner)
11925        .iter()
11926        .filter_map(Weak::upgrade)
11927        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
11928    });
11929  }
11930
11931  #[test]
11932  fn unique_ids_fail_before_wraparound() {
11933    let counter = AtomicU64::new(u64::MAX - 1);
11934    assert_eq!(
11935      next_unique_id(&counter, "test id space exhausted"),
11936      u64::MAX - 1
11937    );
11938
11939    let exhausted = catch_unwind(AssertUnwindSafe(|| {
11940      next_unique_id(&counter, "test id space exhausted");
11941    }));
11942    assert!(exhausted.is_err());
11943    assert_eq!(counter.load(Ordering::Relaxed), u64::MAX);
11944  }
11945
11946  #[test]
11947  fn generation_task_ids_fail_before_reusing_an_abort_handle_key() {
11948    let work = GenerationWork::new();
11949    {
11950      let mut state = work.state.lock().unwrap();
11951      state.next_task_id = u64::MAX;
11952    }
11953
11954    let exhausted = catch_unwind(AssertUnwindSafe(|| work.try_register_async()));
11955    assert!(exhausted.is_err());
11956
11957    let state = work.state.lock().unwrap();
11958    assert_eq!(state.next_task_id, u64::MAX);
11959    assert_eq!(state.active, 0);
11960    assert!(state.abort_handles.is_empty());
11961  }
11962
11963  #[test]
11964  fn caught_panic_payload_destructor_runs() {
11965    struct DropSignal(Arc<AtomicBool>);
11966
11967    impl Drop for DropSignal {
11968      fn drop(&mut self) {
11969        self.0.store(true, Ordering::SeqCst);
11970      }
11971    }
11972
11973    let dropped = Arc::new(AtomicBool::new(false));
11974    let payload = DropSignal(Arc::clone(&dropped));
11975
11976    assert!(
11977      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) }).is_none(),
11978      "the original panic must remain contained"
11979    );
11980    assert!(
11981      dropped.load(Ordering::SeqCst),
11982      "an ordinary caught panic payload must be destroyed"
11983    );
11984  }
11985
11986  #[test]
11987  fn hostile_caught_panic_payload_destructor_remains_contained() {
11988    struct PanicOnDrop(Arc<AtomicBool>);
11989
11990    impl Drop for PanicOnDrop {
11991      fn drop(&mut self) {
11992        self.0.store(true, Ordering::SeqCst);
11993        panic!("caught panic payload destructor escaped");
11994      }
11995    }
11996
11997    let dropped = Arc::new(AtomicBool::new(false));
11998    let payload = PanicOnDrop(Arc::clone(&dropped));
11999    let result = catch_unwind(AssertUnwindSafe(|| {
12000      catch_unwind_contained(|| -> () { std::panic::panic_any(payload) })
12001    }));
12002
12003    assert!(
12004      result.is_ok(),
12005      "the payload destructor's nested panic must remain contained"
12006    );
12007    assert!(
12008      result.unwrap().is_none(),
12009      "the original panic must remain contained"
12010    );
12011    assert!(
12012      dropped.load(Ordering::SeqCst),
12013      "the hostile payload destructor must be attempted"
12014    );
12015  }
12016
12017  fn accept_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12018    CurrentThreadHostDispatchResult::Accepted
12019  }
12020
12021  fn reject_current_thread_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12022    CurrentThreadHostDispatchResult::Unavailable
12023  }
12024
12025  fn count_current_thread_recovery_host_dispatch(
12026    _dispatch: u64,
12027  ) -> CurrentThreadHostDispatchResult {
12028    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(dispatches.get() + 1));
12029    CurrentThreadHostDispatchResult::Accepted
12030  }
12031
12032  fn reset_current_thread_recovery_host_dispatches() {
12033    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12034  }
12035
12036  fn current_thread_recovery_host_dispatches() -> usize {
12037    CURRENT_THREAD_RECOVERY_HOST_DISPATCHES.with(std::cell::Cell::get)
12038  }
12039
12040  fn accept_only_first_current_thread_host_dispatch(
12041    _dispatch: u64,
12042  ) -> CurrentThreadHostDispatchResult {
12043    if CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| {
12044      let previous = dispatches.get();
12045      dispatches.set(previous + 1);
12046      previous == 0
12047    }) {
12048      CurrentThreadHostDispatchResult::Accepted
12049    } else {
12050      CurrentThreadHostDispatchResult::Unavailable
12051    }
12052  }
12053
12054  fn reset_current_thread_one_shot_host_dispatches() {
12055    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(|dispatches| dispatches.set(0));
12056  }
12057
12058  fn current_thread_one_shot_host_dispatches() -> usize {
12059    CURRENT_THREAD_ONE_SHOT_HOST_DISPATCHES.with(std::cell::Cell::get)
12060  }
12061
12062  fn count_current_thread_budget_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12063    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12064    CurrentThreadHostDispatchResult::Accepted
12065  }
12066
12067  fn count_current_thread_blocking_budget_host_dispatch(
12068    _dispatch: u64,
12069  ) -> CurrentThreadHostDispatchResult {
12070    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12071    CurrentThreadHostDispatchResult::Accepted
12072  }
12073
12074  fn count_current_thread_admission_host_dispatch(
12075    _dispatch: u64,
12076  ) -> CurrentThreadHostDispatchResult {
12077    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12078    CurrentThreadHostDispatchResult::Accepted
12079  }
12080
12081  fn count_current_thread_interleaved_recovery_host_dispatch(
12082    _dispatch: u64,
12083  ) -> CurrentThreadHostDispatchResult {
12084    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12085    CurrentThreadHostDispatchResult::Accepted
12086  }
12087
12088  fn dispatch_late_registration_current_thread_tasks(
12089    dispatch: u64,
12090  ) -> CurrentThreadHostDispatchResult {
12091    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.dispatch(dispatch)
12092  }
12093
12094  fn dispatch_nested_unwind_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12095    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
12096      .lock()
12097      .unwrap()
12098      .push(dispatch);
12099    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.dispatch(dispatch)
12100  }
12101
12102  fn dispatch_authoritative_panic_current_thread_tasks(
12103    dispatch: u64,
12104  ) -> CurrentThreadHostDispatchResult {
12105    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
12106      .lock()
12107      .unwrap()
12108      .push(dispatch);
12109    match CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.fetch_add(1, Ordering::SeqCst) {
12110      0 => std::panic::panic_any(FirstHostileCurrentThreadPanic),
12111      2 => std::panic::panic_any(LaterHostileCurrentThreadPanic),
12112      _ => CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.dispatch(dispatch),
12113    }
12114  }
12115
12116  fn dispatch_stale_failure_current_thread_tasks(dispatch: u64) -> CurrentThreadHostDispatchResult {
12117    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12118  }
12119
12120  fn dispatch_waker_stack_failure_current_thread_tasks(
12121    dispatch: u64,
12122  ) -> CurrentThreadHostDispatchResult {
12123    CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12124  }
12125
12126  fn dispatch_healthy_after_waker_stack_failure_current_thread_tasks(
12127    dispatch: u64,
12128  ) -> CurrentThreadHostDispatchResult {
12129    CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12130  }
12131
12132  fn dispatch_submission_failure_current_thread_tasks(
12133    dispatch: u64,
12134  ) -> CurrentThreadHostDispatchResult {
12135    CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS.dispatch(dispatch)
12136  }
12137
12138  fn dispatch_retirement_turn_current_thread_tasks(
12139    dispatch: u64,
12140  ) -> CurrentThreadHostDispatchResult {
12141    CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS.dispatch(dispatch)
12142  }
12143
12144  fn dispatch_nested_retirement_current_thread_tasks(
12145    dispatch: u64,
12146  ) -> CurrentThreadHostDispatchResult {
12147    CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS.dispatch(dispatch)
12148  }
12149
12150  fn dispatch_sustained_producer_current_thread_tasks(
12151    dispatch: u64,
12152  ) -> CurrentThreadHostDispatchResult {
12153    CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS.dispatch(dispatch)
12154  }
12155
12156  fn dispatch_retirement_retry_current_thread_tasks(
12157    dispatch: u64,
12158  ) -> CurrentThreadHostDispatchResult {
12159    CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS.dispatch(dispatch)
12160  }
12161
12162  fn dispatch_final_retirement_offer_current_thread_tasks(
12163    dispatch: u64,
12164  ) -> CurrentThreadHostDispatchResult {
12165    CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS.dispatch(dispatch)
12166  }
12167
12168  fn dispatch_inline_final_offer_current_thread_tasks(
12169    dispatch: u64,
12170  ) -> CurrentThreadHostDispatchResult {
12171    CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS.dispatch(dispatch)
12172  }
12173
12174  fn panic_once_current_thread_host_dispatch(dispatch: u64) -> CurrentThreadHostDispatchResult {
12175    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
12176      .lock()
12177      .unwrap()
12178      .push(dispatch);
12179    assert!(
12180      !CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12181      "injected CurrentThread host dispatch panic"
12182    );
12183    CurrentThreadHostDispatchResult::Accepted
12184  }
12185
12186  fn panic_once_current_thread_armed_republish_host_dispatch(
12187    dispatch: u64,
12188  ) -> CurrentThreadHostDispatchResult {
12189    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
12190      .lock()
12191      .unwrap()
12192      .push(dispatch);
12193    assert!(
12194      !CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.swap(false, Ordering::SeqCst),
12195      "injected armed CurrentThread host dispatch panic"
12196    );
12197    CurrentThreadHostDispatchResult::Accepted
12198  }
12199
12200  fn record_current_thread_republish_rearm_dispatch(
12201    dispatch: u64,
12202  ) -> CurrentThreadHostDispatchResult {
12203    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
12204      .lock()
12205      .unwrap()
12206      .push(dispatch);
12207    CurrentThreadHostDispatchResult::Accepted
12208  }
12209
12210  fn reject_initial_current_thread_publication_once(
12211    dispatch: u64,
12212  ) -> CurrentThreadHostDispatchResult {
12213    let mut dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
12214      .lock()
12215      .unwrap();
12216    dispatches.push(dispatch);
12217    if dispatches.len() == 1 {
12218      CurrentThreadHostDispatchResult::Unavailable
12219    } else {
12220      CurrentThreadHostDispatchResult::Accepted
12221    }
12222  }
12223
12224  fn reject_current_thread_replacement_publication_once(
12225    dispatch: u64,
12226  ) -> CurrentThreadHostDispatchResult {
12227    let mut dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
12228      .lock()
12229      .unwrap();
12230    dispatches.push(dispatch);
12231    if dispatches.len() == 2 {
12232      CurrentThreadHostDispatchResult::Unavailable
12233    } else {
12234      CurrentThreadHostDispatchResult::Accepted
12235    }
12236  }
12237
12238  fn fail_current_thread_replacement_publication_once(
12239    dispatch: u64,
12240  ) -> CurrentThreadHostDispatchResult {
12241    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
12242      .lock()
12243      .unwrap()
12244      .push(dispatch);
12245    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.dispatch(dispatch)
12246  }
12247
12248  fn count_current_thread_shutdown_republish_host_dispatch(
12249    _dispatch: u64,
12250  ) -> CurrentThreadHostDispatchResult {
12251    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12252    CurrentThreadHostDispatchResult::Accepted
12253  }
12254
12255  fn count_current_thread_stopped_host_dispatch(_dispatch: u64) -> CurrentThreadHostDispatchResult {
12256    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.fetch_add(1, Ordering::SeqCst);
12257    CurrentThreadHostDispatchResult::Accepted
12258  }
12259
12260  fn current_thread_dispatch(executor: &CurrentThreadExecutor) -> u64 {
12261    let pending = executor.dispatch_pending.load(Ordering::Acquire);
12262    if pending != 0 {
12263      return pending;
12264    }
12265    let dispatch = next_current_thread_dispatch_id();
12266    match executor.dispatch_pending.compare_exchange(
12267      0,
12268      dispatch,
12269      Ordering::AcqRel,
12270      Ordering::Acquire,
12271    ) {
12272      Ok(_) => dispatch,
12273      Err(pending) => pending,
12274    }
12275  }
12276
12277  fn current_thread_dispatch_failure(
12278    dispatch: u64,
12279    epoch: u64,
12280  ) -> CurrentThreadTaskDispatchFailure {
12281    CurrentThreadTaskDispatchFailure { dispatch, epoch }
12282  }
12283
12284  fn controller_current_thread_dispatch(controller: &RuntimeController) -> u64 {
12285    let executor = controller
12286      .running_current_thread_executor()
12287      .expect("the test controller must have a running CurrentThread executor");
12288    current_thread_dispatch(&executor)
12289  }
12290
12291  #[test]
12292  fn current_thread_executor_drives_spawned_tasks() {
12293    let metrics = Arc::new(RuntimeMetrics::default());
12294    let executor = Arc::new(CurrentThreadExecutor::new(Arc::clone(&metrics)));
12295    let scheduler = Arc::clone(&executor);
12296    let (runnable, task) = async_task::spawn(async { 42 }, move |runnable| {
12297      scheduler.schedule(runnable);
12298    });
12299
12300    executor.schedule(runnable);
12301
12302    let mut output = None;
12303    {
12304      let mut future = std::pin::pin!(async {
12305        output = Some(task.await);
12306      });
12307      executor.block_on(future.as_mut());
12308    }
12309    assert_eq!(output, Some(42));
12310    assert_eq!(metrics.runnable_polls.load(Ordering::Relaxed), 1);
12311    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
12312  }
12313
12314  #[cfg(not(target_family = "wasm"))]
12315  #[test]
12316  fn current_thread_shutdown_wake_does_not_repoll_user_future() {
12317    use std::sync::mpsc;
12318
12319    let executor = Arc::new(CurrentThreadExecutor::with_detection(
12320      Arc::new(RuntimeMetrics::default()),
12321      false,
12322      None,
12323    ));
12324    let polls = Arc::new(AtomicUsize::new(0));
12325    let (first_poll_tx, first_poll_rx) = mpsc::channel();
12326    let (outcome_tx, outcome_rx) = mpsc::channel();
12327    let driver_executor = Arc::clone(&executor);
12328    let driver_polls = Arc::clone(&polls);
12329    let driver = std::thread::spawn(move || {
12330      let mut future = std::pin::pin!(StopPollProbe {
12331        polls: driver_polls,
12332        first_poll: Some(first_poll_tx)
12333      });
12334      outcome_tx
12335        .send(driver_executor.block_on(future.as_mut()))
12336        .unwrap();
12337    });
12338
12339    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12340    wait_for_generation_parker_to_sleep(&executor.stop);
12341    executor.stop.begin_shutdown();
12342
12343    assert_eq!(
12344      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12345      BlockOnOutcome::Stopped
12346    );
12347    driver.join().unwrap();
12348    assert_eq!(
12349      polls.load(Ordering::SeqCst),
12350      1,
12351      "a shutdown wake must be checked before the next CurrentThread user poll"
12352    );
12353  }
12354
12355  #[cfg(not(target_family = "wasm"))]
12356  #[test]
12357  fn current_thread_admitted_host_turn_does_not_poll_after_stop_publication() {
12358    use std::sync::mpsc;
12359
12360    let metrics = Arc::new(RuntimeMetrics::default());
12361    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12362      Arc::clone(&metrics),
12363      accept_current_thread_host_dispatch,
12364    ));
12365    let polls = Arc::new(AtomicUsize::new(0));
12366    let task_polls = Arc::clone(&polls);
12367    let (runnable, task) = async_task::spawn(
12368      async move {
12369        task_polls.fetch_add(1, Ordering::SeqCst);
12370      },
12371      |_| {},
12372    );
12373    executor.schedule(runnable);
12374    let dispatch = current_thread_dispatch(&executor);
12375
12376    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
12377    let (release_claim_tx, release_claim_rx) = mpsc::channel();
12378    let driver_executor = Arc::clone(&executor);
12379    let driver = std::thread::spawn(move || {
12380      CURRENT_THREAD_RUNNABLE_CLAIM_TEST_HOOK.with(|slot| {
12381        *slot.borrow_mut() = Some(Box::new(move || {
12382          claim_locked_tx.send(()).unwrap();
12383          release_claim_rx.recv().unwrap();
12384        }));
12385      });
12386      driver_executor.drive_host_turn_for_dispatch(dispatch);
12387    });
12388    claim_locked_rx
12389      .recv_timeout(Duration::from_secs(2))
12390      .expect("the admitted host turn must pause before its runnable claim");
12391
12392    executor.stop.publish_stopping();
12393    release_claim_tx.send(()).unwrap();
12394    driver.join().unwrap();
12395
12396    assert_eq!(
12397      polls.load(Ordering::SeqCst),
12398      0,
12399      "stop publication must outrank a later runnable claim by an already-admitted host turn"
12400    );
12401    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12402
12403    executor.begin_shutdown();
12404    assert!(
12405      futures::executor::block_on(task.fallible()).is_none(),
12406      "shutdown must cancel the runnable rejected by the stopped claim"
12407    );
12408    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12409  }
12410
12411  #[cfg(not(target_family = "wasm"))]
12412  #[test]
12413  fn current_thread_terminal_dispatch_cancellation_outranks_runnable_claim() {
12414    use std::sync::mpsc;
12415
12416    let metrics = Arc::new(RuntimeMetrics::default());
12417    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12418      Arc::clone(&metrics),
12419      accept_current_thread_host_dispatch,
12420    ));
12421    let polls = Arc::new(AtomicUsize::new(0));
12422    let task_polls = Arc::clone(&polls);
12423    let (runnable, task) = async_task::spawn(
12424      async move {
12425        task_polls.fetch_add(1, Ordering::SeqCst);
12426      },
12427      |_| {},
12428    );
12429    executor.schedule(runnable);
12430
12431    let queue = executor
12432      .queue
12433      .lock()
12434      .unwrap_or_else(std::sync::PoisonError::into_inner);
12435    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
12436    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
12437    let cancelling_executor = Arc::clone(&executor);
12438    let cancellation = std::thread::spawn(move || {
12439      let scheduler = cancelling_executor
12440        .scheduler_idle_lock
12441        .lock()
12442        .unwrap_or_else(std::sync::PoisonError::into_inner);
12443      let cancelling = cancelling_executor
12444        .begin_failed_dispatch_cancellation()
12445        .expect("the test must publish terminal dispatch cancellation");
12446      drop(scheduler);
12447      cancellation_published_tx.send(()).unwrap();
12448      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
12449      cancellation_done_tx.send(()).unwrap();
12450    });
12451    cancellation_published_rx
12452      .recv_timeout(Duration::from_secs(2))
12453      .expect("terminal dispatch cancellation must publish before queue draining");
12454
12455    let claimant_executor = Arc::clone(&executor);
12456    let claimant = std::thread::spawn(move || claimant_executor.drain_one());
12457    assert!(
12458      !claimant.join().unwrap(),
12459      "a runnable claim after terminal cancellation publication must be rejected"
12460    );
12461    assert_eq!(polls.load(Ordering::SeqCst), 0);
12462    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12463    assert!(
12464      matches!(
12465        cancellation_done_rx.try_recv(),
12466        Err(mpsc::TryRecvError::Empty)
12467      ),
12468      "terminal cancellation must still be waiting to drain the queued runnable"
12469    );
12470
12471    drop(queue);
12472    cancellation_done_rx
12473      .recv_timeout(Duration::from_secs(2))
12474      .expect("terminal cancellation must drain the rejected runnable");
12475    cancellation.join().unwrap();
12476    assert!(
12477      futures::executor::block_on(task.fallible()).is_none(),
12478      "terminal cancellation must cancel the runnable it prevented from starting"
12479    );
12480    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12481  }
12482
12483  #[cfg(not(target_family = "wasm"))]
12484  #[test]
12485  fn multi_thread_parking_shutdown_wake_does_not_repoll_user_future() {
12486    use std::sync::mpsc;
12487
12488    let executor = multi_thread_executor(2, None, "parking-stop-before-poll");
12489    let polls = Arc::new(AtomicUsize::new(0));
12490    let (first_poll_tx, first_poll_rx) = mpsc::channel();
12491    let (outcome_tx, outcome_rx) = mpsc::channel();
12492    let driver_executor = Arc::clone(&executor);
12493    let driver_polls = Arc::clone(&polls);
12494    let driver = std::thread::spawn(move || {
12495      let mut future = std::pin::pin!(StopPollProbe {
12496        polls: driver_polls,
12497        first_poll: Some(first_poll_tx)
12498      });
12499      outcome_tx
12500        .send(driver_executor.block_on(future.as_mut()))
12501        .unwrap();
12502    });
12503
12504    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12505    wait_for_generation_parker_to_sleep(&executor.stop);
12506    executor.stop.begin_shutdown();
12507
12508    assert_eq!(
12509      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12510      BlockOnOutcome::Stopped
12511    );
12512    driver.join().unwrap();
12513    assert_eq!(
12514      polls.load(Ordering::SeqCst),
12515      1,
12516      "a shutdown wake must be checked before the next parking block_on user poll"
12517    );
12518  }
12519
12520  #[cfg(not(target_family = "wasm"))]
12521  #[test]
12522  fn multi_thread_cooperative_shutdown_wake_does_not_repoll_user_future() {
12523    use std::sync::mpsc;
12524
12525    let executor = multi_thread_executor(2, None, "cooperative-stop-before-poll");
12526    let polls = Arc::new(AtomicUsize::new(0));
12527    let (first_poll_tx, first_poll_rx) = mpsc::channel();
12528    let (outcome_tx, outcome_rx) = mpsc::channel();
12529    let driver_executor = Arc::clone(&executor);
12530    let driver_polls = Arc::clone(&polls);
12531    let driver = std::thread::spawn(move || {
12532      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
12533      let mut future = std::pin::pin!(StopPollProbe {
12534        polls: driver_polls,
12535        first_poll: Some(first_poll_tx)
12536      });
12537      outcome_tx
12538        .send(driver_executor.block_on(future.as_mut()))
12539        .unwrap();
12540    });
12541
12542    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12543    wait_for_generation_parker_to_sleep(&executor.stop);
12544    executor.stop.begin_shutdown();
12545
12546    assert_eq!(
12547      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12548      BlockOnOutcome::Stopped
12549    );
12550    driver.join().unwrap();
12551    assert_eq!(
12552      polls.load(Ordering::SeqCst),
12553      1,
12554      "a shutdown wake must be checked before the next cooperative user poll"
12555    );
12556  }
12557
12558  #[test]
12559  fn current_thread_pending_dispatch_does_not_allocate_another_capability() {
12560    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12561      Arc::new(RuntimeMetrics::default()),
12562      accept_current_thread_host_dispatch,
12563    ));
12564    let pending = u64::MAX - 1;
12565    executor.dispatch_pending.store(pending, Ordering::Release);
12566    let mut allocations = 0;
12567
12568    executor.request_drain_with(|| {
12569      allocations += 1;
12570      u64::MAX
12571    });
12572
12573    assert_eq!(allocations, 0);
12574    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), pending);
12575  }
12576
12577  #[cfg(not(target_family = "wasm"))]
12578  #[test]
12579  fn current_thread_stopped_dispatch_claim_is_rejected_under_scheduler_lock() {
12580    use std::sync::mpsc;
12581
12582    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12583      Arc::new(RuntimeMetrics::default()),
12584      accept_current_thread_host_dispatch,
12585    ));
12586    let dispatch = current_thread_dispatch(&executor);
12587    let scheduler = executor
12588      .scheduler_idle_lock
12589      .lock()
12590      .unwrap_or_else(std::sync::PoisonError::into_inner);
12591    let (claiming_tx, claiming_rx) = mpsc::channel();
12592    let (claimed_tx, claimed_rx) = mpsc::channel();
12593    let claiming_executor = Arc::clone(&executor);
12594    let claimant = std::thread::spawn(move || {
12595      claiming_tx.send(()).unwrap();
12596      claimed_tx
12597        .send(claiming_executor.try_admit_host_turn(dispatch).is_some())
12598        .unwrap();
12599    });
12600    claiming_rx.recv_timeout(Duration::from_secs(2)).unwrap();
12601
12602    executor.stop.begin_shutdown();
12603    drop(scheduler);
12604
12605    assert!(
12606      !claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
12607      "an old-generation dispatch must not claim a host turn after stop becomes visible"
12608    );
12609    claimant.join().unwrap();
12610    assert_eq!(
12611      executor.dispatch_pending.load(Ordering::Acquire),
12612      dispatch,
12613      "rejecting the stopped claim must not consume its stale capability"
12614    );
12615  }
12616
12617  #[cfg(not(target_family = "wasm"))]
12618  #[test]
12619  fn current_thread_stopped_dispatch_publication_is_rejected_under_scheduler_lock() {
12620    use std::sync::mpsc;
12621
12622    CURRENT_THREAD_STOPPED_HOST_DISPATCHES.store(0, Ordering::SeqCst);
12623    let metrics = Arc::new(RuntimeMetrics::default());
12624    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12625      Arc::clone(&metrics),
12626      count_current_thread_stopped_host_dispatch,
12627    ));
12628    let scheduler = executor
12629      .scheduler_idle_lock
12630      .lock()
12631      .unwrap_or_else(std::sync::PoisonError::into_inner);
12632    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
12633    let scheduling_executor = Arc::clone(&executor);
12634    let scheduling = std::thread::spawn(move || {
12635      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
12636        *slot.borrow_mut() = Some(Box::new(move || {
12637          publication_blocked_tx.send(()).unwrap();
12638        }));
12639      });
12640      let scheduler = Arc::clone(&scheduling_executor);
12641      let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
12642        scheduler.schedule(runnable);
12643      });
12644      task.detach();
12645      scheduling_executor.schedule(runnable);
12646    });
12647
12648    publication_blocked_rx
12649      .recv_timeout(Duration::from_secs(2))
12650      .expect("dispatch publication must block behind the scheduler-idle mutex");
12651    executor.begin_shutdown();
12652    drop(scheduler);
12653    scheduling.join().unwrap();
12654    executor.wait_until_scheduler_idle();
12655
12656    assert_eq!(
12657      CURRENT_THREAD_STOPPED_HOST_DISPATCHES.load(Ordering::SeqCst),
12658      0,
12659      "a publication that reaches the scheduler-idle barrier after stop must not call the host"
12660    );
12661    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
12662    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12663  }
12664
12665  #[test]
12666  fn current_thread_host_dispatch_does_not_poll_inline_from_shared_wake() {
12667    use std::{sync::mpsc, time::Duration};
12668
12669    let metrics = Arc::new(RuntimeMetrics::default());
12670    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12671      Arc::clone(&metrics),
12672      accept_current_thread_host_dispatch,
12673    ));
12674    let scheduler = Arc::clone(&executor);
12675    let completed = Arc::new(AtomicBool::new(false));
12676    let completed_task = Arc::clone(&completed);
12677    let (wake_tx, wake_rx) = futures::channel::oneshot::channel::<()>();
12678    let shared = async move {
12679      wake_rx.await.expect("wake sender dropped");
12680    }
12681    .boxed()
12682    .shared();
12683    let (runnable, task) = async_task::spawn(
12684      async move {
12685        shared.await;
12686        completed_task.store(true, Ordering::SeqCst);
12687      },
12688      move |runnable| scheduler.schedule(runnable),
12689    );
12690
12691    executor.schedule(runnable);
12692    assert!(
12693      !completed.load(Ordering::SeqCst),
12694      "accepted host dispatch must defer the first poll"
12695    );
12696    executor.drive_host_turn();
12697
12698    let (send_returned_tx, send_returned_rx) = mpsc::sync_channel(1);
12699    std::thread::spawn(move || {
12700      wake_tx.send(()).expect("shared wake receiver dropped");
12701      send_returned_tx.send(()).unwrap();
12702    });
12703    send_returned_rx
12704      .recv_timeout(Duration::from_secs(1))
12705      .expect("Shared wake re-entered its own waker mutex through inline polling");
12706    assert!(
12707      !completed.load(Ordering::SeqCst),
12708      "the wake must remain queued until a fresh host turn"
12709    );
12710
12711    executor.drive_host_turn();
12712    futures::executor::block_on(task);
12713    assert!(completed.load(Ordering::SeqCst));
12714    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12715  }
12716
12717  #[test]
12718  fn current_thread_hostless_wake_is_enqueue_only() {
12719    use std::{sync::mpsc, time::Duration};
12720
12721    struct LockOnSecondPoll {
12722      gate: Arc<Mutex<()>>,
12723      first: bool,
12724      waker: Option<mpsc::Sender<Waker>>,
12725      completed: Arc<AtomicBool>,
12726    }
12727
12728    impl Future for LockOnSecondPoll {
12729      type Output = ();
12730
12731      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
12732        if self.first {
12733          self.first = false;
12734          self.waker.take().unwrap().send(cx.waker().clone()).unwrap();
12735          return Poll::Pending;
12736        }
12737        drop(self.gate.lock().unwrap());
12738        self.completed.store(true, Ordering::SeqCst);
12739        Poll::Ready(())
12740      }
12741    }
12742
12743    let metrics = Arc::new(RuntimeMetrics::default());
12744    let executor = Arc::new(CurrentThreadExecutor::new(metrics));
12745    let gate = Arc::new(Mutex::new(()));
12746    let completed = Arc::new(AtomicBool::new(false));
12747    let (waker_tx, waker_rx) = mpsc::channel();
12748    let scheduler = Arc::clone(&executor);
12749    let (runnable, task) = async_task::spawn(
12750      LockOnSecondPoll {
12751        gate: Arc::clone(&gate),
12752        first: true,
12753        waker: Some(waker_tx),
12754        completed: Arc::clone(&completed),
12755      },
12756      move |runnable| scheduler.schedule(runnable),
12757    );
12758    executor.schedule(runnable);
12759    executor.drive_host_turn();
12760    let waker = waker_rx.recv_timeout(Duration::from_secs(1)).unwrap();
12761
12762    let (returned_tx, returned_rx) = mpsc::channel();
12763    let wake_gate = Arc::clone(&gate);
12764    let waking = std::thread::spawn(move || {
12765      let _held = wake_gate.lock().unwrap();
12766      waker.wake();
12767      returned_tx.send(()).unwrap();
12768    });
12769    returned_rx
12770      .recv_timeout(Duration::from_secs(1))
12771      .expect("hostless wake must enqueue without polling under the caller's lock");
12772    waking.join().unwrap();
12773    assert!(!completed.load(Ordering::SeqCst));
12774
12775    executor.drive_host_turn();
12776    futures::executor::block_on(task);
12777    assert!(completed.load(Ordering::SeqCst));
12778  }
12779
12780  #[cfg(not(target_family = "wasm"))]
12781  #[test]
12782  fn current_thread_hostless_submission_wakes_sleeping_block_on_driver() {
12783    use std::sync::mpsc;
12784
12785    let controller = Arc::new(current_thread_controller(
12786      "current-thread-hostless-late-work",
12787    ));
12788    let backend = controller.backend();
12789    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
12790      panic!("configured CurrentThread backend must create a CurrentThread executor");
12791    };
12792    let executor = Arc::clone(executor);
12793    drop(backend);
12794
12795    let (value_tx, value_rx) = oneshot::channel();
12796    let (done_tx, done_rx) = mpsc::channel();
12797    let block_on_controller = Arc::clone(&controller);
12798    let runner = std::thread::spawn(move || {
12799      let mut output = None;
12800      let outcome = {
12801        let mut future = std::pin::pin!(async {
12802          output = Some(value_rx.await.expect("scheduled sender must deliver"));
12803        });
12804        block_on_controller.block_on(future.as_mut())
12805      };
12806      done_tx.send((outcome, output)).unwrap();
12807    });
12808
12809    wait_until(
12810      "the hostless CurrentThread block_on driver to sleep",
12811      || {
12812        executor
12813          .stop
12814          .parkers
12815          .lock()
12816          .unwrap_or_else(std::sync::PoisonError::into_inner)
12817          .iter()
12818          .filter_map(Weak::upgrade)
12819          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12820      },
12821    );
12822
12823    controller
12824      .try_spawn_detached(async move {
12825        value_tx
12826          .send(37usize)
12827          .expect("block_on receiver must remain alive");
12828      })
12829      .unwrap_or_else(|_| panic!("the running CurrentThread runtime must accept late work"));
12830
12831    let (outcome, output) = done_rx
12832      .recv_timeout(Duration::from_secs(2))
12833      .expect("queue publication must wake the sleeping explicit driver");
12834    assert_eq!(outcome, BlockOnOutcome::Completed);
12835    assert_eq!(output, Some(37));
12836    runner.join().unwrap();
12837    controller.shutdown().unwrap();
12838  }
12839
12840  #[cfg(not(target_family = "wasm"))]
12841  #[test]
12842  fn current_thread_submission_wakes_older_driver_when_newer_is_blocked_in_poll() {
12843    use std::sync::mpsc;
12844
12845    struct BlockingPoll {
12846      entered: Option<mpsc::Sender<()>>,
12847      release: mpsc::Receiver<()>,
12848    }
12849
12850    impl Future for BlockingPoll {
12851      type Output = ();
12852
12853      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
12854        self.entered.take().unwrap().send(()).unwrap();
12855        self.release.recv().unwrap();
12856        Poll::Ready(())
12857      }
12858    }
12859
12860    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12861      Arc::new(RuntimeMetrics::default()),
12862      reject_current_thread_host_dispatch,
12863    ));
12864    let (value_tx, value_rx) = oneshot::channel();
12865    let (older_done_tx, older_done_rx) = mpsc::channel();
12866    let older_executor = Arc::clone(&executor);
12867    let older = std::thread::spawn(move || {
12868      let mut value = None;
12869      {
12870        let mut future = std::pin::pin!(async {
12871          value = Some(value_rx.await.unwrap());
12872        });
12873        assert_eq!(
12874          older_executor.block_on(future.as_mut()),
12875          BlockOnOutcome::Completed
12876        );
12877      }
12878      older_done_tx.send(value.unwrap()).unwrap();
12879    });
12880    wait_until("the older CurrentThread driver to sleep", || {
12881      executor
12882        .stop
12883        .parkers
12884        .lock()
12885        .unwrap_or_else(std::sync::PoisonError::into_inner)
12886        .iter()
12887        .filter_map(Weak::upgrade)
12888        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
12889    });
12890
12891    let (newer_entered_tx, newer_entered_rx) = mpsc::channel();
12892    let (release_newer_tx, release_newer_rx) = mpsc::channel();
12893    let newer_executor = Arc::clone(&executor);
12894    let newer = std::thread::spawn(move || {
12895      let mut future = std::pin::pin!(BlockingPoll {
12896        entered: Some(newer_entered_tx),
12897        release: release_newer_rx
12898      });
12899      assert_eq!(
12900        newer_executor.block_on(future.as_mut()),
12901        BlockOnOutcome::Completed
12902      );
12903    });
12904    newer_entered_rx
12905      .recv_timeout(Duration::from_secs(1))
12906      .expect("the newer driver must be blocked inside its poll");
12907
12908    let scheduler = Arc::clone(&executor);
12909    let (runnable, task) = async_task::spawn(
12910      async move {
12911        value_tx.send(41usize).unwrap();
12912      },
12913      move |runnable| scheduler.schedule(runnable),
12914    );
12915    task.detach();
12916    executor.schedule(runnable);
12917
12918    assert_eq!(
12919      older_done_rx
12920        .recv_timeout(Duration::from_secs(2))
12921        .expect("queue publication must also wake the older sleeping driver"),
12922      41
12923    );
12924    release_newer_tx.send(()).unwrap();
12925    older.join().unwrap();
12926    newer.join().unwrap();
12927  }
12928
12929  #[test]
12930  fn current_thread_shutdown_cancels_host_queued_task() {
12931    use std::sync::mpsc;
12932
12933    struct DropSignal(mpsc::Sender<()>);
12934
12935    impl Drop for DropSignal {
12936      fn drop(&mut self) {
12937        self.0.send(()).unwrap();
12938      }
12939    }
12940
12941    let metrics = Arc::new(RuntimeMetrics::default());
12942    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12943      Arc::clone(&metrics),
12944      accept_current_thread_host_dispatch,
12945    ));
12946    let scheduler = Arc::clone(&executor);
12947    let (dropped_tx, dropped_rx) = mpsc::channel();
12948    let signal = DropSignal(dropped_tx);
12949    let (runnable, task) = async_task::spawn(
12950      async move {
12951        let _signal = signal;
12952        futures::future::pending::<()>().await;
12953      },
12954      move |runnable| scheduler.schedule(runnable),
12955    );
12956
12957    executor.schedule(runnable);
12958    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
12959    executor.begin_shutdown();
12960
12961    dropped_rx
12962      .recv_timeout(Duration::from_secs(1))
12963      .expect("shutdown must drop a task queued behind a host turn");
12964    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
12965    assert!(
12966      futures::executor::block_on(task.fallible()).is_none(),
12967      "the queued task handle must resolve as cancelled"
12968    );
12969  }
12970
12971  #[test]
12972  fn current_thread_new_host_republishes_the_existing_dispatch_capability() {
12973    reset_current_thread_recovery_host_dispatches();
12974    let metrics = Arc::new(RuntimeMetrics::default());
12975    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
12976      Arc::clone(&metrics),
12977      count_current_thread_recovery_host_dispatch,
12978    ));
12979    let scheduler = Arc::clone(&executor);
12980    let completed = Arc::new(AtomicBool::new(false));
12981    let completed_task = Arc::clone(&completed);
12982    let (runnable, task) = async_task::spawn(
12983      async move {
12984        completed_task.store(true, Ordering::SeqCst);
12985      },
12986      move |runnable| scheduler.schedule(runnable),
12987    );
12988
12989    executor.schedule(runnable);
12990    assert_eq!(current_thread_recovery_host_dispatches(), 1);
12991    let lost_dispatch = current_thread_dispatch(&executor);
12992
12993    // Model another host registering while the first host still owns an
12994    // accepted delivery. The executor capability stays stable; the host
12995    // registry gives the new registration a distinct delivery token.
12996    executor.request_drain_if_queued();
12997    assert_eq!(
12998      current_thread_recovery_host_dispatches(),
12999      2,
13000      "the newly registered host must receive the pending executor dispatch"
13001    );
13002    assert_eq!(current_thread_dispatch(&executor), lost_dispatch);
13003    executor.drive_host_turn_for_dispatch(lost_dispatch);
13004    futures::executor::block_on(task);
13005    assert!(completed.load(Ordering::SeqCst));
13006    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13007
13008    let second_completed = Arc::new(AtomicBool::new(false));
13009    let second_completed_task = Arc::clone(&second_completed);
13010    let second_scheduler = Arc::clone(&executor);
13011    let (second_runnable, second_task) = async_task::spawn(
13012      async move {
13013        second_completed_task.store(true, Ordering::SeqCst);
13014      },
13015      move |runnable| second_scheduler.schedule(runnable),
13016    );
13017    executor.schedule(second_runnable);
13018    assert_eq!(
13019      current_thread_recovery_host_dispatches(),
13020      3,
13021      "work queued after the stale callback must receive a new dispatch"
13022    );
13023    let third_dispatch = current_thread_dispatch(&executor);
13024
13025    // The already-consumed capability remains stale relative to dispatch 2 and
13026    // cannot service its work.
13027    executor.drive_host_turn_for_dispatch(lost_dispatch);
13028    assert!(!second_completed.load(Ordering::SeqCst));
13029    executor.drive_host_turn_for_dispatch(third_dispatch);
13030    futures::executor::block_on(second_task);
13031    assert!(second_completed.load(Ordering::SeqCst));
13032
13033    let third_scheduler = Arc::clone(&executor);
13034    let (third_runnable, third_task) =
13035      async_task::spawn(async {}, move |runnable| third_scheduler.schedule(runnable));
13036    executor.schedule(third_runnable);
13037    assert_eq!(
13038      current_thread_recovery_host_dispatches(),
13039      4,
13040      "a stale callback must not leave the dispatch latch stuck"
13041    );
13042    executor.drive_host_turn();
13043    futures::executor::block_on(third_task);
13044  }
13045
13046  #[test]
13047  fn current_thread_new_host_joins_an_existing_recovery_dispatch() {
13048    let metrics = Arc::new(RuntimeMetrics::default());
13049    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13050      Arc::clone(&metrics),
13051      accept_current_thread_host_dispatch,
13052    ));
13053    let scheduler = Arc::clone(&executor);
13054    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13055      scheduler.schedule(runnable)
13056    });
13057
13058    executor.schedule(runnable);
13059    let failed_dispatch = current_thread_dispatch(&executor);
13060    executor.cancel_host_dispatch_for_test(failed_dispatch);
13061    let recovery_dispatch = current_thread_dispatch(&executor);
13062    assert_eq!(
13063      executor.recovery_dispatch.load(Ordering::Acquire),
13064      recovery_dispatch
13065    );
13066
13067    executor.request_drain_if_queued();
13068    let new_host_dispatch = current_thread_dispatch(&executor);
13069    assert_eq!(new_host_dispatch, recovery_dispatch);
13070    assert_eq!(
13071      executor.recovery_dispatch.load(Ordering::Acquire),
13072      recovery_dispatch
13073    );
13074    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13075
13076    executor.drive_host_turn_for_dispatch(new_host_dispatch);
13077    assert_eq!(futures::executor::block_on(task), 17);
13078  }
13079
13080  #[cfg(not(target_family = "wasm"))]
13081  #[test]
13082  fn current_thread_host_admission_serializes_dispatch_publication() {
13083    use std::sync::mpsc;
13084
13085    CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13086    let metrics = Arc::new(RuntimeMetrics::default());
13087    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13088      Arc::clone(&metrics),
13089      count_current_thread_admission_host_dispatch,
13090    ));
13091    let first_scheduler = Arc::clone(&executor);
13092    let (first_runnable, first_task) =
13093      async_task::spawn(async {}, move |runnable| first_scheduler.schedule(runnable));
13094    first_task.detach();
13095    executor.schedule(first_runnable);
13096    let dispatch = current_thread_dispatch(&executor);
13097    assert_eq!(
13098      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13099      1
13100    );
13101
13102    let (claimed_tx, claimed_rx) = mpsc::channel();
13103    let (release_tx, release_rx) = mpsc::channel();
13104    let driver_executor = Arc::clone(&executor);
13105    let driver = std::thread::spawn(move || {
13106      CURRENT_THREAD_DISPATCH_CLAIM_TEST_HOOK.with(|slot| {
13107        *slot.borrow_mut() = Some(Box::new(move || {
13108          claimed_tx.send(()).unwrap();
13109          release_rx.recv().unwrap();
13110        }));
13111      });
13112      driver_executor.drive_host_turn_for_dispatch(dispatch);
13113    });
13114    claimed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13115
13116    let second_ran = Arc::new(AtomicBool::new(false));
13117    let second_ran_task = Arc::clone(&second_ran);
13118    let scheduling_executor = Arc::clone(&executor);
13119    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
13120    let (scheduled_tx, scheduled_rx) = mpsc::channel();
13121    let scheduling = std::thread::spawn(move || {
13122      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
13123        *slot.borrow_mut() = Some(Box::new(move || {
13124          publication_blocked_tx.send(()).unwrap();
13125        }));
13126      });
13127      let scheduler = Arc::clone(&scheduling_executor);
13128      let (runnable, task) = async_task::spawn(
13129        async move {
13130          second_ran_task.store(true, Ordering::SeqCst);
13131        },
13132        move |runnable| scheduler.schedule(runnable),
13133      );
13134      task.detach();
13135      scheduling_executor.schedule(runnable);
13136      scheduled_tx.send(()).unwrap();
13137    });
13138
13139    publication_blocked_rx
13140      .recv_timeout(Duration::from_secs(2))
13141      .unwrap();
13142    assert!(
13143      matches!(scheduled_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
13144      "dispatch publication must wait until host admission publishes its draining role"
13145    );
13146    assert_eq!(
13147      CURRENT_THREAD_ADMISSION_HOST_DISPATCHES.load(Ordering::SeqCst),
13148      1,
13149      "the admission gap must not publish a duplicate host dispatch"
13150    );
13151
13152    release_tx.send(()).unwrap();
13153    scheduled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
13154    scheduling.join().unwrap();
13155    driver.join().unwrap();
13156    if !second_ran.load(Ordering::SeqCst) {
13157      executor.drive_host_turn();
13158    }
13159    assert!(second_ran.load(Ordering::SeqCst));
13160    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13161  }
13162
13163  #[test]
13164  fn current_thread_host_turn_yields_after_runnable_budget() {
13165    struct SelfWakingFuture {
13166      remaining: usize,
13167    }
13168
13169    impl Future for SelfWakingFuture {
13170      type Output = ();
13171
13172      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13173        if self.remaining == 0 {
13174          return Poll::Ready(());
13175        }
13176        self.remaining -= 1;
13177        cx.waker().wake_by_ref();
13178        Poll::Pending
13179      }
13180    }
13181
13182    CURRENT_THREAD_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13183    let metrics = Arc::new(RuntimeMetrics::default());
13184    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13185      Arc::clone(&metrics),
13186      count_current_thread_budget_host_dispatch,
13187    ));
13188    let scheduler = Arc::clone(&executor);
13189    let (runnable, task) = async_task::spawn(
13190      SelfWakingFuture {
13191        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13192      },
13193      move |runnable| scheduler.schedule(runnable),
13194    );
13195
13196    executor.schedule(runnable);
13197    executor.drive_host_turn();
13198
13199    assert_eq!(
13200      metrics.runnable_polls.load(Ordering::Relaxed),
13201      CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET as u64
13202    );
13203    assert_eq!(
13204      CURRENT_THREAD_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13205      2,
13206      "remaining hot work must continue through a fresh host turn"
13207    );
13208
13209    executor.drive_host_turn();
13210    futures::executor::block_on(task);
13211    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13212  }
13213
13214  #[cfg(not(target_family = "wasm"))]
13215  #[test]
13216  fn current_thread_host_turn_yields_after_inline_blocking_releases_queued_fifo() {
13217    const QUEUED_BLOCKING_JOBS: usize = CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1;
13218
13219    CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13220    let metrics = Arc::new(RuntimeMetrics::default());
13221    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13222      Arc::clone(&metrics),
13223      count_current_thread_blocking_budget_host_dispatch,
13224    ));
13225    let work = GenerationWork::new();
13226    let observed = Arc::new(Mutex::new(Vec::new()));
13227
13228    let task_executor = Arc::clone(&executor);
13229    let wake_executor = Arc::clone(&executor);
13230    let producer_executor = Arc::clone(&executor);
13231    let producer_work = Arc::clone(&work);
13232    let producer_observed = Arc::clone(&observed);
13233    let outer_registration = work
13234      .try_register_work()
13235      .expect("the outer blocking job must be accepted");
13236    let (runnable, task) = async_task::spawn(
13237      async move {
13238        let queued = task_executor
13239          .schedule_blocking(
13240            move || {
13241              std::thread::spawn(move || {
13242                (0..QUEUED_BLOCKING_JOBS)
13243                  .map(|index| {
13244                    let registration = producer_work
13245                      .try_register_work()
13246                      .expect("queued blocking work must be accepted");
13247                    let observed = Arc::clone(&producer_observed);
13248                    producer_executor.schedule_blocking(
13249                      move || {
13250                        observed
13251                          .lock()
13252                          .unwrap_or_else(std::sync::PoisonError::into_inner)
13253                          .push(index);
13254                      },
13255                      registration,
13256                    )
13257                  })
13258                  .collect::<Vec<_>>()
13259              })
13260              .join()
13261              .expect("the blocking producer must finish")
13262            },
13263            outer_registration,
13264          )
13265          .await
13266          .expect("the inline blocking job must finish");
13267        for handle in queued {
13268          handle.await.expect("queued blocking work must finish");
13269        }
13270      },
13271      move |runnable| wake_executor.schedule(runnable),
13272    );
13273
13274    executor.schedule(runnable);
13275    executor.drive_host_turn();
13276
13277    let first_turn = observed
13278      .lock()
13279      .unwrap_or_else(std::sync::PoisonError::into_inner)
13280      .clone();
13281    assert!(
13282      !first_turn.is_empty(),
13283      "the first host turn must service queued blocking work"
13284    );
13285    assert!(
13286      first_turn.len() < QUEUED_BLOCKING_JOBS,
13287      "inline lane release must not drain the entire blocking FIFO in one host turn"
13288    );
13289    assert_eq!(first_turn, (0..first_turn.len()).collect::<Vec<_>>());
13290    assert_eq!(
13291      CURRENT_THREAD_BLOCKING_BUDGET_HOST_DISPATCHES.load(Ordering::SeqCst),
13292      2,
13293      "remaining blocking work must continue through a fresh host turn"
13294    );
13295
13296    let mut continuation_turns = 0;
13297    while observed
13298      .lock()
13299      .unwrap_or_else(std::sync::PoisonError::into_inner)
13300      .len()
13301      < QUEUED_BLOCKING_JOBS
13302    {
13303      continuation_turns += 1;
13304      assert!(
13305        continuation_turns <= QUEUED_BLOCKING_JOBS,
13306        "queued blocking work failed to make bounded host-turn progress"
13307      );
13308      executor.drive_host_turn();
13309    }
13310    executor.drive_host_turn();
13311    futures::executor::block_on(task);
13312
13313    assert_eq!(
13314      *observed
13315        .lock()
13316        .unwrap_or_else(std::sync::PoisonError::into_inner),
13317      (0..QUEUED_BLOCKING_JOBS).collect::<Vec<_>>(),
13318      "blocking jobs must preserve FIFO order across host turns"
13319    );
13320    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13321  }
13322
13323  #[cfg(not(target_family = "wasm"))]
13324  #[test]
13325  fn current_thread_shutdown_waits_for_continuation_dispatch_publication() {
13326    use std::sync::mpsc;
13327
13328    struct SelfWakingFuture {
13329      remaining: usize,
13330    }
13331
13332    impl Future for SelfWakingFuture {
13333      type Output = ();
13334
13335      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
13336        if self.remaining == 0 {
13337          return Poll::Ready(());
13338        }
13339        self.remaining -= 1;
13340        cx.waker().wake_by_ref();
13341        Poll::Pending
13342      }
13343    }
13344
13345    let controller = Arc::new(current_thread_controller("host-continuation-shutdown"));
13346    let old_generation = controller.backend().generation();
13347    controller
13348      .try_spawn_detached(SelfWakingFuture {
13349        remaining: CurrentThreadExecutor::HOST_TURN_RUNNABLE_BUDGET + 1,
13350      })
13351      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the self-waking task"));
13352    let dispatch = controller_current_thread_dispatch(&controller);
13353
13354    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
13355    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
13356    let driver_controller = Arc::clone(&controller);
13357    let driver = std::thread::spawn(move || {
13358      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13359        *slot.borrow_mut() = Some(Box::new(move || {
13360          dispatch_entered_tx.send(()).unwrap();
13361          release_dispatch_rx.recv().unwrap();
13362        }));
13363      });
13364      driver_controller.drive_current_thread_dispatch(dispatch, false);
13365    });
13366    dispatch_entered_rx
13367      .recv_timeout(Duration::from_secs(2))
13368      .expect("the bounded host turn must begin publishing its continuation");
13369
13370    let (shutdown_tx, shutdown_rx) = mpsc::channel();
13371    let shutdown_controller = Arc::clone(&controller);
13372    let shutdown = std::thread::spawn(move || {
13373      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
13374    });
13375    wait_until("CurrentThread shutdown to leave Running", || {
13376      let state = controller
13377        .state
13378        .lock()
13379        .unwrap_or_else(std::sync::PoisonError::into_inner);
13380      !matches!(
13381        &state.lifecycle,
13382        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
13383      )
13384    });
13385
13386    let (restart_tx, restart_rx) = mpsc::channel();
13387    let restart_controller = Arc::clone(&controller);
13388    let restart = std::thread::spawn(move || {
13389      let result = restart_controller
13390        .start()
13391        .map(|()| restart_controller.backend().generation());
13392      restart_tx.send(result).unwrap();
13393    });
13394
13395    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
13396    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
13397    let shutdown_overlapped = early_shutdown.is_some();
13398    let restart_overlapped = early_restart.is_some();
13399    release_dispatch_tx.send(()).unwrap();
13400    driver.join().unwrap();
13401
13402    let shutdown_result = early_shutdown.unwrap_or_else(|| {
13403      shutdown_rx
13404        .recv_timeout(Duration::from_secs(2))
13405        .expect("shutdown must finish after continuation publication retires")
13406    });
13407    shutdown_result.unwrap();
13408    let new_generation = early_restart.unwrap_or_else(|| {
13409      restart_rx
13410        .recv_timeout(Duration::from_secs(2))
13411        .expect("restart must follow continuation publication and shutdown")
13412    });
13413    let new_generation = new_generation.unwrap();
13414    shutdown.join().unwrap();
13415    restart.join().unwrap();
13416
13417    assert!(
13418      !shutdown_overlapped,
13419      "shutdown must not publish Stopped while an old-generation continuation dispatch is executing"
13420    );
13421    assert!(
13422      !restart_overlapped,
13423      "restart must not overlap an old-generation continuation dispatch"
13424    );
13425    assert_ne!(new_generation, old_generation);
13426    controller.shutdown().unwrap();
13427  }
13428
13429  #[cfg(not(target_family = "wasm"))]
13430  #[test]
13431  fn current_thread_shutdown_waits_for_initial_dispatch_publication() {
13432    use std::sync::mpsc;
13433
13434    let controller = Arc::new(current_thread_controller("host-initial-dispatch-shutdown"));
13435    let old_generation = controller.backend().generation();
13436    let (reentrant_shutdown_tx, reentrant_shutdown_rx) = mpsc::channel();
13437    let (dispatch_entered_tx, dispatch_entered_rx) = mpsc::channel();
13438    let (release_dispatch_tx, release_dispatch_rx) = mpsc::channel();
13439    let submit_controller = Arc::clone(&controller);
13440    let reentrant_controller = Arc::clone(&controller);
13441    let submitter = std::thread::spawn(move || {
13442      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13443        *slot.borrow_mut() = Some(Box::new(move || {
13444          reentrant_shutdown_tx
13445            .send(reentrant_controller.shutdown())
13446            .unwrap();
13447          dispatch_entered_tx.send(()).unwrap();
13448          release_dispatch_rx.recv().unwrap();
13449        }));
13450      });
13451      submit_controller
13452        .try_spawn_detached(std::future::pending::<()>())
13453        .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the pending task"));
13454    });
13455
13456    let reentrant_shutdown = reentrant_shutdown_rx
13457      .recv_timeout(Duration::from_secs(2))
13458      .expect("dispatch publication must not self-deadlock on reentrant shutdown");
13459    dispatch_entered_rx
13460      .recv_timeout(Duration::from_secs(2))
13461      .expect("the initial host dispatch publication must reach the injected pause");
13462
13463    let (shutdown_tx, shutdown_rx) = mpsc::channel();
13464    let shutdown_controller = Arc::clone(&controller);
13465    let shutdown = std::thread::spawn(move || {
13466      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
13467    });
13468    wait_until("CurrentThread shutdown to leave Running", || {
13469      let state = controller
13470        .state
13471        .lock()
13472        .unwrap_or_else(std::sync::PoisonError::into_inner);
13473      !matches!(
13474        &state.lifecycle,
13475        RuntimeLifecycle::Running(backend) if backend.generation() == old_generation
13476      )
13477    });
13478
13479    let (restart_tx, restart_rx) = mpsc::channel();
13480    let restart_controller = Arc::clone(&controller);
13481    let restart = std::thread::spawn(move || {
13482      let result = restart_controller
13483        .start()
13484        .map(|()| restart_controller.backend().generation());
13485      restart_tx.send(result).unwrap();
13486    });
13487
13488    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
13489    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
13490    let shutdown_overlapped = early_shutdown.is_some();
13491    let restart_overlapped = early_restart.is_some();
13492    release_dispatch_tx.send(()).unwrap();
13493    submitter.join().unwrap();
13494
13495    let shutdown_result = early_shutdown.unwrap_or_else(|| {
13496      shutdown_rx
13497        .recv_timeout(Duration::from_secs(2))
13498        .expect("shutdown must finish after initial dispatch publication retires")
13499    });
13500    shutdown_result.unwrap();
13501    let new_generation = early_restart.unwrap_or_else(|| {
13502      restart_rx
13503        .recv_timeout(Duration::from_secs(2))
13504        .expect("restart must follow initial dispatch publication and shutdown")
13505    });
13506    let new_generation = new_generation.unwrap();
13507    shutdown.join().unwrap();
13508    restart.join().unwrap();
13509
13510    let reentrant_error = reentrant_shutdown
13511      .expect_err("dispatch publication must be generation-scoped scheduler work");
13512    assert!(
13513      reentrant_error
13514        .to_string()
13515        .contains("work running on that runtime")
13516    );
13517    assert!(
13518      !shutdown_overlapped,
13519      "shutdown must not publish Stopped while an old-generation host dispatch call is executing"
13520    );
13521    assert!(
13522      !restart_overlapped,
13523      "restart must not overlap an old-generation host dispatch call"
13524    );
13525    assert_ne!(new_generation, old_generation);
13526    controller.shutdown().unwrap();
13527  }
13528
13529  #[test]
13530  fn current_thread_dispatch_publication_unwind_releases_owner_and_can_republish() {
13531    CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13532      .lock()
13533      .unwrap()
13534      .clear();
13535    CURRENT_THREAD_PANICKING_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
13536    let metrics = Arc::new(RuntimeMetrics::default());
13537    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13538      Arc::clone(&metrics),
13539      panic_once_current_thread_host_dispatch,
13540    ));
13541    let scheduler = Arc::clone(&executor);
13542    let (runnable, task) = async_task::spawn(async { 17usize }, move |runnable| {
13543      scheduler.schedule(runnable)
13544    });
13545
13546    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
13547    assert!(
13548      publication.is_err(),
13549      "the injected host dispatch panic must unwind"
13550    );
13551    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13552    {
13553      let scheduler = executor
13554        .scheduler_idle_lock
13555        .lock()
13556        .unwrap_or_else(std::sync::PoisonError::into_inner);
13557      assert_eq!(scheduler.active_dispatch_calls, 0);
13558      assert!(scheduler.dispatch_publications.is_empty());
13559    }
13560
13561    executor.request_drain_if_queued();
13562
13563    assert_ne!(dispatch, 0);
13564    assert_eq!(
13565      *CURRENT_THREAD_PANICKING_PUBLICATION_DISPATCHES
13566        .lock()
13567        .unwrap(),
13568      vec![dispatch, dispatch],
13569      "a later host registration must retry the same pending capability"
13570    );
13571    {
13572      let scheduler = executor
13573        .scheduler_idle_lock
13574        .lock()
13575        .unwrap_or_else(std::sync::PoisonError::into_inner);
13576      assert_eq!(scheduler.active_dispatch_calls, 0);
13577      assert!(scheduler.dispatch_publications.is_empty());
13578    }
13579    executor.drive_host_turn_for_dispatch(dispatch);
13580    assert_eq!(futures::executor::block_on(task.fallible()), Some(17));
13581    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13582  }
13583
13584  #[cfg(not(target_family = "wasm"))]
13585  #[test]
13586  fn current_thread_publication_unwind_services_armed_republish_before_resuming_panic() {
13587    use std::sync::mpsc;
13588
13589    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13590      .lock()
13591      .unwrap()
13592      .clear();
13593    CURRENT_THREAD_ARMED_PANIC_PUBLICATION_ONCE.store(true, Ordering::SeqCst);
13594    let metrics = Arc::new(RuntimeMetrics::default());
13595    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13596      Arc::clone(&metrics),
13597      panic_once_current_thread_armed_republish_host_dispatch,
13598    ));
13599    let scheduler = Arc::clone(&executor);
13600    let (runnable, task) = async_task::spawn(async { 23usize }, move |runnable| {
13601      scheduler.schedule(runnable)
13602    });
13603
13604    let (called_tx, called_rx) = mpsc::channel();
13605    let (release_tx, release_rx) = mpsc::channel();
13606    let publishing_executor = Arc::clone(&executor);
13607    let publication = std::thread::spawn(move || {
13608      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13609        *slot.borrow_mut() = Some(Box::new(move || {
13610          called_tx.send(()).unwrap();
13611          release_rx.recv().unwrap();
13612        }));
13613      });
13614      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
13615    });
13616    called_rx
13617      .recv_timeout(Duration::from_secs(2))
13618      .expect("the first publication must pause before calling the host dispatcher");
13619
13620    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13621    executor.request_drain_if_queued();
13622    let publication_state = {
13623      let scheduler = executor
13624        .scheduler_idle_lock
13625        .lock()
13626        .unwrap_or_else(std::sync::PoisonError::into_inner);
13627      (
13628        scheduler.active_dispatch_calls,
13629        scheduler.dispatch_publications.len(),
13630        scheduler.dispatch_publications[0].dispatch,
13631        scheduler.dispatch_publications[0].republish_requested,
13632      )
13633    };
13634    release_tx.send(()).unwrap();
13635
13636    let publication = publication.join().unwrap();
13637    assert!(
13638      publication.is_err(),
13639      "the original host-dispatch panic must resume"
13640    );
13641    assert_ne!(dispatch, 0);
13642    assert_eq!(publication_state, (1, 1, dispatch, true));
13643    assert_eq!(
13644      *CURRENT_THREAD_ARMED_PANIC_PUBLICATION_DISPATCHES
13645        .lock()
13646        .unwrap(),
13647      vec![dispatch, dispatch],
13648      "the armed request must retry automatically before the original panic resumes"
13649    );
13650    {
13651      let scheduler = executor
13652        .scheduler_idle_lock
13653        .lock()
13654        .unwrap_or_else(std::sync::PoisonError::into_inner);
13655      assert_eq!(scheduler.active_dispatch_calls, 0);
13656      assert!(scheduler.dispatch_publications.is_empty());
13657    }
13658    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
13659    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
13660
13661    executor.drive_host_turn_for_dispatch(dispatch);
13662    assert_eq!(futures::executor::block_on(task.fallible()), Some(23));
13663    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13664  }
13665
13666  #[cfg(not(target_family = "wasm"))]
13667  #[test]
13668  fn current_thread_first_panic_survives_failed_republish_and_nested_recovery_panic() {
13669    use std::sync::mpsc;
13670
13671    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13672      .lock()
13673      .unwrap()
13674      .clear();
13675    CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCH_CALLS.store(0, Ordering::SeqCst);
13676    CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
13677    CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.store(0, Ordering::SeqCst);
13678    {
13679      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13680        .state
13681        .lock()
13682        .unwrap_or_else(std::sync::PoisonError::into_inner);
13683      assert!(state.entries.is_empty());
13684      assert!(state.dispatches.is_empty());
13685    }
13686    let failed_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13687      .register(Arc::new(FailDuringAuthoritativePanicPublication));
13688    let metrics = Arc::new(RuntimeMetrics::default());
13689    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
13690      Arc::clone(&metrics),
13691      dispatch_authoritative_panic_current_thread_tasks,
13692      &CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS,
13693    ));
13694    let scheduler = Arc::clone(&executor);
13695    let (runnable, task) = async_task::spawn(async { 41usize }, move |runnable| {
13696      scheduler.schedule(runnable)
13697    });
13698
13699    let (called_tx, called_rx) = mpsc::channel();
13700    let (release_tx, release_rx) = mpsc::channel();
13701    let publishing_executor = Arc::clone(&executor);
13702    let publication = std::thread::spawn(move || {
13703      CURRENT_THREAD_DISPATCH_CALL_TEST_HOOK.with(|slot| {
13704        *slot.borrow_mut() = Some(Box::new(move || {
13705          called_tx.send(()).unwrap();
13706          release_rx.recv().unwrap();
13707        }));
13708      });
13709      catch_unwind(AssertUnwindSafe(|| publishing_executor.schedule(runnable)))
13710    });
13711    called_rx
13712      .recv_timeout(Duration::from_secs(2))
13713      .expect("the first publication must pause before raising its panic");
13714
13715    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13716    executor.request_drain_if_queued();
13717    release_tx.send(()).unwrap();
13718
13719    let payload = publication
13720      .join()
13721      .unwrap()
13722      .expect_err("the first host-dispatch panic must resume");
13723    assert!(
13724      payload.is::<FirstHostileCurrentThreadPanic>(),
13725      "the first panic must remain authoritative over nested recovery"
13726    );
13727    assert_eq!(
13728      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13729      1,
13730      "the later hostile payload must be discarded under containment"
13731    );
13732    assert_eq!(
13733      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13734      0,
13735      "the authoritative payload must remain owned by the resumed unwind"
13736    );
13737    discard_panic_payload(payload);
13738    assert_eq!(
13739      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.load(Ordering::SeqCst),
13740      1
13741    );
13742
13743    let dispatches = CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13744      .lock()
13745      .unwrap()
13746      .clone();
13747    assert_eq!(dispatches.len(), 3);
13748    let replacement_dispatch = dispatches[2];
13749    assert_eq!(dispatches[0], failed_dispatch);
13750    assert_eq!(dispatches[1], failed_dispatch);
13751    assert_ne!(replacement_dispatch, failed_dispatch);
13752    assert_eq!(
13753      executor.dispatch_pending.load(Ordering::Acquire),
13754      replacement_dispatch
13755    );
13756    assert_eq!(
13757      executor.recovery_dispatch.load(Ordering::Acquire),
13758      replacement_dispatch
13759    );
13760    {
13761      let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13762        .state
13763        .lock()
13764        .unwrap_or_else(std::sync::PoisonError::into_inner);
13765      assert!(
13766        !state.dispatches.contains_key(&failed_dispatch),
13767        "nested recovery unwind must retire the exact failed epoch"
13768      );
13769      assert!(!state.dispatches.contains_key(&replacement_dispatch));
13770    }
13771
13772    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(failed_registration);
13773    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
13774    let healthy_registration = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13775      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
13776    executor.request_drain_if_queued();
13777
13778    assert_eq!(
13779      *CURRENT_THREAD_AUTHORITATIVE_PANIC_DISPATCHES
13780        .lock()
13781        .unwrap(),
13782      vec![
13783        failed_dispatch,
13784        failed_dispatch,
13785        replacement_dispatch,
13786        replacement_dispatch
13787      ],
13788      "retry must retain the replacement reserved before the nested panic"
13789    );
13790    let delivery = healthy.deliveries()[0];
13791    assert_eq!(
13792      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.claim_delivery(delivery.capability()),
13793      Some(replacement_dispatch)
13794    );
13795    let mut host_turn = executor
13796      .try_admit_host_turn(replacement_dispatch)
13797      .expect("the retried replacement delivery must be admitted");
13798    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.mark_serviced(replacement_dispatch);
13799    host_turn.drive();
13800    assert!(
13801      CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13802        .finish_delivery(delivery, false)
13803        .failed_dispatches
13804        .is_empty()
13805    );
13806    assert_eq!(futures::executor::block_on(task.fallible()), Some(41));
13807    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13808
13809    CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.unregister(healthy_registration);
13810    let state = CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS
13811      .state
13812      .lock()
13813      .unwrap_or_else(std::sync::PoisonError::into_inner);
13814    assert!(state.entries.is_empty());
13815    assert!(state.dispatches.is_empty());
13816  }
13817
13818  #[cfg(not(target_family = "wasm"))]
13819  #[test]
13820  fn current_thread_shutdown_suppresses_queued_republish() {
13821    use std::sync::mpsc;
13822
13823    CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.store(0, Ordering::SeqCst);
13824    let metrics = Arc::new(RuntimeMetrics::default());
13825    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13826      Arc::clone(&metrics),
13827      count_current_thread_shutdown_republish_host_dispatch,
13828    ));
13829    let scheduler = Arc::clone(&executor);
13830    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
13831      scheduler.schedule(runnable)
13832    });
13833
13834    let (returned_tx, returned_rx) = mpsc::channel();
13835    let (release_tx, release_rx) = mpsc::channel();
13836    let publishing_executor = Arc::clone(&executor);
13837    let publication = std::thread::spawn(move || {
13838      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13839        *slot.borrow_mut() = Some(Box::new(move || {
13840          returned_tx.send(()).unwrap();
13841          release_rx.recv().unwrap();
13842        }));
13843      });
13844      publishing_executor.schedule(runnable);
13845    });
13846    returned_rx
13847      .recv_timeout(Duration::from_secs(2))
13848      .expect("the initial publication must reach its completion barrier");
13849
13850    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13851    executor.request_drain_if_queued();
13852    let publication_state = {
13853      let scheduler = executor
13854        .scheduler_idle_lock
13855        .lock()
13856        .unwrap_or_else(std::sync::PoisonError::into_inner);
13857      (
13858        scheduler.active_dispatch_calls,
13859        scheduler.dispatch_publications.len(),
13860        scheduler.dispatch_publications[0].dispatch,
13861        scheduler.dispatch_publications[0].republish_requested,
13862      )
13863    };
13864    executor.begin_shutdown();
13865    release_tx.send(()).unwrap();
13866    publication.join().unwrap();
13867    executor.wait_until_scheduler_idle();
13868
13869    assert_ne!(dispatch, 0);
13870    assert_eq!(publication_state, (1, 1, dispatch, true));
13871    assert_eq!(
13872      CURRENT_THREAD_SHUTDOWN_REPUBLISH_HOST_DISPATCHES.load(Ordering::SeqCst),
13873      1
13874    );
13875    {
13876      let scheduler = executor
13877        .scheduler_idle_lock
13878        .lock()
13879        .unwrap_or_else(std::sync::PoisonError::into_inner);
13880      assert_eq!(scheduler.active_dispatch_calls, 0);
13881      assert!(scheduler.dispatch_publications.is_empty());
13882    }
13883    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
13884    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
13885    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13886    assert_eq!(futures::executor::block_on(task.fallible()), None);
13887  }
13888
13889  #[cfg(not(target_family = "wasm"))]
13890  #[test]
13891  fn current_thread_republish_bit_rearms_across_successive_broadcasts() {
13892    use std::sync::mpsc;
13893
13894    CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES
13895      .lock()
13896      .unwrap()
13897      .clear();
13898    let metrics = Arc::new(RuntimeMetrics::default());
13899    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
13900      Arc::clone(&metrics),
13901      record_current_thread_republish_rearm_dispatch,
13902    ));
13903    let scheduler = Arc::clone(&executor);
13904    let (runnable, task) = async_task::spawn(async { 19usize }, move |runnable| {
13905      scheduler.schedule(runnable)
13906    });
13907
13908    let (first_returned_tx, first_returned_rx) = mpsc::channel();
13909    let (release_first_tx, release_first_rx) = mpsc::channel();
13910    let (second_returned_tx, second_returned_rx) = mpsc::channel();
13911    let (release_second_tx, release_second_rx) = mpsc::channel();
13912    let publishing_executor = Arc::clone(&executor);
13913    let publication = std::thread::spawn(move || {
13914      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13915        *slot.borrow_mut() = Some(Box::new(move || {
13916          first_returned_tx.send(()).unwrap();
13917          release_first_rx.recv().unwrap();
13918          CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
13919            *slot.borrow_mut() = Some(Box::new(move || {
13920              second_returned_tx.send(()).unwrap();
13921              release_second_rx.recv().unwrap();
13922            }));
13923          });
13924        }));
13925      });
13926      publishing_executor.schedule(runnable);
13927    });
13928    first_returned_rx
13929      .recv_timeout(Duration::from_secs(2))
13930      .expect("the first publication must reach its completion barrier");
13931
13932    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
13933    executor.request_drain_if_queued();
13934    executor.request_drain_if_queued();
13935    let first_publication_state = {
13936      let scheduler = executor
13937        .scheduler_idle_lock
13938        .lock()
13939        .unwrap_or_else(std::sync::PoisonError::into_inner);
13940      (
13941        scheduler.active_dispatch_calls,
13942        scheduler.dispatch_publications.len(),
13943        scheduler.dispatch_publications[0].dispatch,
13944        scheduler.dispatch_publications[0].republish_requested,
13945      )
13946    };
13947    release_first_tx.send(()).unwrap();
13948    second_returned_rx
13949      .recv_timeout(Duration::from_secs(2))
13950      .expect("the second publication must reach its completion barrier");
13951
13952    executor.request_drain_if_queued();
13953    executor.request_drain_if_queued();
13954    let second_publication_state = {
13955      let scheduler = executor
13956        .scheduler_idle_lock
13957        .lock()
13958        .unwrap_or_else(std::sync::PoisonError::into_inner);
13959      (
13960        scheduler.active_dispatch_calls,
13961        scheduler.dispatch_publications.len(),
13962        scheduler.dispatch_publications[0].dispatch,
13963        scheduler.dispatch_publications[0].republish_requested,
13964      )
13965    };
13966    release_second_tx.send(()).unwrap();
13967    publication.join().unwrap();
13968
13969    assert_ne!(dispatch, 0);
13970    assert_eq!(first_publication_state, (1, 1, dispatch, true));
13971    assert_eq!(second_publication_state, (1, 1, dispatch, true));
13972    assert_eq!(
13973      *CURRENT_THREAD_REPUBLISH_REARM_DISPATCHES.lock().unwrap(),
13974      vec![dispatch, dispatch, dispatch],
13975      "each newly armed republish bit must add one broadcast while duplicate requests coalesce"
13976    );
13977    {
13978      let scheduler = executor
13979        .scheduler_idle_lock
13980        .lock()
13981        .unwrap_or_else(std::sync::PoisonError::into_inner);
13982      assert_eq!(scheduler.active_dispatch_calls, 0);
13983      assert!(scheduler.dispatch_publications.is_empty());
13984    }
13985    executor.drive_host_turn_for_dispatch(dispatch);
13986    assert_eq!(futures::executor::block_on(task.fallible()), Some(19));
13987    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
13988  }
13989
13990  #[cfg(not(target_family = "wasm"))]
13991  #[test]
13992  fn current_thread_initial_unavailable_cannot_retire_later_accepted_publication() {
13993    use std::sync::mpsc;
13994
13995    CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
13996      .lock()
13997      .unwrap()
13998      .clear();
13999    let metrics = Arc::new(RuntimeMetrics::default());
14000    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14001      Arc::clone(&metrics),
14002      reject_initial_current_thread_publication_once,
14003    ));
14004    let scheduler = Arc::clone(&executor);
14005    let (runnable, task) = async_task::spawn(async { 11usize }, move |runnable| {
14006      scheduler.schedule(runnable)
14007    });
14008
14009    let (returned_tx, returned_rx) = mpsc::channel();
14010    let (release_tx, release_rx) = mpsc::channel();
14011    let publishing_executor = Arc::clone(&executor);
14012    let publication = std::thread::spawn(move || {
14013      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14014        *slot.borrow_mut() = Some(Box::new(move || {
14015          returned_tx.send(()).unwrap();
14016          release_rx.recv().unwrap();
14017        }));
14018      });
14019      publishing_executor.schedule(runnable);
14020    });
14021    returned_rx
14022      .recv_timeout(Duration::from_secs(2))
14023      .expect("the unavailable initial publication must reach its completion barrier");
14024
14025    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14026    executor.request_drain_if_queued();
14027    let attempts_while_unavailable_is_paused = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14028      .lock()
14029      .unwrap()
14030      .len();
14031
14032    release_tx.send(()).unwrap();
14033    publication.join().unwrap();
14034
14035    let dispatches = CURRENT_THREAD_INITIAL_PUBLICATION_RACE_DISPATCHES
14036      .lock()
14037      .unwrap()
14038      .clone();
14039    assert_ne!(dispatch, 0);
14040    assert_eq!(
14041      attempts_while_unavailable_is_paused, 1,
14042      "a racing host request must join the in-flight publication instead of retiring it independently"
14043    );
14044    assert_eq!(
14045      dispatches,
14046      vec![dispatch, dispatch],
14047      "the publication owner must retry the same internal capability after the new host joins"
14048    );
14049    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), dispatch);
14050    executor.drive_host_turn_for_dispatch(dispatch);
14051    assert_eq!(futures::executor::block_on(task.fallible()), Some(11));
14052    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14053  }
14054
14055  #[cfg(not(target_family = "wasm"))]
14056  #[test]
14057  fn current_thread_replacement_unavailable_cannot_cancel_later_accepted_publication() {
14058    use std::sync::mpsc;
14059
14060    CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14061      .lock()
14062      .unwrap()
14063      .clear();
14064    let metrics = Arc::new(RuntimeMetrics::default());
14065    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14066      Arc::clone(&metrics),
14067      reject_current_thread_replacement_publication_once,
14068    ));
14069    let scheduler = Arc::clone(&executor);
14070    let (runnable, task) = async_task::spawn(async { 13usize }, move |runnable| {
14071      scheduler.schedule(runnable)
14072    });
14073    executor.schedule(runnable);
14074    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14075    assert_ne!(failed_dispatch, 0);
14076
14077    let (returned_tx, returned_rx) = mpsc::channel();
14078    let (release_tx, release_rx) = mpsc::channel();
14079    let cancelling_executor = Arc::clone(&executor);
14080    let cancellation = std::thread::spawn(move || {
14081      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14082        *slot.borrow_mut() = Some(Box::new(move || {
14083          returned_tx.send(()).unwrap();
14084          release_rx.recv().unwrap();
14085        }));
14086      });
14087      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
14088    });
14089    returned_rx
14090      .recv_timeout(Duration::from_secs(2))
14091      .expect("the unavailable replacement publication must reach its completion barrier");
14092
14093    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14094    executor.request_drain_if_queued();
14095    let attempts_while_unavailable_is_paused =
14096      CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14097        .lock()
14098        .unwrap()
14099        .len();
14100
14101    release_tx.send(()).unwrap();
14102    cancellation.join().unwrap();
14103
14104    let dispatches = CURRENT_THREAD_REPLACEMENT_PUBLICATION_RACE_DISPATCHES
14105      .lock()
14106      .unwrap()
14107      .clone();
14108    assert_ne!(replacement_dispatch, 0);
14109    assert_ne!(replacement_dispatch, failed_dispatch);
14110    assert_eq!(
14111      attempts_while_unavailable_is_paused, 2,
14112      "a racing host request must join the in-flight replacement publication"
14113    );
14114    assert_eq!(
14115      dispatches,
14116      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14117      "republication must reuse the sole replacement capability instead of allocating another"
14118    );
14119    assert_eq!(
14120      executor.dispatch_pending.load(Ordering::Acquire),
14121      replacement_dispatch
14122    );
14123    assert_eq!(
14124      executor.recovery_dispatch.load(Ordering::Acquire),
14125      replacement_dispatch
14126    );
14127    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14128
14129    executor.drive_host_turn_for_dispatch(replacement_dispatch);
14130    assert_eq!(futures::executor::block_on(task.fallible()), Some(13));
14131    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14132    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14133  }
14134
14135  #[cfg(not(target_family = "wasm"))]
14136  #[test]
14137  fn current_thread_replacement_failed_retries_armed_republish_before_cancellation() {
14138    use std::sync::mpsc;
14139
14140    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14141      .lock()
14142      .unwrap()
14143      .clear();
14144    {
14145      let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14146        .state
14147        .lock()
14148        .unwrap_or_else(std::sync::PoisonError::into_inner);
14149      assert!(state.entries.is_empty());
14150      assert!(state.dispatches.is_empty());
14151    }
14152    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14153    let initial_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14154      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14155    let metrics = Arc::new(RuntimeMetrics::default());
14156    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14157      Arc::clone(&metrics),
14158      fail_current_thread_replacement_publication_once,
14159      &CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS,
14160    ));
14161    let scheduler = Arc::clone(&executor);
14162    let (runnable, task) = async_task::spawn(async { 29usize }, move |runnable| {
14163      scheduler.schedule(runnable)
14164    });
14165    executor.schedule(runnable);
14166    let failed_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14167    assert_ne!(failed_dispatch, 0);
14168    let initial_delivery = initial.deliveries()[0];
14169    let failed_completion = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14170      .finish_delivery(initial_delivery, true);
14171    let [failed_dispatch_failure] = failed_completion.failed_dispatches.as_slice() else {
14172      panic!("the initial accepted delivery must report one exact failure");
14173    };
14174    assert_eq!(failed_dispatch_failure.dispatch, failed_dispatch);
14175    let failed_dispatch_failure = *failed_dispatch_failure;
14176    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(initial_registration);
14177    let failed_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14178      .register(Arc::new(FailDuringReplacementRepublishPublication));
14179
14180    let (returned_tx, returned_rx) = mpsc::channel();
14181    let (release_tx, release_rx) = mpsc::channel();
14182    let cancelling_executor = Arc::clone(&executor);
14183    let cancellation = std::thread::spawn(move || {
14184      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14185        *slot.borrow_mut() = Some(Box::new(move || {
14186          returned_tx.send(()).unwrap();
14187          release_rx.recv().unwrap();
14188        }));
14189      });
14190      cancelling_executor.cancel_host_dispatch(failed_dispatch_failure);
14191    });
14192    returned_rx
14193      .recv_timeout(Duration::from_secs(2))
14194      .expect("the failed replacement publication must reach its completion barrier");
14195
14196    let replacement_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14197    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14198    let healthy_registration = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14199      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14200    executor.request_drain_if_queued();
14201    let publication_state = {
14202      let scheduler = executor
14203        .scheduler_idle_lock
14204        .lock()
14205        .unwrap_or_else(std::sync::PoisonError::into_inner);
14206      (
14207        scheduler.active_dispatch_calls,
14208        scheduler.dispatch_publications.len(),
14209        scheduler.dispatch_publications[0].dispatch,
14210        scheduler.dispatch_publications[0].republish_requested,
14211      )
14212    };
14213    release_tx.send(()).unwrap();
14214    cancellation.join().unwrap();
14215
14216    assert_ne!(replacement_dispatch, 0);
14217    assert_ne!(replacement_dispatch, failed_dispatch);
14218    assert_eq!(publication_state, (1, 1, replacement_dispatch, true));
14219    assert_eq!(
14220      *CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_DISPATCHES
14221        .lock()
14222        .unwrap(),
14223      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14224      "the newly armed host request must receive the exact replacement before cancellation"
14225    );
14226    assert_eq!(
14227      executor.dispatch_pending.load(Ordering::Acquire),
14228      replacement_dispatch
14229    );
14230    assert_eq!(
14231      executor.recovery_dispatch.load(Ordering::Acquire),
14232      replacement_dispatch
14233    );
14234    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 1);
14235
14236    let delivery = healthy.deliveries()[0];
14237    assert_eq!(delivery.dispatch, replacement_dispatch);
14238    assert_eq!(
14239      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14240        .claim_delivery(delivery.capability()),
14241      Some(replacement_dispatch)
14242    );
14243    let mut host_turn = executor
14244      .try_admit_host_turn(replacement_dispatch)
14245      .expect("the rearmed replacement delivery must be admitted");
14246    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14247    host_turn.drive();
14248    assert!(
14249      CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14250        .finish_delivery(delivery, false)
14251        .failed_dispatches
14252        .is_empty()
14253    );
14254    assert_eq!(futures::executor::block_on(task.fallible()), Some(29));
14255    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14256    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14257
14258    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(failed_registration);
14259    CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.unregister(healthy_registration);
14260    let state = CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS
14261      .state
14262      .lock()
14263      .unwrap_or_else(std::sync::PoisonError::into_inner);
14264    assert!(state.entries.is_empty());
14265    assert!(state.dispatches.is_empty());
14266  }
14267
14268  #[cfg(not(target_family = "wasm"))]
14269  #[test]
14270  fn current_thread_delayed_failure_cannot_cancel_successfully_rearmed_epoch() {
14271    use std::sync::mpsc;
14272
14273    {
14274      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14275        .state
14276        .lock()
14277        .unwrap_or_else(std::sync::PoisonError::into_inner);
14278      assert!(state.entries.is_empty());
14279      assert!(state.dispatches.is_empty());
14280    }
14281    let initial = Arc::new(ManualCurrentThreadTaskDriver::default());
14282    let initial_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14283      .register(Arc::clone(&initial) as Arc<dyn CurrentThreadTaskDriver>);
14284    let metrics = Arc::new(RuntimeMetrics::default());
14285    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14286      Arc::clone(&metrics),
14287      dispatch_stale_failure_current_thread_tasks,
14288      &CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS,
14289    ));
14290    let scheduler = Arc::clone(&executor);
14291    let (runnable, task) = async_task::spawn(async { 43usize }, move |runnable| {
14292      scheduler.schedule(runnable)
14293    });
14294    executor.schedule(runnable);
14295
14296    let dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14297    let initial_delivery = initial.deliveries()[0];
14298    let delayed_completion =
14299      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.finish_delivery(initial_delivery, true);
14300    let [delayed_failure] = delayed_completion.failed_dispatches.as_slice() else {
14301      panic!("the initial accepted delivery must report one exact failure");
14302    };
14303    assert_eq!(
14304      *delayed_failure,
14305      current_thread_dispatch_failure(dispatch, 0)
14306    );
14307    let delayed_failure = *delayed_failure;
14308
14309    let (returned_tx, returned_rx) = mpsc::channel();
14310    let (release_tx, release_rx) = mpsc::channel();
14311    let publishing_executor = Arc::clone(&executor);
14312    let publication = std::thread::spawn(move || {
14313      CURRENT_THREAD_DISPATCH_RETURNED_TEST_HOOK.with(|slot| {
14314        *slot.borrow_mut() = Some(Box::new(move || {
14315          returned_tx.send(()).unwrap();
14316          release_rx.recv().unwrap();
14317        }));
14318      });
14319      publishing_executor.request_drain_if_queued();
14320    });
14321    returned_rx
14322      .recv_timeout(Duration::from_secs(2))
14323      .expect("the old failed epoch must pause before executor recovery");
14324
14325    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14326    let healthy_registration = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14327      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14328    executor.request_drain_if_queued();
14329    release_tx.send(()).unwrap();
14330    publication.join().unwrap();
14331
14332    let healthy_delivery = healthy.deliveries()[0];
14333    let delayed_initial_delivery = initial.deliveries()[1];
14334    {
14335      let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14336        .state
14337        .lock()
14338        .unwrap_or_else(std::sync::PoisonError::into_inner);
14339      let dispatch_state = state
14340        .dispatches
14341        .get(&dispatch)
14342        .expect("the rearmed dispatch must remain registered");
14343      assert_eq!(dispatch_state.epoch, 1);
14344      assert!(!dispatch_state.is_cancelling());
14345    }
14346
14347    executor.cancel_host_dispatch(delayed_failure);
14348
14349    assert_eq!(
14350      executor.dispatch_pending.load(Ordering::Acquire),
14351      dispatch,
14352      "the delayed old-epoch failure must not consume the rearmed executor capability"
14353    );
14354    assert_eq!(
14355      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14356        .state
14357        .lock()
14358        .unwrap_or_else(std::sync::PoisonError::into_inner)
14359        .dispatches
14360        .get(&dispatch)
14361        .map(|state| (state.epoch, state.phase)),
14362      Some((1, CurrentThreadDispatchPhase::Pending)),
14363      "the delayed old-epoch cancellation must not remove the accepted rearm"
14364    );
14365
14366    assert_eq!(
14367      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.claim_delivery(healthy_delivery.capability()),
14368      Some(dispatch)
14369    );
14370    let mut host_turn = executor
14371      .try_admit_host_turn(dispatch)
14372      .expect("the current-epoch delivery must be admitted");
14373    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
14374    host_turn.drive();
14375    assert!(
14376      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14377        .finish_delivery(healthy_delivery, false)
14378        .failed_dispatches
14379        .is_empty()
14380    );
14381    assert!(
14382      CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14383        .finish_delivery(delayed_initial_delivery, false)
14384        .failed_dispatches
14385        .is_empty()
14386    );
14387    assert_eq!(futures::executor::block_on(task.fallible()), Some(43));
14388    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14389
14390    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(initial_registration);
14391    CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS.unregister(healthy_registration);
14392    let state = CURRENT_THREAD_STALE_FAILURE_TASK_DRIVERS
14393      .state
14394      .lock()
14395      .unwrap_or_else(std::sync::PoisonError::into_inner);
14396    assert!(state.entries.is_empty());
14397    assert!(state.dispatches.is_empty());
14398  }
14399
14400  #[cfg(not(target_family = "wasm"))]
14401  #[test]
14402  fn current_thread_failed_replacement_closes_ownerless_registration_gap() {
14403    use std::sync::mpsc;
14404
14405    {
14406      let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14407        .state
14408        .lock()
14409        .unwrap_or_else(std::sync::PoisonError::into_inner);
14410      assert!(state.entries.is_empty());
14411      assert!(state.dispatches.is_empty());
14412    }
14413    let failed_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14414      .register(Arc::new(FailDuringLateRegistrationPublication));
14415
14416    let metrics = Arc::new(RuntimeMetrics::default());
14417    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14418      Arc::clone(&metrics),
14419      dispatch_late_registration_current_thread_tasks,
14420      &CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS,
14421    ));
14422    let scheduler = Arc::clone(&executor);
14423    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14424      scheduler.schedule(runnable)
14425    });
14426    metrics.runnable_scheduled();
14427    executor
14428      .queue
14429      .lock()
14430      .unwrap_or_else(std::sync::PoisonError::into_inner)
14431      .runnables
14432      .push_back(runnable);
14433    let replacement_dispatch = next_current_thread_dispatch_id();
14434    executor
14435      .recovery_dispatch
14436      .store(replacement_dispatch, Ordering::Release);
14437    executor
14438      .dispatch_pending
14439      .store(replacement_dispatch, Ordering::Release);
14440
14441    let (owner_retired_tx, owner_retired_rx) = mpsc::channel();
14442    let (request_blocked_tx, request_blocked_rx) = mpsc::channel();
14443    let publishing_executor = Arc::clone(&executor);
14444    let publication = std::thread::spawn(move || {
14445      CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
14446        *slot.borrow_mut() = Some(Box::new(move || {
14447          owner_retired_tx.send(()).unwrap();
14448          request_blocked_rx
14449            .recv_timeout(Duration::from_secs(2))
14450            .expect("the binding-like drain request must wait for the failure transition");
14451        }));
14452      });
14453      publishing_executor.request_drain_if_queued();
14454    });
14455    owner_retired_rx
14456      .recv_timeout(Duration::from_secs(2))
14457      .expect("the failed replacement publication must retire its owner");
14458
14459    // Match the binding sequence: publish the host registration first, then
14460    // request service for work that may have accumulated before registration.
14461    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14462    let healthy_registration = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14463      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14464    let requesting_executor = Arc::clone(&executor);
14465    let request = std::thread::spawn(move || {
14466      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
14467        *slot.borrow_mut() = Some(Box::new(move || {
14468          request_blocked_tx.send(()).unwrap();
14469        }));
14470      });
14471      requesting_executor.request_drain_if_queued();
14472    });
14473
14474    publication.join().unwrap();
14475    request.join().unwrap();
14476
14477    assert!(
14478      healthy.deliveries().is_empty(),
14479      "publication admission must not recreate the closed replacement between owner retirement and terminal cancellation"
14480    );
14481    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14482    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14483    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14484    assert_eq!(futures::executor::block_on(task.fallible()), None);
14485    {
14486      let scheduler = executor
14487        .scheduler_idle_lock
14488        .lock()
14489        .unwrap_or_else(std::sync::PoisonError::into_inner);
14490      assert_eq!(scheduler.active_dispatch_calls, 0);
14491      assert!(scheduler.dispatch_publications.is_empty());
14492    }
14493
14494    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(failed_registration);
14495
14496    let scheduler = Arc::clone(&executor);
14497    let (later_runnable, later_task) = async_task::spawn(async { 31usize }, move |runnable| {
14498      scheduler.schedule(runnable)
14499    });
14500    executor.schedule(later_runnable);
14501    let later_dispatch = executor.dispatch_pending.load(Ordering::Acquire);
14502    let later_delivery = healthy.deliveries()[0];
14503    assert_eq!(
14504      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.claim_delivery(later_delivery.capability()),
14505      Some(later_dispatch)
14506    );
14507    let mut host_turn = executor
14508      .try_admit_host_turn(later_dispatch)
14509      .expect("the later exact delivery must be admitted");
14510    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.mark_serviced(later_dispatch);
14511    host_turn.drive();
14512    assert!(
14513      CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14514        .finish_delivery(later_delivery, false)
14515        .failed_dispatches
14516        .is_empty()
14517    );
14518    assert_eq!(futures::executor::block_on(later_task.fallible()), Some(31));
14519    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14520
14521    CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.unregister(healthy_registration);
14522    let state = CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS
14523      .state
14524      .lock()
14525      .unwrap_or_else(std::sync::PoisonError::into_inner);
14526    assert!(state.entries.is_empty());
14527    assert!(state.dispatches.is_empty());
14528  }
14529
14530  #[cfg(not(target_family = "wasm"))]
14531  #[test]
14532  fn current_thread_nested_replacement_unwind_finalizes_old_dispatch_and_retries_same_replacement()
14533  {
14534    CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
14535      .lock()
14536      .unwrap()
14537      .clear();
14538    {
14539      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14540        .state
14541        .lock()
14542        .unwrap_or_else(std::sync::PoisonError::into_inner);
14543      assert!(state.entries.is_empty());
14544      assert!(state.dispatches.is_empty());
14545    }
14546    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14547      .next_delivery
14548      .store(0, Ordering::Relaxed);
14549    let failed_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14550      .register(Arc::new(FailDuringNestedUnwindPublication));
14551
14552    let metrics = Arc::new(RuntimeMetrics::default());
14553    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14554      Arc::clone(&metrics),
14555      dispatch_nested_unwind_current_thread_tasks,
14556      &CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS,
14557    ));
14558    let scheduler = Arc::clone(&executor);
14559    let (runnable, task) = async_task::spawn(async { 37usize }, move |runnable| {
14560      scheduler.schedule(runnable)
14561    });
14562
14563    CURRENT_THREAD_FAILED_PUBLICATION_RETIRED_TEST_HOOK.with(|slot| {
14564      *slot.borrow_mut() = Some(Box::new(|| {
14565        CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14566          .next_delivery
14567          .store(u64::MAX, Ordering::Relaxed);
14568      }));
14569    });
14570    let publication = catch_unwind(AssertUnwindSafe(|| executor.schedule(runnable)));
14571    assert!(
14572      publication.is_err(),
14573      "replacement delivery-id exhaustion must unwind through the outer failed publication"
14574    );
14575
14576    let dispatches = CURRENT_THREAD_NESTED_UNWIND_DISPATCHES
14577      .lock()
14578      .unwrap()
14579      .clone();
14580    assert_eq!(dispatches.len(), 2);
14581    let failed_dispatch = dispatches[0];
14582    let replacement_dispatch = dispatches[1];
14583    assert_ne!(failed_dispatch, replacement_dispatch);
14584    assert_eq!(
14585      executor.dispatch_pending.load(Ordering::Acquire),
14586      replacement_dispatch
14587    );
14588    assert_eq!(
14589      executor.recovery_dispatch.load(Ordering::Acquire),
14590      replacement_dispatch
14591    );
14592    {
14593      let scheduler = executor
14594        .scheduler_idle_lock
14595        .lock()
14596        .unwrap_or_else(std::sync::PoisonError::into_inner);
14597      assert_eq!(scheduler.active_dispatch_calls, 0);
14598      assert!(scheduler.dispatch_publications.is_empty());
14599    }
14600    {
14601      let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14602        .state
14603        .lock()
14604        .unwrap_or_else(std::sync::PoisonError::into_inner);
14605      assert!(
14606        !state.dispatches.contains_key(&failed_dispatch),
14607        "outer unwind cleanup must retire the exact old cancelling dispatch"
14608      );
14609      assert!(
14610        !state.dispatches.contains_key(&replacement_dispatch),
14611        "the replacement publication guard must release its aborted registry state"
14612      );
14613    }
14614
14615    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14616      .next_delivery
14617      .store(1, Ordering::Relaxed);
14618    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(failed_registration);
14619    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
14620    let healthy_registration = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14621      .register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
14622
14623    executor.request_drain_if_queued();
14624
14625    assert_eq!(
14626      *CURRENT_THREAD_NESTED_UNWIND_DISPATCHES.lock().unwrap(),
14627      vec![failed_dispatch, replacement_dispatch, replacement_dispatch],
14628      "retry must reuse the replacement reserved before the nested unwind"
14629    );
14630    let delivery = healthy.deliveries()[0];
14631    assert_eq!(delivery.dispatch, replacement_dispatch);
14632    assert_eq!(
14633      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.claim_delivery(delivery.capability()),
14634      Some(replacement_dispatch)
14635    );
14636    let mut host_turn = executor
14637      .try_admit_host_turn(replacement_dispatch)
14638      .expect("the retried replacement delivery must be admitted");
14639    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.mark_serviced(replacement_dispatch);
14640    host_turn.drive();
14641    assert!(
14642      CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14643        .finish_delivery(delivery, false)
14644        .failed_dispatches
14645        .is_empty()
14646    );
14647    assert_eq!(futures::executor::block_on(task.fallible()), Some(37));
14648    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14649
14650    CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.unregister(healthy_registration);
14651    let state = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS
14652      .state
14653      .lock()
14654      .unwrap_or_else(std::sync::PoisonError::into_inner);
14655    assert!(state.entries.is_empty());
14656    assert!(state.dispatches.is_empty());
14657  }
14658
14659  #[test]
14660  fn current_thread_cancelled_host_dispatch_recovers_without_a_later_wake() {
14661    reset_current_thread_recovery_host_dispatches();
14662    let metrics = Arc::new(RuntimeMetrics::default());
14663    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14664      Arc::clone(&metrics),
14665      count_current_thread_recovery_host_dispatch,
14666    ));
14667
14668    let scheduler = Arc::clone(&executor);
14669    let (runnable, task) = async_task::spawn(async { 1usize }, move |runnable| {
14670      scheduler.schedule(runnable)
14671    });
14672    executor.schedule(runnable);
14673    assert_eq!(current_thread_recovery_host_dispatches(), 1);
14674    let failed_dispatch = current_thread_dispatch(&executor);
14675
14676    // Model the host scheduler throwing after Rust accepted the callback but
14677    // before JavaScript could queue a fresh turn.
14678    executor.cancel_host_dispatch_for_test(failed_dispatch);
14679    assert_eq!(
14680      current_thread_recovery_host_dispatches(),
14681      2,
14682      "cancellation must request one replacement without another submission"
14683    );
14684    let replacement_dispatch = current_thread_dispatch(&executor);
14685    assert_ne!(replacement_dispatch, failed_dispatch);
14686    assert_eq!(
14687      executor.recovery_dispatch.load(Ordering::Acquire),
14688      replacement_dispatch
14689    );
14690
14691    executor.drive_host_turn_for_dispatch(replacement_dispatch);
14692    assert_eq!(futures::executor::block_on(task), 1);
14693    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14694    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14695  }
14696
14697  #[test]
14698  fn current_thread_second_host_dispatch_failure_cancels_work_and_reopens() {
14699    reset_current_thread_recovery_host_dispatches();
14700    let metrics = Arc::new(RuntimeMetrics::default());
14701    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14702      Arc::clone(&metrics),
14703      count_current_thread_recovery_host_dispatch,
14704    ));
14705    let backend =
14706      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14707
14708    let registration = backend
14709      .work
14710      .try_register_async()
14711      .expect("the backend must accept work");
14712    let task = spawn_registered(
14713      &backend,
14714      Arc::clone(&metrics),
14715      std::future::pending::<()>(),
14716      registration,
14717    );
14718    let failed_dispatch = current_thread_dispatch(&executor);
14719    executor.cancel_host_dispatch_for_test(failed_dispatch);
14720    let replacement_dispatch = current_thread_dispatch(&executor);
14721
14722    executor.cancel_host_dispatch_for_test(replacement_dispatch);
14723
14724    assert_eq!(
14725      current_thread_recovery_host_dispatches(),
14726      2,
14727      "a second scheduler failure must not create an unbounded retry loop"
14728    );
14729    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
14730    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
14731    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14732    assert_eq!(
14733      futures::executor::block_on(task).unwrap_err().to_string(),
14734      "async runtime stopped before the task completed",
14735      "terminal host failure must settle the original operation as cancelled"
14736    );
14737    assert_eq!(backend.work.state.lock().unwrap().active, 0);
14738
14739    let later_registration = backend
14740      .work
14741      .try_register_async()
14742      .expect("terminal cancellation must reopen acceptance");
14743    let later_task = spawn_registered(
14744      &backend,
14745      Arc::clone(&metrics),
14746      async { 2usize },
14747      later_registration,
14748    );
14749    assert_eq!(
14750      current_thread_recovery_host_dispatches(),
14751      3,
14752      "terminal cancellation must not permanently close the executor"
14753    );
14754    executor.drive_host_turn();
14755    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
14756  }
14757
14758  /// Regression test for the hazard shape rolldown hit when a `worker_threads`
14759  /// Worker running watch mode is torn down on the CurrentThread flavor: the
14760  /// cleanup-hook dispatch failure cancels the queued coordinator runnable,
14761  /// whose JoinHandle is wrapped in `futures::Shared`. `Shared`'s notifier
14762  /// wakes its awaiters while holding its `wakers` mutex; the parked awaiter's
14763  /// schedule is rejected by the cancellation sweep, and dropping that
14764  /// runnable inline destroys the awaiter's `Shared` clone, whose destructor
14765  /// re-locks `wakers` on the same thread.
14766  #[cfg(not(target_family = "wasm"))]
14767  #[test]
14768  fn current_thread_failed_dispatch_cancellation_does_not_drop_rejected_runnable_inline() {
14769    use std::{sync::mpsc, time::Duration};
14770
14771    let (done_tx, done_rx) = mpsc::channel();
14772    std::thread::spawn(move || {
14773      let metrics = Arc::new(RuntimeMetrics::default());
14774      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14775        Arc::clone(&metrics),
14776        accept_current_thread_host_dispatch,
14777      ));
14778
14779      // Task A: its JoinHandle is shared, exactly like a watcher close handle.
14780      let scheduler_a = Arc::clone(&executor);
14781      let (runnable_a, task_a) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
14782        scheduler_a.schedule(runnable)
14783      });
14784      let shared = async move { task_a.fallible().await }.boxed().shared();
14785      let shared_for_b = shared.clone();
14786
14787      // Task B: awaits a clone of that Shared and parks after one poll.
14788      let scheduler_b = Arc::clone(&executor);
14789      let (runnable_b, task_b) = async_task::spawn(
14790        async move {
14791          shared_for_b.await;
14792        },
14793        move |runnable| scheduler_b.schedule(runnable),
14794      );
14795      executor.schedule(runnable_b);
14796      executor.drive_host_turn();
14797      assert_eq!(
14798        metrics.queued_runnables.load(Ordering::Relaxed),
14799        0,
14800        "B must be parked and unscheduled after its first poll"
14801      );
14802
14803      // A's runnable is queued (woken, not yet polled) when the host dispatch
14804      // fails twice, so the terminal cancellation sweep drops it.
14805      executor.schedule(runnable_a);
14806      let failed_dispatch = current_thread_dispatch(&executor);
14807      executor.cancel_host_dispatch_for_test(failed_dispatch);
14808      let replacement_dispatch = current_thread_dispatch(&executor);
14809      executor.cancel_host_dispatch_for_test(replacement_dispatch);
14810
14811      assert!(
14812        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
14813        "the cancellation sweep must reopen the executor"
14814      );
14815      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14816      assert!(
14817        futures::executor::block_on(task_b.fallible()).is_none(),
14818        "B's rejected wake must still cancel B"
14819      );
14820      drop(shared);
14821      done_tx.send(()).unwrap();
14822    });
14823    done_rx
14824      .recv_timeout(Duration::from_secs(5))
14825      .expect("teardown deadlocked: inline runnable drop re-entered a waker lock");
14826  }
14827
14828  /// Regression test for the closed-queue sibling of the hazard above. A wake
14829  /// can be in flight on any thread when shutdown starts: `async-task` marks
14830  /// the task `SCHEDULED` before it invokes the schedule closure, so the abort
14831  /// that `close_and_abort` issues for that task coalesces with the in-flight
14832  /// wake and returns, `begin_shutdown` closes the queue without waiting for
14833  /// the closure to land, and the resumed closure is rejected by the closed
14834  /// queue. That rejection must not drop the runnable inline either: the
14835  /// waker here is still inside `futures::Shared`'s notifier, holding its
14836  /// `wakers` mutex, and the rejected future owns a clone of the same `Shared`.
14837  #[cfg(not(target_family = "wasm"))]
14838  #[test]
14839  fn current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
14840    use std::{
14841      sync::{Barrier, mpsc},
14842      time::Duration,
14843    };
14844
14845    let (done_tx, done_rx) = mpsc::channel();
14846    std::thread::spawn(move || {
14847      let metrics = Arc::new(RuntimeMetrics::default());
14848      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
14849        Arc::clone(&metrics),
14850        accept_current_thread_host_dispatch,
14851      ));
14852      let backend =
14853        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
14854      let generation = backend.generation();
14855
14856      // Task A: registered like production work; its JoinHandle is shared.
14857      let (abort_a, guard_a) = backend
14858        .work
14859        .try_register_async()
14860        .expect("the backend must accept work");
14861      let scheduler_a = Arc::clone(&executor);
14862      let (runnable_a, task_a) = async_task::spawn(
14863        RegisteredTaskFuture::new(
14864          Abortable::new(std::future::pending::<()>(), abort_a),
14865          guard_a,
14866          generation,
14867        ),
14868        move |runnable| scheduler_a.schedule(runnable),
14869      );
14870      let shared = async move { task_a.fallible().await.is_some() }
14871        .boxed()
14872        .shared();
14873      let shared_for_b = shared.clone();
14874
14875      // Task B: registered and abortable too, awaiting a clone of that Shared.
14876      // Its schedule closure pauses before it reaches the executor, exactly
14877      // where a waker thread may be descheduled while shutdown runs.
14878      let (abort_b, guard_b) = backend
14879        .work
14880        .try_register_async()
14881        .expect("the backend must accept work");
14882      let wake_in_flight = Arc::new(Barrier::new(2));
14883      let wake_release = Arc::new(Barrier::new(2));
14884      let scheduler_b = Arc::clone(&executor);
14885      let (runnable_b, task_b) = async_task::spawn(
14886        RegisteredTaskFuture::new(
14887          Abortable::new(
14888            async move {
14889              shared_for_b.await;
14890            },
14891            abort_b,
14892          ),
14893          guard_b,
14894          generation,
14895        ),
14896        {
14897          let wake_in_flight = Arc::clone(&wake_in_flight);
14898          let wake_release = Arc::clone(&wake_release);
14899          move |runnable| {
14900            wake_in_flight.wait();
14901            wake_release.wait();
14902            scheduler_b.schedule(runnable);
14903          }
14904        },
14905      );
14906      executor.schedule(runnable_b);
14907      executor.drive_host_turn();
14908      assert_eq!(
14909        metrics.queued_runnables.load(Ordering::Relaxed),
14910        0,
14911        "B must be parked and unscheduled after its first poll"
14912      );
14913
14914      // Cancel A on another thread: closing A notifies its awaiter, and the
14915      // Shared notifier wakes B while holding `wakers`. B's schedule closure
14916      // then parks at the barrier with that lock still held.
14917      let waker_thread = std::thread::spawn(move || drop(runnable_a));
14918      wake_in_flight.wait();
14919
14920      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
14921      // the abort wake is a no-op; the queue then closes.
14922      backend.begin_shutdown();
14923      wake_release.wait();
14924      backend.wait_until_idle();
14925
14926      waker_thread
14927        .join()
14928        .expect("the waker thread must not panic");
14929      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
14930      assert!(
14931        futures::executor::block_on(task_b.fallible()).is_none(),
14932        "B's rejected wake must still cancel B"
14933      );
14934      assert_eq!(backend.work.state.lock().unwrap().active, 0);
14935      drop(shared);
14936      done_tx.send(()).unwrap();
14937    });
14938    done_rx
14939      .recv_timeout(Duration::from_secs(5))
14940      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
14941  }
14942
14943  /// Regression test for the terminal-failure sibling of the two hazards
14944  /// above. Task B awaits a `futures::Shared` and parks. Completing the Shared
14945  /// wakes B from inside its notifier, which holds `wakers`, and `schedule`
14946  /// publishes a host dispatch on that same stack. The host fails the
14947  /// delivery before `dispatch` returns, and fails the one replacement the
14948  /// same way, so the terminal failure transition runs while the waker's
14949  /// stack is still under `wakers`. Nothing may be dropped there: B's future
14950  /// owns a clone of the same `Shared`, whose destructor re-locks `wakers`.
14951  /// The transition moves B aside and reopens; the next host turn cancels B.
14952  #[cfg(not(target_family = "wasm"))]
14953  #[test]
14954  fn current_thread_synchronous_dispatch_failure_does_not_sweep_on_the_waker_stack() {
14955    use std::{sync::mpsc, time::Duration};
14956
14957    let (done_tx, done_rx) = mpsc::channel();
14958    std::thread::spawn(move || {
14959      {
14960        let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14961          .state
14962          .lock()
14963          .unwrap_or_else(std::sync::PoisonError::into_inner);
14964        assert!(state.entries.is_empty());
14965        assert!(state.dispatches.is_empty());
14966      }
14967      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(
14968        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
14969      ));
14970      let registration = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14971        .register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
14972      let metrics = Arc::new(RuntimeMetrics::default());
14973      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
14974        Arc::clone(&metrics),
14975        dispatch_waker_stack_failure_current_thread_tasks,
14976        &CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS,
14977      ));
14978
14979      // Task B awaits a clone of a Shared and parks after its first poll.
14980      let (tx, rx) = futures::channel::oneshot::channel::<()>();
14981      let shared = rx.map(|_| ()).boxed().shared();
14982      let shared_for_b = shared.clone();
14983      let scheduler_b = Arc::clone(&executor);
14984      let (runnable_b, task_b) = async_task::spawn(
14985        async move {
14986          shared_for_b.await;
14987        },
14988        move |runnable| scheduler_b.schedule(runnable),
14989      );
14990      executor.schedule(runnable_b);
14991
14992      // Service the accepted delivery the way `drive_current_thread_tasks`
14993      // and `acknowledge_current_thread_task_delivery` do.
14994      let delivery = driver.deliveries()[0];
14995      let dispatch = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
14996        .claim_delivery(delivery.capability())
14997        .expect("the accepted delivery must be claimable");
14998      let mut host_turn = executor
14999        .try_admit_host_turn(dispatch)
15000        .expect("the claimed delivery must be admitted");
15001      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.mark_serviced(dispatch);
15002      host_turn.drive();
15003      drop(host_turn);
15004      let completion =
15005        CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.finish_delivery(delivery, false);
15006      assert!(completion.failed_dispatches.is_empty());
15007      assert!(!completion.redispatch);
15008      assert_eq!(
15009        metrics.queued_runnables.load(Ordering::Relaxed),
15010        0,
15011        "B must be parked and unscheduled after its first poll"
15012      );
15013
15014      // From now on every accepted delivery fails before `dispatch` returns.
15015      driver.fail.store(true, Ordering::SeqCst);
15016      // Completing the Shared wakes B from inside its notifier, under
15017      // `wakers`; B's schedule publishes the dispatch and its replacement on
15018      // this stack, both fail, and the terminal transition runs here.
15019      tx.send(()).expect("B must still hold its Shared clone");
15020      assert_eq!(
15021        driver.deliveries().len(),
15022        // The initial service, the wake's dispatch and its one replacement,
15023        // then the retirement turns and the final offer -- this host refuses
15024        // every one of them.
15025        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1,
15026        "the wake must publish the dispatch, its one replacement, and the \
15027         retirement turns the terminal transition owes itself"
15028      );
15029      assert!(
15030        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15031        "the terminal failure must reopen the executor before the waker returns"
15032      );
15033      assert!(
15034        executor.has_rejected_work(),
15035        "B must be moved aside, not dropped on the waker's stack"
15036      );
15037      assert_eq!(
15038        metrics.queued_runnables.load(Ordering::Relaxed),
15039        0,
15040        "the moved runnable carries its queued debit"
15041      );
15042      let mut task_b = task_b.fallible();
15043      assert!(
15044        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15045        "B must still be pending: nothing may be dropped on the waker's stack"
15046      );
15047
15048      // The next host turn (what `drive_current_thread_tasks` runs for the
15049      // delivery a later submission publishes) cancels B on its own stack.
15050      executor.drive_host_turn();
15051      assert!(!executor.has_rejected_work());
15052      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15053      assert!(
15054        poll_ready_within(&mut task_b, Duration::from_secs(5))
15055          .expect("the host turn must cancel B")
15056          .is_none(),
15057        "the terminal failure must still cancel B"
15058      );
15059      drop(shared);
15060
15061      CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS.unregister(registration);
15062      let state = CURRENT_THREAD_WAKER_STACK_FAILURE_TASK_DRIVERS
15063        .state
15064        .lock()
15065        .unwrap_or_else(std::sync::PoisonError::into_inner);
15066      assert!(state.entries.is_empty());
15067      assert!(state.dispatches.is_empty());
15068      done_tx.send(()).unwrap();
15069    });
15070    done_rx
15071      .recv_timeout(Duration::from_secs(5))
15072      .expect("terminal cancellation deadlocked: the sweep ran on the waker's stack");
15073  }
15074
15075  /// Poll `future` with a no-op waker until it is ready or `timeout` passes.
15076  /// The tests below drive the executor by hand, so readiness is produced on
15077  /// this thread and a wait can only fail by design, never by scheduling.
15078  #[cfg(not(target_family = "wasm"))]
15079  fn poll_ready_within<F: Future + Unpin>(
15080    future: &mut F,
15081    timeout: std::time::Duration,
15082  ) -> Option<F::Output> {
15083    let deadline = std::time::Instant::now() + timeout;
15084    let mut cx = Context::from_waker(futures::task::noop_waker_ref());
15085    loop {
15086      if let Poll::Ready(output) = Pin::new(&mut *future).poll(&mut cx) {
15087        return Some(output);
15088      }
15089      if std::time::Instant::now() >= deadline {
15090        return None;
15091      }
15092      std::thread::sleep(std::time::Duration::from_millis(1));
15093    }
15094  }
15095
15096  /// Liveness regression test for the transition above. After the waker-stack
15097  /// terminal failure the driver stays registered and becomes healthy again.
15098  /// Work submitted from then on is new work: its schedule must publish a
15099  /// fresh delivery, and the host turn that services it cancels the work that
15100  /// was queued when the failure happened (B) before it runs the new task.
15101  /// Nothing else prompts the executor: no drain request, no re-registration,
15102  /// no shutdown.
15103  #[cfg(not(target_family = "wasm"))]
15104  #[test]
15105  fn current_thread_submission_after_waker_stack_failure_is_delivered_to_a_healthy_host() {
15106    use std::{sync::mpsc, time::Duration};
15107
15108    const WAIT: Duration = Duration::from_secs(5);
15109    let (done_tx, done_rx) = mpsc::channel();
15110    std::thread::spawn(move || {
15111      let registry = &*CURRENT_THREAD_HEALTHY_AFTER_WAKER_STACK_FAILURE_TASK_DRIVERS;
15112      {
15113        let state = registry
15114          .state
15115          .lock()
15116          .unwrap_or_else(std::sync::PoisonError::into_inner);
15117        assert!(state.entries.is_empty());
15118        assert!(state.dispatches.is_empty());
15119      }
15120      let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
15121      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15122      let metrics = Arc::new(RuntimeMetrics::default());
15123      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15124        Arc::clone(&metrics),
15125        dispatch_healthy_after_waker_stack_failure_current_thread_tasks,
15126        registry,
15127      ));
15128
15129      // Service one accepted delivery the way the host does.
15130      let service = |index: usize| {
15131        let delivery = driver.deliveries()[index];
15132        let dispatch = registry
15133          .claim_delivery(delivery.capability())
15134          .expect("the accepted delivery must be claimable");
15135        let mut host_turn = executor
15136          .try_admit_host_turn(dispatch)
15137          .expect("the claimed delivery must be admitted");
15138        registry.mark_serviced(dispatch);
15139        host_turn.drive();
15140        drop(host_turn);
15141        let completion = registry.finish_delivery(delivery, false);
15142        assert!(completion.failed_dispatches.is_empty());
15143        assert!(!completion.redispatch);
15144      };
15145
15146      // Task B awaits a clone of a Shared and parks after its first poll.
15147      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15148      let shared = rx.map(|_| ()).boxed().shared();
15149      let shared_for_b = shared.clone();
15150      let scheduler_b = Arc::clone(&executor);
15151      let (runnable_b, task_b) = async_task::spawn(
15152        async move {
15153          shared_for_b.await;
15154        },
15155        move |runnable| scheduler_b.schedule(runnable),
15156      );
15157      executor.schedule(runnable_b);
15158      service(0);
15159      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15160
15161      // The waker-stack terminal failure: the wake's dispatch and its one
15162      // replacement both fail before `dispatch` returns.
15163      driver.fail.store(true, Ordering::SeqCst);
15164      tx.send(()).expect("B must still hold its Shared clone");
15165      // The dispatch, its one replacement, and the retirement turns the
15166      // transition publishes for itself -- which this still-failing host
15167      // loses too, down to the final offer.
15168      const AFTER_WAKE: usize =
15169        1 + 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15170      assert_eq!(driver.deliveries().len(), AFTER_WAKE);
15171      let mut task_b = task_b.fallible();
15172      assert!(
15173        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15174        "B must still be pending: nothing may be dropped on the waker's stack"
15175      );
15176
15177      // The host is healthy again and still registered. A later submission
15178      // must publish a delivery on its own.
15179      driver.fail.store(false, Ordering::SeqCst);
15180      let scheduler_later = Arc::clone(&executor);
15181      let (runnable_later, task_later) = async_task::spawn(async { 42usize }, move |runnable| {
15182        scheduler_later.schedule(runnable)
15183      });
15184      executor.schedule(runnable_later);
15185      assert_eq!(
15186        driver.deliveries().len(),
15187        AFTER_WAKE + 1,
15188        "a submission after the terminal failure must publish a fresh delivery"
15189      );
15190      assert!(
15191        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15192        "the executor must be open for the healthy host"
15193      );
15194
15195      // The host services that delivery: the pre-failure work is cancelled
15196      // and the new task runs.
15197      service(AFTER_WAKE);
15198      let mut task_later = task_later.fallible();
15199      assert_eq!(
15200        poll_ready_within(&mut task_later, WAIT).expect("the later task must have run"),
15201        Some(42),
15202        "the later task must complete on the delivered host turn"
15203      );
15204      assert!(
15205        poll_ready_within(&mut task_b, WAIT)
15206          .expect("B must have been cancelled by the host turn")
15207          .is_none(),
15208        "the terminal failure must still cancel the work queued when it happened"
15209      );
15210      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15211      drop(shared);
15212
15213      registry.unregister(registration);
15214      let state = registry
15215        .state
15216        .lock()
15217        .unwrap_or_else(std::sync::PoisonError::into_inner);
15218      assert!(state.entries.is_empty());
15219      assert!(state.dispatches.is_empty());
15220      done_tx.send(()).unwrap();
15221    });
15222    done_rx
15223      .recv_timeout(WAIT)
15224      .expect("the healthy host never got a delivery for the later submission");
15225  }
15226
15227  /// Regression test for the continuation a WAKE owes itself.
15228  ///
15229  /// `6d452174` gave an ordinary submission its own continuation, but a
15230  /// genuine wake still had none. B parks on a `futures::Shared`; completing
15231  /// the Shared wakes B from inside its notifier, which holds `wakers`, so
15232  /// `schedule` publishes on a stack where nothing may be destroyed. The host
15233  /// loses that publication and the one replacement before `dispatch`
15234  /// returns, and the terminal transition moves B into `queue.rejected` and
15235  /// reopens -- correctly dropping nothing.
15236  ///
15237  /// What was missing is what happens next. The transition consumed the
15238  /// executor's one outstanding host request and left the queue empty, so
15239  /// `has_serviceable_work` is false and nothing published again: during
15240  /// normal operation there is no generation idle waiter, no host turn
15241  /// follows the failed publications, and B's `JoinHandle` and
15242  /// `GenerationWorkGuard` stayed live forever. Nothing in this test supplies
15243  /// an unrelated entry -- no later submission, no drain request, no
15244  /// unregistration, no shutdown. The host only services the delivery the
15245  /// transition published for ITSELF.
15246  #[cfg(not(target_family = "wasm"))]
15247  #[test]
15248  fn current_thread_waker_stack_failure_publishes_its_own_retirement_turn() {
15249    use std::{sync::mpsc, time::Duration};
15250
15251    const WAIT: Duration = Duration::from_secs(5);
15252    let (done_tx, done_rx) = mpsc::channel();
15253    std::thread::spawn(move || {
15254      let registry = &*CURRENT_THREAD_RETIREMENT_TURN_TASK_DRIVERS;
15255      {
15256        let state = registry
15257          .state
15258          .lock()
15259          .unwrap_or_else(std::sync::PoisonError::into_inner);
15260        assert!(state.entries.is_empty());
15261        assert!(state.dispatches.is_empty());
15262      }
15263      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15264      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15265      let metrics = Arc::new(RuntimeMetrics::default());
15266      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15267        Arc::clone(&metrics),
15268        dispatch_retirement_turn_current_thread_tasks,
15269        registry,
15270      ));
15271
15272      // Service one accepted delivery the way the host does.
15273      let service = |index: usize| {
15274        let delivery = driver.deliveries()[index];
15275        let dispatch = registry
15276          .claim_delivery(delivery.capability())
15277          .expect("the accepted delivery must be claimable");
15278        let mut host_turn = executor
15279          .try_admit_host_turn(dispatch)
15280          .expect("the claimed delivery must be admitted");
15281        registry.mark_serviced(dispatch);
15282        host_turn.drive();
15283        drop(host_turn);
15284        let completion = registry.finish_delivery(delivery, false);
15285        assert!(completion.failed_dispatches.is_empty());
15286        assert!(!completion.redispatch);
15287      };
15288
15289      // Task B awaits a clone of a Shared and parks after its first poll.
15290      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15291      let shared = rx.map(|_| ()).boxed().shared();
15292      let shared_for_b = shared.clone();
15293      let scheduler_b = Arc::clone(&executor);
15294      let (runnable_b, task_b) = async_task::spawn(
15295        async move {
15296          shared_for_b.await;
15297        },
15298        move |runnable| scheduler_b.schedule(runnable),
15299      );
15300      executor.schedule(runnable_b);
15301      service(0);
15302      assert_eq!(
15303        metrics.queued_runnables.load(Ordering::Relaxed),
15304        0,
15305        "B must be parked and unscheduled after its first poll"
15306      );
15307
15308      // Exactly the wake's publication and its one replacement are lost; the
15309      // host is healthy again from the third delivery on.
15310      driver.fail_budget.store(2, Ordering::SeqCst);
15311      tx.send(()).expect("B must still hold its Shared clone");
15312
15313      assert_eq!(
15314        driver.deliveries().len(),
15315        4,
15316        "the wake must publish the dispatch, its one replacement, and the \
15317         retirement turn the terminal transition owes itself; without that \
15318         third publication no host turn ever follows and B stays pending \
15319         forever"
15320      );
15321      assert!(
15322        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15323        "the terminal failure must reopen the executor before the waker returns"
15324      );
15325      assert!(
15326        executor.has_rejected_work(),
15327        "B must be moved aside, not dropped on the waker's stack"
15328      );
15329      assert_eq!(
15330        metrics.queued_runnables.load(Ordering::Relaxed),
15331        0,
15332        "the moved runnable carries its queued debit"
15333      );
15334      let mut task_b = task_b.fallible();
15335      assert!(
15336        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15337        "B must still be pending: nothing may be dropped on the waker's stack"
15338      );
15339
15340      // The ONLY continuation: the host serves the turn the transition asked
15341      // for. No submission, no drain request, no unregistration, no shutdown.
15342      service(3);
15343      assert!(
15344        !executor.has_rejected_work(),
15345        "the retirement turn must cancel the moved work on its own stack"
15346      );
15347      assert!(
15348        poll_ready_within(&mut task_b, WAIT)
15349          .expect("the retirement turn must cancel B")
15350          .is_none(),
15351        "the terminal failure must still cancel the work queued when it happened"
15352      );
15353      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15354      drop(shared);
15355
15356      registry.unregister(registration);
15357      let state = registry
15358        .state
15359        .lock()
15360        .unwrap_or_else(std::sync::PoisonError::into_inner);
15361      assert!(state.entries.is_empty());
15362      assert!(state.dispatches.is_empty());
15363      done_tx.send(()).unwrap();
15364    });
15365    done_rx.recv_timeout(WAIT).expect(
15366      "a waker-stack terminal failure published no retirement turn: B's runnable stayed in \
15367       `queue.rejected` and its JoinHandle never settled",
15368    );
15369  }
15370
15371  /// Regression test for the continuation a NESTED terminal failure owes.
15372  ///
15373  /// `fc4d8e3b` gave a waker-stack terminal failure a continuation of its own:
15374  /// it publishes one fresh host dispatch, and the host turn that services it
15375  /// is the clean stack that retires the moved work. That publication calls
15376  /// the host driver again, so a one-shot bounds it -- and the one-shot used
15377  /// to DISCARD the nested request instead of handing it back:
15378  ///
15379  ///   thread W (waker)                     | thread T (producer)
15380  ///   -------------------------------------+---------------------------
15381  ///   Shared notifier wakes B -> schedule  |
15382  ///   publish D1 .. host fails             |
15383  ///   publish D2 (replacement) .. fails    |
15384  ///   terminal: B -> queue.rejected, reopen|
15385  ///   retirement scope ENTER, publish D3 --+--> schedule(C) -> queue.runnables
15386  ///                                        |    request_drain -> Coalesced
15387  ///   host fails D3, sees C serviceable    |
15388  ///   publish D4 (replacement) .. fails    |
15389  ///   terminal: C -> queue.rejected, reopen|
15390  ///   retirement scope already ACTIVE      |
15391  ///     -> returned, publishing NOTHING    |
15392  ///
15393  /// Four deliveries during the wake, every one of them lost, no host turn at
15394  /// all, and both B and C sat in `queue.rejected` with their `JoinHandle`s
15395  /// pending and their `GenerationWorkGuard`s live -- even though the host is
15396  /// healthy for its very next delivery. The nested request now goes back to
15397  /// the frame that owns the publication, which publishes a fifth delivery for
15398  /// it once D3 has unwound. Nothing in this test supplies an unrelated entry:
15399  /// no later submission, no drain request, no unregistration, no shutdown.
15400  #[cfg(not(target_family = "wasm"))]
15401  #[test]
15402  fn current_thread_nested_retirement_failure_keeps_its_continuation() {
15403    use std::{sync::mpsc, time::Duration};
15404
15405    const WAIT: Duration = Duration::from_secs(5);
15406    let (done_tx, done_rx) = mpsc::channel();
15407    std::thread::spawn(move || {
15408      let registry = &*CURRENT_THREAD_NESTED_RETIREMENT_TASK_DRIVERS;
15409      {
15410        let state = registry
15411          .state
15412          .lock()
15413          .unwrap_or_else(std::sync::PoisonError::into_inner);
15414        assert!(state.entries.is_empty());
15415        assert!(state.dispatches.is_empty());
15416      }
15417      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
15418      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15419      let metrics = Arc::new(RuntimeMetrics::default());
15420      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15421        Arc::clone(&metrics),
15422        dispatch_nested_retirement_current_thread_tasks,
15423        registry,
15424      ));
15425
15426      // Service one accepted delivery the way the host does.
15427      let service = |index: usize| {
15428        let delivery = driver.deliveries()[index];
15429        let dispatch = registry
15430          .claim_delivery(delivery.capability())
15431          .expect("the accepted delivery must be claimable");
15432        let mut host_turn = executor
15433          .try_admit_host_turn(dispatch)
15434          .expect("the claimed delivery must be admitted");
15435        registry.mark_serviced(dispatch);
15436        host_turn.drive();
15437        drop(host_turn);
15438        let completion = registry.finish_delivery(delivery, false);
15439        assert!(completion.failed_dispatches.is_empty());
15440        assert!(!completion.redispatch);
15441      };
15442
15443      // Task B awaits a clone of a Shared and parks after its first poll, so
15444      // its wake arrives from inside the notifier's `wakers` lock.
15445      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15446      let shared = rx.map(|_| ()).boxed().shared();
15447      let shared_for_b = shared.clone();
15448      let scheduler_b = Arc::clone(&executor);
15449      let (runnable_b, task_b) = async_task::spawn(
15450        async move {
15451          shared_for_b.await;
15452        },
15453        move |runnable| scheduler_b.schedule(runnable),
15454      );
15455      executor.schedule(runnable_b);
15456      service(0);
15457      assert_eq!(
15458        metrics.queued_runnables.load(Ordering::Relaxed),
15459        0,
15460        "B must be parked and unscheduled after its first poll"
15461      );
15462
15463      // Task C is queued by ANOTHER thread, from inside the retirement turn's
15464      // publication. The executor is reopened by then, so C lands in
15465      // `queue.runnables`; the publication already owns `dispatch_pending`, so
15466      // C's own drain request coalesces and adds no delivery of its own.
15467      let scheduler_c = Arc::clone(&executor);
15468      let (runnable_c, task_c) = async_task::spawn(async {}, move |runnable| {
15469        scheduler_c.schedule(runnable);
15470      });
15471      let (gate_tx, gate_rx) = mpsc::channel::<Runnable>();
15472      let (queued_tx, queued_rx) = mpsc::channel::<()>();
15473      let producer_executor = Arc::clone(&executor);
15474      let producer = std::thread::spawn(move || {
15475        let runnable = gate_rx
15476          .recv()
15477          .expect("the retirement turn must open the window");
15478        producer_executor.schedule(runnable);
15479        queued_tx.send(()).expect("W must still be waiting");
15480      });
15481      driver.during_delivery(3, move || {
15482        gate_tx.send(runnable_c).expect("the producer must be live");
15483        queued_rx
15484          .recv_timeout(WAIT)
15485          .expect("the producer must queue C before the retirement turn fails");
15486      });
15487
15488      // Every delivery of the interleaving is lost: the wake's publication,
15489      // its one replacement, the retirement turn the terminal transition
15490      // publishes for itself, and the replacement that turn's failure reserves
15491      // once it sees C queued. The host is healthy again from the sixth.
15492      driver.fail_budget.store(4, Ordering::SeqCst);
15493      tx.send(()).expect("B must still hold its Shared clone");
15494      producer.join().expect("the producer must not panic");
15495
15496      assert_eq!(
15497        driver.deliveries().len(),
15498        6,
15499        "the nested terminal transition may not publish on this stack, but the \
15500         retirement request it hands back must be serviced after the outer \
15501         publication unwinds; without that sixth delivery no host turn ever \
15502         follows and both B and C stay pending forever"
15503      );
15504      assert!(
15505        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15506        "both terminal failures must reopen the executor before the waker returns"
15507      );
15508      assert!(
15509        executor.has_rejected_work(),
15510        "B and C must be moved aside, not dropped on the waker's stack"
15511      );
15512      assert_eq!(
15513        metrics.queued_runnables.load(Ordering::Relaxed),
15514        0,
15515        "the moved runnables carry their queued debit"
15516      );
15517      let mut task_b = task_b.fallible();
15518      let mut task_c = task_c.fallible();
15519      assert!(
15520        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15521        "B must still be pending: nothing may be dropped on the waker's stack"
15522      );
15523      assert!(
15524        poll_ready_within(&mut task_c, Duration::ZERO).is_none(),
15525        "C must still be pending: nothing may be dropped on the waker's stack"
15526      );
15527
15528      // The ONLY continuation: the host serves the delivery the retirement
15529      // request produced. No submission, no drain request, no unregistration,
15530      // no shutdown.
15531      service(5);
15532      assert!(
15533        !executor.has_rejected_work(),
15534        "the retirement turn must cancel both moved runnables on its own stack"
15535      );
15536      assert!(
15537        poll_ready_within(&mut task_b, WAIT)
15538          .expect("the retirement turn must cancel B")
15539          .is_none(),
15540        "the terminal failure must still cancel the work queued when it happened"
15541      );
15542      assert!(
15543        poll_ready_within(&mut task_c, WAIT)
15544          .expect("the retirement turn must cancel C")
15545          .is_none(),
15546        "the nested terminal failure must cancel the work it moved aside too"
15547      );
15548      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15549      drop(shared);
15550
15551      registry.unregister(registration);
15552      let state = registry
15553        .state
15554        .lock()
15555        .unwrap_or_else(std::sync::PoisonError::into_inner);
15556      assert!(state.entries.is_empty());
15557      assert!(state.dispatches.is_empty());
15558      done_tx.send(()).unwrap();
15559    });
15560    done_rx.recv_timeout(WAIT).expect(
15561      "a nested waker-stack terminal failure discarded its retirement request: B's and C's \
15562       runnables stayed in `queue.rejected` and neither JoinHandle ever settled",
15563    );
15564  }
15565
15566  /// Regression test for the BOUND on the retirement turns `e2a8ed9c` opened.
15567  ///
15568  /// That commit made the frame owning a waker-stack retirement publication
15569  /// loop while nested terminal transitions kept handing work back. Its
15570  /// termination argument -- every turn consumes one distinct live task --
15571  /// bounds the turns by the live tasks, which is no bound at all in wall
15572  /// clock: a producer that keeps submitting keeps supplying the runnable each
15573  /// nested transition needs.
15574  ///
15575  ///   thread W (waker)                     | thread T (producer)
15576  ///   -------------------------------------+---------------------------
15577  ///   Shared notifier wakes B -> schedule  |
15578  ///   publish D1 --------------------------+--> schedule(C1) -> runnables
15579  ///   host fails D1 synchronously          |    request_drain -> Coalesced
15580  ///   publish D2 (replacement) ------------+--> schedule(C2)
15581  ///   host fails D2 synchronously          |
15582  ///   terminal: C1,C2 -> rejected, reopen  |
15583  ///   retirement turn 1: publish D3 -------+--> schedule(C3)   ... and so on
15584  ///
15585  /// Every turn is two more host callbacks and two more producer submissions,
15586  /// so the turns ran for exactly as long as the producer did: measured on
15587  /// `e2a8ed9c`, 32768 submissions produced 32769 deliveries inside one wake,
15588  /// and a producer left running for 400ms held this single `schedule`
15589  /// callback -- and `futures::Shared`'s `wakers` mutex under it -- for 432ms.
15590  ///
15591  /// The owner frame now takes at most `REJECTED_WORK_RETIREMENT_ATTEMPTS`
15592  /// turns a producer can interleave with, and then makes the one offer a
15593  /// producer cannot inflate (`publish_final_rejected_work_retirement`). This
15594  /// host refuses that one too, so what is left is the honest end state for a
15595  /// host that never accepts: the work waits, undropped, for a depth-0 entry.
15596  /// Nothing here supplies one until `wait_until_idle` -- no submission, no
15597  /// drain request, no unregistration -- and that is what
15598  /// `current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget`
15599  /// pins from the other side, where the host accepts the final offer.
15600  #[cfg(not(target_family = "wasm"))]
15601  #[test]
15602  fn current_thread_rejected_work_retirement_is_bounded_under_a_sustained_producer() {
15603    use std::{sync::mpsc, time::Duration};
15604
15605    const WAIT: Duration = Duration::from_secs(5);
15606    /// Far more submissions than the turns may ever spend, so what stops them
15607    /// is the executor's budget and not the producer running dry.
15608    const PRODUCER_BUDGET: usize = 4096;
15609    /// One publication and its one replacement for the wake itself, two more
15610    /// for each retirement turn, and the single final offer.
15611    const BOUNDED_DELIVERIES: usize =
15612      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS + 1;
15613
15614    let (done_tx, done_rx) = mpsc::channel();
15615    std::thread::spawn(move || {
15616      let registry = &*CURRENT_THREAD_SUSTAINED_PRODUCER_TASK_DRIVERS;
15617      {
15618        let state = registry
15619          .state
15620          .lock()
15621          .unwrap_or_else(std::sync::PoisonError::into_inner);
15622        assert!(state.entries.is_empty());
15623        assert!(state.dispatches.is_empty());
15624      }
15625      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
15626      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15627      let metrics = Arc::new(RuntimeMetrics::default());
15628      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15629        Arc::clone(&metrics),
15630        dispatch_sustained_producer_current_thread_tasks,
15631        registry,
15632      ));
15633      let backend =
15634        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15635
15636      // Service one accepted delivery the way the host does.
15637      let service = |index: usize| {
15638        let delivery = driver.deliveries()[index];
15639        let dispatch = registry
15640          .claim_delivery(delivery.capability())
15641          .expect("the accepted delivery must be claimable");
15642        let mut host_turn = executor
15643          .try_admit_host_turn(dispatch)
15644          .expect("the claimed delivery must be admitted");
15645        registry.mark_serviced(dispatch);
15646        host_turn.drive();
15647        drop(host_turn);
15648        let completion = registry.finish_delivery(delivery, false);
15649        assert!(completion.failed_dispatches.is_empty());
15650        assert!(!completion.redispatch);
15651      };
15652
15653      // B awaits a clone of a Shared and parks after its first poll, so its
15654      // wake arrives from inside the notifier's `wakers` lock. It is a
15655      // registered task, so its `GenerationWorkGuard` is observable.
15656      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15657      let shared = rx.map(|_| ()).boxed().shared();
15658      let shared_for_b = shared.clone();
15659      let work_registration = backend
15660        .work
15661        .try_register_async()
15662        .expect("the backend must accept work");
15663      let task_b = spawn_registered(
15664        &backend,
15665        Arc::clone(&metrics),
15666        async move {
15667          shared_for_b.await;
15668        },
15669        work_registration,
15670      );
15671      service(0);
15672      assert_eq!(
15673        metrics.queued_runnables.load(Ordering::Relaxed),
15674        0,
15675        "B must be parked and unscheduled after its first poll"
15676      );
15677      assert_eq!(backend.work.state.lock().unwrap().active, 1);
15678
15679      // The producer queues one runnable from inside every publication. The
15680      // publication already owns `dispatch_pending`, so each of those drain
15681      // requests coalesces and adds no delivery of its own -- every delivery
15682      // below is one the waker's stack published.
15683      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
15684      let (queued_tx, queued_rx) = mpsc::channel::<()>();
15685      let producer_executor = Arc::clone(&executor);
15686      let producer = std::thread::spawn(move || {
15687        let mut submissions = 0usize;
15688        while queue_one_rx.recv().is_ok() {
15689          let scheduler = Arc::clone(&producer_executor);
15690          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15691            scheduler.schedule(runnable);
15692          });
15693          task.detach();
15694          producer_executor.schedule(runnable);
15695          submissions += 1;
15696          if queued_tx.send(()).is_err() {
15697            break;
15698          }
15699        }
15700        submissions
15701      });
15702      driver.arm_producer(PRODUCER_BUDGET, queue_one_tx, queued_rx);
15703
15704      // From here the host fails every delivery synchronously, on the calling
15705      // thread, and never recovers.
15706      driver.fail_budget.store(usize::MAX, Ordering::SeqCst);
15707      let before = driver.deliveries().len();
15708      tx.send(()).expect("B must still hold its Shared clone");
15709      let during_wake = driver.deliveries().len() - before;
15710      let unspent = driver.disarm_producer();
15711      let submissions = producer.join().expect("the producer must not panic");
15712
15713      assert_eq!(
15714        during_wake, BOUNDED_DELIVERIES,
15715        "the retirement must take at most REJECTED_WORK_RETIREMENT_ATTEMPTS turns \
15716         on the waker's stack plus its one final offer; without a budget it \
15717         services the producer's stream instead and this wake never returns"
15718      );
15719      assert_eq!(
15720        submissions, during_wake,
15721        "the producer must have queued a runnable during every publication"
15722      );
15723      assert!(
15724        unspent > 0,
15725        "the producer must still have been willing to submit when the turns stopped"
15726      );
15727      assert!(
15728        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15729        "every terminal failure, and the final offer, must reopen the executor \
15730         before the waker returns"
15731      );
15732      assert!(
15733        executor.has_rejected_work(),
15734        "the moved work must be parked, not dropped on the waker's stack"
15735      );
15736      assert_eq!(
15737        executor.dispatch_pending.load(Ordering::Acquire),
15738        0,
15739        "a host that refused every offer including the final one leaves none \
15740         outstanding: this is the documented end state for a host that never accepts"
15741      );
15742      assert_eq!(
15743        metrics.queued_runnables.load(Ordering::Relaxed),
15744        0,
15745        "the moved runnables carry their queued debit"
15746      );
15747      let mut task_b = Box::pin(task_b);
15748      assert!(
15749        poll_ready_within(&mut task_b, Duration::ZERO).is_none(),
15750        "B must still be pending: nothing may be dropped on the waker's stack"
15751      );
15752      assert_eq!(
15753        backend.work.state.lock().unwrap().active,
15754        1,
15755        "B's generation guard rides with its parked runnable"
15756      );
15757
15758      // The first depth-0 entry retires it, and no `GenerationWorkGuard`
15759      // outlives that wait. Nothing above supplied this entry: no submission,
15760      // no drain request, no unregistration.
15761      backend.wait_until_idle();
15762      assert!(!executor.has_rejected_work());
15763      assert_eq!(backend.work.state.lock().unwrap().active, 0);
15764      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15765      assert!(
15766        poll_ready_within(&mut task_b, WAIT)
15767          .expect("the idle wait must cancel B")
15768          .is_err(),
15769        "the terminal failure must still cancel the work queued when it happened"
15770      );
15771      drop(shared);
15772
15773      registry.unregister(registration);
15774      let state = registry
15775        .state
15776        .lock()
15777        .unwrap_or_else(std::sync::PoisonError::into_inner);
15778      assert!(state.entries.is_empty());
15779      assert!(state.dispatches.is_empty());
15780      done_tx.send(()).unwrap();
15781    });
15782    done_rx.recv_timeout(WAIT).expect(
15783      "an unbounded retirement loop serviced the producer's stream on the waker's stack: \
15784       the wake never returned",
15785    );
15786  }
15787
15788  /// Regression test for the offer the attempt budget may not swallow -- the
15789  /// reviewer's interleaving, with a FINITE producer.
15790  ///
15791  /// `fd471429` capped the retirement turns at
15792  /// `REJECTED_WORK_RETIREMENT_ATTEMPTS` and argued that exhausting the budget
15793  /// strands nothing, because the producer that drove it to exhaustion
15794  /// supplies the next continuation itself with its next ordinary submission.
15795  /// It does not, if it has just made its last one:
15796  ///
15797  ///   thread W (waker)                     | thread T (producer, budget 18)
15798  ///   -------------------------------------+---------------------------
15799  ///   ... turns 1..7 ...                   | ... C3..C16 ...
15800  ///   turn 8: publish D17 -----------------+--> schedule(C17)
15801  ///   host fails D17, sees C17 serviceable |
15802  ///   publish D18 (replacement) -----------+--> schedule(C18)  <- last one
15803  ///   host fails D18                       |    (producer now quiescent)
15804  ///   terminal #9: C18 -> rejected, reopen |
15805  ///   budget spent -> turns stop           |
15806  ///
15807  /// The host is healthy from its next delivery and stays registered, but no
15808  /// dispatch is outstanding, `queue.rejected` holds B and every C, and
15809  /// nothing publishes again. Measured on `fd471429`: 18 deliveries during the
15810  /// wake, `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle`
15811  /// pending, its future never dropped, its `GenerationWorkGuard` still
15812  /// active, and no delivery afterwards.
15813  ///
15814  /// The budget bounds what a producer can inflate; it may not swallow the
15815  /// continuation itself. After it, one final offer is made with the
15816  /// terminal-cancellation claim held, so nothing can interleave with it and
15817  /// it needs no budget of its own -- and this host accepts it. That host turn
15818  /// is the depth-0 stack that retires everything the wake moved aside.
15819  /// Nothing here supplies any other entry: no submission after the wake, no
15820  /// drain request, no unregistration, no shutdown.
15821  #[cfg(not(target_family = "wasm"))]
15822  #[test]
15823  fn current_thread_final_retirement_offer_outlives_a_producer_that_spends_the_budget() {
15824    use std::{sync::mpsc, time::Duration};
15825
15826    const WAIT: Duration = Duration::from_secs(5);
15827    /// Every delivery the budget can pay for, and not one more: the producer's
15828    /// last runnable is queued inside the last of them and it then goes
15829    /// quiescent.
15830    const BUDGETED_DELIVERIES: usize =
15831      2 + 2 * CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
15832
15833    let (done_tx, done_rx) = mpsc::channel();
15834    std::thread::spawn(move || {
15835      struct DropFlag(Arc<AtomicBool>);
15836      impl Drop for DropFlag {
15837        fn drop(&mut self) {
15838          self.0.store(true, Ordering::SeqCst);
15839        }
15840      }
15841
15842      let registry = &*CURRENT_THREAD_FINAL_RETIREMENT_OFFER_TASK_DRIVERS;
15843      {
15844        let state = registry
15845          .state
15846          .lock()
15847          .unwrap_or_else(std::sync::PoisonError::into_inner);
15848        assert!(state.entries.is_empty());
15849        assert!(state.dispatches.is_empty());
15850      }
15851      let driver = Arc::new(SustainedProducerCurrentThreadTaskDriver::new(registry));
15852      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
15853      let metrics = Arc::new(RuntimeMetrics::default());
15854      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
15855        Arc::clone(&metrics),
15856        dispatch_final_retirement_offer_current_thread_tasks,
15857        registry,
15858      ));
15859      let backend =
15860        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
15861
15862      let service = |index: usize| {
15863        let delivery = driver.deliveries()[index];
15864        let dispatch = registry
15865          .claim_delivery(delivery.capability())
15866          .expect("the accepted delivery must be claimable");
15867        let mut host_turn = executor
15868          .try_admit_host_turn(dispatch)
15869          .expect("the claimed delivery must be admitted");
15870        registry.mark_serviced(dispatch);
15871        host_turn.drive();
15872        drop(host_turn);
15873        let completion = registry.finish_delivery(delivery, false);
15874        assert!(completion.failed_dispatches.is_empty());
15875        assert!(!completion.redispatch);
15876      };
15877
15878      let dropped = Arc::new(AtomicBool::new(false));
15879      let flag = DropFlag(Arc::clone(&dropped));
15880      let (tx, rx) = futures::channel::oneshot::channel::<()>();
15881      let shared = rx.map(|_| ()).boxed().shared();
15882      let shared_for_b = shared.clone();
15883      let work_registration = backend
15884        .work
15885        .try_register_async()
15886        .expect("the backend must accept work");
15887      let task_b = spawn_registered(
15888        &backend,
15889        Arc::clone(&metrics),
15890        async move {
15891          let _flag = flag;
15892          shared_for_b.await;
15893        },
15894        work_registration,
15895      );
15896      service(0);
15897      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15898      assert_eq!(backend.work.state.lock().unwrap().active, 1);
15899
15900      let (queue_one_tx, queue_one_rx) = mpsc::channel::<()>();
15901      let (queued_tx, queued_rx) = mpsc::channel::<()>();
15902      let producer_executor = Arc::clone(&executor);
15903      let producer = std::thread::spawn(move || {
15904        let mut submissions = 0usize;
15905        while queue_one_rx.recv().is_ok() {
15906          let scheduler = Arc::clone(&producer_executor);
15907          let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
15908            scheduler.schedule(runnable);
15909          });
15910          task.detach();
15911          producer_executor.schedule(runnable);
15912          submissions += 1;
15913          if queued_tx.send(()).is_err() {
15914            break;
15915          }
15916        }
15917        submissions
15918      });
15919      // The producer feeds exactly the deliveries the budget can pay for, and
15920      // the host refuses exactly those: it is healthy from the next one.
15921      driver.arm_producer(BUDGETED_DELIVERIES, queue_one_tx, queued_rx);
15922      driver
15923        .fail_budget
15924        .store(BUDGETED_DELIVERIES, Ordering::SeqCst);
15925
15926      let before = driver.deliveries().len();
15927      tx.send(()).expect("B must still hold its Shared clone");
15928      let during_wake = driver.deliveries().len() - before;
15929      let unspent = driver.disarm_producer();
15930      let submissions = producer.join().expect("the producer must not panic");
15931
15932      assert_eq!(
15933        during_wake,
15934        BUDGETED_DELIVERIES + 1,
15935        "the budget must be followed by the one offer a producer cannot \
15936         inflate; without it the wake stops after {BUDGETED_DELIVERIES} \
15937         deliveries and never asks the host that would have accepted"
15938      );
15939      assert_eq!(
15940        submissions, BUDGETED_DELIVERIES,
15941        "the producer must have queued a runnable during every budgeted \
15942         publication and gone quiescent before the final offer"
15943      );
15944      assert_eq!(
15945        unspent, 0,
15946        "the producer must have run dry exactly as the budget ran out"
15947      );
15948      assert_eq!(
15949        driver.fail_budget.load(Ordering::SeqCst),
15950        0,
15951        "the host must have refused every budgeted delivery and no more"
15952      );
15953      assert!(
15954        !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
15955        "the final offer must reopen the executor before the waker returns"
15956      );
15957      assert!(
15958        executor.has_rejected_work(),
15959        "nothing may be dropped on the waker's stack"
15960      );
15961      assert_ne!(
15962        executor.dispatch_pending.load(Ordering::Acquire),
15963        0,
15964        "the wake must leave a dispatch outstanding: that accepted delivery is \
15965         the continuation the parked work has"
15966      );
15967      let mut task_b = Box::pin(task_b);
15968      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
15969      assert!(!dropped.load(Ordering::SeqCst));
15970      assert_eq!(backend.work.state.lock().unwrap().active, 1);
15971
15972      // The ONLY continuation: the host serves the final offer. No submission,
15973      // no drain request, no unregistration, no shutdown.
15974      service(driver.deliveries().len() - 1);
15975      assert!(
15976        !executor.has_rejected_work(),
15977        "that host turn is a depth-0 stack and must retire everything the wake \
15978         moved aside"
15979      );
15980      assert!(
15981        poll_ready_within(&mut task_b, WAIT)
15982          .expect("the final offer's host turn must cancel B")
15983          .is_err()
15984      );
15985      assert!(
15986        dropped.load(Ordering::SeqCst),
15987        "B's future must be destroyed, on the host's stack and not the waker's"
15988      );
15989      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
15990      assert_eq!(
15991        backend.work.state.lock().unwrap().active,
15992        0,
15993        "no GenerationWorkGuard may outlive the retirement"
15994      );
15995      drop(shared);
15996
15997      registry.unregister(registration);
15998      let state = registry
15999        .state
16000        .lock()
16001        .unwrap_or_else(std::sync::PoisonError::into_inner);
16002      assert!(state.entries.is_empty());
16003      assert!(state.dispatches.is_empty());
16004      done_tx.send(()).unwrap();
16005    });
16006    done_rx.recv_timeout(WAIT).expect(
16007      "the retirement turns never returned, or the final offer deadlocked on the waker's stack",
16008    );
16009  }
16010
16011  /// Regression test for the turn a REFUSED offer owes, with no producer at
16012  /// all.
16013  ///
16014  /// Until this commit the turns were driven by a thread-local flag that only
16015  /// a NESTED terminal transition set. An offer the host simply refused with
16016  /// an empty queue reserves no replacement, so it reached no transition and
16017  /// set no flag, and the owner frame returned after a single offer with seven
16018  /// attempts unspent -- the same stranded state the budget's exhaustion
16019  /// leaves, reached with nothing racing at all. Measured before: 3 deliveries
16020  /// during the wake, `dispatch_pending` 0, B parked in `queue.rejected` with
16021  /// its guard held, and a host that was healthy for its very next delivery
16022  /// never asked for one.
16023  ///
16024  /// What a turn owes is executor state, not a message: work parked and no
16025  /// dispatch outstanding. Reading that instead of a flag makes both routes
16026  /// one route, and this host -- which refuses the wake's publication, its
16027  /// replacement and the first offer, and accepts the second -- is served.
16028  #[cfg(not(target_family = "wasm"))]
16029  #[test]
16030  fn current_thread_rejected_work_retirement_offers_again_when_the_host_refuses() {
16031    use std::{sync::mpsc, time::Duration};
16032
16033    const WAIT: Duration = Duration::from_secs(5);
16034    let (done_tx, done_rx) = mpsc::channel();
16035    std::thread::spawn(move || {
16036      struct DropFlag(Arc<AtomicBool>);
16037      impl Drop for DropFlag {
16038        fn drop(&mut self) {
16039          self.0.store(true, Ordering::SeqCst);
16040        }
16041      }
16042
16043      let registry = &*CURRENT_THREAD_RETIREMENT_RETRY_TASK_DRIVERS;
16044      {
16045        let state = registry
16046          .state
16047          .lock()
16048          .unwrap_or_else(std::sync::PoisonError::into_inner);
16049        assert!(state.entries.is_empty());
16050        assert!(state.dispatches.is_empty());
16051      }
16052      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16053      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16054      let metrics = Arc::new(RuntimeMetrics::default());
16055      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16056        Arc::clone(&metrics),
16057        dispatch_retirement_retry_current_thread_tasks,
16058        registry,
16059      ));
16060      let backend =
16061        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16062
16063      let service = |index: usize| {
16064        let delivery = driver.deliveries()[index];
16065        let dispatch = registry
16066          .claim_delivery(delivery.capability())
16067          .expect("the accepted delivery must be claimable");
16068        let mut host_turn = executor
16069          .try_admit_host_turn(dispatch)
16070          .expect("the claimed delivery must be admitted");
16071        registry.mark_serviced(dispatch);
16072        host_turn.drive();
16073        drop(host_turn);
16074        let completion = registry.finish_delivery(delivery, false);
16075        assert!(completion.failed_dispatches.is_empty());
16076        assert!(!completion.redispatch);
16077      };
16078
16079      let dropped = Arc::new(AtomicBool::new(false));
16080      let flag = DropFlag(Arc::clone(&dropped));
16081      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16082      let shared = rx.map(|_| ()).boxed().shared();
16083      let shared_for_b = shared.clone();
16084      let work_registration = backend
16085        .work
16086        .try_register_async()
16087        .expect("the backend must accept work");
16088      let task_b = spawn_registered(
16089        &backend,
16090        Arc::clone(&metrics),
16091        async move {
16092          let _flag = flag;
16093          shared_for_b.await;
16094        },
16095        work_registration,
16096      );
16097      service(0);
16098      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16099      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16100
16101      // The wake's publication, its one replacement, and the first retirement
16102      // offer are lost. The host is healthy from the second offer on, and no
16103      // producer ever runs: every offer after the first is refused with an
16104      // empty queue, which reserves no replacement and reaches no transition.
16105      let before = driver.deliveries().len();
16106      driver.fail_budget.store(3, Ordering::SeqCst);
16107      tx.send(()).expect("B must still hold its Shared clone");
16108      let during_wake = driver.deliveries().len() - before;
16109
16110      assert_eq!(
16111        during_wake, 4,
16112        "a refused offer owes another turn just as a nested transition does: \
16113         work is parked and no dispatch is outstanding"
16114      );
16115      assert_ne!(
16116        executor.dispatch_pending.load(Ordering::Acquire),
16117        0,
16118        "the accepted second offer is the continuation the parked work has"
16119      );
16120      assert!(executor.has_rejected_work());
16121      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16122      let mut task_b = Box::pin(task_b);
16123      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16124      assert!(!dropped.load(Ordering::SeqCst));
16125
16126      // The ONLY continuation: the host serves the offer it accepted.
16127      service(driver.deliveries().len() - 1);
16128      assert!(!executor.has_rejected_work());
16129      assert!(
16130        poll_ready_within(&mut task_b, WAIT)
16131          .expect("the accepted offer's host turn must cancel B")
16132          .is_err()
16133      );
16134      assert!(dropped.load(Ordering::SeqCst));
16135      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16136      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16137      drop(shared);
16138
16139      registry.unregister(registration);
16140      let state = registry
16141        .state
16142        .lock()
16143        .unwrap_or_else(std::sync::PoisonError::into_inner);
16144      assert!(state.entries.is_empty());
16145      assert!(state.dispatches.is_empty());
16146      done_tx.send(()).unwrap();
16147    });
16148    done_rx
16149      .recv_timeout(WAIT)
16150      .expect("a refused retirement offer left B stranded with a healthy host");
16151  }
16152
16153  /// Regression test for the continuation the FINAL offer owes when the host
16154  /// services it from inside `dispatch`.
16155  ///
16156  /// `6cfe2002` makes one last offer after the attempt budget with the
16157  /// terminal-cancellation claim held, so nothing can inflate it, and then
16158  /// reopens and returns. A driver may claim, drive and acknowledge a delivery
16159  /// inside `dispatch` -- the registry supports that synchronous completion,
16160  /// and `RacingTaskDriver` in this module drives one inline -- and such a turn
16161  /// on THIS offer can do nothing at all:
16162  /// `retire_rejected_work_off_waker_stack` returns at nonzero schedule depth,
16163  /// and `take_runnable` returns `None` while `cancelling_failed_dispatch` is
16164  /// held. It only consumes `dispatch_pending`. The offer then returned with
16165  /// the executor reopened, no dispatch outstanding and `queue.rejected` still
16166  /// holding B. Measured before this commit: 11 deliveries during the wake,
16167  /// `dispatch_pending` 0, `queued_runnables` 0, B's `JoinHandle` pending, its
16168  /// future never dropped, its `GenerationWorkGuard` still active -- and not
16169  /// one delivery afterwards, with the driver registered and healthy.
16170  ///
16171  /// An offer the host ACCEPTED but could not act on leaves exactly the state
16172  /// a refused one leaves, and the invariant is the one this branch already
16173  /// reads off the executor: while work is parked in `queue.rejected` and no
16174  /// dispatch is outstanding, this stack still owes a continuation. So the
16175  /// offer reports whether the host accepted it, and an accepted offer whose
16176  /// dispatch was consumed is followed by ONE ordinary publication -- the
16177  /// executor is open again, so that is the path `5857092f` reopened it for.
16178  /// It is one publication and not a loop, and a nested terminal transition
16179  /// inside it finds `CURRENT_THREAD_RETIRING_REJECTED_WORK` held and
16180  /// publishes nothing.
16181  ///
16182  /// Nothing here supplies any other entry: no submission after the wake, no
16183  /// drain request, no unregistration, no shutdown.
16184  #[cfg(not(target_family = "wasm"))]
16185  #[test]
16186  fn current_thread_final_retirement_offer_serviced_inside_dispatch_leaves_a_continuation() {
16187    use std::{sync::mpsc, time::Duration};
16188
16189    const WAIT: Duration = Duration::from_secs(5);
16190    /// The wake's publication, its one replacement, and every budgeted turn.
16191    const REFUSED_DELIVERIES: usize = 2 + CurrentThreadExecutor::REJECTED_WORK_RETIREMENT_ATTEMPTS;
16192
16193    let (done_tx, done_rx) = mpsc::channel();
16194    std::thread::spawn(move || {
16195      struct DropFlag(Arc<AtomicBool>);
16196      impl Drop for DropFlag {
16197        fn drop(&mut self) {
16198          self.0.store(true, Ordering::SeqCst);
16199        }
16200      }
16201
16202      let registry = &*CURRENT_THREAD_INLINE_FINAL_OFFER_TASK_DRIVERS;
16203      {
16204        let state = registry
16205          .state
16206          .lock()
16207          .unwrap_or_else(std::sync::PoisonError::into_inner);
16208        assert!(state.entries.is_empty());
16209        assert!(state.dispatches.is_empty());
16210      }
16211      let driver = Arc::new(BoundedFailureCurrentThreadTaskDriver::new(registry));
16212      let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16213      let metrics = Arc::new(RuntimeMetrics::default());
16214      let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16215        Arc::clone(&metrics),
16216        dispatch_inline_final_offer_current_thread_tasks,
16217        registry,
16218      ));
16219      let backend =
16220        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16221
16222      let service = |index: usize| {
16223        let delivery = driver.deliveries()[index];
16224        let dispatch = registry
16225          .claim_delivery(delivery.capability())
16226          .expect("the accepted delivery must be claimable");
16227        let mut host_turn = executor
16228          .try_admit_host_turn(dispatch)
16229          .expect("the claimed delivery must be admitted");
16230        registry.mark_serviced(dispatch);
16231        host_turn.drive();
16232        drop(host_turn);
16233        let completion = registry.finish_delivery(delivery, false);
16234        assert!(completion.failed_dispatches.is_empty());
16235        assert!(!completion.redispatch);
16236      };
16237
16238      let dropped = Arc::new(AtomicBool::new(false));
16239      let flag = DropFlag(Arc::clone(&dropped));
16240      let (tx, rx) = futures::channel::oneshot::channel::<()>();
16241      let shared = rx.map(|_| ()).boxed().shared();
16242      let shared_for_b = shared.clone();
16243      let work_registration = backend
16244        .work
16245        .try_register_async()
16246        .expect("the backend must accept work");
16247      let task_b = spawn_registered(
16248        &backend,
16249        Arc::clone(&metrics),
16250        async move {
16251          let _flag = flag;
16252          shared_for_b.await;
16253        },
16254        work_registration,
16255      );
16256      service(0);
16257      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16258      assert_eq!(backend.work.state.lock().unwrap().active, 1);
16259
16260      // The wake's publication, its one replacement and every budgeted turn
16261      // are refused. The delivery right after them is the final offer, and the
16262      // host claims, drives and acknowledges THAT one from inside `dispatch`:
16263      // on the waker's stack, with the terminal-cancellation claim held.
16264      let before = driver.deliveries().len();
16265      let serviced_inline = Arc::new(AtomicBool::new(false));
16266      {
16267        let executor = Arc::clone(&executor);
16268        let hook_driver = Arc::clone(&driver);
16269        let serviced_inline = Arc::clone(&serviced_inline);
16270        driver.during_delivery(before + REFUSED_DELIVERIES, move || {
16271          let delivery = *hook_driver
16272            .deliveries()
16273            .last()
16274            .expect("the final offer must be recorded before the hook runs");
16275          let dispatch = registry
16276            .claim_delivery(delivery.capability())
16277            .expect("the final offer must be claimable");
16278          let mut host_turn = executor
16279            .try_admit_host_turn(dispatch)
16280            .expect("the claimed final offer must be admitted");
16281          registry.mark_serviced(dispatch);
16282          host_turn.drive();
16283          drop(host_turn);
16284          let completion = registry.finish_delivery(delivery, false);
16285          assert!(completion.failed_dispatches.is_empty());
16286          serviced_inline.store(true, Ordering::SeqCst);
16287        });
16288      }
16289      driver
16290        .fail_budget
16291        .store(REFUSED_DELIVERIES, Ordering::SeqCst);
16292      tx.send(()).expect("B must still hold its Shared clone");
16293      let during_wake = driver.deliveries().len() - before;
16294
16295      assert!(
16296        serviced_inline.load(Ordering::SeqCst),
16297        "the host must have claimed, driven and acknowledged the final offer inside `dispatch`"
16298      );
16299      assert_eq!(
16300        during_wake,
16301        REFUSED_DELIVERIES + 2,
16302        "an accepted offer whose host turn could not act owes the same continuation a refused \
16303         one owes: work parked and no dispatch outstanding"
16304      );
16305      assert_ne!(
16306        executor.dispatch_pending.load(Ordering::Acquire),
16307        0,
16308        "the publication that follows the consumed offer is the continuation the parked work has"
16309      );
16310      assert!(executor.has_rejected_work());
16311      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16312      let mut task_b = Box::pin(task_b);
16313      assert!(poll_ready_within(&mut task_b, Duration::ZERO).is_none());
16314      assert!(!dropped.load(Ordering::SeqCst));
16315
16316      // The ONLY continuation: the host serves it on a clean stack.
16317      service(driver.deliveries().len() - 1);
16318      assert!(!executor.has_rejected_work());
16319      assert!(
16320        poll_ready_within(&mut task_b, WAIT)
16321          .expect("the host turn that follows the consumed offer must cancel B")
16322          .is_err()
16323      );
16324      assert!(dropped.load(Ordering::SeqCst));
16325      assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16326      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16327      drop(shared);
16328
16329      registry.unregister(registration);
16330      let state = registry
16331        .state
16332        .lock()
16333        .unwrap_or_else(std::sync::PoisonError::into_inner);
16334      assert!(state.entries.is_empty());
16335      assert!(state.dispatches.is_empty());
16336      done_tx.send(()).unwrap();
16337    });
16338    done_rx.recv_timeout(WAIT).expect(
16339      "a final offer serviced inside `dispatch` left B stranded with a registered, healthy host",
16340    );
16341  }
16342
16343  /// Regression test for the continuation an ordinary `spawn` owes itself.
16344  /// `async-task` runs one scheduler callback for two different things: a
16345  /// wake, which any `Waker` can raise from any stack, and the first schedule
16346  /// of a freshly submitted task, which `spawn` makes on its caller's own
16347  /// stack. Only the wake can arrive under a foreign lock. When the host fails
16348  /// a submission's publication and its one replacement synchronously, the
16349  /// terminal transition is that submission's ONLY continuation: it produced
16350  /// no host turn, `complete_current_thread_task_delivery` has already run,
16351  /// and nothing else will reach `retire_rejected_work`. Treating the frame as
16352  /// a waker's parked the runnable in `queue.rejected` with the driver
16353  /// registered and healthy again, so the `JoinHandle` stayed pending forever
16354  /// and its `GenerationWorkGuard` stayed active. Nothing here supplies a
16355  /// later entry: no drain request, no host turn, no unregistration, no
16356  /// shutdown.
16357  #[cfg(not(target_family = "wasm"))]
16358  #[test]
16359  fn current_thread_submission_dispatch_failure_cancels_on_the_submitter_stack() {
16360    use std::time::Duration;
16361
16362    let registry = &*CURRENT_THREAD_SUBMISSION_FAILURE_TASK_DRIVERS;
16363    {
16364      let state = registry
16365        .state
16366        .lock()
16367        .unwrap_or_else(std::sync::PoisonError::into_inner);
16368      assert!(state.entries.is_empty());
16369      assert!(state.dispatches.is_empty());
16370    }
16371    let driver = Arc::new(SynchronouslyFailingCurrentThreadTaskDriver::new(registry));
16372    let registration = registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>);
16373    let metrics = Arc::new(RuntimeMetrics::default());
16374    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch_registry(
16375      Arc::clone(&metrics),
16376      dispatch_submission_failure_current_thread_tasks,
16377      registry,
16378    ));
16379    let backend =
16380      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16381
16382    // Every accepted delivery fails before `dispatch` returns, the way a
16383    // closing threadsafe function does.
16384    driver.fail.store(true, Ordering::SeqCst);
16385
16386    let work_registration = backend
16387      .work
16388      .try_register_async()
16389      .expect("the backend must accept work");
16390    let task = spawn_registered(
16391      &backend,
16392      Arc::clone(&metrics),
16393      std::future::pending::<()>(),
16394      work_registration,
16395    );
16396
16397    assert_eq!(
16398      driver.deliveries().len(),
16399      2,
16400      "the submission must publish the dispatch and exactly one replacement"
16401    );
16402    assert!(
16403      !executor.cancelling_failed_dispatch.load(Ordering::Acquire),
16404      "the terminal failure must reopen the executor"
16405    );
16406    assert!(
16407      !executor.has_rejected_work(),
16408      "a submission's own stack is not a waker's: the rejected work must be        cancelled here, because nothing else will"
16409    );
16410    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16411    assert_eq!(backend.work.state.lock().unwrap().active, 0);
16412
16413    let mut task = Box::pin(task);
16414    assert_eq!(
16415      poll_ready_within(&mut task, Duration::ZERO)
16416        .expect("the submission must settle without any later entry")
16417        .unwrap_err()
16418        .to_string(),
16419      "async runtime stopped before the task completed",
16420      "terminal host failure must settle the submitted operation as cancelled"
16421    );
16422
16423    registry.unregister(registration);
16424    let state = registry
16425      .state
16426      .lock()
16427      .unwrap_or_else(std::sync::PoisonError::into_inner);
16428    assert!(state.entries.is_empty());
16429    assert!(state.dispatches.is_empty());
16430  }
16431
16432  /// MultiThread twin of
16433  /// `current_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline`.
16434  /// The same wake is in flight when shutdown starts: B's schedule closure
16435  /// runs inside `futures::Shared`'s notifier, under its `wakers` mutex, and
16436  /// B's future owns a clone of that `Shared`. `MultiThreadExecutor::schedule`
16437  /// has no closed-queue branch: the late runnable is pushed to the injector
16438  /// (or the worker's LIFO slot) exactly like any other, and the drainer that
16439  /// claims it under the published stop cancels it on its own stack
16440  /// (`claim_runnable` -> `RunnableClaim::Cancel` -> `cancel_runnable`). That
16441  /// stack never holds `wakers`, so the drop only waits for the waker thread
16442  /// to leave the notifier. This pins that shape: a closed-queue rejection
16443  /// added to `schedule` that dropped the runnable inline would deadlock
16444  /// here, exactly as the CurrentThread executor did.
16445  #[cfg(not(target_family = "wasm"))]
16446  #[test]
16447  fn multi_thread_shutdown_does_not_drop_in_flight_rejected_runnable_inline() {
16448    use std::{
16449      sync::{Barrier, mpsc},
16450      time::Duration,
16451    };
16452
16453    let (done_tx, done_rx) = mpsc::channel();
16454    std::thread::spawn(move || {
16455      let metrics = Arc::new(RuntimeMetrics::default());
16456      let options = RuntimeOptions {
16457        flavor: RuntimeFlavor::MultiThread,
16458        worker_threads: 2,
16459        max_blocking_tasks: 1,
16460        thread_name_prefix: "mt-inline-drop".to_string(),
16461        park_deadline: None,
16462        drain_linger: DEFAULT_DRAIN_LINGER,
16463      };
16464      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
16465      let backend =
16466        RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(&executor)));
16467      let generation = backend.generation();
16468
16469      // Task A: registered like production work; its JoinHandle is shared.
16470      let (abort_a, guard_a) = backend
16471        .work
16472        .try_register_async()
16473        .expect("the backend must accept work");
16474      let scheduler_a = Arc::clone(&executor);
16475      let (runnable_a, task_a) = async_task::spawn(
16476        RegisteredTaskFuture::new(
16477          Abortable::new(std::future::pending::<()>(), abort_a),
16478          guard_a,
16479          generation,
16480        ),
16481        move |runnable| scheduler_a.schedule(runnable),
16482      );
16483      let shared = async move { task_a.fallible().await.is_some() }
16484        .boxed()
16485        .shared();
16486      let shared_for_b = shared.clone();
16487
16488      // Task B: registered and abortable too, awaiting a clone of that Shared.
16489      // Its schedule closure pauses before it reaches the executor, exactly
16490      // where a waker thread may be descheduled while shutdown runs.
16491      let (abort_b, guard_b) = backend
16492        .work
16493        .try_register_async()
16494        .expect("the backend must accept work");
16495      let wake_in_flight = Arc::new(Barrier::new(2));
16496      let wake_release = Arc::new(Barrier::new(2));
16497      let scheduler_b = Arc::clone(&executor);
16498      let (runnable_b, task_b) = async_task::spawn(
16499        RegisteredTaskFuture::new(
16500          Abortable::new(
16501            async move {
16502              shared_for_b.await;
16503            },
16504            abort_b,
16505          ),
16506          guard_b,
16507          generation,
16508        ),
16509        {
16510          let wake_in_flight = Arc::clone(&wake_in_flight);
16511          let wake_release = Arc::clone(&wake_release);
16512          move |runnable| {
16513            wake_in_flight.wait();
16514            wake_release.wait();
16515            scheduler_b.schedule(runnable);
16516          }
16517        },
16518      );
16519      // The first poll runs on a pool drainer; wait until B has parked on the
16520      // Shared and left the drainer, so the wake below is a real re-schedule
16521      // from the notifier rather than a wake-while-running that the drainer
16522      // would re-publish from its own stack.
16523      executor.schedule(runnable_b);
16524      wait_until("B parked after its first poll", || {
16525        metrics.runnable_polls.load(Ordering::SeqCst) == 1
16526          && metrics.active_runnables.load(Ordering::SeqCst) == 0
16527          && metrics.queued_runnables.load(Ordering::SeqCst) == 0
16528      });
16529
16530      // Cancel A on another thread: closing A notifies its awaiter, and the
16531      // Shared notifier wakes B while holding `wakers`. B's schedule closure
16532      // then parks at the barrier with that lock still held.
16533      let waker_thread = std::thread::spawn(move || drop(runnable_a));
16534      wake_in_flight.wait();
16535
16536      // Shutdown: `close_and_abort` aborts B, which is already SCHEDULED, so
16537      // the abort wake is a no-op; the stop is then published.
16538      backend.begin_shutdown();
16539      wake_release.wait();
16540      backend.wait_until_idle();
16541
16542      waker_thread
16543        .join()
16544        .expect("the waker thread must not panic");
16545      assert_eq!(metrics.queued_runnables.load(Ordering::SeqCst), 0);
16546      assert!(
16547        futures::executor::block_on(task_b.fallible()).is_none(),
16548        "B's late wake must still cancel B"
16549      );
16550      assert_eq!(backend.work.state.lock().unwrap().active, 0);
16551      drop(shared);
16552      done_tx.send(()).unwrap();
16553    });
16554    done_rx
16555      .recv_timeout(Duration::from_secs(5))
16556      .expect("shutdown deadlocked: inline runnable drop re-entered a waker lock");
16557  }
16558
16559  #[cfg(not(target_family = "wasm"))]
16560  #[test]
16561  fn current_thread_recovery_reservation_blocks_new_host_publication() {
16562    use std::sync::mpsc;
16563
16564    CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.store(0, Ordering::SeqCst);
16565    let metrics = Arc::new(RuntimeMetrics::default());
16566    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16567      Arc::clone(&metrics),
16568      count_current_thread_interleaved_recovery_host_dispatch,
16569    ));
16570    let backend =
16571      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16572
16573    let registration = backend
16574      .work
16575      .try_register_async()
16576      .expect("the backend must accept work");
16577    let task = spawn_registered(
16578      &backend,
16579      Arc::clone(&metrics),
16580      std::future::pending::<()>(),
16581      registration,
16582    );
16583    let failed_dispatch = current_thread_dispatch(&executor);
16584    assert_eq!(
16585      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16586      1
16587    );
16588
16589    let (recovery_cleared_tx, recovery_cleared_rx) = mpsc::channel();
16590    let (release_recovery_tx, release_recovery_rx) = mpsc::channel();
16591    let cancelling_executor = Arc::clone(&executor);
16592    let cancellation = std::thread::spawn(move || {
16593      CURRENT_THREAD_DISPATCH_RECOVERY_TEST_HOOK.with(|slot| {
16594        *slot.borrow_mut() = Some(Box::new(move || {
16595          recovery_cleared_tx.send(()).unwrap();
16596          release_recovery_rx.recv().unwrap();
16597        }));
16598      });
16599      cancelling_executor.cancel_host_dispatch_for_test(failed_dispatch);
16600    });
16601    recovery_cleared_rx
16602      .recv_timeout(Duration::from_secs(2))
16603      .expect("failed dispatch cancellation must pause after consuming the old capability");
16604
16605    let (publication_blocked_tx, publication_blocked_rx) = mpsc::channel();
16606    let (publication_done_tx, publication_done_rx) = mpsc::channel();
16607    let publishing_executor = Arc::clone(&executor);
16608    let publication = std::thread::spawn(move || {
16609      CURRENT_THREAD_DISPATCH_PUBLICATION_BLOCKED_TEST_HOOK.with(|slot| {
16610        *slot.borrow_mut() = Some(Box::new(move || {
16611          publication_blocked_tx.send(()).unwrap();
16612        }));
16613      });
16614      publishing_executor.request_drain_if_queued();
16615      publication_done_tx.send(()).unwrap();
16616    });
16617
16618    if let Err(error) = publication_blocked_rx.recv_timeout(Duration::from_secs(2)) {
16619      release_recovery_tx.send(()).unwrap();
16620      cancellation.join().unwrap();
16621      publication.join().unwrap();
16622      panic!("a newly registered host must wait for recovery reservation: {error}");
16623    }
16624    assert!(
16625      matches!(
16626        publication_done_rx.try_recv(),
16627        Err(mpsc::TryRecvError::Empty)
16628      ),
16629      "the new host must not publish in the consumed-to-recovery gap"
16630    );
16631    release_recovery_tx.send(()).unwrap();
16632
16633    cancellation.join().unwrap();
16634    publication_done_rx
16635      .recv_timeout(Duration::from_secs(2))
16636      .unwrap();
16637    publication.join().unwrap();
16638
16639    let replacement_dispatch = current_thread_dispatch(&executor);
16640    assert_ne!(replacement_dispatch, failed_dispatch);
16641    assert_eq!(
16642      executor.recovery_dispatch.load(Ordering::Acquire),
16643      replacement_dispatch
16644    );
16645    assert_eq!(
16646      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16647      3,
16648      "the original publication and two host attempts for its one replacement must run"
16649    );
16650
16651    executor.cancel_host_dispatch_for_test(replacement_dispatch);
16652
16653    assert_eq!(
16654      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16655      3,
16656      "failure of the tagged replacement must cancel work without a second replacement"
16657    );
16658    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
16659    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
16660    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16661    assert_eq!(
16662      futures::executor::block_on(task).unwrap_err().to_string(),
16663      "async runtime stopped before the task completed"
16664    );
16665
16666    let later_registration = backend
16667      .work
16668      .try_register_async()
16669      .expect("terminal cancellation must reopen acceptance");
16670    let later_task = spawn_registered(
16671      &backend,
16672      Arc::clone(&metrics),
16673      async { 2usize },
16674      later_registration,
16675    );
16676    assert_eq!(
16677      CURRENT_THREAD_INTERLEAVED_RECOVERY_HOST_DISPATCHES.load(Ordering::SeqCst),
16678      4
16679    );
16680    executor.drive_host_turn();
16681    assert_eq!(futures::executor::block_on(later_task).unwrap(), 2);
16682  }
16683
16684  #[cfg(not(target_family = "wasm"))]
16685  #[test]
16686  fn current_thread_terminal_dispatch_failure_keeps_blocking_capture_registered_until_drop() {
16687    use std::sync::mpsc;
16688
16689    struct BlockingDrop {
16690      entered: mpsc::Sender<()>,
16691      release: mpsc::Receiver<()>,
16692    }
16693
16694    impl Drop for BlockingDrop {
16695      fn drop(&mut self) {
16696        self.entered.send(()).unwrap();
16697        self.release.recv().unwrap();
16698      }
16699    }
16700
16701    reset_current_thread_recovery_host_dispatches();
16702    let metrics = Arc::new(RuntimeMetrics::default());
16703    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16704      Arc::clone(&metrics),
16705      count_current_thread_recovery_host_dispatch,
16706    ));
16707    let backend =
16708      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
16709
16710    let holder_registration = backend
16711      .work
16712      .try_register_work()
16713      .expect("the backend must accept the blocking holder");
16714    let (holder_started_tx, holder_started_rx) = mpsc::channel();
16715    let (release_holder_tx, release_holder_rx) = mpsc::channel();
16716    let holder_executor = Arc::clone(&executor);
16717    let holder = std::thread::spawn(move || {
16718      let handle = holder_executor.schedule_blocking(
16719        move || {
16720          holder_started_tx.send(()).unwrap();
16721          release_holder_rx.recv().unwrap();
16722        },
16723        holder_registration,
16724      );
16725      futures::executor::block_on(handle).unwrap();
16726    });
16727    holder_started_rx
16728      .recv_timeout(Duration::from_secs(2))
16729      .expect("the first blocking call must hold the CurrentThread lane");
16730
16731    let cancelled_registration = backend
16732      .work
16733      .try_register_work()
16734      .expect("the backend must accept queued blocking work");
16735    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
16736    let (release_drop_tx, release_drop_rx) = mpsc::channel();
16737    let blocking_drop = BlockingDrop {
16738      entered: drop_entered_tx,
16739      release: release_drop_rx,
16740    };
16741    let cancelled = executor.schedule_blocking(
16742      move || {
16743        let _blocking_drop = blocking_drop;
16744      },
16745      cancelled_registration,
16746    );
16747
16748    let async_registration = backend
16749      .work
16750      .try_register_async()
16751      .expect("the backend must accept queued async work");
16752    let pending = spawn_registered(
16753      &backend,
16754      Arc::clone(&metrics),
16755      std::future::pending::<()>(),
16756      async_registration,
16757    );
16758    let failed_dispatch = current_thread_dispatch(&executor);
16759    executor.cancel_host_dispatch_for_test(failed_dispatch);
16760    let replacement_dispatch = current_thread_dispatch(&executor);
16761
16762    let (cancel_done_tx, cancel_done_rx) = mpsc::channel();
16763    let cancelling_executor = Arc::clone(&executor);
16764    let cancellation = std::thread::spawn(move || {
16765      cancelling_executor.cancel_host_dispatch_for_test(replacement_dispatch);
16766      cancel_done_tx.send(()).unwrap();
16767    });
16768    drop_entered_rx
16769      .recv_timeout(Duration::from_secs(2))
16770      .expect("terminal dispatch cancellation must destroy the queued blocking capture");
16771    let active_during_drop = backend.work.state.lock().unwrap().active;
16772    let cancellation_blocked = cancel_done_rx.try_recv().is_err();
16773
16774    release_drop_tx.send(()).unwrap();
16775    cancel_done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
16776    cancellation.join().unwrap();
16777
16778    assert_eq!(
16779      active_during_drop, 2,
16780      "the blocking capture's work registration must remain active until its destructor returns"
16781    );
16782    assert!(
16783      cancellation_blocked,
16784      "terminal cancellation must wait for capture destruction"
16785    );
16786    assert!(futures::executor::block_on(cancelled).is_err());
16787    assert!(futures::executor::block_on(pending).is_err());
16788    assert_eq!(backend.work.state.lock().unwrap().active, 1);
16789
16790    release_holder_tx.send(()).unwrap();
16791    holder.join().unwrap();
16792    assert_eq!(backend.work.state.lock().unwrap().active, 0);
16793  }
16794
16795  #[test]
16796  fn current_thread_unavailable_recovery_dispatch_cancels_original_work() {
16797    reset_current_thread_one_shot_host_dispatches();
16798    let metrics = Arc::new(RuntimeMetrics::default());
16799    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
16800      Arc::clone(&metrics),
16801      accept_only_first_current_thread_host_dispatch,
16802    ));
16803
16804    let scheduler = Arc::clone(&executor);
16805    let (runnable, task) = async_task::spawn(std::future::pending::<()>(), move |runnable| {
16806      scheduler.schedule(runnable)
16807    });
16808    executor.schedule(runnable);
16809    let failed_dispatch = current_thread_dispatch(&executor);
16810
16811    executor.cancel_host_dispatch_for_test(failed_dispatch);
16812
16813    assert_eq!(
16814      current_thread_one_shot_host_dispatches(),
16815      2,
16816      "the original and its one bounded replacement must be attempted"
16817    );
16818    assert_eq!(executor.dispatch_pending.load(Ordering::Acquire), 0);
16819    assert_eq!(executor.recovery_dispatch.load(Ordering::Acquire), 0);
16820    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
16821    assert!(
16822      futures::executor::block_on(task.fallible()).is_none(),
16823      "an unavailable replacement host must settle the original task as cancelled"
16824    );
16825  }
16826
16827  enum TestCurrentThreadTaskDriverBehavior {
16828    Healthy,
16829    PanicInLivenessAndSweep,
16830    PanicInDispatchAndSweep,
16831  }
16832
16833  struct TestCurrentThreadTaskDriver {
16834    behavior: TestCurrentThreadTaskDriverBehavior,
16835    dispatched: AtomicBool,
16836  }
16837
16838  #[derive(Default)]
16839  struct ManualCurrentThreadTaskDriver {
16840    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16841  }
16842
16843  struct FailDuringAuthoritativePanicPublication;
16844  struct FailDuringLateRegistrationPublication;
16845  struct FailDuringNestedUnwindPublication;
16846  struct FailDuringReplacementRepublishPublication;
16847  struct FirstHostileCurrentThreadPanic;
16848  struct LaterHostileCurrentThreadPanic;
16849
16850  impl Drop for FirstHostileCurrentThreadPanic {
16851    fn drop(&mut self) {
16852      CURRENT_THREAD_FIRST_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
16853      panic!("the first retained CurrentThread panic payload was dropped");
16854    }
16855  }
16856
16857  impl Drop for LaterHostileCurrentThreadPanic {
16858    fn drop(&mut self) {
16859      CURRENT_THREAD_LATER_HOSTILE_PANIC_DROPS.fetch_add(1, Ordering::SeqCst);
16860      panic!("a later CurrentThread panic payload escaped containment");
16861    }
16862  }
16863
16864  impl ManualCurrentThreadTaskDriver {
16865    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16866      self.deliveries.lock().unwrap().clone()
16867    }
16868  }
16869
16870  impl CurrentThreadTaskDriver for ManualCurrentThreadTaskDriver {
16871    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16872      self.deliveries.lock().unwrap().push(delivery);
16873      true
16874    }
16875  }
16876
16877  /// Accepts every delivery; once `fail` is set it also fails the accepted
16878  /// delivery before `dispatch` returns, from a helper thread it joins, the
16879  /// way a host whose env is tearing down reports a threadsafe-function call
16880  /// that cannot land.
16881  struct SynchronouslyFailingCurrentThreadTaskDriver {
16882    registry: &'static CurrentThreadTaskDriverRegistry,
16883    fail: AtomicBool,
16884    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16885  }
16886
16887  impl SynchronouslyFailingCurrentThreadTaskDriver {
16888    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
16889      Self {
16890        registry,
16891        fail: AtomicBool::new(false),
16892        deliveries: Mutex::default(),
16893      }
16894    }
16895
16896    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16897      self.deliveries.lock().unwrap().clone()
16898    }
16899  }
16900
16901  impl CurrentThreadTaskDriver for SynchronouslyFailingCurrentThreadTaskDriver {
16902    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16903      self.deliveries.lock().unwrap().push(delivery);
16904      if self.fail.load(Ordering::SeqCst) {
16905        let registry = self.registry;
16906        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
16907          .join()
16908          .expect("the failing helper must not panic");
16909        assert!(
16910          completion.failed_dispatches.is_empty(),
16911          "the failed delivery must remain provisional until its publication retires"
16912        );
16913      }
16914      true
16915    }
16916  }
16917
16918  /// Accepts every delivery, and fails the first `fail_budget` of them before
16919  /// `dispatch` returns -- from a helper thread it joins, exactly like
16920  /// `SynchronouslyFailingCurrentThreadTaskDriver`. Once the budget is spent
16921  /// the host is healthy again, so the retirement turn a waker-stack terminal
16922  /// failure publishes for itself is accepted instead of lost.
16923  /// A one-shot the test runs from inside a chosen delivery; see
16924  /// `BoundedFailureCurrentThreadTaskDriver::during_delivery`.
16925  type DeliveryHook = Box<dyn FnOnce() + Send>;
16926
16927  struct BoundedFailureCurrentThreadTaskDriver {
16928    registry: &'static CurrentThreadTaskDriverRegistry,
16929    fail_budget: AtomicUsize,
16930    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
16931    during_delivery: Mutex<Option<(usize, DeliveryHook)>>,
16932  }
16933
16934  impl BoundedFailureCurrentThreadTaskDriver {
16935    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
16936      Self {
16937        registry,
16938        fail_budget: AtomicUsize::new(0),
16939        deliveries: Mutex::default(),
16940        during_delivery: Mutex::default(),
16941      }
16942    }
16943
16944    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
16945      self.deliveries.lock().unwrap().clone()
16946    }
16947
16948    /// Run `hook` from inside the `index`-th delivery (0-based over every
16949    /// delivery this driver receives), before that delivery is failed. This is
16950    /// the window another thread needs to queue work while a publication is in
16951    /// flight.
16952    fn during_delivery(&self, index: usize, hook: impl FnOnce() + Send + 'static) {
16953      *self.during_delivery.lock().unwrap() = Some((index, Box::new(hook)));
16954    }
16955  }
16956
16957  impl CurrentThreadTaskDriver for BoundedFailureCurrentThreadTaskDriver {
16958    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
16959      let index = {
16960        let mut deliveries = self.deliveries.lock().unwrap();
16961        deliveries.push(delivery);
16962        deliveries.len() - 1
16963      };
16964      let hook = {
16965        let mut slot = self.during_delivery.lock().unwrap();
16966        if matches!(slot.as_ref(), Some((at, _)) if *at == index) {
16967          slot.take().map(|(_, hook)| hook)
16968        } else {
16969          None
16970        }
16971      };
16972      if let Some(hook) = hook {
16973        hook();
16974      }
16975      let budget = self.fail_budget.load(Ordering::SeqCst);
16976      if budget != 0 {
16977        self.fail_budget.store(budget - 1, Ordering::SeqCst);
16978        let registry = self.registry;
16979        let completion = std::thread::spawn(move || registry.finish_delivery(delivery, true))
16980          .join()
16981          .expect("the failing helper must not panic");
16982        assert!(
16983          completion.failed_dispatches.is_empty(),
16984          "the failed delivery must remain provisional until its publication retires"
16985        );
16986      }
16987      true
16988    }
16989  }
16990
16991  /// Accepts every delivery and, while `fail` is set, fails it synchronously
16992  /// from inside `dispatch` on the calling thread -- the option
16993  /// [`CurrentThreadTaskDriver::dispatch`] documents for an SPI host. While a
16994  /// producer is armed it also rendezvouses with the producer thread from
16995  /// inside every delivery, so exactly one runnable is queued while each
16996  /// publication is in flight.
16997  struct SustainedProducerGate {
16998    remaining: usize,
16999    queue_one: std::sync::mpsc::Sender<()>,
17000    queued: std::sync::mpsc::Receiver<()>,
17001  }
17002
17003  struct SustainedProducerCurrentThreadTaskDriver {
17004    registry: &'static CurrentThreadTaskDriverRegistry,
17005    /// Deliveries still to be failed: `usize::MAX` fails every one of them,
17006    /// `n` fails the next `n` and is healthy after, `0` is healthy.
17007    fail_budget: AtomicUsize,
17008    deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17009    producer: Mutex<Option<SustainedProducerGate>>,
17010  }
17011
17012  impl SustainedProducerCurrentThreadTaskDriver {
17013    fn new(registry: &'static CurrentThreadTaskDriverRegistry) -> Self {
17014      Self {
17015        registry,
17016        fail_budget: AtomicUsize::new(0),
17017        deliveries: Mutex::default(),
17018        producer: Mutex::default(),
17019      }
17020    }
17021
17022    fn deliveries(&self) -> Vec<CurrentThreadTaskDelivery> {
17023      self.deliveries.lock().unwrap().clone()
17024    }
17025
17026    fn arm_producer(
17027      &self,
17028      remaining: usize,
17029      queue_one: std::sync::mpsc::Sender<()>,
17030      queued: std::sync::mpsc::Receiver<()>,
17031    ) {
17032      *self.producer.lock().unwrap() = Some(SustainedProducerGate {
17033        remaining,
17034        queue_one,
17035        queued,
17036      });
17037    }
17038
17039    /// Close the gate and report how many submissions the producer was still
17040    /// willing to make.
17041    fn disarm_producer(&self) -> usize {
17042      self
17043        .producer
17044        .lock()
17045        .unwrap()
17046        .take()
17047        .map_or(0, |gate| gate.remaining)
17048    }
17049  }
17050
17051  impl CurrentThreadTaskDriver for SustainedProducerCurrentThreadTaskDriver {
17052    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17053      self.deliveries.lock().unwrap().push(delivery);
17054      {
17055        let mut slot = self.producer.lock().unwrap();
17056        if let Some(gate) = slot.as_mut()
17057          && gate.remaining != 0
17058        {
17059          gate.remaining -= 1;
17060          gate.queue_one.send(()).expect("the producer must be live");
17061          gate
17062            .queued
17063            .recv_timeout(std::time::Duration::from_secs(5))
17064            .expect("the producer must queue one runnable per publication");
17065        }
17066      }
17067      let budget = self.fail_budget.load(Ordering::SeqCst);
17068      if budget != 0 {
17069        if budget != usize::MAX {
17070          self.fail_budget.store(budget - 1, Ordering::SeqCst);
17071        }
17072        let completion = self.registry.finish_delivery(delivery, true);
17073        assert!(
17074          completion.failed_dispatches.is_empty(),
17075          "the failed delivery must remain provisional until its publication retires"
17076        );
17077      }
17078      true
17079    }
17080  }
17081
17082  impl CurrentThreadTaskDriver for FailDuringAuthoritativePanicPublication {
17083    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17084      let completion =
17085        CURRENT_THREAD_AUTHORITATIVE_PANIC_TASK_DRIVERS.finish_delivery(delivery, true);
17086      assert!(
17087        completion.failed_dispatches.is_empty(),
17088        "the failed delivery must remain provisional until its publication retires"
17089      );
17090      true
17091    }
17092  }
17093
17094  impl CurrentThreadTaskDriver for FailDuringLateRegistrationPublication {
17095    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17096      let completion =
17097        CURRENT_THREAD_LATE_REGISTRATION_TASK_DRIVERS.finish_delivery(delivery, true);
17098      assert!(
17099        completion.failed_dispatches.is_empty(),
17100        "the failed delivery must remain provisional until its publication retires"
17101      );
17102      true
17103    }
17104  }
17105
17106  impl CurrentThreadTaskDriver for FailDuringNestedUnwindPublication {
17107    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17108      let completion = CURRENT_THREAD_NESTED_UNWIND_TASK_DRIVERS.finish_delivery(delivery, true);
17109      assert!(
17110        completion.failed_dispatches.is_empty(),
17111        "the failed delivery must remain provisional until its publication retires"
17112      );
17113      true
17114    }
17115  }
17116
17117  impl CurrentThreadTaskDriver for FailDuringReplacementRepublishPublication {
17118    fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17119      let completion =
17120        CURRENT_THREAD_REPLACEMENT_FAILED_REPUBLISH_TASK_DRIVERS.finish_delivery(delivery, true);
17121      assert!(
17122        completion.failed_dispatches.is_empty(),
17123        "the failed delivery must remain provisional until its publication retires"
17124      );
17125      true
17126    }
17127  }
17128
17129  struct ReentrantCurrentThreadTaskDriver {
17130    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17131    registration: Mutex<Option<CurrentThreadTaskDriverId>>,
17132  }
17133
17134  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriver {
17135    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17136      true
17137    }
17138
17139    fn is_live(&self) -> bool {
17140      let registration = *self.registration.lock().unwrap();
17141      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
17142        registry.unregister(id);
17143      }
17144      false
17145    }
17146  }
17147
17148  struct ReentrantMultiUnregisterCurrentThreadTaskDriver {
17149    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17150    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17151  }
17152
17153  impl CurrentThreadTaskDriver for ReentrantMultiUnregisterCurrentThreadTaskDriver {
17154    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17155      true
17156    }
17157
17158    fn is_live(&self) -> bool {
17159      if let Some(registry) = self.registry.upgrade() {
17160        for registration in self.registrations.lock().unwrap().iter().copied() {
17161          let completion = registry.unregister(registration);
17162          assert!(completion.failed_dispatches.is_empty());
17163          assert!(!completion.redispatch);
17164        }
17165      }
17166      false
17167    }
17168  }
17169
17170  struct ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17171    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17172    registrations: Mutex<Vec<CurrentThreadTaskDriverId>>,
17173    drops: Arc<AtomicUsize>,
17174  }
17175
17176  impl CurrentThreadTaskDriver for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17177    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17178      if let Some(registry) = self.registry.upgrade() {
17179        for registration in self.registrations.lock().unwrap().iter().copied() {
17180          let completion = registry.unregister(registration);
17181          assert!(completion.failed_dispatches.is_empty());
17182          assert!(!completion.redispatch);
17183        }
17184      }
17185      true
17186    }
17187  }
17188
17189  impl Drop for ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
17190    fn drop(&mut self) {
17191      self.drops.fetch_add(1, Ordering::SeqCst);
17192      panic!("intentional reentrant CurrentThread task-driver destructor panic");
17193    }
17194  }
17195
17196  struct PanickingDropCurrentThreadTaskDriver {
17197    drops: Arc<AtomicUsize>,
17198  }
17199
17200  impl CurrentThreadTaskDriver for PanickingDropCurrentThreadTaskDriver {
17201    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17202      true
17203    }
17204  }
17205
17206  impl Drop for PanickingDropCurrentThreadTaskDriver {
17207    fn drop(&mut self) {
17208      self.drops.fetch_add(1, Ordering::SeqCst);
17209      panic!("intentional CurrentThread task-driver destructor panic");
17210    }
17211  }
17212
17213  struct ReentrantCurrentThreadTaskDriverDrop {
17214    registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17215    fallback: CurrentThreadTaskDriverId,
17216  }
17217
17218  impl CurrentThreadTaskDriver for ReentrantCurrentThreadTaskDriverDrop {
17219    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17220      true
17221    }
17222  }
17223
17224  impl Drop for ReentrantCurrentThreadTaskDriverDrop {
17225    fn drop(&mut self) {
17226      if let Some(registry) = self.registry.upgrade() {
17227        registry.unregister(self.fallback);
17228      }
17229    }
17230  }
17231
17232  impl CurrentThreadTaskDriver for TestCurrentThreadTaskDriver {
17233    fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17234      assert!(
17235        !matches!(
17236          self.behavior,
17237          TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep
17238        ),
17239        "intentional task-driver dispatch panic"
17240      );
17241      self.dispatched.store(true, Ordering::SeqCst);
17242      true
17243    }
17244
17245    fn is_live(&self) -> bool {
17246      assert!(
17247        !matches!(
17248          self.behavior,
17249          TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep
17250        ),
17251        "intentional task-driver liveness panic"
17252      );
17253      true
17254    }
17255
17256    fn on_swept(&self) {
17257      assert!(
17258        matches!(self.behavior, TestCurrentThreadTaskDriverBehavior::Healthy),
17259        "intentional task-driver sweep panic"
17260      );
17261    }
17262  }
17263
17264  #[test]
17265  fn current_thread_task_driver_panics_fall_back_to_a_live_host() {
17266    let registry = CurrentThreadTaskDriverRegistry::default();
17267    let fallback = Arc::new(TestCurrentThreadTaskDriver {
17268      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
17269      dispatched: AtomicBool::new(false),
17270    });
17271    registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
17272    registry.register(Arc::new(TestCurrentThreadTaskDriver {
17273      behavior: TestCurrentThreadTaskDriverBehavior::PanicInLivenessAndSweep,
17274      dispatched: AtomicBool::new(false),
17275    }));
17276    registry.register(Arc::new(TestCurrentThreadTaskDriver {
17277      behavior: TestCurrentThreadTaskDriverBehavior::PanicInDispatchAndSweep,
17278      dispatched: AtomicBool::new(false),
17279    }));
17280
17281    assert_eq!(
17282      registry.dispatch(37),
17283      CurrentThreadHostDispatchResult::Accepted,
17284      "a panicking newest host must fall back to a live host"
17285    );
17286    assert!(fallback.dispatched.load(Ordering::SeqCst));
17287  }
17288
17289  #[test]
17290  fn current_thread_registry_publication_unwind_releases_barrier_and_can_retry() {
17291    let registry = CurrentThreadTaskDriverRegistry::default();
17292    let first = Arc::new(ManualCurrentThreadTaskDriver::default());
17293    let second = Arc::new(ManualCurrentThreadTaskDriver::default());
17294    registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
17295    registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
17296    registry
17297      .next_delivery
17298      .store(u64::MAX - 1, Ordering::Relaxed);
17299
17300    let publication = catch_unwind(AssertUnwindSafe(|| registry.dispatch(38)));
17301    assert!(
17302      publication.is_err(),
17303      "delivery-id exhaustion on the second host must unwind the publication"
17304    );
17305    {
17306      let state = registry
17307        .state
17308        .lock()
17309        .unwrap_or_else(std::sync::PoisonError::into_inner);
17310      assert!(
17311        !state.dispatches.contains_key(&38),
17312        "unwind cleanup must release the exact registry publication barrier"
17313      );
17314      assert!(
17315        state
17316          .entries
17317          .values()
17318          .all(|entry| entry.delivery.is_none() && entry.pending.is_none()),
17319        "failed transactional reservation must leave every physical host slot clean"
17320      );
17321    }
17322    assert!(first.deliveries().is_empty());
17323    assert!(second.deliveries().is_empty());
17324
17325    registry.next_delivery.store(0, Ordering::Relaxed);
17326    assert_eq!(
17327      registry.dispatch(38),
17328      CurrentThreadHostDispatchResult::Accepted
17329    );
17330    let first_delivery = first.deliveries()[0];
17331    let second_delivery = second.deliveries()[0];
17332    assert_eq!(
17333      registry.claim_delivery(first_delivery.capability()),
17334      Some(38)
17335    );
17336    assert_eq!(
17337      registry.claim_delivery(second_delivery.capability()),
17338      Some(38)
17339    );
17340    registry.mark_serviced(38);
17341    assert!(
17342      registry
17343        .finish_delivery(first_delivery, false)
17344        .failed_dispatches
17345        .is_empty()
17346    );
17347    assert!(
17348      registry
17349        .finish_delivery(second_delivery, false)
17350        .failed_dispatches
17351        .is_empty()
17352    );
17353
17354    assert_eq!(
17355      registry.dispatch(39),
17356      CurrentThreadHostDispatchResult::Accepted
17357    );
17358    let first_later = first.deliveries()[1];
17359    let second_later = second.deliveries()[1];
17360    assert_eq!(registry.claim_delivery(first_later.capability()), Some(39));
17361    assert_eq!(registry.claim_delivery(second_later.capability()), Some(39));
17362    registry.mark_serviced(39);
17363    assert!(
17364      registry
17365        .finish_delivery(first_later, false)
17366        .failed_dispatches
17367        .is_empty()
17368    );
17369    assert!(
17370      registry
17371        .finish_delivery(second_later, false)
17372        .failed_dispatches
17373        .is_empty()
17374    );
17375    assert!(
17376      registry
17377        .state
17378        .lock()
17379        .unwrap_or_else(std::sync::PoisonError::into_inner)
17380        .dispatches
17381        .is_empty()
17382    );
17383    let state = registry
17384      .state
17385      .lock()
17386      .unwrap_or_else(std::sync::PoisonError::into_inner);
17387    assert!(
17388      state
17389        .entries
17390        .values()
17391        .all(|entry| entry.delivery.is_none() && entry.pending.is_none())
17392    );
17393  }
17394
17395  #[test]
17396  fn current_thread_registry_rearms_failed_dispatch_for_new_host() {
17397    let registry = CurrentThreadTaskDriverRegistry::default();
17398    let failed = Arc::new(ManualCurrentThreadTaskDriver::default());
17399    let failed_id = registry.register(Arc::clone(&failed) as Arc<dyn CurrentThreadTaskDriver>);
17400    let dispatch = 40;
17401
17402    assert_eq!(
17403      registry.dispatch(dispatch),
17404      CurrentThreadHostDispatchResult::Accepted
17405    );
17406    let failed_delivery = failed.deliveries()[0];
17407    let failure = current_thread_dispatch_failure(dispatch, 0);
17408    assert_eq!(
17409      registry
17410        .finish_delivery(failed_delivery, true)
17411        .failed_dispatches,
17412      vec![failure]
17413    );
17414    registry.unregister(failed_id);
17415
17416    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
17417    registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
17418    assert!(
17419      registry.rearm_failed_publication(failure),
17420      "a closed unreferenced dispatch must reopen for its armed publication owner"
17421    );
17422    assert_eq!(
17423      registry.dispatch(dispatch),
17424      CurrentThreadHostDispatchResult::Accepted
17425    );
17426    let replacement_delivery = replacement.deliveries()[0];
17427    assert_eq!(replacement_delivery.dispatch, dispatch);
17428    assert_eq!(
17429      registry.claim_delivery(replacement_delivery.capability()),
17430      Some(dispatch)
17431    );
17432    registry.mark_serviced(dispatch);
17433    assert!(
17434      registry
17435        .finish_delivery(replacement_delivery, false)
17436        .failed_dispatches
17437        .is_empty()
17438    );
17439    assert!(registry.state.lock().unwrap().dispatches.is_empty());
17440  }
17441
17442  #[test]
17443  fn current_thread_dispatch_races_all_live_hosts_with_one_capability() {
17444    struct RacingTaskDriver {
17445      executor: Arc<CurrentThreadExecutor>,
17446      drives: bool,
17447      dispatches: Mutex<Vec<u64>>,
17448    }
17449
17450    impl CurrentThreadTaskDriver for RacingTaskDriver {
17451      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17452        self.dispatches.lock().unwrap().push(delivery.dispatch);
17453        if self.drives {
17454          self
17455            .executor
17456            .drive_host_turn_for_dispatch(delivery.dispatch);
17457        }
17458        true
17459      }
17460    }
17461
17462    let executor = Arc::new(CurrentThreadExecutor::with_task_dispatch(
17463      Arc::new(RuntimeMetrics::default()),
17464      accept_current_thread_host_dispatch,
17465    ));
17466    let polls = Arc::new(AtomicUsize::new(0));
17467    let task_polls = Arc::clone(&polls);
17468    let scheduler = Arc::clone(&executor);
17469    let (runnable, task) = async_task::spawn(
17470      async move {
17471        task_polls.fetch_add(1, Ordering::SeqCst);
17472      },
17473      move |runnable| scheduler.schedule(runnable),
17474    );
17475    executor.schedule(runnable);
17476    let dispatch = current_thread_dispatch(&executor);
17477
17478    let registry = CurrentThreadTaskDriverRegistry::default();
17479    let responsive = Arc::new(RacingTaskDriver {
17480      executor: Arc::clone(&executor),
17481      drives: true,
17482      dispatches: Mutex::new(Vec::new()),
17483    });
17484    let starved = Arc::new(RacingTaskDriver {
17485      executor: Arc::clone(&executor),
17486      drives: false,
17487      dispatches: Mutex::new(Vec::new()),
17488    });
17489    registry.register(Arc::clone(&responsive) as Arc<dyn CurrentThreadTaskDriver>);
17490    // The newest host is intentionally live but unresponsive.
17491    registry.register(Arc::clone(&starved) as Arc<dyn CurrentThreadTaskDriver>);
17492
17493    assert_eq!(
17494      registry.dispatch(dispatch),
17495      CurrentThreadHostDispatchResult::Accepted
17496    );
17497    assert_eq!(
17498      responsive.dispatches.lock().unwrap().as_slice(),
17499      &[dispatch]
17500    );
17501    assert_eq!(starved.dispatches.lock().unwrap().as_slice(), &[dispatch]);
17502    assert_eq!(
17503      polls.load(Ordering::SeqCst),
17504      1,
17505      "the responsive older host must win even when the newest host is starved"
17506    );
17507
17508    // The delayed callback from the starved host carries the same consumed
17509    // capability and cannot poll the task a second time.
17510    executor.drive_host_turn_for_dispatch(dispatch);
17511    assert_eq!(polls.load(Ordering::SeqCst), 1);
17512    futures::executor::block_on(task);
17513  }
17514
17515  #[test]
17516  fn current_thread_task_driver_indexes_preserve_large_pool_lifecycle() {
17517    const HOSTS: usize = 1_024;
17518
17519    let registry = CurrentThreadTaskDriverRegistry::default();
17520    let mut ids = Vec::with_capacity(HOSTS);
17521    let mut drivers = Vec::with_capacity(HOSTS);
17522    for _ in 0..HOSTS {
17523      let driver = Arc::new(ManualCurrentThreadTaskDriver::default());
17524      ids.push(registry.register(Arc::clone(&driver) as Arc<dyn CurrentThreadTaskDriver>));
17525      drivers.push(driver);
17526    }
17527
17528    let dispatch = u64::MAX - 300;
17529    assert_eq!(
17530      registry.dispatch(dispatch),
17531      CurrentThreadHostDispatchResult::Accepted
17532    );
17533    let deliveries = drivers
17534      .iter()
17535      .map(|driver| driver.deliveries()[0])
17536      .collect::<Vec<_>>();
17537    {
17538      let state = registry
17539        .state
17540        .lock()
17541        .unwrap_or_else(std::sync::PoisonError::into_inner);
17542      assert_eq!(state.delivery_drivers.len(), HOSTS);
17543      assert_eq!(
17544        state
17545          .dispatches
17546          .get(&dispatch)
17547          .map(|state| state.references),
17548        Some(HOSTS)
17549      );
17550      assert_eq!(
17551        state
17552          .ordered_snapshot()
17553          .iter()
17554          .map(|(id, _)| *id)
17555          .collect::<Vec<_>>(),
17556        ids,
17557        "keyed driver lookup must retain registration order"
17558      );
17559    }
17560
17561    for delivery in &deliveries {
17562      assert_eq!(
17563        registry.claim_delivery(delivery.capability()),
17564        Some(dispatch)
17565      );
17566    }
17567    registry.mark_serviced(dispatch);
17568    for delivery in deliveries {
17569      assert!(
17570        registry
17571          .finish_delivery(delivery, false)
17572          .failed_dispatches
17573          .is_empty()
17574      );
17575    }
17576    {
17577      let state = registry
17578        .state
17579        .lock()
17580        .unwrap_or_else(std::sync::PoisonError::into_inner);
17581      assert!(state.delivery_drivers.is_empty());
17582      assert!(state.dispatches.is_empty());
17583    }
17584
17585    let removed = ids.iter().copied().step_by(2).collect::<FxHashSet<_>>();
17586    for id in &removed {
17587      let completion = registry.unregister(*id);
17588      assert!(completion.failed_dispatches.is_empty());
17589      assert!(!completion.redispatch);
17590    }
17591    let expected = ids
17592      .iter()
17593      .copied()
17594      .filter(|id| !removed.contains(id))
17595      .collect::<Vec<_>>();
17596    let live = registry.live().live;
17597    assert_eq!(
17598      live
17599        .as_slice()
17600        .iter()
17601        .map(|(id, _)| *id)
17602        .collect::<Vec<_>>(),
17603      expected,
17604      "O(1) removals must preserve the relative order of surviving host registrations"
17605    );
17606    drop(live);
17607
17608    for id in expected {
17609      registry.unregister(id);
17610    }
17611    let state = registry
17612      .state
17613      .lock()
17614      .unwrap_or_else(std::sync::PoisonError::into_inner);
17615    assert!(state.entries.is_empty());
17616    assert!(state.oldest.is_none());
17617    assert!(state.newest.is_none());
17618  }
17619
17620  #[test]
17621  fn current_thread_host_failure_waits_for_delayed_and_healthy_attempts() {
17622    let registry = CurrentThreadTaskDriverRegistry::default();
17623    let failing = Arc::new(ManualCurrentThreadTaskDriver::default());
17624    let delayed = Arc::new(ManualCurrentThreadTaskDriver::default());
17625    let healthy = Arc::new(ManualCurrentThreadTaskDriver::default());
17626    registry.register(Arc::clone(&failing) as Arc<dyn CurrentThreadTaskDriver>);
17627    registry.register(Arc::clone(&delayed) as Arc<dyn CurrentThreadTaskDriver>);
17628    registry.register(Arc::clone(&healthy) as Arc<dyn CurrentThreadTaskDriver>);
17629
17630    let dispatch = 41;
17631    assert_eq!(
17632      registry.dispatch(dispatch),
17633      CurrentThreadHostDispatchResult::Accepted
17634    );
17635    let failing_delivery = failing.deliveries()[0];
17636    let delayed_delivery = delayed.deliveries()[0];
17637    let healthy_delivery = healthy.deliveries()[0];
17638    assert_eq!(failing_delivery.dispatch, dispatch);
17639    assert_eq!(delayed_delivery.dispatch, dispatch);
17640    assert_eq!(healthy_delivery.dispatch, dispatch);
17641
17642    assert!(
17643      registry
17644        .finish_delivery(failing_delivery, true)
17645        .failed_dispatches
17646        .is_empty(),
17647      "one host failure must not recover while other accepted attempts remain"
17648    );
17649    assert_eq!(
17650      registry.claim_delivery(healthy_delivery.capability()),
17651      Some(dispatch)
17652    );
17653    registry.mark_serviced(dispatch);
17654    assert!(
17655      registry
17656        .finish_delivery(healthy_delivery, false)
17657        .failed_dispatches
17658        .is_empty()
17659    );
17660    assert_eq!(
17661      registry.claim_delivery(delayed_delivery.capability()),
17662      None,
17663      "a delayed losing delivery must become stale after another host services the dispatch"
17664    );
17665    assert!(
17666      registry
17667        .finish_delivery(delayed_delivery, true)
17668        .failed_dispatches
17669        .is_empty(),
17670      "a stale losing failure must not recover or cancel a serviced dispatch"
17671    );
17672    assert!(
17673      registry.state.lock().unwrap().dispatches.is_empty(),
17674      "all per-dispatch acknowledgement state must retire"
17675    );
17676  }
17677
17678  #[test]
17679  fn current_thread_unclaimed_callback_success_is_delivery_failure() {
17680    let registry = CurrentThreadTaskDriverRegistry::default();
17681    let no_op = Arc::new(ManualCurrentThreadTaskDriver::default());
17682    registry.register(Arc::clone(&no_op) as Arc<dyn CurrentThreadTaskDriver>);
17683
17684    let dispatch = 56;
17685    assert_eq!(
17686      registry.dispatch(dispatch),
17687      CurrentThreadHostDispatchResult::Accepted
17688    );
17689    let delivery = no_op.deliveries()[0];
17690    let completion = registry.finish_delivery(delivery, false);
17691    let failure = current_thread_dispatch_failure(dispatch, 0);
17692    assert_eq!(
17693      completion.failed_dispatches,
17694      vec![failure],
17695      "a callback return without an exact capability claim must enter recovery"
17696    );
17697    assert!(
17698      registry
17699        .state
17700        .lock()
17701        .unwrap()
17702        .dispatches
17703        .get(&dispatch)
17704        .is_some_and(CurrentThreadDispatchState::is_cancelling)
17705    );
17706    registry.finish_cancellation(failure);
17707    assert!(registry.state.lock().unwrap().dispatches.is_empty());
17708  }
17709
17710  #[test]
17711  fn current_thread_publication_retains_accepted_failure_until_provisionals_resolve() {
17712    struct FailDuringPublication {
17713      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17714    }
17715
17716    impl CurrentThreadTaskDriver for FailDuringPublication {
17717      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17718        let completion = self
17719          .registry
17720          .upgrade()
17721          .expect("the registry must outlive publication")
17722          .finish_delivery(delivery, true);
17723        assert!(
17724          completion.failed_dispatches.is_empty(),
17725          "failure must remain provisional until the broadcast closes"
17726        );
17727        true
17728      }
17729    }
17730
17731    struct RejectProvisional;
17732
17733    impl CurrentThreadTaskDriver for RejectProvisional {
17734      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17735        false
17736      }
17737    }
17738
17739    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
17740    registry.register(Arc::new(RejectProvisional));
17741    // Newest-first publication calls the accepted-then-failing host while the
17742    // rejecting host still has an unresolved provisional delivery.
17743    registry.register(Arc::new(FailDuringPublication {
17744      registry: Arc::downgrade(&registry),
17745    }));
17746
17747    let failure = current_thread_dispatch_failure(57, 0);
17748    assert_eq!(
17749      registry.dispatch(57),
17750      CurrentThreadHostDispatchResult::Failed(failure),
17751      "the final provisional rejection must expose the earlier accepted failure"
17752    );
17753    assert!(
17754      registry
17755        .state
17756        .lock()
17757        .unwrap()
17758        .dispatches
17759        .get(&57)
17760        .is_some_and(CurrentThreadDispatchState::is_cancelling)
17761    );
17762    assert_eq!(
17763      registry.dispatch(57),
17764      CurrentThreadHostDispatchResult::Failed(failure),
17765      "a racing replacement publication must preserve terminal failure until cancellation runs"
17766    );
17767    registry.finish_cancellation(failure);
17768  }
17769
17770  #[test]
17771  fn current_thread_publication_distinguishes_no_host_acceptance_from_accepted_failure() {
17772    struct RejectProvisional;
17773
17774    impl CurrentThreadTaskDriver for RejectProvisional {
17775      fn dispatch(&self, _delivery: CurrentThreadTaskDelivery) -> bool {
17776        false
17777      }
17778    }
17779
17780    let registry = CurrentThreadTaskDriverRegistry::default();
17781    registry.register(Arc::new(RejectProvisional));
17782    registry.register(Arc::new(RejectProvisional));
17783
17784    assert_eq!(
17785      registry.dispatch(58),
17786      CurrentThreadHostDispatchResult::Unavailable
17787    );
17788    assert!(
17789      registry.state.lock().unwrap().dispatches.is_empty(),
17790      "a broadcast with no accepted attempt must not enter failure recovery"
17791    );
17792  }
17793
17794  #[test]
17795  fn current_thread_blocked_host_keeps_one_delivery_and_only_the_latest_pending_capability() {
17796    let registry = CurrentThreadTaskDriverRegistry::default();
17797    let blocked = Arc::new(ManualCurrentThreadTaskDriver::default());
17798    let blocked_id = registry.register(Arc::clone(&blocked) as Arc<dyn CurrentThreadTaskDriver>);
17799
17800    assert_eq!(
17801      registry.dispatch(1),
17802      CurrentThreadHostDispatchResult::Accepted
17803    );
17804    let first = blocked.deliveries()[0];
17805    for dispatch in 2..=256 {
17806      assert_eq!(
17807        registry.dispatch(dispatch),
17808        CurrentThreadHostDispatchResult::Accepted
17809      );
17810      assert_eq!(
17811        blocked.deliveries().len(),
17812        1,
17813        "a blocked host must never receive a second queued or in-flight callback"
17814      );
17815    }
17816    {
17817      let state = registry.state.lock().unwrap();
17818      let entry = state
17819        .entries
17820        .get(&blocked_id)
17821        .expect("the blocked host must remain registered");
17822      assert_eq!(entry.pending, Some(256));
17823      assert_eq!(
17824        state.dispatches.len(),
17825        2,
17826        "only the in-flight and latest pending dispatches may retain registry state"
17827      );
17828    }
17829
17830    assert_eq!(registry.claim_delivery(first.capability()), Some(1));
17831    registry.mark_serviced(1);
17832    let completion = registry.finish_delivery(first, false);
17833    assert!(completion.failed_dispatches.is_empty());
17834    assert!(completion.redispatch);
17835    assert_eq!(
17836      registry.dispatch(256),
17837      CurrentThreadHostDispatchResult::Accepted
17838    );
17839    let deliveries = blocked.deliveries();
17840    assert_eq!(deliveries.len(), 2);
17841    assert_eq!(
17842      deliveries[1].dispatch, 256,
17843      "completion must schedule exactly the latest coalesced capability"
17844    );
17845    assert_eq!(registry.claim_delivery(first.capability()), None);
17846    assert_eq!(
17847      registry.claim_delivery(deliveries[1].capability()),
17848      Some(256)
17849    );
17850    registry.mark_serviced(256);
17851    assert!(
17852      registry
17853        .finish_delivery(deliveries[1], false)
17854        .failed_dispatches
17855        .is_empty()
17856    );
17857    assert!(registry.state.lock().unwrap().dispatches.is_empty());
17858  }
17859
17860  #[test]
17861  fn current_thread_coalesced_follow_up_rejection_is_reported_as_delivery_failure() {
17862    struct RejectAfterFirst {
17863      calls: AtomicUsize,
17864      deliveries: Mutex<Vec<CurrentThreadTaskDelivery>>,
17865    }
17866
17867    impl CurrentThreadTaskDriver for RejectAfterFirst {
17868      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17869        self.deliveries.lock().unwrap().push(delivery);
17870        self.calls.fetch_add(1, Ordering::SeqCst) == 0
17871      }
17872    }
17873
17874    let registry = CurrentThreadTaskDriverRegistry::default();
17875    let host = Arc::new(RejectAfterFirst {
17876      calls: AtomicUsize::new(0),
17877      deliveries: Mutex::new(Vec::new()),
17878    });
17879    registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
17880    assert_eq!(
17881      registry.dispatch(11),
17882      CurrentThreadHostDispatchResult::Accepted
17883    );
17884    let first = host.deliveries.lock().unwrap()[0];
17885    assert_eq!(
17886      registry.dispatch(12),
17887      CurrentThreadHostDispatchResult::Accepted
17888    );
17889
17890    assert_eq!(registry.claim_delivery(first.capability()), Some(11));
17891    registry.mark_serviced(11);
17892    let completion = registry.finish_delivery(first, false);
17893    assert!(completion.redispatch);
17894    let failure = current_thread_dispatch_failure(12, 0);
17895    assert_eq!(
17896      registry.dispatch(12),
17897      CurrentThreadHostDispatchResult::Failed(failure),
17898      "an accepted coalesced capability must report failure if its eventual host queue rejects it"
17899    );
17900    assert!(
17901      registry
17902        .state
17903        .lock()
17904        .unwrap()
17905        .dispatches
17906        .get(&12)
17907        .is_some_and(CurrentThreadDispatchState::is_cancelling)
17908    );
17909    registry.finish_cancellation(failure);
17910    assert!(registry.state.lock().unwrap().dispatches.is_empty());
17911  }
17912
17913  #[test]
17914  fn current_thread_unregister_reregister_rejects_old_delivery_generation() {
17915    let registry = CurrentThreadTaskDriverRegistry::default();
17916    let old = Arc::new(ManualCurrentThreadTaskDriver::default());
17917    let old_id = registry.register(Arc::clone(&old) as Arc<dyn CurrentThreadTaskDriver>);
17918    assert_eq!(
17919      registry.dispatch(73),
17920      CurrentThreadHostDispatchResult::Accepted
17921    );
17922    let old_delivery = old.deliveries()[0];
17923
17924    let completion = registry.unregister(old_id);
17925    let failure = current_thread_dispatch_failure(73, 0);
17926    assert_eq!(
17927      completion.failed_dispatches,
17928      vec![failure],
17929      "unregistering the final accepting host must retire its exact dispatch"
17930    );
17931    registry.finish_cancellation(failure);
17932    let replacement = Arc::new(ManualCurrentThreadTaskDriver::default());
17933    let replacement_id =
17934      registry.register(Arc::clone(&replacement) as Arc<dyn CurrentThreadTaskDriver>);
17935    assert_ne!(replacement_id, old_id);
17936    assert_eq!(
17937      registry.dispatch(74),
17938      CurrentThreadHostDispatchResult::Accepted
17939    );
17940    let replacement_delivery = replacement.deliveries()[0];
17941    assert_ne!(replacement_delivery.capability(), old_delivery.capability());
17942
17943    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
17944    assert!(
17945      registry
17946        .finish_delivery(old_delivery, true)
17947        .failed_dispatches
17948        .is_empty()
17949    );
17950    assert!(
17951      registry
17952        .fail_delivery(old_delivery.capability())
17953        .failed_dispatches
17954        .is_empty()
17955    );
17956    assert_eq!(
17957      registry.claim_delivery(replacement_delivery.capability()),
17958      Some(74)
17959    );
17960    registry.mark_serviced(74);
17961    assert!(
17962      registry
17963        .finish_delivery(replacement_delivery, false)
17964        .failed_dispatches
17965        .is_empty()
17966    );
17967    assert!(registry.state.lock().unwrap().dispatches.is_empty());
17968  }
17969
17970  #[test]
17971  fn current_thread_rejected_publication_reports_concurrent_pending_failure() {
17972    struct QueuePendingThenReject {
17973      registry: std::sync::Weak<CurrentThreadTaskDriverRegistry>,
17974    }
17975
17976    impl CurrentThreadTaskDriver for QueuePendingThenReject {
17977      fn dispatch(&self, delivery: CurrentThreadTaskDelivery) -> bool {
17978        assert_eq!(delivery.dispatch, 81);
17979        let completion = self
17980          .registry
17981          .upgrade()
17982          .expect("the registry must outlive publication")
17983          .dispatch_with_completion(82);
17984        assert_eq!(completion.result, CurrentThreadHostDispatchResult::Accepted);
17985        assert!(completion.failed_dispatches.is_empty());
17986        false
17987      }
17988    }
17989
17990    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
17991    registry.register(Arc::new(QueuePendingThenReject {
17992      registry: Arc::downgrade(&registry),
17993    }));
17994
17995    let completion = registry.dispatch_with_completion(81);
17996    assert_eq!(
17997      completion.result,
17998      CurrentThreadHostDispatchResult::Unavailable
17999    );
18000    let failure = current_thread_dispatch_failure(82, 0);
18001    assert_eq!(
18002      completion.failed_dispatches,
18003      vec![failure],
18004      "rejecting the physical slot must surface the accepted coalesced dispatch it retired"
18005    );
18006    assert!(
18007      registry
18008        .state
18009        .lock()
18010        .unwrap()
18011        .dispatches
18012        .get(&82)
18013        .is_some_and(CurrentThreadDispatchState::is_cancelling)
18014    );
18015    registry.finish_cancellation(failure);
18016    assert!(registry.state.lock().unwrap().dispatches.is_empty());
18017  }
18018
18019  #[test]
18020  fn current_thread_shutdown_retires_pending_state_without_breaking_single_flight() {
18021    let registry = CurrentThreadTaskDriverRegistry::default();
18022    let host = Arc::new(ManualCurrentThreadTaskDriver::default());
18023    let host_id = registry.register(Arc::clone(&host) as Arc<dyn CurrentThreadTaskDriver>);
18024    assert_eq!(
18025      registry.dispatch(91),
18026      CurrentThreadHostDispatchResult::Accepted
18027    );
18028    let old_delivery = host.deliveries()[0];
18029    assert_eq!(
18030      registry.dispatch(92),
18031      CurrentThreadHostDispatchResult::Accepted
18032    );
18033
18034    registry.retire_for_shutdown();
18035    {
18036      let state = registry.state.lock().unwrap();
18037      assert!(state.dispatches.is_empty());
18038      let entry = state
18039        .entries
18040        .get(&host_id)
18041        .expect("the host must remain registered");
18042      assert_eq!(entry.pending, None);
18043      assert!(
18044        entry.delivery.is_some(),
18045        "the physical callback remains single-flight until it actually retires"
18046      );
18047    }
18048    assert_eq!(registry.claim_delivery(old_delivery.capability()), None);
18049
18050    assert_eq!(
18051      registry.dispatch(93),
18052      CurrentThreadHostDispatchResult::Accepted
18053    );
18054    assert_eq!(host.deliveries().len(), 1);
18055    let completion = registry.finish_delivery(old_delivery, true);
18056    assert!(
18057      completion.failed_dispatches.is_empty(),
18058      "a retired-generation failure must not cancel restarted work"
18059    );
18060    assert!(completion.redispatch);
18061    assert_eq!(
18062      registry.dispatch(93),
18063      CurrentThreadHostDispatchResult::Accepted
18064    );
18065    let restarted_delivery = host.deliveries()[1];
18066    assert_eq!(restarted_delivery.dispatch, 93);
18067    assert_eq!(
18068      registry.claim_delivery(restarted_delivery.capability()),
18069      Some(93)
18070    );
18071    registry.mark_serviced(93);
18072    assert!(
18073      registry
18074        .finish_delivery(restarted_delivery, false)
18075        .failed_dispatches
18076        .is_empty()
18077    );
18078
18079    registry.unregister(host_id);
18080    let state = registry.state.lock().unwrap();
18081    assert!(state.entries.is_empty());
18082    assert!(state.dispatches.is_empty());
18083  }
18084
18085  #[test]
18086  fn current_thread_task_driver_callbacks_and_drops_run_outside_registry_lock() {
18087    use std::{sync::mpsc, time::Duration};
18088
18089    let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18090    let fallback = Arc::new(TestCurrentThreadTaskDriver {
18091      behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18092      dispatched: AtomicBool::new(false),
18093    });
18094    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18095    let reentrant = Arc::new(ReentrantCurrentThreadTaskDriver {
18096      registry: Arc::downgrade(&registry),
18097      registration: Mutex::new(None),
18098    });
18099    let reentrant_id =
18100      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18101    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
18102
18103    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
18104    let selection_registry = Arc::clone(&registry);
18105    let selection_thread = std::thread::spawn(move || {
18106      selection_tx
18107        .send(selection_registry.current().map(|(id, _)| id))
18108        .unwrap();
18109    });
18110    assert_eq!(
18111      selection_rx
18112        .recv_timeout(Duration::from_secs(10))
18113        .expect("is_live re-entry deadlocked"),
18114      Some(fallback_id)
18115    );
18116    selection_thread.join().unwrap();
18117
18118    let drop_driver: Arc<dyn CurrentThreadTaskDriver> =
18119      Arc::new(ReentrantCurrentThreadTaskDriverDrop {
18120        registry: Arc::downgrade(&registry),
18121        fallback: fallback_id,
18122      });
18123    let drop_id = registry.register(Arc::clone(&drop_driver));
18124    drop(drop_driver);
18125
18126    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
18127    let drop_registry = Arc::clone(&registry);
18128    let drop_thread = std::thread::spawn(move || {
18129      drop_registry.unregister(drop_id);
18130      drop_tx.send(()).unwrap();
18131    });
18132    drop_rx
18133      .recv_timeout(Duration::from_secs(10))
18134      .expect("driver destructor re-entry deadlocked");
18135    drop_thread.join().unwrap();
18136    assert!(
18137      registry.current().is_none(),
18138      "the destructor must unregister the fallback"
18139    );
18140  }
18141
18142  #[test]
18143  fn current_thread_task_driver_registry_snapshot_contains_each_driver_drop() {
18144    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
18145
18146    if std::env::var_os(CHILD_ENV).is_some() {
18147      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18148      let fallback = Arc::new(TestCurrentThreadTaskDriver {
18149        behavior: TestCurrentThreadTaskDriverBehavior::Healthy,
18150        dispatched: AtomicBool::new(false),
18151      });
18152      let fallback_id =
18153        registry.register(Arc::clone(&fallback) as Arc<dyn CurrentThreadTaskDriver>);
18154      let reentrant = Arc::new(ReentrantMultiUnregisterCurrentThreadTaskDriver {
18155        registry: Arc::downgrade(&registry),
18156        registrations: Mutex::new(Vec::new()),
18157      });
18158      registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18159
18160      let first_drops = Arc::new(AtomicUsize::new(0));
18161      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18162        drops: Arc::clone(&first_drops),
18163      });
18164      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18165      drop(first);
18166
18167      let second_drops = Arc::new(AtomicUsize::new(0));
18168      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18169        drops: Arc::clone(&second_drops),
18170      });
18171      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18172      drop(second);
18173      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
18174
18175      assert_eq!(
18176        registry.current().map(|(id, _)| id),
18177        Some(fallback_id),
18178        "selection must retry after reentrant removals and preserve the healthy fallback"
18179      );
18180      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18181      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18182      let state = registry
18183        .state
18184        .lock()
18185        .unwrap_or_else(std::sync::PoisonError::into_inner);
18186      assert_eq!(state.entries.len(), 1);
18187      assert!(state.entries.contains_key(&fallback_id));
18188      assert!(state.dispatches.is_empty());
18189      return;
18190    }
18191
18192    let output = std::process::Command::new(std::env::current_exe().unwrap())
18193      .arg("--exact")
18194      .arg(
18195        "async_runtime::tests::current_thread_task_driver_registry_snapshot_contains_each_driver_drop",
18196      )
18197      .arg("--nocapture")
18198      .env(CHILD_ENV, "1")
18199      .output()
18200      .expect("the hostile task-driver snapshot subprocess test must start");
18201    assert!(
18202      output.status.success(),
18203      "reentrant liveness removal must not combine task-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18204      output.status.code(),
18205      String::from_utf8_lossy(&output.stdout),
18206      String::from_utf8_lossy(&output.stderr)
18207    );
18208  }
18209
18210  #[test]
18211  fn current_thread_task_driver_dispatch_contains_each_delivery_driver_drop() {
18212    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TASK_DRIVER_DELIVERY_DROP_PANIC_CHILD";
18213
18214    if std::env::var_os(CHILD_ENV).is_some() {
18215      let registry = Arc::new(CurrentThreadTaskDriverRegistry::default());
18216
18217      let first_drops = Arc::new(AtomicUsize::new(0));
18218      let first = Arc::new(PanickingDropCurrentThreadTaskDriver {
18219        drops: Arc::clone(&first_drops),
18220      });
18221      let first_id = registry.register(Arc::clone(&first) as Arc<dyn CurrentThreadTaskDriver>);
18222      drop(first);
18223
18224      let second_drops = Arc::new(AtomicUsize::new(0));
18225      let second = Arc::new(PanickingDropCurrentThreadTaskDriver {
18226        drops: Arc::clone(&second_drops),
18227      });
18228      let second_id = registry.register(Arc::clone(&second) as Arc<dyn CurrentThreadTaskDriver>);
18229      drop(second);
18230
18231      let reentrant_drops = Arc::new(AtomicUsize::new(0));
18232      let reentrant = Arc::new(ReentrantDispatchMultiUnregisterCurrentThreadTaskDriver {
18233        registry: Arc::downgrade(&registry),
18234        registrations: Mutex::new(Vec::new()),
18235        drops: Arc::clone(&reentrant_drops),
18236      });
18237      let reentrant_id =
18238        registry.register(Arc::clone(&reentrant) as Arc<dyn CurrentThreadTaskDriver>);
18239      *reentrant.registrations.lock().unwrap() = vec![reentrant_id, second_id, first_id];
18240      drop(reentrant);
18241
18242      let dispatch = 59;
18243      let failure = current_thread_dispatch_failure(dispatch, 0);
18244      assert_eq!(
18245        registry.dispatch(dispatch),
18246        CurrentThreadHostDispatchResult::Failed(failure),
18247        "reentrant removal of every provisional host must report accepted delivery failure"
18248      );
18249      assert_eq!(reentrant_drops.load(Ordering::SeqCst), 1);
18250      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
18251      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
18252      registry.finish_cancellation(failure);
18253      let state = registry
18254        .state
18255        .lock()
18256        .unwrap_or_else(std::sync::PoisonError::into_inner);
18257      assert!(state.entries.is_empty());
18258      assert!(state.dispatches.is_empty());
18259      return;
18260    }
18261
18262    let output = std::process::Command::new(std::env::current_exe().unwrap())
18263      .arg("--exact")
18264      .arg(
18265        "async_runtime::tests::current_thread_task_driver_dispatch_contains_each_delivery_driver_drop",
18266      )
18267      .arg("--nocapture")
18268      .env(CHILD_ENV, "1")
18269      .output()
18270      .expect("the hostile task-driver delivery subprocess test must start");
18271    assert!(
18272      output.status.success(),
18273      "reentrant dispatch removal must not combine delivery-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
18274      output.status.code(),
18275      String::from_utf8_lossy(&output.stdout),
18276      String::from_utf8_lossy(&output.stderr)
18277    );
18278  }
18279
18280  #[test]
18281  fn dropping_join_handle_detaches_task_like_tokio() {
18282    let completed = Arc::new(AtomicBool::new(false));
18283    let completed_task = Arc::clone(&completed);
18284    let (runnable, task) = async_task::spawn(
18285      async move {
18286        completed_task.store(true, Ordering::SeqCst);
18287        Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX))
18288      },
18289      |_| {},
18290    );
18291    let handle = JoinHandle(JoinHandleInner::Task {
18292      task: task.fallible(),
18293      awaiter: None,
18294      dependency: Arc::new(TaskDependency::default()),
18295    });
18296
18297    drop(handle);
18298    runnable.run();
18299    assert!(completed.load(Ordering::SeqCst));
18300  }
18301
18302  #[cfg(not(target_family = "wasm"))]
18303  #[test]
18304  fn join_handle_completion_contains_panicking_awaiter_wake_and_drop() {
18305    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_COMPLETION_PANIC_CHILD";
18306
18307    if std::env::var_os(CHILD_ENV).is_some() {
18308      let wakes = Arc::new(AtomicUsize::new(0));
18309      let drops = Arc::new(AtomicUsize::new(0));
18310      let (runnable, task) = async_task::spawn(
18311        async { Ok::<ContainedTaskOutput<()>, JoinError>(ContainedTaskOutput::new((), u64::MAX)) },
18312        |_| {},
18313      );
18314      let mut handle = JoinHandle(JoinHandleInner::Task {
18315        task: task.fallible(),
18316        awaiter: None,
18317        dependency: Arc::new(TaskDependency::default()),
18318      });
18319      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
18320      {
18321        let mut cx = Context::from_waker(&waker);
18322        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18323      }
18324      drop(waker);
18325
18326      runnable.run();
18327
18328      let waker = futures::task::noop_waker();
18329      let mut cx = Context::from_waker(&waker);
18330      assert!(matches!(
18331        Pin::new(&mut handle).poll(&mut cx),
18332        Poll::Ready(Ok(()))
18333      ));
18334      assert_eq!(
18335        drops.load(Ordering::SeqCst),
18336        1,
18337        "a completed handle must not retain the caller's awaiter"
18338      );
18339      drop(handle);
18340      assert!(wakes.load(Ordering::SeqCst) >= 1);
18341      assert_eq!(drops.load(Ordering::SeqCst), 1);
18342      return;
18343    }
18344
18345    let output = std::process::Command::new(std::env::current_exe().unwrap())
18346      .arg("--exact")
18347      .arg("async_runtime::tests::join_handle_completion_contains_panicking_awaiter_wake_and_drop")
18348      .arg("--nocapture")
18349      .env(CHILD_ENV, "1")
18350      .output()
18351      .expect("the JoinHandle completion awaiter subprocess must start");
18352    assert!(
18353      output.status.success(),
18354      "a panicking JoinHandle completion awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
18355      output.status.code(),
18356      String::from_utf8_lossy(&output.stdout),
18357      String::from_utf8_lossy(&output.stderr)
18358    );
18359  }
18360
18361  #[cfg(not(target_family = "wasm"))]
18362  #[test]
18363  fn join_handle_shutdown_contains_panicking_awaiter_wake_and_drop() {
18364    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_JOIN_HANDLE_SHUTDOWN_PANIC_CHILD";
18365
18366    if std::env::var_os(CHILD_ENV).is_some() {
18367      let controller = multi_thread_controller("join-awaiter-shutdown", 2, 1);
18368      let wakes = Arc::new(AtomicUsize::new(0));
18369      let drops = Arc::new(AtomicUsize::new(0));
18370      let mut handle = controller.spawn(std::future::pending::<()>());
18371      let waker = panicking_join_awaiter_waker(Arc::clone(&wakes), Arc::clone(&drops));
18372      {
18373        let mut cx = Context::from_waker(&waker);
18374        assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18375      }
18376      drop(waker);
18377
18378      controller
18379        .shutdown()
18380        .expect("shutdown must survive the hostile awaiter");
18381      drop(handle);
18382      assert!(wakes.load(Ordering::SeqCst) >= 1);
18383      assert_eq!(drops.load(Ordering::SeqCst), 1);
18384      return;
18385    }
18386
18387    let output = std::process::Command::new(std::env::current_exe().unwrap())
18388      .arg("--exact")
18389      .arg("async_runtime::tests::join_handle_shutdown_contains_panicking_awaiter_wake_and_drop")
18390      .arg("--nocapture")
18391      .env(CHILD_ENV, "1")
18392      .output()
18393      .expect("the JoinHandle shutdown awaiter subprocess must start");
18394    assert!(
18395      output.status.success(),
18396      "a panicking JoinHandle shutdown awaiter must not abort the process; status={:?}\nstdout={}\nstderr={}",
18397      output.status.code(),
18398      String::from_utf8_lossy(&output.stdout),
18399      String::from_utf8_lossy(&output.stderr)
18400    );
18401  }
18402
18403  #[cfg(not(target_family = "wasm"))]
18404  #[test]
18405  fn join_handle_awaiter_completion_retains_its_registered_generation_after_restart() {
18406    let controller = multi_thread_controller("join-awaiter-completion-generation", 2, 1);
18407    let mut handle = controller.spawn(std::future::pending::<()>());
18408    let old_generation = controller.backend().generation();
18409    let state = Arc::new(JoinAwaiterWakerState {
18410      events: Mutex::new(Vec::new()),
18411    });
18412    let waker = {
18413      let _generation = RuntimeGenerationGuard::enter(old_generation);
18414      join_awaiter_waker(Arc::clone(&state))
18415    };
18416    {
18417      let _generation = RuntimeGenerationGuard::enter(old_generation);
18418      let mut cx = Context::from_waker(&waker);
18419      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18420    }
18421    {
18422      let _generation = RuntimeGenerationGuard::enter(old_generation);
18423      drop(waker);
18424    }
18425
18426    controller
18427      .shutdown()
18428      .expect("the first generation must stop");
18429    controller.start().expect("the runtime must restart");
18430    let new_generation = controller.backend().generation();
18431    assert_ne!(new_generation, old_generation);
18432    state.clear();
18433
18434    let replacement = futures::task::noop_waker();
18435    {
18436      let _generation = RuntimeGenerationGuard::enter(new_generation);
18437      let mut cx = Context::from_waker(&replacement);
18438      assert!(matches!(
18439        Pin::new(&mut handle).poll(&mut cx),
18440        Poll::Ready(Err(_))
18441      ));
18442    }
18443
18444    let events = state.snapshot();
18445    assert!(
18446      !events.is_empty(),
18447      "completion must retire the registered awaiter waker"
18448    );
18449    assert!(
18450      events.iter().all(|(kind, generation)| {
18451        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
18452      }),
18453      "old-generation awaiter retirement escaped its generation: {events:?}"
18454    );
18455    controller
18456      .shutdown()
18457      .expect("the replacement generation must stop");
18458  }
18459
18460  #[cfg(not(target_family = "wasm"))]
18461  #[test]
18462  fn dropping_join_handle_retains_awaiter_generation_after_restart() {
18463    let controller = multi_thread_controller("join-awaiter-drop-generation", 2, 1);
18464    let mut handle = controller.spawn(std::future::pending::<()>());
18465    let old_generation = controller.backend().generation();
18466    let state = Arc::new(JoinAwaiterWakerState {
18467      events: Mutex::new(Vec::new()),
18468    });
18469    let waker = {
18470      let _generation = RuntimeGenerationGuard::enter(old_generation);
18471      join_awaiter_waker(Arc::clone(&state))
18472    };
18473    {
18474      let _generation = RuntimeGenerationGuard::enter(old_generation);
18475      let mut cx = Context::from_waker(&waker);
18476      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
18477    }
18478    {
18479      let _generation = RuntimeGenerationGuard::enter(old_generation);
18480      drop(waker);
18481    }
18482
18483    controller
18484      .shutdown()
18485      .expect("the first generation must stop");
18486    controller.start().expect("the runtime must restart");
18487    let new_generation = controller.backend().generation();
18488    assert_ne!(new_generation, old_generation);
18489    state.clear();
18490
18491    {
18492      let _generation = RuntimeGenerationGuard::enter(new_generation);
18493      drop(handle);
18494    }
18495
18496    let events = state.snapshot();
18497    assert!(
18498      !events.is_empty(),
18499      "handle drop must retire the registered awaiter waker"
18500    );
18501    assert!(
18502      events.iter().all(|(kind, generation)| {
18503        *kind == JoinAwaiterWakerEventKind::Drop && *generation == Some(old_generation)
18504      }),
18505      "old-generation awaiter destruction escaped its generation: {events:?}"
18506    );
18507    controller
18508      .shutdown()
18509      .expect("the replacement generation must stop");
18510  }
18511
18512  #[cfg(not(target_family = "wasm"))]
18513  #[test]
18514  fn every_rayon_worker_is_classified_without_enabling_the_lifo_slot() {
18515    use std::{sync::mpsc, time::Duration};
18516
18517    let options = RuntimeOptions {
18518      flavor: RuntimeFlavor::MultiThread,
18519      worker_threads: 2,
18520      max_blocking_tasks: 1,
18521      thread_name_prefix: "worker-classification".to_string(),
18522      park_deadline: None,
18523      drain_linger: DEFAULT_DRAIN_LINGER,
18524    };
18525    let executor =
18526      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
18527    let id = executor.id;
18528    let (tx, rx) = mpsc::sync_channel(1);
18529
18530    executor.pool.spawn(move || {
18531      tx.send((
18532        ON_POOL_WORKER.with(std::cell::Cell::get),
18533        IN_SCHEDULER_DRIVER.with(std::cell::Cell::get),
18534      ))
18535      .unwrap();
18536    });
18537
18538    let (worker, driver) = rx.recv_timeout(Duration::from_secs(1)).unwrap();
18539    assert_eq!(
18540      worker,
18541      Some(id),
18542      "nested Rayon work must use cooperative block_on"
18543    );
18544    assert_eq!(
18545      driver, None,
18546      "arbitrary Rayon work must not use a LIFO slot that no scheduler frame will drain"
18547    );
18548  }
18549
18550  #[cfg(not(target_family = "wasm"))]
18551  #[test]
18552  fn multi_thread_block_on_does_not_park_pool_worker() {
18553    // Pool-worker reentrancy invariant: a task that calls `block_on` on an inner
18554    // future depending on pool-scheduled blocking work must not park its worker.
18555    // If every worker parks this way, no worker remains to pick up the blocking
18556    // jobs they await.
18557    use std::sync::{Barrier, mpsc};
18558    use std::time::Duration;
18559
18560    let (done_tx, done_rx) = mpsc::channel();
18561    let runner = std::thread::spawn(move || {
18562      let metrics = Arc::new(RuntimeMetrics::default());
18563      let options = RuntimeOptions {
18564        flavor: RuntimeFlavor::MultiThread,
18565        worker_threads: 2,
18566        max_blocking_tasks: 2,
18567        thread_name_prefix: "rd1-test".to_string(),
18568        park_deadline: None,
18569        drain_linger: DEFAULT_DRAIN_LINGER,
18570      };
18571      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18572
18573      let task_count = 2usize;
18574      // Guarantees every task is being polled on its own pool worker *before*
18575      // any of them parks in `block_on`, so the deadlock is deterministic.
18576      let barrier = Arc::new(Barrier::new(task_count));
18577
18578      let mut tasks = Vec::new();
18579      for _ in 0..task_count {
18580        let exec = Arc::clone(&executor);
18581        let barrier = Arc::clone(&barrier);
18582        let body = async move {
18583          barrier.wait();
18584          let inner_exec = Arc::clone(&exec);
18585          let mut inner = std::pin::pin!(async move {
18586            let value = inner_exec.schedule_blocking(|| 7usize).await.unwrap();
18587            assert_eq!(value, 7);
18588          });
18589          // Synchronous `block_on` from inside a pool-worker task: the vector.
18590          exec.block_on(inner.as_mut());
18591        };
18592        let scheduler = Arc::clone(&executor);
18593        let (runnable, task) =
18594          async_task::spawn(body, move |runnable| scheduler.schedule(runnable));
18595        executor.schedule(runnable);
18596        tasks.push(task);
18597      }
18598
18599      for task in tasks {
18600        futures::executor::block_on(task);
18601      }
18602      let _ = done_tx.send(());
18603    });
18604
18605    match done_rx.recv_timeout(Duration::from_secs(10)) {
18606      Ok(()) => runner.join().unwrap(),
18607      Err(error) => panic!(
18608        "MultiThread block_on deadlocked ({error}): pool workers parked waiting on pool-scheduled work"
18609      ),
18610    }
18611  }
18612
18613  #[cfg(not(target_family = "wasm"))]
18614  #[test]
18615  fn lingering_drainer_periodic_work_does_not_starve_rayon_jobs() {
18616    // Idle-linger residence bound (adversarial rayon-starvation shape).
18617    //
18618    // W=2 pool. Worker B runs a blocking closure that spawns a raw rayon job
18619    // R onto the SAME pool (it lands in worker B's rayon deque) and then
18620    // cooperatively block_on's R's completion -- so worker B never returns to
18621    // rayon's scheduler. Worker A's drain frame runs the queues empty and
18622    // lingers on its own parker. A producer then publishes one trivial
18623    // runnable every ~200us (< the 500us idle budget), stopping only after R
18624    // ran. Every wake pops the MOST recently registered parker -- the
18625    // lingering drainer, which re-registers on top after each task -- and
18626    // every executed task clears the idle deadline, so without a bound on the
18627    // frame's TOTAL lingering residence worker A never exits its drain frame,
18628    // never returns its thread to rayon, and R (which only an idle rayon
18629    // worker can steal) starves indefinitely: producer never stops, worker B
18630    // never unparks. The frame residence bound must force the drainer out
18631    // through `finish_draining` (exit-and-respawn through rayon's queue is
18632    // exactly the interleaving point that lets R run), well inside the
18633    // watchdog below.
18634    use std::sync::mpsc;
18635
18636    for round in 0..3 {
18637      let metrics = Arc::new(RuntimeMetrics::default());
18638      let options = RuntimeOptions {
18639        flavor: RuntimeFlavor::MultiThread,
18640        worker_threads: 2,
18641        max_blocking_tasks: 1,
18642        thread_name_prefix: format!("linger-starve-{round}"),
18643        park_deadline: None,
18644        drain_linger: DEFAULT_DRAIN_LINGER,
18645      };
18646      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18647
18648      let r_done = Arc::new(AtomicBool::new(false));
18649      let give_up = Arc::new(AtomicBool::new(false));
18650      let (done_tx, done_rx) = mpsc::channel::<Duration>();
18651
18652      // T1 pins worker A until the cooperative driver (worker B, inside the
18653      // blocking closure's re-entrant block_on) has parked. Worker A's drain
18654      // frame then observes empty queues and starts lingering AFTER the
18655      // cooperative driver registered, making the lingering drainer the
18656      // most-recently-registered (i.e. wake-preferred) parker.
18657      let t1_executor = Arc::clone(&executor);
18658      let t1_started = Arc::new(AtomicBool::new(false));
18659      let t1_started_setter = Arc::clone(&t1_started);
18660      let t1_body = async move {
18661        t1_started_setter.store(true, Ordering::SeqCst);
18662        let deadline = Instant::now() + Duration::from_secs(10);
18663        while t1_executor.parked_drivers.count.load(Ordering::SeqCst) == 0 {
18664          if Instant::now() >= deadline {
18665            // Give the worker back; the watchdog below reports the failure.
18666            return;
18667          }
18668          std::hint::spin_loop();
18669        }
18670      };
18671      let t1_scheduler = Arc::clone(&executor);
18672      let (t1_runnable, t1_task) = async_task::spawn(t1_body, move |r| t1_scheduler.schedule(r));
18673      executor.schedule(t1_runnable);
18674      wait_until("the worker-pinning task to start", || {
18675        t1_started.load(Ordering::SeqCst)
18676      });
18677
18678      // The blocking closure on worker B: spawn R, then block_on its result.
18679      let blocking_executor = Arc::clone(&executor);
18680      let r_done_setter = Arc::clone(&r_done);
18681      let started = Instant::now();
18682      let handle = executor.schedule_blocking(move || {
18683        let (r_tx, r_rx) = oneshot::channel::<()>();
18684        blocking_executor.pool.spawn(move || {
18685          r_done_setter.store(true, Ordering::SeqCst);
18686          let _ = r_tx.send(());
18687        });
18688        let mut wait_for_r = std::pin::pin!(async move {
18689          let _ = r_rx.await;
18690        });
18691        blocking_executor.block_on(wait_for_r.as_mut());
18692        let _ = done_tx.send(started.elapsed());
18693      });
18694
18695      // Wait until both the cooperative driver and the lingering drainer are
18696      // registered; if the linger expired first (R already ran), that round
18697      // completed without ever reaching the adversarial interleaving.
18698      let arm_deadline = Instant::now() + Duration::from_secs(15);
18699      while executor.parked_drivers.count.load(Ordering::SeqCst) != 2
18700        && !r_done.load(Ordering::SeqCst)
18701      {
18702        assert!(
18703          Instant::now() < arm_deadline,
18704          "timed out arming the linger-starvation interleaving"
18705        );
18706        std::thread::yield_now();
18707      }
18708
18709      // Producer: one trivial runnable every ~200us (spin-paced, so cadence
18710      // stays far below the 500us idle budget), until R completed.
18711      let producer_executor = Arc::clone(&executor);
18712      let producer_r_done = Arc::clone(&r_done);
18713      let producer_give_up = Arc::clone(&give_up);
18714      let producer = std::thread::spawn(move || {
18715        let cadence = Duration::from_micros(200);
18716        let mut next_tick = Instant::now();
18717        while !producer_r_done.load(Ordering::SeqCst) && !producer_give_up.load(Ordering::SeqCst) {
18718          let scheduler = Arc::clone(&producer_executor);
18719          let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
18720          producer_executor.schedule(runnable);
18721          task.detach();
18722          next_tick += cadence;
18723          while Instant::now() < next_tick {
18724            std::hint::spin_loop();
18725          }
18726        }
18727      });
18728
18729      let outcome = done_rx.recv_timeout(Duration::from_secs(5));
18730      give_up.store(true, Ordering::SeqCst);
18731      producer.join().unwrap();
18732      match outcome {
18733        Ok(elapsed) => {
18734          futures::executor::block_on(handle).expect("blocking closure join");
18735          drop(t1_task);
18736          eprintln!("round {round}: completed in {elapsed:?}");
18737        }
18738        Err(error) => panic!(
18739          "round {round}: lingering drainer starved the rayon job for 5s ({error}): \
18740           sub-budget periodic work must not keep a drain frame resident forever"
18741        ),
18742      }
18743    }
18744  }
18745
18746  #[cfg(not(target_family = "wasm"))]
18747  #[test]
18748  #[ignore = "microbenchmark: run explicitly in release mode"]
18749  fn parked_drivers_registry_cycle_microbench() {
18750    // Isolates the registry traffic of one steady-state linger cycle at
18751    // various registry sizes: wake_one pops the LIFO top (O(1)), the woken
18752    // drainer deregisters (the entry is already gone, so the position scan
18753    // walks the WHOLE vector and fails), then re-registers (push). Also
18754    // measures the timeout-path deregister (entry present) and a
18755    // wake_one_owner_dependency sweep over dependency-less lingerers.
18756    for &n in &[8usize, 64, 256] {
18757      let registry = ParkedDrivers::default();
18758      let parkers: Vec<Arc<DriverParker>> =
18759        (0..n).map(|_| Arc::new(DriverParker::default())).collect();
18760      for parker in &parkers {
18761        registry.register_with_role(parker, true, None);
18762      }
18763      let hot = &parkers[n - 1];
18764
18765      let iters = 2_000_000u32;
18766      let start = Instant::now();
18767      for _ in 0..iters {
18768        assert!(registry.wake_one());
18769        registry.deregister(hot);
18770        assert!(hot.consume_permit());
18771        registry.register_with_role(hot, true, None);
18772      }
18773      let wake_cycle = start.elapsed() / iters;
18774
18775      let start = Instant::now();
18776      for _ in 0..iters {
18777        registry.deregister(hot);
18778        registry.register_with_role(hot, true, None);
18779      }
18780      let timeout_cycle = start.elapsed() / iters;
18781
18782      let owner = BlockingOwnerToken {
18783        executor_id: u64::MAX,
18784        frame: u64::MAX,
18785      };
18786      let start = Instant::now();
18787      for _ in 0..iters {
18788        assert!(!registry.wake_one_owner_dependency(owner));
18789      }
18790      let owner_sweep = start.elapsed() / iters;
18791
18792      eprintln!(
18793        "N={n:>3}: wake+failed-deregister+re-register {wake_cycle:?}, \
18794         timeout-deregister+re-register {timeout_cycle:?}, \
18795         owner-dependency sweep {owner_sweep:?}"
18796      );
18797    }
18798  }
18799
18800  #[cfg(not(target_family = "wasm"))]
18801  #[test]
18802  fn multi_thread_block_on_from_caller_thread_still_parks() {
18803    // The non-pool (napi caller) path must keep using the plain parking
18804    // `block_on`; here we just assert it drives a ready future to completion.
18805    let metrics = Arc::new(RuntimeMetrics::default());
18806    let options = RuntimeOptions {
18807      flavor: RuntimeFlavor::MultiThread,
18808      worker_threads: 3,
18809      max_blocking_tasks: 2,
18810      thread_name_prefix: "rd1-caller".to_string(),
18811      park_deadline: None,
18812      drain_linger: DEFAULT_DRAIN_LINGER,
18813    };
18814    let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18815
18816    let mut output = None;
18817    let mut future = std::pin::pin!(async {
18818      output = Some(41usize + 1);
18819    });
18820    executor.block_on(future.as_mut());
18821    assert_eq!(output, Some(42));
18822  }
18823
18824  #[cfg(not(target_family = "wasm"))]
18825  #[test]
18826  fn multi_thread_block_on_from_caller_thread_receives_pool_produced_value() {
18827    // Cross-thread liveness guard for the NON-pool (napi-caller) path of
18828    // `MultiThreadExecutor::block_on`. The caller thread is not a pool worker, so
18829    // `block_on` takes the parking branch (`futures::executor::block_on`) while a
18830    // SEPARATELY spawned task runs on a pool worker and produces the awaited value.
18831    // This pins ONLY the cross-thread wake path: the pool task completing must wake
18832    // the parked caller's waker so `block_on` returns the pool-computed value.
18833    //
18834    // HONEST ORDERING (no sleeps): the awaited pool task is GATED on a oneshot and
18835    // therefore returns `Pending` on its first poll. A `CallerProbe` future wraps the
18836    // task and, ONLY after the task poll returns `Pending` (i.e. AFTER `block_on`'s
18837    // parking waker is registered on the task), signals "caller parked" to a releaser
18838    // thread. The releaser then -- and only then -- releases the gate, so the pool
18839    // task completes strictly AFTER the caller registered its waker. This forecloses
18840    // the "already-ready before await" interleaving: the task cannot be `Ready` on the
18841    // caller's first poll, because its gate is opened only in response to that first
18842    // `Pending` poll. The completion is thus forced to travel the real cross-thread
18843    // wake (pool worker completes task -> wakes parked caller -> `block_on` returns);
18844    // if that wake is broken the caller parks forever and the bounded recv_timeout
18845    // below fails loudly instead of hanging the suite.
18846    //
18847    // SCOPE: this is the cross-thread liveness guard only. It does NOT exercise the
18848    // caller-is-pool-worker case, which has dedicated reentrancy tests.
18849    use std::sync::mpsc;
18850    use std::time::Duration;
18851
18852    // Wraps the pool task so the caller's FIRST poll registers `block_on`'s waker on
18853    // the task and, only when that poll is `Pending`, hands the "caller parked" signal
18854    // to the releaser. Sending strictly after the `Pending` poll guarantees the waker
18855    // is already registered before the gate can open.
18856    struct CallerProbe {
18857      task: Task<usize>,
18858      parked_tx: Option<mpsc::Sender<()>>,
18859    }
18860    impl Future for CallerProbe {
18861      type Output = usize;
18862      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<usize> {
18863        // `Task` is `Unpin`; poll it through a fresh `Pin`.
18864        let poll = Pin::new(&mut self.task).poll(cx);
18865        if poll.is_pending()
18866          && let Some(tx) = self.parked_tx.take()
18867        {
18868          // Waker is now registered on `task`; release the gate is safe.
18869          let _ = tx.send(());
18870        }
18871        poll
18872      }
18873    }
18874
18875    let (done_tx, done_rx) = mpsc::channel();
18876    let runner = std::thread::spawn(move || {
18877      let metrics = Arc::new(RuntimeMetrics::default());
18878      let options = RuntimeOptions {
18879        flavor: RuntimeFlavor::MultiThread,
18880        worker_threads: 2,
18881        max_blocking_tasks: 2,
18882        thread_name_prefix: "rd10-caller".to_string(),
18883        park_deadline: None,
18884        drain_linger: DEFAULT_DRAIN_LINGER,
18885      };
18886      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18887
18888      // The pool task's inner future is GATED on a oneshot: its first poll on a pool
18889      // worker registers the gate waker and returns `Pending`. When the releaser sends
18890      // on `gate_tx`, the gate's wake reschedules the task (via `schedule` ->
18891      // `ensure_drainer`) onto a pool worker, where it resolves to 42.
18892      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
18893      let scheduler = Arc::clone(&executor);
18894      let (runnable, task) = async_task::spawn(
18895        async move { gate_rx.await.expect("gate sender dropped") },
18896        move |r| {
18897          scheduler.schedule(r);
18898        },
18899      );
18900      executor.schedule(runnable);
18901
18902      // Releaser thread: opens the gate ONLY after the caller has parked (registered
18903      // its waker on the task). This enforces caller-parked HAPPENS-BEFORE pool-task
18904      // completes, so completion must travel the cross-thread wake path.
18905      let (parked_tx, parked_rx) = mpsc::channel::<()>();
18906      let releaser = std::thread::spawn(move || {
18907        if parked_rx.recv().is_ok() {
18908          let _ = gate_tx.send(42usize);
18909        }
18910      });
18911
18912      // The caller (this child) thread is NOT a pool worker, so `block_on` parks via
18913      // `futures::executor::block_on` rather than driving the queue cooperatively.
18914      let mut output = None;
18915      {
18916        let mut future = std::pin::pin!(async {
18917          output = Some(
18918            CallerProbe {
18919              task,
18920              parked_tx: Some(parked_tx),
18921            }
18922            .await,
18923          );
18924        });
18925        executor.block_on(future.as_mut());
18926      }
18927
18928      releaser.join().unwrap();
18929      assert_eq!(
18930        output,
18931        Some(42usize),
18932        "block_on must return the pool-computed value"
18933      );
18934      let _ = done_tx.send(());
18935    });
18936
18937    match done_rx.recv_timeout(Duration::from_secs(10)) {
18938      Ok(()) => runner.join().unwrap(),
18939      Err(error) => panic!(
18940        "non-pool block_on did not receive the pool-produced value ({error}): the cross-thread wake path stalled"
18941      ),
18942    }
18943  }
18944
18945  #[cfg(not(target_family = "wasm"))]
18946  #[test]
18947  fn multi_thread_block_on_wakes_parked_driver_after_workers_park() {
18948    // Parked-driver wake invariant: a pool-worker task may park in `block_on`
18949    // before the work that will wake it is scheduled. Once every pool worker
18950    // is parked this way, `ensure_drainer` cannot spawn a replacement because
18951    // the parked workers are still counted as active drainers. A later
18952    // `schedule()` must wake a parked driver directly so the runnable can run.
18953    use std::sync::{Barrier, mpsc};
18954    use std::time::Duration;
18955
18956    let (done_tx, done_rx) = mpsc::channel();
18957    let runner = std::thread::spawn(move || {
18958      let metrics = Arc::new(RuntimeMetrics::default());
18959      let options = RuntimeOptions {
18960        flavor: RuntimeFlavor::MultiThread,
18961        worker_threads: 2,
18962        max_blocking_tasks: 2,
18963        thread_name_prefix: "rd1-park".to_string(),
18964        park_deadline: None,
18965        drain_linger: DEFAULT_DRAIN_LINGER,
18966      };
18967      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
18968
18969      let task_count = 2usize;
18970      let barrier = Arc::new(Barrier::new(task_count));
18971
18972      let mut senders = Vec::new();
18973      let mut tasks = Vec::new();
18974      for _ in 0..task_count {
18975        let (tx, rx) = oneshot::channel::<usize>();
18976        senders.push(tx);
18977        let exec = Arc::clone(&executor);
18978        let barrier = Arc::clone(&barrier);
18979        let body = async move {
18980          // Make sure both pool workers are occupied before either parks.
18981          barrier.wait();
18982          // Child task whose wake routes through `schedule()`: the oneshot
18983          // receiver's waker -> child runnable -> `schedule` -> `ensure_drainer`.
18984          let child_exec = Arc::clone(&exec);
18985          let (child_runnable, child_task) =
18986            async_task::spawn(async move { rx.await.unwrap() }, move |r| {
18987              child_exec.schedule(r)
18988            });
18989          exec.schedule(child_runnable);
18990          let mut inner = std::pin::pin!(async move {
18991            assert_eq!(child_task.await, 9usize);
18992          });
18993          exec.block_on(inner.as_mut());
18994        };
18995        let scheduler = Arc::clone(&executor);
18996        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
18997        executor.schedule(runnable);
18998        tasks.push(task);
18999      }
19000
19001      // Give both workers time to poll their child Pending and park in block_on.
19002      std::thread::sleep(Duration::from_millis(200));
19003      for tx in senders {
19004        let _ = tx.send(9usize);
19005      }
19006
19007      for task in tasks {
19008        futures::executor::block_on(task);
19009      }
19010      let _ = done_tx.send(());
19011    });
19012
19013    match done_rx.recv_timeout(Duration::from_secs(10)) {
19014      Ok(()) => runner.join().unwrap(),
19015      Err(error) => panic!("parked pool drivers missed a post-park schedule wake ({error})"),
19016    }
19017  }
19018
19019  /// Spin (yielding) until `condition` holds, failing loudly on timeout.
19020  /// Used to observe executor-internal states (e.g. "both drivers are
19021  /// registered as parked") without sleeps. The bound is a HANG detector,
19022  /// not a latency assertion: starved CI runners can stall fresh threads for
19023  /// hundreds of milliseconds, so it is deliberately generous.
19024  #[cfg(not(target_family = "wasm"))]
19025  fn wait_until(what: &str, condition: impl Fn() -> bool) {
19026    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(15);
19027    while !condition() {
19028      assert!(
19029        std::time::Instant::now() < deadline,
19030        "timed out waiting for: {what}"
19031      );
19032      std::thread::yield_now();
19033    }
19034  }
19035
19036  /// Bounded join for harness threads that already signalled completion of
19037  /// their assertions: everything left on the thread is teardown (executor
19038  /// drop -- rayon pool release, `TimerHeap::drop`'s timekeeper join -- plus
19039  /// TLS destructors). An unbounded `join()` there turns a teardown wedge
19040  /// into a silent libtest-worker hang for the full CI step timeout (observed
19041  /// on Windows in run 29195160953: several park-deadline-family tests never
19042  /// completed while every in-test assertion had already passed). Fail loudly
19043  /// and NAME the awaited phase instead.
19044  #[cfg(not(target_family = "wasm"))]
19045  fn join_within(what: &str, handle: std::thread::JoinHandle<()>, timeout: Duration) {
19046    let deadline = std::time::Instant::now() + timeout;
19047    while !handle.is_finished() {
19048      assert!(
19049        std::time::Instant::now() < deadline,
19050        "thread wedged past its {timeout:?} bound while: {what}"
19051      );
19052      std::thread::sleep(Duration::from_millis(10));
19053    }
19054    handle.join().unwrap();
19055  }
19056
19057  #[cfg(not(target_family = "wasm"))]
19058  fn run_claim_with_deadlock_gate_assertion<T: Send + 'static>(
19059    executor: &Arc<MultiThreadExecutor>,
19060    start_counter: &AtomicU64,
19061    claim: impl FnOnce() -> T + Send + 'static,
19062  ) -> T {
19063    use std::sync::mpsc;
19064
19065    let (claimed_tx, claimed_rx) = mpsc::channel();
19066    let (release_claim_tx, release_claim_rx) = mpsc::channel();
19067    let (started_tx, started_rx) = mpsc::channel();
19068    let (release_start_tx, release_start_rx) = mpsc::channel();
19069    let starts_before = start_counter.load(Ordering::SeqCst);
19070    let worker = std::thread::spawn(move || {
19071      MULTI_THREAD_WORK_CLAIMED_TEST_HOOK.with(|slot| {
19072        *slot.borrow_mut() = Some(Box::new(move || {
19073          claimed_tx.send(()).unwrap();
19074          release_claim_rx.recv().unwrap();
19075        }));
19076      });
19077      MULTI_THREAD_WORK_STARTED_TEST_HOOK.with(|slot| {
19078        *slot.borrow_mut() = Some(Box::new(move || {
19079          started_tx.send(()).unwrap();
19080          release_start_rx.recv().unwrap();
19081        }));
19082      });
19083      claim()
19084    });
19085    claimed_rx
19086      .recv_timeout(Duration::from_secs(2))
19087      .expect("the scheduler claim must pause after removing work");
19088
19089    let admission_active = executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19090    let verdict = executor.try_begin_deadlock_verdict();
19091    let claim_verdict_rejected = verdict.is_none();
19092    drop(verdict);
19093    release_claim_tx.send(()).unwrap();
19094
19095    started_rx
19096      .recv_timeout(Duration::from_secs(2))
19097      .expect("the scheduler claim must pause after publishing its start");
19098    let admission_active_after_start =
19099      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0;
19100    let start_published = start_counter.load(Ordering::SeqCst) > starts_before;
19101    let verdict = executor.try_begin_deadlock_verdict();
19102    let start_verdict_rejected = verdict.is_none();
19103    drop(verdict);
19104    release_start_tx.send(()).unwrap();
19105    let output = worker.join().unwrap();
19106
19107    assert!(
19108      admission_active,
19109      "removing scheduler work must retain a detector admission until start accounting"
19110    );
19111    assert!(
19112      claim_verdict_rejected,
19113      "a deadline verdict must not close while removed work has not published its start"
19114    );
19115    assert!(
19116      start_published,
19117      "work start accounting must publish before admission retires"
19118    );
19119    assert!(
19120      admission_active_after_start,
19121      "the detector admission must remain active until after start accounting"
19122    );
19123    assert!(
19124      start_verdict_rejected,
19125      "a deadline verdict must not close between start accounting and admission retirement"
19126    );
19127    output
19128  }
19129
19130  #[cfg(not(target_family = "wasm"))]
19131  #[test]
19132  fn multi_thread_runnable_claims_hold_deadlock_admission_until_start() {
19133    let executor =
19134      multi_thread_executor(1, Some(Duration::from_secs(1)), "runnable-claim-admission");
19135
19136    let fifo_ran = Arc::new(AtomicBool::new(false));
19137    let fifo_task_ran = Arc::clone(&fifo_ran);
19138    let (fifo, fifo_task) = async_task::spawn(
19139      async move {
19140        fifo_task_ran.store(true, Ordering::SeqCst);
19141      },
19142      |_| {},
19143    );
19144    executor.metrics.runnable_scheduled();
19145    executor.push_fifo_raw(fifo);
19146    let fifo_executor = Arc::clone(&executor);
19147    assert!(run_claim_with_deadlock_gate_assertion(
19148      &executor,
19149      &executor.metrics.runnable_polls,
19150      move || {
19151        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19152        fifo_executor.run_one_fifo_runnable()
19153      }
19154    ));
19155    futures::executor::block_on(fifo_task);
19156    assert!(fifo_ran.load(Ordering::SeqCst));
19157
19158    let fifo_order = Arc::new(Mutex::new(Vec::new()));
19159    let mut fifo_tasks = Vec::new();
19160    for name in ["first", "second"] {
19161      let task_order = Arc::clone(&fifo_order);
19162      let (runnable, task) = async_task::spawn(
19163        async move {
19164          task_order.lock().unwrap().push(name);
19165        },
19166        |_| {},
19167      );
19168      executor.metrics.runnable_scheduled();
19169      executor.push_fifo_raw(runnable);
19170      fifo_tasks.push(task);
19171    }
19172    for _ in 0..2 {
19173      let fifo_executor = Arc::clone(&executor);
19174      assert!(run_claim_with_deadlock_gate_assertion(
19175        &executor,
19176        &executor.metrics.runnable_polls,
19177        move || {
19178          let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19179          fifo_executor.run_one_runnable()
19180        }
19181      ));
19182    }
19183    for task in fifo_tasks {
19184      futures::executor::block_on(task);
19185    }
19186    assert_eq!(
19187      *fifo_order.lock().unwrap(),
19188      ["first", "second"],
19189      "the detector-enabled normal fallback must preserve FIFO order"
19190    );
19191
19192    let lifo_ran = Arc::new(AtomicBool::new(false));
19193    let lifo_task_ran = Arc::clone(&lifo_ran);
19194    let (lifo, lifo_task) = async_task::spawn(
19195      async move {
19196        lifo_task_ran.store(true, Ordering::SeqCst);
19197      },
19198      |_| {},
19199    );
19200    executor.metrics.runnable_scheduled();
19201    let lifo_executor = Arc::clone(&executor);
19202    assert!(run_claim_with_deadlock_gate_assertion(
19203      &executor,
19204      &executor.metrics.runnable_polls,
19205      move || {
19206        let _driver = OnPoolWorkerGuard::enter(lifo_executor.id);
19207        LIFO_SLOT.with(|slot| {
19208          assert!(slot.replace(Some((lifo_executor.id, lifo))).is_none());
19209        });
19210        lifo_executor.run_one_runnable()
19211      }
19212    ));
19213    futures::executor::block_on(lifo_task);
19214    assert!(lifo_ran.load(Ordering::SeqCst));
19215
19216    executor.begin_shutdown();
19217    executor.wait_until_scheduler_idle();
19218  }
19219
19220  #[cfg(not(target_family = "wasm"))]
19221  #[test]
19222  fn multi_thread_blocking_claims_hold_deadlock_admission_until_start() {
19223    let executor =
19224      multi_thread_executor(1, Some(Duration::from_secs(1)), "blocking-claim-admission");
19225
19226    let fifo_ran = Arc::new(AtomicBool::new(false));
19227    let fifo_job_ran = Arc::clone(&fifo_ran);
19228    executor.metrics.blocking_scheduled();
19229    executor
19230      .blocking_queue
19231      .lock()
19232      .unwrap()
19233      .push(QueuedBlocking {
19234        id: BlockingJobId(u64::MAX - 90),
19235        run: Box::new(move || {
19236          fifo_job_ran.store(true, Ordering::SeqCst);
19237        }),
19238      });
19239    let fifo_executor = Arc::clone(&executor);
19240    assert!(run_claim_with_deadlock_gate_assertion(
19241      &executor,
19242      &executor.metrics.blocking_tasks_started,
19243      move || {
19244        let _driver = OnPoolWorkerGuard::enter(fifo_executor.id);
19245        fifo_executor.run_one_blocking()
19246      }
19247    ));
19248    assert!(fifo_ran.load(Ordering::SeqCst));
19249
19250    let owner = executor.fresh_owner_token();
19251    let active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
19252    let exact_job = BlockingJobId(u64::MAX - 91);
19253    let exact_ran = Arc::new(AtomicBool::new(false));
19254    let exact_job_ran = Arc::clone(&exact_ran);
19255    executor.metrics.blocking_scheduled();
19256    executor
19257      .blocking_queue
19258      .lock()
19259      .unwrap()
19260      .push(QueuedBlocking {
19261        id: exact_job,
19262        run: Box::new(move || {
19263          exact_job_ran.store(true, Ordering::SeqCst);
19264        }),
19265      });
19266    let dependency = Arc::new(TaskDependency::new(executor.generation, Some(owner)));
19267    dependency.set_owned(BlockingDependency {
19268      job: exact_job,
19269      owner: Some(owner),
19270    });
19271    let exact_executor = Arc::clone(&executor);
19272    assert!(run_claim_with_deadlock_gate_assertion(
19273      &executor,
19274      &executor.metrics.blocking_tasks_started,
19275      move || {
19276        let _driver = OnPoolWorkerGuard::enter(exact_executor.id);
19277        let _owner = BlockingOwnerGuard::enter(Some(owner));
19278        exact_executor.try_owned_blocking_over_cap(&dependency)
19279      }
19280    ));
19281    assert!(exact_ran.load(Ordering::SeqCst));
19282
19283    drop(active_owner);
19284    executor.begin_shutdown();
19285    executor.wait_until_scheduler_idle();
19286  }
19287
19288  #[cfg(not(target_family = "wasm"))]
19289  #[test]
19290  fn parked_drivers_wake_one_with_no_parked_drivers_skips_the_lock() {
19291    // Empty-registry behavior: with nothing registered, `wake_one` must return
19292    // `false` WITHOUT touching the registry mutex (the no-waiter fast path --
19293    // the common case, since queue-wakes fire on every schedule while drivers
19294    // park only in rare re-entrant `block_on`). Proxy for "did not take the
19295    // mutex": the test HOLDS the registry lock; a fast-path wake_one completes
19296    // anyway, while (control) a wake_one issued with a parker registered must
19297    // take the slow path and block on that held lock until it is released.
19298    use std::sync::mpsc;
19299    use std::time::Duration;
19300
19301    let registry = Arc::new(ParkedDrivers::default());
19302
19303    // Fast path: empty registry -> wake_one completes although the lock is held.
19304    let guard = registry.parked.lock().unwrap();
19305    let (done_tx, done_rx) = mpsc::channel();
19306    let waker_thread = {
19307      let registry = Arc::clone(&registry);
19308      std::thread::spawn(move || {
19309        done_tx.send(registry.wake_one()).unwrap();
19310      })
19311    };
19312    let woke = done_rx
19313      .recv_timeout(Duration::from_secs(5))
19314      .expect("wake_one with an empty registry must skip the lock (no-waiter fast path)");
19315    assert!(!woke, "an empty registry has nobody to wake");
19316    waker_thread.join().unwrap();
19317    drop(guard);
19318
19319    // Control: with a parker registered the SAME held lock must block
19320    // wake_one (slow path). This proves the fast path above genuinely skipped
19321    // the lock rather than wake_one never locking at all.
19322    let parker = Arc::new(DriverParker::default());
19323    registry.register(&parker);
19324    let guard = registry.parked.lock().unwrap();
19325    let (done_tx, done_rx) = mpsc::channel();
19326    let waker_thread = {
19327      let registry = Arc::clone(&registry);
19328      std::thread::spawn(move || {
19329        done_tx.send(registry.wake_one()).unwrap();
19330      })
19331    };
19332    assert!(
19333      done_rx.recv_timeout(Duration::from_millis(300)).is_err(),
19334      "wake_one with a registered parker must take the slow path (block on the held lock)"
19335    );
19336    drop(guard);
19337    let woke = done_rx
19338      .recv_timeout(Duration::from_secs(5))
19339      .expect("slow-path wake_one must complete once the lock is released");
19340    assert!(
19341      woke,
19342      "the slow path must pop and wake the registered parker"
19343    );
19344    waker_thread.join().unwrap();
19345    // The popped parker received its permit: a park now returns immediately
19346    // instead of sleeping (bounded by the test harness if broken).
19347    parker.park();
19348    assert_eq!(
19349      registry.count.load(Ordering::SeqCst),
19350      0,
19351      "wake_one deregisters what it pops"
19352    );
19353  }
19354
19355  #[cfg(not(target_family = "wasm"))]
19356  #[test]
19357  fn driver_parker_permit_granted_while_running_is_not_lost() {
19358    // Stored-permit behavior: a wake delivered while the driver is RUNNING (not
19359    // parked) must be stored as a permit that makes the next `park` return
19360    // immediately; multiple such wakes coalesce into ONE permit.
19361    use std::sync::mpsc;
19362    use std::time::Duration;
19363
19364    let parker = Arc::new(DriverParker::default());
19365    // Two wakes while "running" (nobody parked yet).
19366    parker.unpark();
19367    parker.unpark();
19368
19369    let (tx, rx) = mpsc::channel();
19370    let driver = {
19371      let parker = Arc::clone(&parker);
19372      std::thread::spawn(move || {
19373        parker.park(); // must consume the stored permit without sleeping
19374        tx.send(1).unwrap();
19375        parker.park(); // no permit left: must genuinely sleep
19376        tx.send(2).unwrap();
19377      })
19378    };
19379
19380    assert_eq!(
19381      rx.recv_timeout(Duration::from_secs(5))
19382        .expect("a stored permit must not be lost"),
19383      1
19384    );
19385    assert!(
19386      rx.recv_timeout(Duration::from_millis(300)).is_err(),
19387      "two pre-park wakes must coalesce into one permit (second park must sleep)"
19388    );
19389    parker.unpark();
19390    assert_eq!(
19391      rx.recv_timeout(Duration::from_secs(5))
19392        .expect("unpark must wake a sleeping driver"),
19393      2
19394    );
19395    driver.join().unwrap();
19396  }
19397
19398  #[cfg(not(target_family = "wasm"))]
19399  #[test]
19400  fn driver_parker_unrepresentable_timeout_remains_wakeable() {
19401    use std::sync::mpsc;
19402
19403    let parker = Arc::new(DriverParker::default());
19404    assert!(
19405      Instant::now().checked_add(Duration::MAX).is_none(),
19406      "Duration::MAX must exercise the unrepresentable-deadline path"
19407    );
19408    let (done_tx, done_rx) = mpsc::channel();
19409    let driver = {
19410      let parker = Arc::clone(&parker);
19411      std::thread::spawn(move || {
19412        done_tx.send(parker.park_timeout(Duration::MAX)).unwrap();
19413      })
19414    };
19415    wait_until("the unbounded timeout driver to sleep", || {
19416      parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING
19417    });
19418    assert!(
19419      done_rx.recv_timeout(Duration::from_millis(100)).is_err(),
19420      "an unrepresentable timeout must not expire immediately"
19421    );
19422
19423    let unparker = {
19424      let parker = Arc::clone(&parker);
19425      std::thread::spawn(move || parker.unpark())
19426    };
19427    assert!(
19428      done_rx
19429        .recv_timeout(Duration::from_secs(1))
19430        .expect("an unrepresentable timeout must remain wakeable"),
19431      "a permit must be reported as a wake"
19432    );
19433    unparker.join().unwrap();
19434    driver.join().unwrap();
19435  }
19436
19437  #[cfg(not(target_family = "wasm"))]
19438  #[test]
19439  fn parked_drivers_wake_one_wakes_exactly_one() {
19440    // Single-wake behavior: 2 parked drivers + 1 wake -> exactly one wakes, the
19441    // other stays parked. Targeted wakes must neither wake everyone nor lose
19442    // the wake.
19443    use std::sync::mpsc;
19444    use std::time::Duration;
19445
19446    let registry = Arc::new(ParkedDrivers::default());
19447    let (woke_tx, woke_rx) = mpsc::channel();
19448    let mut drivers = Vec::new();
19449    for index in 0..2 {
19450      let registry = Arc::clone(&registry);
19451      let woke_tx = woke_tx.clone();
19452      drivers.push(std::thread::spawn(move || {
19453        let parker = Arc::new(DriverParker::default());
19454        registry.register(&parker);
19455        parker.park();
19456        registry.deregister(&parker);
19457        woke_tx.send(index).unwrap();
19458      }));
19459    }
19460    wait_until("both drivers registered as parked", || {
19461      registry.count.load(Ordering::SeqCst) == 2
19462    });
19463
19464    assert!(registry.wake_one(), "wake_one must wake a parked driver");
19465    woke_rx
19466      .recv_timeout(Duration::from_secs(5))
19467      .expect("exactly one driver must wake");
19468    assert!(
19469      woke_rx.recv_timeout(Duration::from_millis(300)).is_err(),
19470      "the second driver must stay parked after a single wake_one"
19471    );
19472    assert_eq!(
19473      registry.count.load(Ordering::SeqCst),
19474      1,
19475      "one driver must remain registered"
19476    );
19477
19478    assert!(
19479      registry.wake_one(),
19480      "a second wake_one must wake the remaining driver"
19481    );
19482    woke_rx
19483      .recv_timeout(Duration::from_secs(5))
19484      .expect("the second driver must wake");
19485    for driver in drivers {
19486      driver.join().unwrap();
19487    }
19488  }
19489
19490  #[cfg(not(target_family = "wasm"))]
19491  #[test]
19492  fn parked_driver_dependency_retirement_drops_waiter_outside_registry_lock() {
19493    use std::sync::mpsc;
19494
19495    struct ReenterRegistryOnDrop {
19496      registry: Arc<ParkedDrivers>,
19497      dropped: mpsc::Sender<()>,
19498    }
19499
19500    #[expect(
19501      clippy::manual_noop_waker,
19502      reason = "the test observes the waker payload's Drop"
19503    )]
19504    impl std::task::Wake for ReenterRegistryOnDrop {
19505      fn wake(self: Arc<Self>) {}
19506    }
19507
19508    impl Drop for ReenterRegistryOnDrop {
19509      fn drop(&mut self) {
19510        let parker = Arc::new(DriverParker::default());
19511        self.registry.register(&parker);
19512        self.registry.deregister(&parker);
19513        self.dropped.send(()).unwrap();
19514      }
19515    }
19516
19517    let registry = Arc::new(ParkedDrivers::default());
19518    let dependency = Arc::new(TaskDependency::default());
19519    let (dropped_tx, dropped_rx) = mpsc::channel();
19520    let waiter = Waker::from(Arc::new(ReenterRegistryOnDrop {
19521      registry: Arc::clone(&registry),
19522      dropped: dropped_tx,
19523    }));
19524    dependency.register_waiter(&waiter);
19525    drop(waiter);
19526
19527    let parker = Arc::new(DriverParker::default());
19528    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
19529    drop(dependency);
19530
19531    let retiring_registry = Arc::clone(&registry);
19532    let retiring_parker = Arc::clone(&parker);
19533    let retiring = std::thread::spawn(move || retiring_registry.deregister(&retiring_parker));
19534    dropped_rx
19535      .recv_timeout(Duration::from_secs(1))
19536      .expect("retained dependency waiters must be destroyed after releasing the registry lock");
19537    retiring.join().unwrap();
19538  }
19539
19540  #[cfg(not(target_family = "wasm"))]
19541  #[test]
19542  fn owner_handoff_dependency_predicate_runs_outside_registry_lock() {
19543    use std::sync::mpsc;
19544
19545    let registry = Arc::new(ParkedDrivers::default());
19546    let owner = BlockingOwnerToken {
19547      executor_id: u64::MAX - 70,
19548      frame: u64::MAX - 71,
19549    };
19550    let dependency = Arc::new(TaskDependency::default());
19551    dependency.set_owned(BlockingDependency {
19552      job: BlockingJobId(u64::MAX - 72),
19553      owner: Some(owner),
19554    });
19555    let parker = Arc::new(DriverParker::default());
19556    registry.register_with_role(&parker, true, Some(Arc::clone(&dependency)));
19557
19558    let (predicate_tx, predicate_rx) = mpsc::channel();
19559    let (release_tx, release_rx) = mpsc::channel();
19560    let selecting_registry = Arc::clone(&registry);
19561    let selector = std::thread::spawn(move || {
19562      DEPENDENCY_PREDICATE_TEST_HOOK.with(|slot| {
19563        *slot.borrow_mut() = Some(Box::new(move || {
19564          predicate_tx.send(()).unwrap();
19565          release_rx.recv().unwrap();
19566        }));
19567      });
19568      selecting_registry.wake_one_owner_dependency(owner)
19569    });
19570
19571    predicate_rx
19572      .recv_timeout(Duration::from_secs(2))
19573      .expect("owner selection must reach the dependency predicate");
19574    let registry_was_unlocked = registry.parked.try_lock().is_ok();
19575    release_tx.send(()).unwrap();
19576    assert!(
19577      selector.join().unwrap(),
19578      "the exact-owner parker must still be selected"
19579    );
19580
19581    assert!(
19582      registry_was_unlocked,
19583      "owner dependency predicates must not run while the parked-driver registry is locked"
19584    );
19585    assert!(parker.consume_permit());
19586    assert_eq!(parker.take_owner_handoff(), Some(owner));
19587  }
19588
19589  #[cfg(not(target_family = "wasm"))]
19590  #[test]
19591  fn owner_handoff_skips_newer_untagged_dependency() {
19592    let registry = ParkedDrivers::default();
19593    let owner = BlockingOwnerToken {
19594      executor_id: u64::MAX - 1,
19595      frame: u64::MAX - 2,
19596    };
19597    let owned_dependency = Arc::new(TaskDependency::default());
19598    owned_dependency.set_owned(BlockingDependency {
19599      job: BlockingJobId(u64::MAX - 3),
19600      owner: Some(owner),
19601    });
19602    let untagged_dependency = Arc::new(TaskDependency::default());
19603    untagged_dependency.set_owned(BlockingDependency {
19604      job: BlockingJobId(u64::MAX - 4),
19605      owner: None,
19606    });
19607    let owned = Arc::new(DriverParker::default());
19608    let untagged = Arc::new(DriverParker::default());
19609
19610    registry.register_with_role(&owned, true, Some(Arc::clone(&owned_dependency)));
19611    // Register the unrelated dependency last so an unfiltered LIFO retry
19612    // would consume the exact owner handoff.
19613    registry.register_with_role(&untagged, true, Some(Arc::clone(&untagged_dependency)));
19614
19615    assert!(registry.wake_one_owner_dependency(owner));
19616    assert!(
19617      owned.consume_permit(),
19618      "the exact owner dependency must receive the retry"
19619    );
19620    assert!(
19621      !untagged.consume_permit(),
19622      "an untagged dependency must not consume an exact owner handoff"
19623    );
19624    registry.deregister(&untagged);
19625  }
19626
19627  #[cfg(not(target_family = "wasm"))]
19628  #[test]
19629  fn withdrawn_owner_handoff_rearms_next_same_owner_dependency() {
19630    let executor = multi_thread_executor(2, None, "withdrawn-owner-handoff");
19631    let owner = executor.fresh_owner_token();
19632    let valid_dependency = Arc::new(TaskDependency::default());
19633    valid_dependency.set_owned(BlockingDependency {
19634      job: BlockingJobId(u64::MAX - 45),
19635      owner: Some(owner),
19636    });
19637    let withdrawn_dependency = Arc::new(TaskDependency::default());
19638    withdrawn_dependency.set_owned(BlockingDependency {
19639      job: BlockingJobId(u64::MAX - 46),
19640      owner: Some(owner),
19641    });
19642    let valid = Arc::new(DriverParker::default());
19643    let withdrawn = Arc::new(DriverParker::default());
19644
19645    executor
19646      .parked_drivers
19647      .register_with_role(&valid, true, Some(Arc::clone(&valid_dependency)));
19648    executor.parked_drivers.register_with_role(
19649      &withdrawn,
19650      true,
19651      Some(Arc::clone(&withdrawn_dependency)),
19652    );
19653    assert!(executor.parked_drivers.wake_one_owner_dependency(owner));
19654    withdrawn_dependency.clear();
19655
19656    assert_eq!(
19657      executor.service_owner_handoff(&withdrawn, &withdrawn_dependency, true),
19658      Some(false),
19659      "a selected dependency withdrawn before retry must forward the owner handoff"
19660    );
19661    assert!(
19662      valid.consume_permit(),
19663      "the next live same-owner dependency must receive the forwarded wake"
19664    );
19665    assert_eq!(
19666      valid.take_owner_handoff(),
19667      Some(owner),
19668      "the forwarded wake must retain the exact owner identity"
19669    );
19670  }
19671
19672  #[cfg(not(target_family = "wasm"))]
19673  #[test]
19674  fn direct_future_wake_cannot_overtake_owner_handoff_publication() {
19675    use std::sync::mpsc;
19676
19677    let executor = multi_thread_executor(2, None, "owner-handoff-publication");
19678    let owner = executor.fresh_owner_token();
19679    let fallback_dependency = Arc::new(TaskDependency::default());
19680    fallback_dependency.set_owned(BlockingDependency {
19681      job: BlockingJobId(u64::MAX - 47),
19682      owner: Some(owner),
19683    });
19684    let exiting_dependency = Arc::new(TaskDependency::default());
19685    exiting_dependency.set_owned(BlockingDependency {
19686      job: BlockingJobId(u64::MAX - 48),
19687      owner: Some(owner),
19688    });
19689    let fallback = Arc::new(DriverParker::default());
19690    let exiting = Arc::new(DriverParker::default());
19691
19692    executor.parked_drivers.register_with_role(
19693      &fallback,
19694      true,
19695      Some(Arc::clone(&fallback_dependency)),
19696    );
19697    executor.parked_drivers.register_with_role(
19698      &exiting,
19699      true,
19700      Some(Arc::clone(&exiting_dependency)),
19701    );
19702
19703    let (selected_tx, selected_rx) = mpsc::channel();
19704    let (release_selection_tx, release_selection_rx) = mpsc::channel();
19705    let selector_executor = Arc::clone(&executor);
19706    let selector = std::thread::spawn(move || {
19707      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
19708        *slot.borrow_mut() = Some(Box::new(move || {
19709          selected_tx.send(()).unwrap();
19710          release_selection_rx.recv().unwrap();
19711        }));
19712      });
19713      assert!(
19714        selector_executor
19715          .parked_drivers
19716          .wake_one_owner_dependency(owner)
19717      );
19718    });
19719    selected_rx
19720      .recv_timeout(Duration::from_secs(2))
19721      .expect("selection must pause after removing the exact-owner parker");
19722
19723    let (exited_tx, exited_rx) = mpsc::channel();
19724    let exiting_executor = Arc::clone(&executor);
19725    let exiting_driver = Arc::clone(&exiting);
19726    let driver = std::thread::spawn(move || {
19727      exiting_driver.park();
19728      exiting_executor.parked_drivers.deregister(&exiting_driver);
19729      exiting_executor.forward_owner_handoff(&exiting_driver);
19730      exited_tx.send(()).unwrap();
19731    });
19732    exiting.unpark();
19733    exited_rx
19734      .recv_timeout(Duration::from_secs(2))
19735      .expect("the direct future wake must let the selected driver exit");
19736
19737    release_selection_tx.send(()).unwrap();
19738    selector.join().unwrap();
19739    driver.join().unwrap();
19740
19741    assert!(
19742      fallback.consume_permit(),
19743      "an exiting selected driver must forward the owner handoff to the next dependency"
19744    );
19745    assert_eq!(
19746      fallback.take_owner_handoff(),
19747      Some(owner),
19748      "the forwarded handoff must retain the exact owner identity"
19749    );
19750  }
19751
19752  #[cfg(not(target_family = "wasm"))]
19753  #[test]
19754  fn cooperative_panic_forwards_pending_owner_handoff() {
19755    use std::sync::mpsc;
19756
19757    struct PanicAfterWake {
19758      dependency: BlockingDependency,
19759      first_poll: Option<mpsc::Sender<()>>,
19760    }
19761
19762    impl Future for PanicAfterWake {
19763      type Output = ();
19764
19765      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19766        if let Some(first_poll) = self.first_poll.take() {
19767          // This shape never retires its publication -- the driver panics
19768          // instead -- so the returned token is dropped straight away.
19769          let mut published = PublishedBlockingDependencies::default();
19770          record_blocking_dependency(&mut published, self.dependency);
19771          first_poll.send(()).unwrap();
19772          return Poll::Pending;
19773        }
19774        panic!("intentional cooperative driver panic after owner handoff");
19775      }
19776    }
19777
19778    let executor = multi_thread_executor(2, None, "panic-owner-handoff");
19779    let owner = executor.fresh_owner_token();
19780    let fallback_dependency = Arc::new(TaskDependency::default());
19781    fallback_dependency.set_owned(BlockingDependency {
19782      job: BlockingJobId(u64::MAX - 49),
19783      owner: Some(owner),
19784    });
19785    let fallback = Arc::new(DriverParker::default());
19786    executor.parked_drivers.register_with_role(
19787      &fallback,
19788      true,
19789      Some(Arc::clone(&fallback_dependency)),
19790    );
19791
19792    let (first_poll_tx, first_poll_rx) = mpsc::channel();
19793    let (panicked_tx, panicked_rx) = mpsc::channel();
19794    let driver_executor = Arc::clone(&executor);
19795    let driver = std::thread::spawn(move || {
19796      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
19797      let mut future = std::pin::pin!(PanicAfterWake {
19798        dependency: BlockingDependency {
19799          job: BlockingJobId(u64::MAX - 50),
19800          owner: Some(owner)
19801        },
19802        first_poll: Some(first_poll_tx),
19803      });
19804      let panicked = catch_unwind(AssertUnwindSafe(|| {
19805        driver_executor.block_on(future.as_mut());
19806      }))
19807      .is_err();
19808      panicked_tx.send(panicked).unwrap();
19809    });
19810
19811    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
19812    wait_until("the panicking cooperative driver to park", || {
19813      executor.parked_drivers.count.load(Ordering::SeqCst) == 2
19814    });
19815    assert!(
19816      executor.parked_drivers.wake_one_owner_dependency(owner),
19817      "the exact owner handoff must select the cooperative driver"
19818    );
19819    assert!(
19820      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
19821      "the test future must unwind from its second poll"
19822    );
19823    driver.join().unwrap();
19824
19825    assert!(
19826      fallback.consume_permit(),
19827      "panic cleanup must forward the unconsumed owner handoff"
19828    );
19829    assert_eq!(
19830      fallback.take_owner_handoff(),
19831      Some(owner),
19832      "panic cleanup must preserve the exact owner identity"
19833    );
19834  }
19835
19836  #[cfg(not(target_family = "wasm"))]
19837  #[test]
19838  fn cooperative_panic_flushes_lifo_slot() {
19839    struct ScheduleThenPanic {
19840      executor: Arc<MultiThreadExecutor>,
19841      ran: Arc<AtomicBool>,
19842    }
19843
19844    impl Future for ScheduleThenPanic {
19845      type Output = ();
19846
19847      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19848        let ran = Arc::clone(&self.ran);
19849        let scheduler = Arc::clone(&self.executor);
19850        let (runnable, task) = async_task::spawn(
19851          async move {
19852            ran.store(true, Ordering::SeqCst);
19853          },
19854          move |runnable| scheduler.schedule(runnable),
19855        );
19856        self.executor.schedule(runnable);
19857        task.detach();
19858        panic!("intentional cooperative driver panic with a populated LIFO slot");
19859      }
19860    }
19861
19862    let executor = multi_thread_executor(2, None, "panic-lifo-flush");
19863    executor
19864      .active_drainers
19865      .store(executor.max_drainers, Ordering::SeqCst);
19866    let ran = Arc::new(AtomicBool::new(false));
19867    let panicked = {
19868      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
19869      let mut future = std::pin::pin!(ScheduleThenPanic {
19870        executor: Arc::clone(&executor),
19871        ran: Arc::clone(&ran),
19872      });
19873      catch_unwind(AssertUnwindSafe(|| {
19874        executor.block_on(future.as_mut());
19875      }))
19876      .is_err()
19877    };
19878
19879    assert!(panicked, "the test future must unwind from its first poll");
19880    assert!(
19881      executor.pop_lifo_slot().is_none(),
19882      "panic cleanup must empty the LIFO slot"
19883    );
19884    assert_eq!(
19885      executor.injector.len(),
19886      1,
19887      "panic cleanup must move the slot runnable to the shared FIFO"
19888    );
19889    assert!(
19890      executor.run_one(),
19891      "the flushed runnable must remain executable"
19892    );
19893    assert!(ran.load(Ordering::SeqCst));
19894    executor.active_drainers.store(0, Ordering::SeqCst);
19895  }
19896
19897  #[cfg(not(target_family = "wasm"))]
19898  #[test]
19899  fn cooperative_panic_compensates_absorbed_queue_wake() {
19900    use std::sync::mpsc;
19901
19902    struct PanicAfterWake {
19903      first_poll: Option<mpsc::Sender<()>>,
19904    }
19905
19906    impl Future for PanicAfterWake {
19907      type Output = ();
19908
19909      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
19910        if let Some(first_poll) = self.first_poll.take() {
19911          first_poll.send(()).unwrap();
19912          return Poll::Pending;
19913        }
19914        panic!("intentional cooperative driver panic after a queue wake");
19915      }
19916    }
19917
19918    let executor = multi_thread_executor(2, None, "panic-wake-compensation");
19919    executor
19920      .active_drainers
19921      .store(executor.max_drainers, Ordering::SeqCst);
19922    let fallback = Arc::new(DriverParker::default());
19923    executor.parked_drivers.register(&fallback);
19924    let (first_poll_tx, first_poll_rx) = mpsc::channel();
19925    let (panicked_tx, panicked_rx) = mpsc::channel();
19926    let driver_executor = Arc::clone(&executor);
19927    let driver = std::thread::spawn(move || {
19928      let _on_pool = OnPoolWorkerGuard::enter(driver_executor.id);
19929      let mut future = std::pin::pin!(PanicAfterWake {
19930        first_poll: Some(first_poll_tx)
19931      });
19932      let panicked = catch_unwind(AssertUnwindSafe(|| {
19933        driver_executor.block_on(future.as_mut());
19934      }))
19935      .is_err();
19936      panicked_tx.send(panicked).unwrap();
19937    });
19938
19939    first_poll_rx.recv_timeout(Duration::from_secs(2)).unwrap();
19940    wait_until(
19941      "the panicking cooperative driver to park after the fallback",
19942      || executor.parked_drivers.count.load(Ordering::SeqCst) == 2,
19943    );
19944
19945    let ran = Arc::new(AtomicBool::new(false));
19946    let ran_task = Arc::clone(&ran);
19947    let scheduler = Arc::clone(&executor);
19948    let (runnable, task) = async_task::spawn(
19949      async move {
19950        ran_task.store(true, Ordering::SeqCst);
19951      },
19952      move |runnable| scheduler.schedule(runnable),
19953    );
19954    task.detach();
19955    executor.metrics.runnable_scheduled();
19956    executor.push_fifo_raw(runnable);
19957    assert!(
19958      executor.parked_drivers.wake_one(),
19959      "the queued runnable's wake must be absorbed by the cooperative driver"
19960    );
19961
19962    assert!(
19963      panicked_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
19964      "the test future must unwind after absorbing the queue wake"
19965    );
19966    driver.join().unwrap();
19967    assert!(
19968      fallback.consume_permit(),
19969      "panic cleanup must compensate the absorbed queue wake to the fallback driver"
19970    );
19971    assert!(
19972      executor.run_one(),
19973      "the queued runnable must remain executable"
19974    );
19975    assert!(ran.load(Ordering::SeqCst));
19976    executor.active_drainers.store(0, Ordering::SeqCst);
19977  }
19978
19979  #[cfg(not(target_family = "wasm"))]
19980  #[test]
19981  fn multi_thread_future_wake_targets_its_own_parked_driver() {
19982    // Future-wake targeting invariant: a future-wake must reach the driver that
19983    // AWAITS that future, not "some parked driver". D1 parks first awaiting
19984    // rx1; D2 parks after it awaiting rx2. Completing rx1 must wake D1 even
19985    // though D2 parked more recently -- a future-wake misrouted through the
19986    // registry (which pops most-recently-parked) would wake D2, leave D1
19987    // parked forever, and trip the bounded timeout. The old notify_all
19988    // broadcast never distinguished drivers, so this pins the new obligation.
19989    use std::sync::mpsc;
19990    use std::time::Duration;
19991
19992    let (done_tx, done_rx) = mpsc::channel();
19993    let runner = std::thread::spawn(move || {
19994      let metrics = Arc::new(RuntimeMetrics::default());
19995      let options = RuntimeOptions {
19996        flavor: RuntimeFlavor::MultiThread,
19997        worker_threads: 2,
19998        max_blocking_tasks: 2,
19999        thread_name_prefix: "wake-target".to_string(),
20000        park_deadline: None,
20001        drain_linger: DEFAULT_DRAIN_LINGER,
20002      };
20003      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20004
20005      let (tx1, rx1) = oneshot::channel::<usize>();
20006      let (tx2, rx2) = oneshot::channel::<usize>();
20007      let t1_done = Arc::new(AtomicBool::new(false));
20008      let t2_done = Arc::new(AtomicBool::new(false));
20009
20010      // T1: parks FIRST, awaiting rx1.
20011      let t1_exec = Arc::clone(&executor);
20012      let t1_flag = Arc::clone(&t1_done);
20013      let t1_body = async move {
20014        let mut inner = std::pin::pin!(async move {
20015          assert_eq!(rx1.await.unwrap(), 7usize);
20016        });
20017        t1_exec.block_on(inner.as_mut());
20018        t1_flag.store(true, Ordering::SeqCst);
20019      };
20020      let sched1 = Arc::clone(&executor);
20021      let (runnable1, task1) = async_task::spawn(t1_body, move |r| sched1.schedule(r));
20022      executor.schedule(runnable1);
20023      wait_until("driver 1 parked", || {
20024        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20025      });
20026
20027      // T2: parks SECOND (most recently). Its runnable is pushed RAW (no
20028      // `schedule`, so no wake_one can pop the already-parked D1) and a
20029      // drainer is spawned for it on the free second worker.
20030      let t2_exec = Arc::clone(&executor);
20031      let t2_flag = Arc::clone(&t2_done);
20032      let t2_body = async move {
20033        let mut inner = std::pin::pin!(async move {
20034          assert_eq!(rx2.await.unwrap(), 9usize);
20035        });
20036        t2_exec.block_on(inner.as_mut());
20037        t2_flag.store(true, Ordering::SeqCst);
20038      };
20039      let sched2 = Arc::clone(&executor);
20040      let (runnable2, task2) = async_task::spawn(t2_body, move |r| sched2.schedule(r));
20041      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20042      executor.push_fifo_raw(runnable2);
20043      executor.ensure_drainer();
20044      wait_until("driver 2 parked", || {
20045        executor.parked_drivers.count.load(Ordering::SeqCst) == 2
20046      });
20047
20048      // Complete D1's future. The wake must land on D1 (first-parked), the
20049      // driver that owns rx1's waker -- not on the more recently parked D2.
20050      tx1.send(7usize).unwrap();
20051      wait_until("T1 completed via its own driver's wake", || {
20052        t1_done.load(Ordering::SeqCst)
20053      });
20054      futures::executor::block_on(task1);
20055      assert!(
20056        !t2_done.load(Ordering::SeqCst),
20057        "D2 must still be parked: its future was never completed"
20058      );
20059      // D1's drain frame LINGERS after T1 retires (see `drain`'s idle
20060      // linger): its linger parker joins the registry alongside D2 for the
20061      // idle budget. The registry count is NON-MONOTONIC here -- it reads 1
20062      // in the window between the cooperative parker's wake-deregistration
20063      // and the drain frame's linger registration, then 2 while lingering,
20064      // then 1 again after expiry -- so a `count == 1` wait can return
20065      // during the transient window and prove nothing about the linger.
20066      // Observe the lifecycle itself instead, via the test counters that
20067      // `drain` maintains: the linger parker must REGISTER, and its idle
20068      // budget must EXPIRE (`drain_linger_expiries` is incremented only
20069      // after the expiry path deregistered the parker, and no further work
20070      // exists to start another episode), after which the registry has
20071      // deterministically settled: EXACTLY the untargeted driver D2 remains
20072      // (a misrouted wake would have left D1 cooperatively parked instead,
20073      // already excluded by the `t1_done` wait above, and a linger parker
20074      // stranded in the registry would read 2 here).
20075      wait_until(
20076        "the retiring drain frame registered its linger parker",
20077        || executor.drain_linger_registrations.load(Ordering::SeqCst) >= 1,
20078      );
20079      wait_until("the lingering drainer's idle budget expired", || {
20080        executor.drain_linger_expiries.load(Ordering::SeqCst) >= 1
20081      });
20082      assert_eq!(
20083        executor.parked_drivers.count.load(Ordering::SeqCst),
20084        1,
20085        "exactly the untargeted driver must remain parked after the linger expired"
20086      );
20087      assert!(
20088        !t2_done.load(Ordering::SeqCst),
20089        "D2 must still be parked after the lingering drainer expired"
20090      );
20091
20092      // Teardown: release D2 as well.
20093      tx2.send(9usize).unwrap();
20094      futures::executor::block_on(task2);
20095      assert!(t2_done.load(Ordering::SeqCst));
20096      let _ = done_tx.send(());
20097    });
20098
20099    match done_rx.recv_timeout(Duration::from_secs(10)) {
20100      Ok(()) => runner.join().unwrap(),
20101      Err(error) => panic!(
20102        "future-wake did not reach its own parked driver ({error}): the wake was misdirected or lost"
20103      ),
20104    }
20105  }
20106
20107  #[cfg(not(target_family = "wasm"))]
20108  #[test]
20109  fn multi_thread_cooperative_exit_hands_absorbed_wake_to_parked_driver() {
20110    // Miss-compensation invariant: a driver that exits `cooperative_block_on`
20111    // with queue work remaining must hand the wake on because it may have
20112    // absorbed a `wake_one` permit meant for that work. The SOLE pool worker
20113    // is parked as a cooperative driver, a runnable is pushed without a wake,
20114    // and the test thread forces a ready future through the cooperative branch.
20115    // Exit compensation is the ONLY mechanism left that can wake the parked
20116    // worker to drain the runnable.
20117    use std::sync::mpsc;
20118    use std::time::Duration;
20119
20120    let (done_tx, done_rx) = mpsc::channel();
20121    let runner = std::thread::spawn(move || {
20122      let metrics = Arc::new(RuntimeMetrics::default());
20123      let options = RuntimeOptions {
20124        flavor: RuntimeFlavor::MultiThread,
20125        worker_threads: 1,
20126        max_blocking_tasks: 1,
20127        thread_name_prefix: "wake-compensate".to_string(),
20128        park_deadline: None,
20129        drain_linger: DEFAULT_DRAIN_LINGER,
20130      };
20131      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
20132
20133      // Park the sole worker as a cooperative driver awaiting a gate.
20134      let (gate_tx, gate_rx) = oneshot::channel::<()>();
20135      let td_exec = Arc::clone(&executor);
20136      let td_body = async move {
20137        let mut inner = std::pin::pin!(async move {
20138          gate_rx.await.unwrap();
20139        });
20140        td_exec.block_on(inner.as_mut());
20141      };
20142      let sched = Arc::clone(&executor);
20143      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
20144      executor.schedule(td_runnable);
20145      wait_until("the sole driver parked", || {
20146        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20147      });
20148
20149      // Strand a runnable: pushed RAW, so no wake accompanies it, and the
20150      // sole worker is parked-as-drainer, so `ensure_drainer` cannot help.
20151      let (ran_tx, ran_rx) = mpsc::channel::<()>();
20152      let sched_stranded = Arc::clone(&executor);
20153      let (stranded_runnable, stranded_task) = async_task::spawn(
20154        async move {
20155          ran_tx.send(()).unwrap();
20156        },
20157        move |r| sched_stranded.schedule(r),
20158      );
20159      executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20160      executor.push_fifo_raw(stranded_runnable);
20161      stranded_task.detach();
20162
20163      // Drive a ready future through the cooperative branch on THIS thread
20164      // (fake on-pool marker, as in the executor-scoping test). Its first
20165      // poll is Ready, so the loop performs no work itself -- only the exit
20166      // compensation can hand the queued runnable to the parked driver.
20167      {
20168        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20169        let mut ready = std::pin::pin!(async {});
20170        executor.block_on(ready.as_mut());
20171      }
20172
20173      ran_rx.recv_timeout(Duration::from_secs(5)).expect(
20174        "exit compensation must wake the parked driver to run the stranded queued runnable",
20175      );
20176
20177      // Teardown: release the parked driver.
20178      gate_tx.send(()).unwrap();
20179      futures::executor::block_on(td_task);
20180      let _ = done_tx.send(());
20181    });
20182
20183    match done_rx.recv_timeout(Duration::from_secs(10)) {
20184      Ok(()) => runner.join().unwrap(),
20185      Err(error) => panic!("cooperative-exit miss compensation failed ({error})"),
20186    }
20187  }
20188
20189  #[cfg(not(target_family = "wasm"))]
20190  #[test]
20191  fn cooperative_exit_compensates_blocking_only_residue() {
20192    use std::sync::mpsc;
20193
20194    struct Controlled {
20195      ready: Arc<AtomicBool>,
20196      entered: Option<mpsc::Sender<()>>,
20197    }
20198
20199    impl Future for Controlled {
20200      type Output = ();
20201
20202      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
20203        if let Some(entered) = self.entered.take() {
20204          entered.send(()).unwrap();
20205        }
20206        if self.ready.load(Ordering::SeqCst) {
20207          Poll::Ready(())
20208        } else {
20209          Poll::Pending
20210        }
20211      }
20212    }
20213
20214    let options = RuntimeOptions {
20215      flavor: RuntimeFlavor::MultiThread,
20216      worker_threads: 2,
20217      max_blocking_tasks: 1,
20218      thread_name_prefix: "blocking-exit-compensation".to_string(),
20219      park_deadline: None,
20220      drain_linger: DEFAULT_DRAIN_LINGER,
20221    };
20222    let executor =
20223      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20224    let ready = Arc::new(AtomicBool::new(false));
20225    let (entered_tx, entered_rx) = mpsc::channel();
20226    let (returned_tx, returned_rx) = mpsc::channel();
20227    let (release_tx, release_rx) = mpsc::channel();
20228    let task_exec = Arc::clone(&executor);
20229    let task_ready = Arc::clone(&ready);
20230    let scheduler = Arc::clone(&executor);
20231    let (runnable, task) = async_task::spawn(
20232      async move {
20233        let mut future = std::pin::pin!(Controlled {
20234          ready: task_ready,
20235          entered: Some(entered_tx)
20236        });
20237        task_exec.block_on(future.as_mut());
20238        returned_tx.send(()).unwrap();
20239        release_rx.recv().unwrap();
20240      },
20241      move |runnable| scheduler.schedule(runnable),
20242    );
20243    executor.schedule(runnable);
20244    entered_rx.recv_timeout(Duration::from_secs(10)).unwrap();
20245
20246    // The cooperative frame must be PARKED (registered) before the timer task
20247    // is scheduled, so that schedule's wake targets the frame instead of
20248    // spawning a second drainer. A second drainer would later LINGER as a
20249    // registered blocking-capable parker, and `wake_one_blocking` prefers the
20250    // most recently parked candidate: on a starved runner (observed on the
20251    // Windows CI) the linger parker absorbs the blocking wake below, the
20252    // drainer services the job, and the frame -- whose exit compensation is
20253    // the subject under test -- sleeps in its timer-bounded park until the
20254    // 120s heap timer, far past any sane recv bound.
20255    wait_until("the cooperative frame parked", || {
20256      executor.parked_drivers.count.load(Ordering::SeqCst) == 1
20257    });
20258
20259    let timer_exec = Arc::clone(&executor);
20260    let timer_scheduler = Arc::clone(&executor);
20261    let (timer_runnable, timer_task) = async_task::spawn(
20262      async move {
20263        heap_sleep(&timer_exec, Instant::now() + Duration::from_secs(120)).await;
20264      },
20265      move |runnable| timer_scheduler.schedule(runnable),
20266    );
20267    executor.schedule(timer_runnable);
20268    wait_until("the dedicated timekeeper parked", || {
20269      executor
20270        .timers
20271        .inner
20272        .lock()
20273        .unwrap()
20274        .timekeeper_parker
20275        .is_some()
20276    });
20277    // Re-parked after running the timer runnable: the blocking wake below
20278    // must find the frame registered (the sole candidate), so the frame
20279    // absorbs the wake, observes its ready future, and its EXIT COMPENSATION
20280    // services the blocking-only residue.
20281    wait_until(
20282      "the cooperative frame re-parked in its timer-bounded park",
20283      || executor.parked_drivers.count.load(Ordering::SeqCst) == 1,
20284    );
20285
20286    ready.store(true, Ordering::SeqCst);
20287    let (blocking_tx, blocking_rx) = mpsc::channel();
20288    let blocking = executor.schedule_blocking(move || {
20289      blocking_tx.send(()).unwrap();
20290    });
20291    // Generous hang-detector bounds, not latency assertions: starved CI
20292    // runners (msys2 CLANG64) can stall a woken thread for whole seconds.
20293    blocking_rx
20294      .recv_timeout(Duration::from_secs(10))
20295      .expect("blocking-only residue must be serviced by a Rayon worker");
20296    returned_rx
20297      .recv_timeout(Duration::from_secs(10))
20298      .expect("the cooperative frame must observe its ready future and return after the absorbed blocking wake");
20299    release_tx.send(()).unwrap();
20300    futures::executor::block_on(task);
20301    futures::executor::block_on(blocking).unwrap();
20302    executor.shutdown_timers();
20303    futures::executor::block_on(timer_task);
20304  }
20305
20306  #[cfg(not(target_family = "wasm"))]
20307  #[test]
20308  fn cooperative_exit_compensation_does_not_start_blocking_after_stop() {
20309    let options = RuntimeOptions {
20310      flavor: RuntimeFlavor::MultiThread,
20311      worker_threads: 2,
20312      max_blocking_tasks: 1,
20313      thread_name_prefix: "stopped-exit-compensation".to_string(),
20314      park_deadline: None,
20315      drain_linger: DEFAULT_DRAIN_LINGER,
20316    };
20317    let executor =
20318      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20319    let ran = Arc::new(AtomicBool::new(false));
20320    let blocking_ran = Arc::clone(&ran);
20321
20322    // Keep schedule_blocking from starting a Rayon drainer so the stop flag can
20323    // become observable while this job remains queued, matching the original
20324    // begin_shutdown ordering window exactly.
20325    executor
20326      .active_drainers
20327      .store(executor.max_drainers, Ordering::SeqCst);
20328    let blocking = executor.schedule_blocking(move || {
20329      blocking_ran.store(true, Ordering::SeqCst);
20330    });
20331    assert!(!executor.blocking_queue.lock().unwrap().is_empty());
20332
20333    executor.stop.begin_shutdown();
20334    executor.compensate_queued_work_wake(true);
20335    let ran_after_stop = ran.load(Ordering::SeqCst);
20336
20337    executor.active_drainers.store(0, Ordering::SeqCst);
20338    executor.begin_shutdown();
20339    let blocking_result = futures::executor::block_on(blocking);
20340
20341    assert!(
20342      !ran_after_stop,
20343      "exit compensation started queued blocking work after stop"
20344    );
20345    assert!(
20346      blocking_result.is_err(),
20347      "shutdown must cancel the queued blocking job"
20348    );
20349  }
20350
20351  #[cfg(not(target_family = "wasm"))]
20352  #[test]
20353  fn wake_path_wake_one_vs_park_interleaving_loses_no_wakeups() {
20354    // Targeted-wake stress: N rounds of {producer: publish work,
20355    // wake_one()} racing {consumer: check work, register, re-check, park} --
20356    // the exact schedule-vs-park protocol `schedule` and
20357    // `cooperative_block_on` run. This walks the interleaving at every offset
20358    // the OS scheduler produces; a single lost wakeup parks the consumer
20359    // forever with work pending, and the bounded recv_timeout turns that into
20360    // a loud failure instead of a hung suite. (No loom in this repo; std
20361    // threads.)
20362    use std::sync::mpsc;
20363    use std::time::Duration;
20364
20365    const ROUNDS: usize = 20_000;
20366
20367    let (done_tx, done_rx) = mpsc::channel();
20368    let registry = Arc::new(ParkedDrivers::default());
20369    let work = Arc::new(AtomicBool::new(false));
20370
20371    let consumer = {
20372      let registry = Arc::clone(&registry);
20373      let work = Arc::clone(&work);
20374      std::thread::spawn(move || {
20375        let parker = Arc::new(DriverParker::default());
20376        for _ in 0..ROUNDS {
20377          loop {
20378            if work.swap(false, Ordering::SeqCst) {
20379              break;
20380            }
20381            // The cooperative park protocol: register FIRST, then re-check,
20382            // then park (see `ParkedDrivers::wake_one`).
20383            registry.register(&parker);
20384            if work.load(Ordering::SeqCst) {
20385              registry.deregister(&parker);
20386              continue;
20387            }
20388            parker.park();
20389            registry.deregister(&parker);
20390          }
20391        }
20392        done_tx.send(()).unwrap();
20393      })
20394    };
20395
20396    for _ in 0..ROUNDS {
20397      work.store(true, Ordering::SeqCst);
20398      // The schedule side: publish work, then wake one parked driver. When
20399      // wake_one misses (consumer not registered yet), the consumer's
20400      // post-register re-check must see the work instead.
20401      let _ = registry.wake_one();
20402      while work.load(Ordering::SeqCst) {
20403        std::hint::spin_loop();
20404      }
20405    }
20406
20407    done_rx
20408      .recv_timeout(Duration::from_secs(30))
20409      .expect("a wake_one()-side wakeup was lost: the consumer is parked with work pending");
20410    consumer.join().unwrap();
20411  }
20412
20413  #[cfg(not(target_family = "wasm"))]
20414  #[test]
20415  fn lifo_slot_same_worker_schedule_runs_before_older_fifo_work() {
20416    // LIFO locality invariant: a runnable scheduled from a pool worker of the SAME
20417    // executor lands in that worker's LIFO slot (no FIFO push, no wake) and is
20418    // run by the next drain/run_one iteration BEFORE older shared-FIFO work.
20419    let options = RuntimeOptions {
20420      flavor: RuntimeFlavor::MultiThread,
20421      worker_threads: 2,
20422      max_blocking_tasks: 2,
20423      thread_name_prefix: "lifo-order".to_string(),
20424      park_deadline: None,
20425      drain_linger: DEFAULT_DRAIN_LINGER,
20426    };
20427    let executor =
20428      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20429    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20430
20431    // Older work, pushed RAW onto the FIFO (so no drainer is spawned that
20432    // could race the manual `run_one` calls below).
20433    let old_order = Arc::clone(&order);
20434    let sched = Arc::clone(&executor);
20435    let (old_runnable, old_task) = async_task::spawn(
20436      async move {
20437        old_order.lock().unwrap().push("old");
20438      },
20439      move |r| sched.schedule(r),
20440    );
20441    executor.metrics.runnable_scheduled(); // keep the raw push's accounting balanced
20442    executor.push_fifo_raw(old_runnable);
20443    old_task.detach();
20444
20445    // Simulate being on this executor's pool worker: the schedule below must
20446    // take the slot path (which never touches the FIFO nor spawns a drainer).
20447    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20448    let new_order = Arc::clone(&order);
20449    let sched = Arc::clone(&executor);
20450    let (new_runnable, new_task) = async_task::spawn(
20451      async move {
20452        new_order.lock().unwrap().push("new");
20453      },
20454      move |r| sched.schedule(r),
20455    );
20456    executor.schedule(new_runnable);
20457    new_task.detach();
20458    assert_eq!(
20459      executor.injector.len(),
20460      1,
20461      "the slot-path schedule must not push to the shared FIFO"
20462    );
20463
20464    assert!(executor.run_one(), "run_one must pop the slot first");
20465    assert!(executor.run_one(), "run_one must then pop the FIFO");
20466    assert!(!executor.run_one(), "no work must remain");
20467    assert_eq!(
20468      *order.lock().unwrap(),
20469      vec!["new", "old"],
20470      "the slot runnable (newest) must run before older FIFO work"
20471    );
20472  }
20473
20474  #[cfg(not(target_family = "wasm"))]
20475  #[test]
20476  fn drain_fifo_runnable_does_not_inherit_blocking_owner() {
20477    let options = RuntimeOptions {
20478      flavor: RuntimeFlavor::MultiThread,
20479      worker_threads: 2,
20480      max_blocking_tasks: 1,
20481      thread_name_prefix: "fifo-owner".to_string(),
20482      park_deadline: None,
20483      drain_linger: DEFAULT_DRAIN_LINGER,
20484    };
20485    let executor =
20486      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20487    let observed_owner = Arc::new(Mutex::new(None));
20488    let observed = Arc::clone(&observed_owner);
20489    let (runnable, task) = async_task::spawn(
20490      async move {
20491        *observed.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
20492      },
20493      |_| {},
20494    );
20495    task.detach();
20496    executor.metrics.runnable_scheduled();
20497    executor.push_fifo_raw(runnable);
20498    executor.active_drainers.store(1, Ordering::Release);
20499
20500    let owner = executor.fresh_owner_token();
20501    let _owner_guard = BlockingOwnerGuard::enter(Some(owner));
20502    Arc::clone(&executor).drain();
20503
20504    assert_eq!(
20505      *observed_owner.lock().unwrap(),
20506      Some(None),
20507      "FIFO runnables must not borrow a blocking closure's over-cap privilege"
20508    );
20509    assert_eq!(
20510      BLOCKING_OWNER.with(std::cell::Cell::get),
20511      Some(owner),
20512      "the ambient blocking owner must be restored after the runnable"
20513    );
20514  }
20515
20516  #[cfg(not(target_family = "wasm"))]
20517  #[test]
20518  fn lifo_slot_drain_runs_slot_before_older_fifo_work() {
20519    // Drain integration: with one worker, task P scheduling
20520    // child C mid-drain must see C run IMMEDIATELY after P (via the slot),
20521    // jumping ahead of the older queued T1/T2.
20522    use std::sync::mpsc;
20523    use std::time::Duration;
20524
20525    let (done_tx, done_rx) = mpsc::channel();
20526    let runner = std::thread::spawn(move || {
20527      let options = RuntimeOptions {
20528        flavor: RuntimeFlavor::MultiThread,
20529        worker_threads: 1,
20530        max_blocking_tasks: 1,
20531        thread_name_prefix: "lifo-drain".to_string(),
20532        park_deadline: None,
20533        drain_linger: DEFAULT_DRAIN_LINGER,
20534      };
20535      let executor =
20536        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20537      let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20538
20539      let p_exec = Arc::clone(&executor);
20540      let p_order = Arc::clone(&order);
20541      let p_body = async move {
20542        p_order.lock().unwrap().push("P");
20543        // Scheduled from the pool worker mid-drain: must land in the slot.
20544        let c_order = Arc::clone(&p_order);
20545        let sched = Arc::clone(&p_exec);
20546        let (c_runnable, c_task) = async_task::spawn(
20547          async move {
20548            c_order.lock().unwrap().push("C");
20549          },
20550          move |r| sched.schedule(r),
20551        );
20552        p_exec.schedule(c_runnable);
20553        c_task.detach();
20554      };
20555
20556      // Push P, T1, T2 RAW (deterministic FIFO order, exactly one drainer),
20557      // mirroring the `runnable_scheduled` accounting a real schedule does so
20558      // the queued-runnables counter stays balanced.
20559      let mut tasks = Vec::new();
20560      let sched = Arc::clone(&executor);
20561      let (p_runnable, p_task) = async_task::spawn(p_body, move |r| sched.schedule(r));
20562      executor.metrics.runnable_scheduled();
20563      executor.push_fifo_raw(p_runnable);
20564      tasks.push(p_task);
20565      for name in ["T1", "T2"] {
20566        let t_order = Arc::clone(&order);
20567        let sched = Arc::clone(&executor);
20568        let (t_runnable, t_task) = async_task::spawn(
20569          async move {
20570            t_order.lock().unwrap().push(name);
20571          },
20572          move |r| sched.schedule(r),
20573        );
20574        executor.metrics.runnable_scheduled();
20575        executor.push_fifo_raw(t_runnable);
20576        tasks.push(t_task);
20577      }
20578      executor.ensure_drainer();
20579
20580      for task in tasks {
20581        futures::executor::block_on(task);
20582      }
20583      wait_until("the detached child C ran", || {
20584        order.lock().unwrap().len() == 4
20585      });
20586      assert_eq!(
20587        *order.lock().unwrap(),
20588        vec!["P", "C", "T1", "T2"],
20589        "the slot child C must run right after its scheduler P, ahead of older FIFO work"
20590      );
20591      let _ = done_tx.send(());
20592    });
20593
20594    match done_rx.recv_timeout(Duration::from_secs(10)) {
20595      Ok(()) => runner.join().unwrap(),
20596      Err(error) => panic!("LIFO drain-order test did not complete ({error})"),
20597    }
20598  }
20599
20600  #[cfg(not(target_family = "wasm"))]
20601  #[test]
20602  fn lifo_slot_displaced_occupant_falls_back_to_fifo() {
20603    // Single-slot displacement invariant: two same-executor schedules from one worker -- the
20604    // NEWEST takes the slot; the displaced occupant must land on the shared
20605    // FIFO (with a wake), never be dropped. `active_drainers` is saturated so
20606    // the displacement wake cannot spawn a real drainer racing the manual
20607    // `run_one` calls.
20608    let options = RuntimeOptions {
20609      flavor: RuntimeFlavor::MultiThread,
20610      worker_threads: 2,
20611      max_blocking_tasks: 2,
20612      thread_name_prefix: "lifo-displace".to_string(),
20613      park_deadline: None,
20614      drain_linger: DEFAULT_DRAIN_LINGER,
20615    };
20616    let executor =
20617      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20618    executor
20619      .active_drainers
20620      .store(executor.max_drainers, Ordering::SeqCst);
20621    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20622
20623    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20624    for name in ["a", "b"] {
20625      let n_order = Arc::clone(&order);
20626      let sched = Arc::clone(&executor);
20627      let (runnable, task) = async_task::spawn(
20628        async move {
20629          n_order.lock().unwrap().push(name);
20630        },
20631        move |r| sched.schedule(r),
20632      );
20633      executor.schedule(runnable);
20634      task.detach();
20635    }
20636    assert_eq!(
20637      executor.injector.len(),
20638      1,
20639      "the displaced occupant (a) must have been pushed to the shared FIFO"
20640    );
20641
20642    assert!(executor.run_one(), "run_one must pop the slot (b) first");
20643    assert!(
20644      executor.run_one(),
20645      "run_one must then pop the displaced (a) from the FIFO"
20646    );
20647    assert!(!executor.run_one(), "no work must remain");
20648    assert_eq!(
20649      *order.lock().unwrap(),
20650      vec!["b", "a"],
20651      "the newest schedule must win the slot; the displaced one must survive via the FIFO"
20652    );
20653    executor.active_drainers.store(0, Ordering::SeqCst);
20654  }
20655
20656  #[cfg(not(target_family = "wasm"))]
20657  #[test]
20658  fn lifo_slot_is_executor_scoped() {
20659    // Executor-scoping invariant: the slot is tagged with the executor id (mirroring the
20660    // ON_POOL_WORKER / BlockingOwnerToken scoping). A worker of executor A
20661    // scheduling onto executor B must bypass the slot (B's FIFO); and with
20662    // A's runnable IN the slot, B must neither pop nor displace it.
20663    let options = RuntimeOptions {
20664      flavor: RuntimeFlavor::MultiThread,
20665      worker_threads: 2,
20666      max_blocking_tasks: 2,
20667      thread_name_prefix: "lifo-scope".to_string(),
20668      park_deadline: None,
20669      drain_linger: DEFAULT_DRAIN_LINGER,
20670    };
20671    let exec1 =
20672      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20673    let exec2 =
20674      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20675    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
20676    exec1
20677      .active_drainers
20678      .store(exec1.max_drainers, Ordering::SeqCst);
20679    exec2
20680      .active_drainers
20681      .store(exec2.max_drainers, Ordering::SeqCst);
20682    let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
20683
20684    // As exec1's worker: r1 goes to the slot, tagged exec1.
20685    let _on_pool1 = OnPoolWorkerGuard::enter(exec1.id);
20686    let r1_order = Arc::clone(&order);
20687    let sched = Arc::clone(&exec1);
20688    let (r1, t1) = async_task::spawn(
20689      async move {
20690        r1_order.lock().unwrap().push("one");
20691      },
20692      move |r| sched.schedule(r),
20693    );
20694    exec1.schedule(r1);
20695    t1.detach();
20696    assert!(
20697      exec1.injector.is_empty(),
20698      "r1 must sit in the slot, not exec1's FIFO"
20699    );
20700
20701    {
20702      // Nested: now act as exec2's worker while exec1's runnable occupies the
20703      // slot. exec2 must not displace the foreign entry; r2 goes to its FIFO.
20704      let _on_pool2 = OnPoolWorkerGuard::enter(exec2.id);
20705      let r2_order = Arc::clone(&order);
20706      let sched = Arc::clone(&exec2);
20707      let (r2, t2) = async_task::spawn(
20708        async move {
20709          r2_order.lock().unwrap().push("two");
20710        },
20711        move |r| sched.schedule(r),
20712      );
20713      exec2.schedule(r2);
20714      t2.detach();
20715      assert_eq!(
20716        exec2.injector.len(),
20717        1,
20718        "a foreign-occupied slot must route exec2's schedule to exec2's FIFO"
20719      );
20720      assert!(
20721        exec2.pop_lifo_slot().is_none(),
20722        "exec2 must not pop exec1's slot entry (executor-scoped pop)"
20723      );
20724      assert!(exec2.run_one(), "exec2 must drive r2 from its own FIFO");
20725    }
20726
20727    // Back as exec1's worker: its slot entry is intact and runnable.
20728    assert!(
20729      exec1.run_one(),
20730      "exec1's slot entry must have survived the foreign activity"
20731    );
20732    assert_eq!(*order.lock().unwrap(), vec!["two", "one"]);
20733    exec1.active_drainers.store(0, Ordering::SeqCst);
20734    exec2.active_drainers.store(0, Ordering::SeqCst);
20735  }
20736
20737  #[cfg(not(target_family = "wasm"))]
20738  #[test]
20739  fn lifo_slot_flushed_on_budget_exhaustion_completes_long_chains() {
20740    // LIFO chain flush invariant: a 200-link chain in which each task schedules the
20741    // next FROM the pool worker (every link lands in the slot) crosses
20742    // multiple RUNNABLE_BUDGET windows. At each exhaustion the slot MUST be
20743    // flushed to the FIFO before `finish_draining` (so its re-check sees the
20744    // runnable and re-arms a drainer); a missing flush strands the chain at
20745    // the first budget boundary (lost task + queued_runnables leak).
20746    use std::sync::mpsc;
20747    use std::time::Duration;
20748
20749    const CHAIN: u64 = 200; // > RUNNABLE_BUDGET (64)
20750
20751    fn spawn_link(
20752      executor: &Arc<MultiThreadExecutor>,
20753      remaining: u64,
20754      counter: Arc<AtomicU64>,
20755      done: mpsc::Sender<()>,
20756    ) {
20757      let body_exec = Arc::clone(executor);
20758      let body = async move {
20759        counter.fetch_add(1, Ordering::SeqCst);
20760        if remaining > 0 {
20761          spawn_link(&body_exec, remaining - 1, counter, done);
20762        } else {
20763          done.send(()).unwrap();
20764        }
20765      };
20766      let sched = Arc::clone(executor);
20767      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
20768      executor.schedule(runnable);
20769      task.detach();
20770    }
20771
20772    let (done_tx, done_rx) = mpsc::channel();
20773    let runner = std::thread::spawn(move || {
20774      let metrics = Arc::new(RuntimeMetrics::default());
20775      let options = RuntimeOptions {
20776        flavor: RuntimeFlavor::MultiThread,
20777        worker_threads: 1,
20778        max_blocking_tasks: 1,
20779        thread_name_prefix: "lifo-budget".to_string(),
20780        park_deadline: None,
20781        drain_linger: DEFAULT_DRAIN_LINGER,
20782      };
20783      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
20784
20785      let counter = Arc::new(AtomicU64::new(0));
20786      let (chain_tx, chain_rx) = mpsc::channel();
20787      spawn_link(&executor, CHAIN - 1, Arc::clone(&counter), chain_tx);
20788
20789      chain_rx
20790        .recv_timeout(Duration::from_secs(10))
20791        .expect("the slot chain stalled at a budget boundary: slot not flushed on drain exit");
20792      assert_eq!(
20793        counter.load(Ordering::SeqCst),
20794        CHAIN,
20795        "every chain link must have run"
20796      );
20797      wait_until("runnable counters settle to zero (no slot leak)", || {
20798        metrics.queued_runnables.load(Ordering::Relaxed) == 0
20799          && metrics.active_runnables.load(Ordering::Relaxed) == 0
20800      });
20801      let _ = done_tx.send(());
20802    });
20803
20804    match done_rx.recv_timeout(Duration::from_secs(15)) {
20805      Ok(()) => runner.join().unwrap(),
20806      Err(error) => panic!("LIFO budget-flush chain test did not complete ({error})"),
20807    }
20808  }
20809
20810  #[cfg(not(target_family = "wasm"))]
20811  #[test]
20812  fn lifo_slot_flush_moves_slot_runnable_to_shared_fifo() {
20813    // Direct flush primitive test: pin the flush behavior used at drain
20814    // exits, before cooperative parks and by the unwind guard -- a
20815    // slot-resident runnable moves to the shared FIFO with its task still
20816    // completable, never dropped. (The pre-park call is defensive: `run_one`
20817    // pops the slot before any park is reachable, so this primitive is its
20818    // only observable seam.)
20819    let options = RuntimeOptions {
20820      flavor: RuntimeFlavor::MultiThread,
20821      worker_threads: 2,
20822      max_blocking_tasks: 2,
20823      thread_name_prefix: "lifo-flush".to_string(),
20824      park_deadline: None,
20825      drain_linger: DEFAULT_DRAIN_LINGER,
20826    };
20827    let executor =
20828      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20829    executor
20830      .active_drainers
20831      .store(executor.max_drainers, Ordering::SeqCst);
20832    let ran = Arc::new(AtomicBool::new(false));
20833
20834    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20835    let ran_flag = Arc::clone(&ran);
20836    let sched = Arc::clone(&executor);
20837    let (runnable, task) = async_task::spawn(
20838      async move {
20839        ran_flag.store(true, Ordering::SeqCst);
20840      },
20841      move |r| sched.schedule(r),
20842    );
20843    executor.schedule(runnable);
20844    task.detach();
20845    assert!(
20846      executor.injector.is_empty(),
20847      "the runnable must start in the slot"
20848    );
20849
20850    executor.flush_lifo_slot();
20851    assert!(
20852      executor.pop_lifo_slot().is_none(),
20853      "the slot must be empty after a flush"
20854    );
20855    assert_eq!(
20856      executor.injector.len(),
20857      1,
20858      "the flushed runnable must be on the shared FIFO"
20859    );
20860    assert!(executor.run_one(), "the flushed runnable must still run");
20861    assert!(
20862      ran.load(Ordering::SeqCst),
20863      "the flushed task must complete, not be dropped"
20864    );
20865    executor.active_drainers.store(0, Ordering::SeqCst);
20866  }
20867
20868  #[cfg(not(target_family = "wasm"))]
20869  #[test]
20870  fn lifo_slot_runnable_does_not_inherit_blocking_owner() {
20871    // Blocking-owner isolation must cover the LIFO slot path: a runnable
20872    // popped from the slot runs with BLOCKING_OWNER cleared, so it cannot
20873    // borrow the driving owner frame's over-cap privilege; the ambient owner
20874    // is restored afterwards.
20875    let options = RuntimeOptions {
20876      flavor: RuntimeFlavor::MultiThread,
20877      worker_threads: 2,
20878      max_blocking_tasks: 2,
20879      thread_name_prefix: "lifo-owner".to_string(),
20880      park_deadline: None,
20881      drain_linger: DEFAULT_DRAIN_LINGER,
20882    };
20883    let executor =
20884      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
20885    let observed = Arc::new(Mutex::new(None::<Option<BlockingOwnerToken>>));
20886
20887    let _on_pool = OnPoolWorkerGuard::enter(executor.id);
20888
20889    // Install the slot entry from RUNNABLE context (no ambient owner), the
20890    // only context that may use the slot because schedules issued under an
20891    // owner frame are diverted to the shared FIFO.
20892    let observed_slot = Arc::clone(&observed);
20893    let sched = Arc::clone(&executor);
20894    let (runnable, task) = async_task::spawn(
20895      async move {
20896        *observed_slot.lock().unwrap() = Some(BLOCKING_OWNER.with(std::cell::Cell::get));
20897      },
20898      move |r| sched.schedule(r),
20899    );
20900    executor.schedule(runnable);
20901    task.detach();
20902    assert!(
20903      executor.injector.is_empty(),
20904      "the runnable must sit in the slot"
20905    );
20906
20907    // The POP can still happen while an owner frame is ambient (a cooperative
20908    // `block_on` entered from a blocking closure drives `run_one` with the
20909    // closure's token on the stack); the slot branch must clear it.
20910    let token = executor.fresh_owner_token();
20911    let _owner = BlockingOwnerGuard::enter(Some(token));
20912    assert!(executor.run_one(), "the slot runnable must run");
20913    assert_eq!(
20914      *observed.lock().unwrap(),
20915      Some(None),
20916      "a slot runnable must observe a CLEARED blocking owner"
20917    );
20918    assert_eq!(
20919      BLOCKING_OWNER.with(std::cell::Cell::get),
20920      Some(token),
20921      "the ambient owner frame must be restored after the slot runnable"
20922    );
20923  }
20924
20925  // Shared ping-pong probe for the LIFO fairness tests: two tasks that wake
20926  // each other on every poll. Scheduled from a pool worker, each wake lands in
20927  // that worker's LIFO slot, keeping the slot hot indefinitely. A task
20928  // finishes when `stop` is raised (or `stop_after` polls happened), releasing
20929  // its partner's stored waker so both complete.
20930  #[cfg(not(target_family = "wasm"))]
20931  struct PingPongShared {
20932    count: AtomicU64,
20933    stop: AtomicBool,
20934    stop_after: u64,
20935    wakers: Mutex<[Option<std::task::Waker>; 2]>,
20936  }
20937
20938  #[cfg(not(target_family = "wasm"))]
20939  struct PingPong {
20940    shared: Arc<PingPongShared>,
20941    me: usize,
20942  }
20943
20944  #[cfg(not(target_family = "wasm"))]
20945  impl Future for PingPong {
20946    type Output = ();
20947    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
20948      let shared = &self.shared;
20949      let polls = shared.count.fetch_add(1, Ordering::SeqCst);
20950      if shared.stop.load(Ordering::SeqCst) || polls >= shared.stop_after {
20951        let partner = shared.wakers.lock().unwrap()[1 - self.me].take();
20952        if let Some(partner) = partner {
20953          partner.wake();
20954        }
20955        return Poll::Ready(());
20956      }
20957      let mut wakers = shared.wakers.lock().unwrap();
20958      wakers[self.me] = Some(cx.waker().clone());
20959      if let Some(partner) = wakers[1 - self.me].take() {
20960        drop(wakers);
20961        partner.wake();
20962      }
20963      Poll::Pending
20964    }
20965  }
20966
20967  #[cfg(not(target_family = "wasm"))]
20968  fn spawn_ping_pong_pair(
20969    executor: &Arc<MultiThreadExecutor>,
20970    shared: &Arc<PingPongShared>,
20971  ) -> Vec<Task<()>> {
20972    let mut tasks = Vec::new();
20973    for me in 0..2 {
20974      let sched = Arc::clone(executor);
20975      let (runnable, task) = async_task::spawn(
20976        PingPong {
20977          shared: Arc::clone(shared),
20978          me,
20979        },
20980        move |r| sched.schedule(r),
20981      );
20982      executor.schedule(runnable);
20983      tasks.push(task);
20984    }
20985    tasks
20986  }
20987
20988  #[cfg(not(target_family = "wasm"))]
20989  #[test]
20990  fn lifo_slot_ping_pong_does_not_starve_queued_fifo_task() {
20991    // LIFO streak-cap invariant: slot pops share `drain`'s RUNNABLE_BUDGET, so
20992    // a hot wake/await pair holds a worker for at most one budget window
20993    // before the slot is flushed and the FIFO gets its turn. With ONE worker,
20994    // a third task queued behind an unbounded ping-pong pair must still run;
20995    // if slot pops bypassed the budget the drain would never exit and T3
20996    // would starve forever (caught by the bounded timeout).
20997    use std::sync::mpsc;
20998    use std::time::Duration;
20999
21000    let (done_tx, done_rx) = mpsc::channel();
21001    let runner = std::thread::spawn(move || {
21002      let options = RuntimeOptions {
21003        flavor: RuntimeFlavor::MultiThread,
21004        worker_threads: 1,
21005        max_blocking_tasks: 0,
21006        thread_name_prefix: "lifo-streak".to_string(),
21007        park_deadline: None,
21008        drain_linger: DEFAULT_DRAIN_LINGER,
21009      };
21010      let executor =
21011        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21012
21013      let shared = Arc::new(PingPongShared {
21014        count: AtomicU64::new(0),
21015        stop: AtomicBool::new(false),
21016        stop_after: u64::MAX,
21017        wakers: Mutex::new([None, None]),
21018      });
21019      let pair = spawn_ping_pong_pair(&executor, &shared);
21020
21021      // T3: queued on the shared FIFO behind the hot pair.
21022      let (t3_tx, t3_rx) = mpsc::channel::<()>();
21023      let sched = Arc::clone(&executor);
21024      let (t3_runnable, t3_task) = async_task::spawn(
21025        async move {
21026          t3_tx.send(()).unwrap();
21027        },
21028        move |r| sched.schedule(r),
21029      );
21030      executor.schedule(t3_runnable);
21031
21032      t3_rx
21033        .recv_timeout(Duration::from_secs(10))
21034        .expect("a hot LIFO pair starved the FIFO: the budget streak cap is broken");
21035      assert!(
21036        shared.count.load(Ordering::SeqCst) >= 32,
21037        "the pair must have been bouncing through the slot before T3 got its turn"
21038      );
21039
21040      // Teardown: stop the pair and release whichever side is waiting.
21041      shared.stop.store(true, Ordering::SeqCst);
21042      let stored: Vec<_> = shared
21043        .wakers
21044        .lock()
21045        .unwrap()
21046        .iter_mut()
21047        .filter_map(Option::take)
21048        .collect();
21049      for waker in stored {
21050        waker.wake();
21051      }
21052      for task in pair {
21053        futures::executor::block_on(task);
21054      }
21055      futures::executor::block_on(t3_task);
21056      let _ = done_tx.send(());
21057    });
21058
21059    match done_rx.recv_timeout(Duration::from_secs(15)) {
21060      Ok(()) => runner.join().unwrap(),
21061      Err(error) => panic!("LIFO streak-cap test did not complete ({error})"),
21062    }
21063  }
21064
21065  #[cfg(not(target_family = "wasm"))]
21066  #[test]
21067  fn multi_thread_hot_lifo_pair_does_not_wedge_queued_blocking_job() {
21068    // Runnable/blocking fairness invariant: runnables (slot included) outrank blocking work per
21069    // drain iteration -- unchanged from the FIFO-only drain -- but a hot LIFO
21070    // pair must not turn that priority into a permanent wedge. The budget
21071    // flush keeps the drain exiting, `finish_draining` keeps re-checking the
21072    // blocking queue, and the queued blocking job completes once the pair
21073    // finishes its (long) run on the SOLE worker.
21074    use std::sync::mpsc;
21075    use std::time::Duration;
21076
21077    const BOUNCES: u64 = 2_000; // ~31 budget windows on one worker
21078
21079    let (done_tx, done_rx) = mpsc::channel();
21080    let runner = std::thread::spawn(move || {
21081      let options = RuntimeOptions {
21082        flavor: RuntimeFlavor::MultiThread,
21083        worker_threads: 1,
21084        max_blocking_tasks: 1,
21085        thread_name_prefix: "lifo-blocking".to_string(),
21086        park_deadline: None,
21087        drain_linger: DEFAULT_DRAIN_LINGER,
21088      };
21089      let executor =
21090        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21091
21092      let shared = Arc::new(PingPongShared {
21093        count: AtomicU64::new(0),
21094        stop: AtomicBool::new(false),
21095        stop_after: BOUNCES,
21096        wakers: Mutex::new([None, None]),
21097      });
21098      let pair = spawn_ping_pong_pair(&executor, &shared);
21099
21100      // Queued while the pair keeps the slot hot on the only worker.
21101      let blocking = executor.schedule_blocking(|| 11usize);
21102
21103      assert_eq!(
21104        futures::executor::block_on(blocking).unwrap(),
21105        11,
21106        "the queued blocking job must complete despite the hot LIFO pair"
21107      );
21108      for task in pair {
21109        futures::executor::block_on(task);
21110      }
21111      assert!(
21112        shared.count.load(Ordering::SeqCst) >= BOUNCES,
21113        "the pair must have actually run its full bounce quota"
21114      );
21115      let _ = done_tx.send(());
21116    });
21117
21118    match done_rx.recv_timeout(Duration::from_secs(15)) {
21119      Ok(()) => runner.join().unwrap(),
21120      Err(error) => panic!("LIFO blocking-fairness test did not complete ({error})"),
21121    }
21122  }
21123
21124  #[cfg(not(target_family = "wasm"))]
21125  #[test]
21126  fn infinite_hot_runnable_yields_to_blocking_fifo() {
21127    use std::sync::mpsc;
21128
21129    struct HotFuture {
21130      stop: Arc<AtomicBool>,
21131    }
21132
21133    impl Future for HotFuture {
21134      type Output = ();
21135
21136      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21137        if self.stop.load(Ordering::SeqCst) {
21138          Poll::Ready(())
21139        } else {
21140          cx.waker().wake_by_ref();
21141          Poll::Pending
21142        }
21143      }
21144    }
21145
21146    let options = RuntimeOptions {
21147      flavor: RuntimeFlavor::MultiThread,
21148      worker_threads: 2,
21149      max_blocking_tasks: 1,
21150      thread_name_prefix: "blocking-fairness".to_string(),
21151      park_deadline: None,
21152      drain_linger: DEFAULT_DRAIN_LINGER,
21153    };
21154    let executor =
21155      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21156    let stop = Arc::new(AtomicBool::new(false));
21157    let scheduler = Arc::clone(&executor);
21158    let (runnable, task) = async_task::spawn(
21159      HotFuture {
21160        stop: Arc::clone(&stop),
21161      },
21162      move |r| scheduler.schedule(r),
21163    );
21164    task.detach();
21165    let (blocking_tx, blocking_rx) = mpsc::sync_channel(1);
21166    executor
21167      .blocking_queue
21168      .lock()
21169      .unwrap()
21170      .push(QueuedBlocking {
21171        id: BlockingJobId(u64::MAX),
21172        run: Box::new(move || blocking_tx.send(()).unwrap()),
21173      });
21174
21175    let _driver = OnPoolWorkerGuard::enter(executor.id);
21176    executor.schedule(runnable);
21177    let mut runnable_streak = 0;
21178    for _ in 0..=MultiThreadExecutor::RUNNABLE_FAIRNESS_QUANTUM {
21179      assert!(executor.run_one_fair(&mut runnable_streak));
21180    }
21181    blocking_rx
21182      .try_recv()
21183      .expect("an infinite self-waker must yield a bounded quantum to blocking work");
21184
21185    stop.store(true, Ordering::SeqCst);
21186    assert!(executor.run_one_fair(&mut runnable_streak));
21187  }
21188
21189  #[cfg(not(target_family = "wasm"))]
21190  #[test]
21191  fn multi_thread_hot_runnable_yields_to_exact_blocking_dependency() {
21192    use std::sync::mpsc;
21193
21194    struct HotFuture {
21195      stop: Arc<AtomicBool>,
21196    }
21197
21198    impl Future for HotFuture {
21199      type Output = ();
21200
21201      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
21202        if self.stop.load(Ordering::SeqCst) {
21203          Poll::Ready(())
21204        } else {
21205          cx.waker().wake_by_ref();
21206          Poll::Pending
21207        }
21208      }
21209    }
21210
21211    let options = RuntimeOptions {
21212      flavor: RuntimeFlavor::MultiThread,
21213      worker_threads: 1,
21214      max_blocking_tasks: 1,
21215      thread_name_prefix: "exact-dependency-fairness".to_string(),
21216      park_deadline: None,
21217      drain_linger: DEFAULT_DRAIN_LINGER,
21218    };
21219    let executor =
21220      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21221    let stop = Arc::new(AtomicBool::new(false));
21222    let (queued_tx, queued_rx) = mpsc::channel();
21223    let (exact_tx, exact_rx) = mpsc::channel();
21224    let owner_executor = Arc::clone(&executor);
21225    let owner_stop = Arc::clone(&stop);
21226    let owner = executor.schedule_blocking(move || {
21227      let scheduler = Arc::clone(&owner_executor);
21228      let hot_stop = Arc::clone(&owner_stop);
21229      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
21230        scheduler.schedule(runnable);
21231      });
21232      owner_executor.schedule(runnable);
21233
21234      let exact_stop = Arc::clone(&owner_stop);
21235      let exact = owner_executor.schedule_blocking(move || {
21236        exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
21237        41usize
21238      });
21239      queued_tx.send(()).unwrap();
21240
21241      let mut value = None;
21242      {
21243        let mut future = std::pin::pin!(async {
21244          value = Some(exact.await.unwrap());
21245          hot_task.await;
21246        });
21247        assert_eq!(
21248          owner_executor.block_on(future.as_mut()),
21249          BlockOnOutcome::Completed
21250        );
21251      }
21252      value.unwrap()
21253    });
21254
21255    queued_rx
21256      .recv_timeout(Duration::from_secs(2))
21257      .expect("the exact dependency must be queued behind the saturated owner");
21258    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
21259    stop.store(true, Ordering::SeqCst);
21260    assert_eq!(futures::executor::block_on(owner).unwrap(), 41);
21261    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
21262    assert_eq!(
21263      exact_before_stop,
21264      Some(true),
21265      "a hot local runnable must yield to the exact dependency before it is stopped"
21266    );
21267  }
21268
21269  #[cfg(not(target_family = "wasm"))]
21270  #[test]
21271  fn multi_thread_exact_dependency_precedes_last_slot_fifo_admission() {
21272    use std::sync::mpsc;
21273
21274    struct BlockingPoll {
21275      started: Option<mpsc::Sender<()>>,
21276      released: Arc<(Mutex<bool>, Condvar)>,
21277    }
21278
21279    impl Future for BlockingPoll {
21280      type Output = ();
21281
21282      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21283        if let Some(started) = self.started.take() {
21284          started.send(()).unwrap();
21285        }
21286        let (released, changed) = &*self.released;
21287        let mut released = released
21288          .lock()
21289          .unwrap_or_else(std::sync::PoisonError::into_inner);
21290        while !*released {
21291          released = changed
21292            .wait(released)
21293            .unwrap_or_else(std::sync::PoisonError::into_inner);
21294        }
21295        Poll::Ready(())
21296      }
21297    }
21298
21299    let options = RuntimeOptions {
21300      flavor: RuntimeFlavor::MultiThread,
21301      worker_threads: 3,
21302      max_blocking_tasks: 2,
21303      thread_name_prefix: "exact-before-last-slot".to_string(),
21304      park_deadline: None,
21305      drain_linger: DEFAULT_DRAIN_LINGER,
21306    };
21307    let executor =
21308      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21309
21310    let released = Arc::new((Mutex::new(false), Condvar::new()));
21311    let (blocker_started_tx, blocker_started_rx) = mpsc::channel();
21312    let mut blockers = Vec::new();
21313    for _ in 0..2 {
21314      let scheduler = Arc::clone(&executor);
21315      let blocker_released = Arc::clone(&released);
21316      let started = blocker_started_tx.clone();
21317      let (runnable, task) = async_task::spawn(
21318        BlockingPoll {
21319          started: Some(started),
21320          released: blocker_released,
21321        },
21322        move |runnable| scheduler.schedule(runnable),
21323      );
21324      executor.schedule(runnable);
21325      blockers.push(task);
21326    }
21327    drop(blocker_started_tx);
21328    for _ in 0..2 {
21329      blocker_started_rx
21330        .recv_timeout(Duration::from_secs(2))
21331        .expect("two workers must be gated before the owner starts");
21332    }
21333
21334    let dependency_started = Arc::new(AtomicBool::new(false));
21335    let owner_executor = Arc::clone(&executor);
21336    let owner_dependency_started = Arc::clone(&dependency_started);
21337    let (unrelated_observed_tx, unrelated_observed_rx) = mpsc::channel();
21338    let dependency_signal = Arc::new((Mutex::new(false), Condvar::new()));
21339    let owner_signal = Arc::clone(&dependency_signal);
21340    let owner = executor.schedule_blocking(move || {
21341      let unrelated_signal = Arc::clone(&owner_signal);
21342      let unrelated_started = Arc::clone(&owner_dependency_started);
21343      let unrelated = owner_executor.schedule_blocking(move || {
21344        let observed = unrelated_started.load(Ordering::SeqCst);
21345        unrelated_observed_tx.send(observed).unwrap();
21346        let (ready, changed) = &*unrelated_signal;
21347        let mut ready = ready
21348          .lock()
21349          .unwrap_or_else(std::sync::PoisonError::into_inner);
21350        while !*ready {
21351          ready = changed
21352            .wait(ready)
21353            .unwrap_or_else(std::sync::PoisonError::into_inner);
21354        }
21355      });
21356
21357      let exact_signal = Arc::clone(&owner_signal);
21358      let exact_started = Arc::clone(&owner_dependency_started);
21359      let exact = owner_executor.schedule_blocking(move || {
21360        exact_started.store(true, Ordering::SeqCst);
21361        let (ready, changed) = &*exact_signal;
21362        *ready
21363          .lock()
21364          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21365        changed.notify_all();
21366        41usize
21367      });
21368
21369      let mut output = None;
21370      {
21371        let mut future = std::pin::pin!(async {
21372          output = Some(exact.await.unwrap());
21373          unrelated.await.unwrap();
21374        });
21375        assert_eq!(
21376          owner_executor.block_on(future.as_mut()),
21377          BlockOnOutcome::Completed
21378        );
21379      }
21380      output.unwrap()
21381    });
21382
21383    let (owner_done_tx, owner_done_rx) = mpsc::channel();
21384    let owner_waiter = std::thread::spawn(move || {
21385      owner_done_tx
21386        .send(futures::executor::block_on(owner))
21387        .unwrap();
21388    });
21389    let (completed_while_gated, owner_result) =
21390      match owner_done_rx.recv_timeout(Duration::from_secs(2)) {
21391        Ok(result) => (true, result),
21392        Err(_) => {
21393          // Let a broken implementation recover so the regression fails
21394          // deterministically instead of leaving blocked Rayon workers behind.
21395          let (released, changed) = &*released;
21396          *released
21397            .lock()
21398            .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21399          changed.notify_all();
21400          (
21401            false,
21402            owner_done_rx
21403              .recv_timeout(Duration::from_secs(2))
21404              .expect("releasing the other workers must let the broken ordering recover"),
21405          )
21406        }
21407      };
21408    let unrelated_observed = unrelated_observed_rx
21409      .recv_timeout(Duration::from_secs(2))
21410      .expect("the unrelated FIFO job must eventually run");
21411
21412    {
21413      let (released, changed) = &*released;
21414      *released
21415        .lock()
21416        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
21417      changed.notify_all();
21418    }
21419    owner_waiter.join().unwrap();
21420    for blocker in blockers {
21421      futures::executor::block_on(blocker);
21422    }
21423    executor.begin_shutdown();
21424    executor.wait_until_scheduler_idle();
21425
21426    assert!(
21427      completed_while_gated,
21428      "the exact dependency must complete while unrelated workers remain gated"
21429    );
21430    assert_eq!(owner_result.unwrap(), 41);
21431    assert!(
21432      unrelated_observed,
21433      "the exact dependency must signal before unrelated FIFO admission consumes the last slot"
21434    );
21435  }
21436
21437  #[cfg(not(target_family = "wasm"))]
21438  #[test]
21439  fn blocking_saturation_preserves_a_runnable_execution_lane() {
21440    use std::{sync::mpsc, time::Duration};
21441
21442    let options = RuntimeOptions {
21443      flavor: RuntimeFlavor::MultiThread,
21444      worker_threads: 2,
21445      max_blocking_tasks: 2,
21446      thread_name_prefix: "blocking-reserve".to_string(),
21447      park_deadline: None,
21448      drain_linger: DEFAULT_DRAIN_LINGER,
21449    }
21450    .validate()
21451    .expect("production options must reserve a runnable lane");
21452    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
21453    let executor =
21454      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21455    let (release_tx, release_rx) = mpsc::channel();
21456    let (started_tx, started_rx) = mpsc::channel();
21457    let first = executor.schedule_blocking(move || {
21458      started_tx.send(()).unwrap();
21459      release_rx.recv().unwrap();
21460    });
21461    started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
21462
21463    let (queued_tx, queued_rx) = mpsc::channel();
21464    let second = executor.schedule_blocking(move || {
21465      queued_tx.send(()).unwrap();
21466    });
21467    assert!(
21468      queued_rx.try_recv().is_err(),
21469      "the second blocking job must remain queued while the sole blocking slot is occupied"
21470    );
21471
21472    let (runnable_tx, runnable_rx) = mpsc::sync_channel(1);
21473    let scheduler = Arc::clone(&executor);
21474    let (runnable, task) = async_task::spawn(
21475      async move {
21476        runnable_tx.send(()).unwrap();
21477      },
21478      move |r| scheduler.schedule(r),
21479    );
21480    task.detach();
21481    executor.schedule(runnable);
21482    runnable_rx
21483      .recv_timeout(Duration::from_secs(1))
21484      .expect("all admitted blocking jobs consumed the scheduler's runnable execution capacity");
21485
21486    release_tx.send(()).unwrap();
21487    futures::executor::block_on(first).unwrap();
21488    futures::executor::block_on(second).unwrap();
21489    queued_rx
21490      .recv_timeout(Duration::from_secs(1))
21491      .expect("queued blocking work must run after the occupied slot is released");
21492  }
21493
21494  #[cfg(not(target_family = "wasm"))]
21495  #[test]
21496  fn cooperative_exit_flushes_slot_work_spawned_by_final_poll() {
21497    // Final-poll slot-flush invariant: the awaited future's FINAL poll can
21498    // schedule same-executor work, which lands in THIS worker's LIFO slot --
21499    // no FIFO push, no wake. The Ready exit of
21500    // `cooperative_block_on` must flush the slot: the caller may wait on that
21501    // child synchronously (and the enclosing drain frame can be arbitrarily
21502    // far away -- here it does not exist at all, via the fake on-pool
21503    // marker), so a stranded slot entry is an invisible, dead task.
21504    use std::sync::mpsc;
21505    use std::time::Duration;
21506
21507    struct SpawnOnReady {
21508      executor: Arc<MultiThreadExecutor>,
21509      child_tx: Option<mpsc::Sender<u64>>,
21510    }
21511    impl Future for SpawnOnReady {
21512      type Output = ();
21513      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21514        let child_tx = self.child_tx.take().expect("polled once");
21515        let sched = Arc::clone(&self.executor);
21516        let (runnable, task) = async_task::spawn(
21517          async move {
21518            child_tx.send(21).unwrap();
21519          },
21520          move |r| sched.schedule(r),
21521        );
21522        // On-pool + same executor + no ambient owner: lands in the slot.
21523        self.executor.schedule(runnable);
21524        task.detach();
21525        Poll::Ready(())
21526      }
21527    }
21528
21529    let (done_tx, done_rx) = mpsc::channel();
21530    let runner = std::thread::spawn(move || {
21531      let options = RuntimeOptions {
21532        flavor: RuntimeFlavor::MultiThread,
21533        worker_threads: 2,
21534        max_blocking_tasks: 2,
21535        thread_name_prefix: "exit-flush".to_string(),
21536        park_deadline: None,
21537        drain_linger: DEFAULT_DRAIN_LINGER,
21538      };
21539      let executor =
21540        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21541
21542      let (child_tx, child_rx) = mpsc::channel::<u64>();
21543      {
21544        // The test-only on-pool marker runs the cooperative branch on THIS
21545        // thread, with NO enclosing drain loop to bail the slot out.
21546        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
21547        let mut future = std::pin::pin!(SpawnOnReady {
21548          executor: Arc::clone(&executor),
21549          child_tx: Some(child_tx)
21550        });
21551        executor.block_on(future.as_mut() as Pin<&mut dyn Future<Output = ()>>);
21552      }
21553
21554      // Synchronous bounded wait, NOT via the executor: only the exit flush
21555      // (slot -> shared FIFO -> wake/ensure_drainer -> pool worker) can
21556      // complete the child.
21557      let value = child_rx
21558        .recv_timeout(Duration::from_secs(5))
21559        .expect("the child scheduled by the final poll was stranded in the LIFO slot");
21560      assert_eq!(value, 21);
21561      let _ = done_tx.send(());
21562    });
21563
21564    match done_rx.recv_timeout(Duration::from_secs(10)) {
21565      Ok(()) => runner.join().unwrap(),
21566      Err(error) => panic!("cooperative-exit slot-flush test did not complete ({error})"),
21567    }
21568  }
21569
21570  /// Perpetual same-executor chain: each link schedules its successor from
21571  /// the pool worker (so every link lands in the LIFO slot) until `stop` is
21572  /// raised. `hops` counts executed links.
21573  #[cfg(not(target_family = "wasm"))]
21574  fn spawn_hot_chain(
21575    executor: &Arc<MultiThreadExecutor>,
21576    stop: Arc<AtomicBool>,
21577    hops: Arc<AtomicU64>,
21578  ) {
21579    if stop.load(Ordering::SeqCst) {
21580      return;
21581    }
21582    let link_exec = Arc::clone(executor);
21583    let body = async move {
21584      hops.fetch_add(1, Ordering::SeqCst);
21585      spawn_hot_chain(&link_exec, stop, hops);
21586    };
21587    let sched = Arc::clone(executor);
21588    let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
21589    executor.schedule(runnable);
21590    task.detach();
21591  }
21592
21593  #[cfg(not(target_family = "wasm"))]
21594  #[test]
21595  fn cooperative_block_on_budget_prevents_slot_chain_starving_fifo() {
21596    // Runnable-budget invariant: `cooperative_block_on`'s loop must mirror
21597    // `drain`'s RUNNABLE_BUDGET streak cap. `run_one` pops the slot before
21598    // the FIFO, so a hot chain (every link schedules its successor into the
21599    // slot) keeps run_one succeeding forever and the FIFO is never reached
21600    // from this thread. On the direct 1-worker test topology a FIFO task that
21601    // completes the awaited future then never runs: deadlock. The budgeted
21602    // loop must flush the slot every RUNNABLE_BUDGET runs so the FIFO task
21603    // gets its turn.
21604    use std::sync::mpsc;
21605    use std::time::Duration;
21606
21607    let (done_tx, done_rx) = mpsc::channel();
21608    let runner = std::thread::spawn(move || {
21609      let options = RuntimeOptions {
21610        flavor: RuntimeFlavor::MultiThread,
21611        worker_threads: 1,
21612        // This locality test intentionally bypasses RuntimeOptions::validate
21613        // to isolate one physical worker without admitting blocking work.
21614        max_blocking_tasks: 0,
21615        thread_name_prefix: "coop-budget".to_string(),
21616        park_deadline: None,
21617        drain_linger: DEFAULT_DRAIN_LINGER,
21618      };
21619      let executor =
21620        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21621
21622      let stop = Arc::new(AtomicBool::new(false));
21623      let hops = Arc::new(AtomicU64::new(0));
21624
21625      // M runs on the sole worker: it queues F (the task that completes the
21626      // awaited future) on the FIFO, heats the slot with the chain, then
21627      // block_on's the value F produces.
21628      let m_exec = Arc::clone(&executor);
21629      let m_stop = Arc::clone(&stop);
21630      let m_hops = Arc::clone(&hops);
21631      let m_body = async move {
21632        let (tx, rx) = oneshot::channel::<u64>();
21633        // Schedule F first: it takes the slot momentarily...
21634        let sched_f = Arc::clone(&m_exec);
21635        let (f_runnable, f_task) = async_task::spawn(
21636          async move {
21637            let _ = tx.send(7);
21638          },
21639          move |r| sched_f.schedule(r),
21640        );
21641        m_exec.schedule(f_runnable);
21642        f_task.detach();
21643        // ...then the chain starter displaces it to the FIFO and keeps the
21644        // slot hot from here on.
21645        spawn_hot_chain(&m_exec, Arc::clone(&m_stop), m_hops);
21646
21647        let mut inner = std::pin::pin!(async move {
21648          assert_eq!(
21649            rx.await.unwrap(),
21650            7,
21651            "F must run and complete the awaited future"
21652          );
21653        });
21654        m_exec.block_on(inner.as_mut());
21655        m_stop.store(true, Ordering::SeqCst);
21656      };
21657      let sched_m = Arc::clone(&executor);
21658      let (m_runnable, m_task) = async_task::spawn(m_body, move |r| sched_m.schedule(r));
21659      executor.schedule(m_runnable);
21660
21661      futures::executor::block_on(m_task);
21662      assert!(
21663        hops.load(Ordering::SeqCst) >= 64,
21664        "the chain must have been genuinely hot (>= one budget window) before F ran"
21665      );
21666      let _ = done_tx.send(());
21667    });
21668
21669    match done_rx.recv_timeout(Duration::from_secs(10)) {
21670      Ok(()) => runner.join().unwrap(),
21671      Err(error) => panic!(
21672        "cooperative block_on starved the FIFO behind a hot slot chain ({error}): the loop needs the RUNNABLE_BUDGET streak cap"
21673      ),
21674    }
21675  }
21676
21677  #[cfg(not(target_family = "wasm"))]
21678  #[test]
21679  fn cooperative_budget_forces_fifo_after_future_refills_lifo_slot() {
21680    use std::sync::mpsc;
21681
21682    struct RefillLifo {
21683      executor: Arc<MultiThreadExecutor>,
21684      fifo_ran: Arc<AtomicBool>,
21685    }
21686
21687    impl Future for RefillLifo {
21688      type Output = ();
21689
21690      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
21691        if self.fifo_ran.load(Ordering::SeqCst) {
21692          return Poll::Ready(());
21693        }
21694        let scheduler = Arc::clone(&self.executor);
21695        let (runnable, task) =
21696          async_task::spawn(async {}, move |runnable| scheduler.schedule(runnable));
21697        self.executor.schedule(runnable);
21698        task.detach();
21699        Poll::Pending
21700      }
21701    }
21702
21703    let (done_tx, done_rx) = mpsc::channel();
21704    let runner = std::thread::spawn(move || {
21705      let options = RuntimeOptions {
21706        flavor: RuntimeFlavor::MultiThread,
21707        worker_threads: 1,
21708        max_blocking_tasks: 0,
21709        thread_name_prefix: "forced-fifo".to_string(),
21710        park_deadline: None,
21711        drain_linger: DEFAULT_DRAIN_LINGER,
21712      };
21713      let executor =
21714        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21715      executor
21716        .active_drainers
21717        .store(executor.max_drainers, Ordering::SeqCst);
21718
21719      let fifo_ran = Arc::new(AtomicBool::new(false));
21720      let fifo_flag = Arc::clone(&fifo_ran);
21721      let scheduler = Arc::clone(&executor);
21722      let (fifo, task) = async_task::spawn(
21723        async move {
21724          fifo_flag.store(true, Ordering::SeqCst);
21725        },
21726        move |runnable| scheduler.schedule(runnable),
21727      );
21728      task.detach();
21729      executor.metrics.runnable_scheduled();
21730      executor.push_fifo_raw(fifo);
21731
21732      {
21733        let _driver = OnPoolWorkerGuard::enter(executor.id);
21734        let mut future = std::pin::pin!(RefillLifo {
21735          executor: Arc::clone(&executor),
21736          fifo_ran: Arc::clone(&fifo_ran)
21737        });
21738        executor.block_on(future.as_mut());
21739        while executor.run_one() {}
21740      }
21741      executor.active_drainers.store(0, Ordering::SeqCst);
21742      assert!(fifo_ran.load(Ordering::SeqCst));
21743      done_tx.send(()).unwrap();
21744    });
21745    done_rx
21746      .recv_timeout(Duration::from_secs(3))
21747      .expect("a refilled LIFO slot starved the forced shared-FIFO turn");
21748    runner.join().unwrap();
21749  }
21750
21751  #[cfg(not(target_family = "wasm"))]
21752  #[test]
21753  fn blocking_closure_spawn_is_not_stranded_in_slot() {
21754    // Blocking-closure scheduling invariant: ON_POOL_WORKER spans the whole
21755    // drain frame, INCLUDING blocking-closure bodies. A closure that spawns a
21756    // same-executor task and then waits for it synchronously would deadlock if
21757    // the child were slotted because the slot only drains after the closure
21758    // returns. `schedule` must bypass the slot while BLOCKING_OWNER is set and
21759    // push to the shared FIFO with a wake.
21760    use std::sync::mpsc;
21761    use std::time::Duration;
21762
21763    let (done_tx, done_rx) = mpsc::channel();
21764    let runner = std::thread::spawn(move || {
21765      let options = RuntimeOptions {
21766        flavor: RuntimeFlavor::MultiThread,
21767        worker_threads: 2,
21768        max_blocking_tasks: 2,
21769        thread_name_prefix: "blk-spawn-wait".to_string(),
21770        park_deadline: None,
21771        drain_linger: DEFAULT_DRAIN_LINGER,
21772      };
21773      let executor =
21774        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21775
21776      // Closure spawns a child and BLOCKS on the channel the child must fill.
21777      // A spare worker exists; only a real FIFO push + wake can reach it.
21778      let blk_exec = Arc::clone(&executor);
21779      let handle = executor.schedule_blocking(move || {
21780        let (tx, rx) = mpsc::channel::<u64>();
21781        let sched = Arc::clone(&blk_exec);
21782        let (runnable, task) = async_task::spawn(
21783          async move {
21784            tx.send(21).unwrap();
21785          },
21786          move |r| sched.schedule(r),
21787        );
21788        blk_exec.schedule(runnable);
21789        task.detach();
21790        rx.recv_timeout(Duration::from_secs(5))
21791          .expect("child spawned by a blocking closure was stranded in the worker's LIFO slot")
21792      });
21793      assert_eq!(futures::executor::block_on(handle).unwrap(), 21);
21794
21795      // Variant: the closure exits WITHOUT waiting; the child must still
21796      // complete promptly (no stranding on the closure-exit path either).
21797      let (v_tx, v_rx) = mpsc::channel::<u64>();
21798      let v_exec = Arc::clone(&executor);
21799      let v_handle = executor.schedule_blocking(move || {
21800        let sched = Arc::clone(&v_exec);
21801        let (runnable, task) = async_task::spawn(
21802          async move {
21803            v_tx.send(42).unwrap();
21804          },
21805          move |r| sched.schedule(r),
21806        );
21807        v_exec.schedule(runnable);
21808        task.detach();
21809        1u64
21810      });
21811      assert_eq!(futures::executor::block_on(v_handle).unwrap(), 1);
21812      assert_eq!(
21813        v_rx
21814          .recv_timeout(Duration::from_secs(5))
21815          .expect("fire-and-forget child must complete"),
21816        42
21817      );
21818      let _ = done_tx.send(());
21819    });
21820
21821    match done_rx.recv_timeout(Duration::from_secs(15)) {
21822      Ok(()) => runner.join().unwrap(),
21823      Err(error) => panic!("blocking-closure spawn test did not complete ({error})"),
21824    }
21825  }
21826
21827  #[cfg(not(target_family = "wasm"))]
21828  #[test]
21829  fn multi_thread_nested_spawn_blocking_does_not_deadlock_when_saturated() {
21830    // Saturated nested-blocking invariant: a blocking closure re-enters
21831    // `block_on` and awaits another `spawn_blocking` while
21832    // `max_blocking_tasks` is already saturated by the outer blocking jobs.
21833    // The driver must not park while still holding its blocking slot, because
21834    // the inner job would then have no capacity to run.
21835    use std::sync::{Barrier, mpsc};
21836    use std::time::Duration;
21837
21838    let (done_tx, done_rx) = mpsc::channel();
21839    let runner = std::thread::spawn(move || {
21840      let metrics = Arc::new(RuntimeMetrics::default());
21841      let options = RuntimeOptions {
21842        flavor: RuntimeFlavor::MultiThread,
21843        worker_threads: 2,
21844        max_blocking_tasks: 2,
21845        thread_name_prefix: "rd1-nested-blk".to_string(),
21846        park_deadline: None,
21847        drain_linger: DEFAULT_DRAIN_LINGER,
21848      };
21849      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
21850
21851      let outer_count = 2usize; // saturates max_blocking_tasks
21852      let barrier = Arc::new(Barrier::new(outer_count));
21853
21854      let mut handles = Vec::new();
21855      for _ in 0..outer_count {
21856        let exec = Arc::clone(&executor);
21857        let barrier = Arc::clone(&barrier);
21858        let handle = executor.schedule_blocking(move || {
21859          // Both outer blocking jobs hold a slot before either nests.
21860          barrier.wait();
21861          let inner_exec = Arc::clone(&exec);
21862          let mut value = 0usize;
21863          {
21864            let mut inner = std::pin::pin!(async {
21865              value = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
21866            });
21867            exec.block_on(inner.as_mut());
21868          }
21869          value
21870        });
21871        handles.push(handle);
21872      }
21873
21874      for handle in handles {
21875        assert_eq!(futures::executor::block_on(handle).unwrap(), 5usize);
21876      }
21877      let _ = done_tx.send(());
21878    });
21879
21880    match done_rx.recv_timeout(Duration::from_secs(10)) {
21881      Ok(()) => runner.join().unwrap(),
21882      Err(error) => panic!("nested spawn_blocking deadlocked at blocking capacity ({error})"),
21883    }
21884  }
21885
21886  #[cfg(not(target_family = "wasm"))]
21887  #[test]
21888  fn scheduler_unclaimed_worker_cannot_infer_sole_blocking_owner() {
21889    let executor = multi_thread_executor(2, None, "no-owner-cardinality-inference");
21890    let owner = executor.fresh_owner_token();
21891    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
21892    let job = BlockingJobId(u64::MAX - 5);
21893    let dependency = TaskDependency::default();
21894    dependency.set_owned(BlockingDependency { job, owner: None });
21895    let ran = Arc::new(AtomicBool::new(false));
21896    let job_ran = Arc::clone(&ran);
21897    executor
21898      .blocking_queue
21899      .lock()
21900      .unwrap()
21901      .push(QueuedBlocking {
21902        id: job,
21903        run: Box::new(move || job_ran.store(true, Ordering::SeqCst)),
21904      });
21905
21906    assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
21907    assert!(
21908      !executor.try_owned_blocking_over_cap(&dependency),
21909      "an untagged worker must not infer ancestry from a sole active owner"
21910    );
21911    assert!(!ran.load(Ordering::SeqCst));
21912    assert!(
21913      executor
21914        .blocking_queue
21915        .lock()
21916        .unwrap()
21917        .jobs
21918        .contains_key(&job),
21919      "rejected owner inference must leave the exact job queued"
21920    );
21921  }
21922
21923  #[cfg(not(target_family = "wasm"))]
21924  #[test]
21925  fn owner_lending_is_exact_frame_scoped_with_two_saturated_owners() {
21926    use std::sync::{Barrier, mpsc};
21927
21928    let options = RuntimeOptions {
21929      flavor: RuntimeFlavor::MultiThread,
21930      worker_threads: 2,
21931      max_blocking_tasks: 2,
21932      thread_name_prefix: "exact-owner-frame".to_string(),
21933      park_deadline: None,
21934      drain_linger: DEFAULT_DRAIN_LINGER,
21935    };
21936    let executor =
21937      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
21938    let owners_saturated = Arc::new(Barrier::new(3));
21939    let (dependency_queued_tx, dependency_queued_rx) = mpsc::channel();
21940    let begin_lending = Arc::new(Barrier::new(3));
21941    let release_dependencies = Arc::new(Barrier::new(3));
21942    let (dependency_started_tx, dependency_started_rx) = mpsc::channel();
21943    let mut owners = Vec::new();
21944
21945    for owner_index in 0..2 {
21946      let owner_executor = Arc::clone(&executor);
21947      let owners_saturated = Arc::clone(&owners_saturated);
21948      let dependency_queued_tx = dependency_queued_tx.clone();
21949      let begin_lending = Arc::clone(&begin_lending);
21950      let release_dependencies = Arc::clone(&release_dependencies);
21951      let dependency_started_tx = dependency_started_tx.clone();
21952      owners.push(executor.schedule_blocking(move || {
21953        let owner_thread = std::thread::current().id();
21954        owners_saturated.wait();
21955        let child_started = dependency_started_tx.clone();
21956        let child_release = Arc::clone(&release_dependencies);
21957        let child = owner_executor.schedule_blocking(move || {
21958          assert_eq!(
21959            std::thread::current().id(),
21960            owner_thread,
21961            "an owner frame must execute only its own exact dependency on its lent lane"
21962          );
21963          child_started.send(owner_index).unwrap();
21964          child_release.wait();
21965          owner_index
21966        });
21967        dependency_queued_tx.send(()).unwrap();
21968        begin_lending.wait();
21969
21970        let mut result = None;
21971        {
21972          let mut future = std::pin::pin!(async {
21973            result = Some(child.await.unwrap());
21974          });
21975          assert_eq!(
21976            owner_executor.block_on(future.as_mut()),
21977            BlockOnOutcome::Completed
21978          );
21979        }
21980        result.unwrap()
21981      }));
21982    }
21983    drop(dependency_queued_tx);
21984    drop(dependency_started_tx);
21985    owners_saturated.wait();
21986
21987    for _ in 0..2 {
21988      dependency_queued_rx
21989        .recv_timeout(Duration::from_secs(2))
21990        .expect("both owner-scoped dependencies must be queued while the cap is saturated");
21991    }
21992    let (unrelated_tx, unrelated_rx) = mpsc::channel();
21993    let unrelated = executor.schedule_blocking(move || unrelated_tx.send(()).unwrap());
21994    begin_lending.wait();
21995
21996    let mut started = Vec::new();
21997    for _ in 0..2 {
21998      started.push(
21999        dependency_started_rx
22000          .recv_timeout(Duration::from_secs(2))
22001          .expect("each owner must service its own exact dependency"),
22002      );
22003    }
22004    started.sort_unstable();
22005    assert_eq!(started, vec![0, 1]);
22006    assert!(
22007      unrelated_rx.try_recv().is_err(),
22008      "a later unrelated blocking job must remain queued while both exact owner lanes are lent"
22009    );
22010
22011    release_dependencies.wait();
22012    for (owner_index, owner) in owners.into_iter().enumerate() {
22013      assert_eq!(futures::executor::block_on(owner).unwrap(), owner_index);
22014    }
22015    unrelated_rx
22016      .recv_timeout(Duration::from_secs(2))
22017      .expect("the unrelated job must run after an owner releases a counted lane");
22018    futures::executor::block_on(unrelated).unwrap();
22019  }
22020
22021  #[cfg(not(target_family = "wasm"))]
22022  #[test]
22023  fn parked_unrelated_rayon_worker_cannot_block_later_lending_or_idle() {
22024    use std::sync::{Mutex, mpsc};
22025
22026    const DEPENDENCY_COUNT: usize = 4;
22027
22028    let options = RuntimeOptions {
22029      flavor: RuntimeFlavor::MultiThread,
22030      worker_threads: 3,
22031      max_blocking_tasks: 1,
22032      thread_name_prefix: "parked-unrelated-worker".to_string(),
22033      park_deadline: None,
22034      drain_linger: DEFAULT_DRAIN_LINGER,
22035    };
22036    let executor =
22037      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22038    let (parked_tx, parked_rx) = mpsc::channel();
22039    let (release_parked_tx, release_parked_rx) = mpsc::channel();
22040    executor.pool.spawn(move || {
22041      parked_tx.send(()).unwrap();
22042      release_parked_rx.recv().unwrap();
22043    });
22044    parked_rx
22045      .recv_timeout(Duration::from_secs(2))
22046      .expect("the unrelated Rayon worker must remain occupied");
22047
22048    let owner_executor = Arc::clone(&executor);
22049    let owner = executor.schedule_blocking(move || {
22050      let owner = BLOCKING_OWNER
22051        .with(std::cell::Cell::get)
22052        .expect("the blocking closure must carry its exact owner frame");
22053      let (descendant_started_tx, descendant_started_rx) = mpsc::sync_channel(0);
22054      let results = Arc::new(Mutex::new(Vec::new()));
22055      let descendant_results = Arc::clone(&results);
22056      let descendant_executor = Arc::clone(&owner_executor);
22057      rayon::scope(move |scope| {
22058        scope.spawn(move |_| {
22059          assert_eq!(
22060            BLOCKING_OWNER.with(std::cell::Cell::get),
22061            None,
22062            "the parked owner must force its descendant onto the remaining worker"
22063          );
22064          let _owner = BlockingOwnerGuard::enter(Some(owner));
22065          descendant_started_tx.send(()).unwrap();
22066          for value in 0..DEPENDENCY_COUNT {
22067            let handle = descendant_executor.schedule_blocking(move || value);
22068            let mut result = None;
22069            {
22070              let mut future = std::pin::pin!(async {
22071                result = Some(handle.await.unwrap());
22072              });
22073              assert_eq!(
22074                descendant_executor.block_on(future.as_mut()),
22075                BlockOnOutcome::Completed
22076              );
22077            }
22078            descendant_results.lock().unwrap().push(result.unwrap());
22079          }
22080        });
22081        descendant_started_rx
22082          .recv_timeout(Duration::from_secs(2))
22083          .expect("the nested descendant must start on the only unoccupied worker");
22084      });
22085      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22086    });
22087
22088    assert_eq!(
22089      futures::executor::block_on(owner).unwrap(),
22090      (0..DEPENDENCY_COUNT).collect::<Vec<_>>()
22091    );
22092
22093    let (idle_tx, idle_rx) = mpsc::channel();
22094    let idle_executor = Arc::clone(&executor);
22095    let idle_waiter = std::thread::spawn(move || {
22096      idle_executor.begin_shutdown();
22097      idle_executor.wait_until_scheduler_idle();
22098      idle_tx.send(()).unwrap();
22099    });
22100    idle_rx
22101      .recv_timeout(Duration::from_secs(2))
22102      .expect("an unrelated parked worker must not retain owner-probe idle accounting");
22103
22104    release_parked_tx.send(()).unwrap();
22105    idle_waiter.join().unwrap();
22106  }
22107
22108  #[cfg(not(target_family = "wasm"))]
22109  #[test]
22110  fn same_owner_lending_rearms_one_parked_dependency_per_completion() {
22111    use std::sync::{Condvar, Mutex, mpsc};
22112
22113    let options = RuntimeOptions {
22114      flavor: RuntimeFlavor::MultiThread,
22115      worker_threads: 3,
22116      max_blocking_tasks: 1,
22117      thread_name_prefix: "same-owner-rearm".to_string(),
22118      park_deadline: None,
22119      drain_linger: DEFAULT_DRAIN_LINGER,
22120    };
22121    let executor =
22122      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22123    let releases = Arc::new((Mutex::new([false; 2]), Condvar::new()));
22124    let (job_started_tx, job_started_rx) = mpsc::channel();
22125    let owner_executor = Arc::clone(&executor);
22126    let owner_releases = Arc::clone(&releases);
22127    let owner = executor.schedule_blocking(move || {
22128      let owner = BLOCKING_OWNER
22129        .with(std::cell::Cell::get)
22130        .expect("the blocking closure must carry its exact owner frame");
22131      let (descendant_ready_tx, descendant_ready_rx) = mpsc::channel();
22132      let results = Arc::new(Mutex::new(Vec::new()));
22133      let scope_results = Arc::clone(&results);
22134      rayon::scope(move |scope| {
22135        for index in 0..2 {
22136          let descendant_ready_tx = descendant_ready_tx.clone();
22137          let descendant_executor = Arc::clone(&owner_executor);
22138          let descendant_results = Arc::clone(&scope_results);
22139          let job_started_tx = job_started_tx.clone();
22140          let releases = Arc::clone(&owner_releases);
22141          scope.spawn(move |_| {
22142            assert_eq!(BLOCKING_OWNER.with(std::cell::Cell::get), None);
22143            let _owner = BlockingOwnerGuard::enter(Some(owner));
22144            descendant_ready_tx.send(()).unwrap();
22145            let handle = descendant_executor.schedule_blocking(move || {
22146              job_started_tx.send(index).unwrap();
22147              let (released, changed) = &*releases;
22148              let mut state = released.lock().unwrap();
22149              while !state[index] {
22150                state = changed.wait(state).unwrap();
22151              }
22152              index
22153            });
22154            let mut result = None;
22155            {
22156              let mut future = std::pin::pin!(async {
22157                result = Some(handle.await.unwrap());
22158              });
22159              assert_eq!(
22160                descendant_executor.block_on(future.as_mut()),
22161                BlockOnOutcome::Completed
22162              );
22163            }
22164            descendant_results.lock().unwrap().push(result.unwrap());
22165          });
22166        }
22167        drop(descendant_ready_tx);
22168        for _ in 0..2 {
22169          descendant_ready_rx
22170            .recv_timeout(Duration::from_secs(2))
22171            .expect("both descendants must be stolen before the owner leaves its Rayon scope");
22172        }
22173      });
22174      Arc::try_unwrap(results).unwrap().into_inner().unwrap()
22175    });
22176
22177    let first = job_started_rx
22178      .recv_timeout(Duration::from_secs(2))
22179      .expect("one exact dependency must borrow the owner lane");
22180    assert!(
22181      job_started_rx
22182        .recv_timeout(Duration::from_millis(100))
22183        .is_err(),
22184      "one owner frame must not service two dependencies concurrently"
22185    );
22186    let unrelated_parker = Arc::new(DriverParker::default());
22187    executor
22188      .parked_drivers
22189      .register_with_role(&unrelated_parker, true, None);
22190    {
22191      let (released, changed) = &*releases;
22192      released.lock().unwrap()[first] = true;
22193      changed.notify_all();
22194    }
22195
22196    let second = job_started_rx
22197      .recv_timeout(Duration::from_secs(2))
22198      .expect("completion must rearm one parked same-owner dependency");
22199    assert_ne!(second, first);
22200    assert!(
22201      !unrelated_parker.consume_permit(),
22202      "the bounded owner handoff must skip a newer parked driver with no live owner dependency"
22203    );
22204    executor.parked_drivers.deregister(&unrelated_parker);
22205    {
22206      let (released, changed) = &*releases;
22207      released.lock().unwrap()[second] = true;
22208      changed.notify_all();
22209    }
22210
22211    let mut results = futures::executor::block_on(owner).unwrap();
22212    results.sort_unstable();
22213    assert_eq!(results, vec![0, 1]);
22214  }
22215
22216  #[cfg(not(target_family = "wasm"))]
22217  #[test]
22218  fn failed_exact_claim_rearms_parked_same_owner_dependency() {
22219    let executor = multi_thread_executor(2, None, "failed-claim-owner-handoff");
22220    let owner = executor.fresh_owner_token();
22221    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22222    let failed_dependency = Arc::new(TaskDependency::default());
22223    failed_dependency.set_owned(BlockingDependency {
22224      job: BlockingJobId(u64::MAX - 21),
22225      owner: Some(owner),
22226    });
22227    let parked_dependency = Arc::new(TaskDependency::default());
22228    parked_dependency.set_owned(BlockingDependency {
22229      job: BlockingJobId(u64::MAX - 22),
22230      owner: Some(owner),
22231    });
22232    let parker = Arc::new(DriverParker::default());
22233
22234    let queue = executor.blocking_queue.lock().unwrap();
22235    let claimant_executor = Arc::clone(&executor);
22236    let claimant_dependency = Arc::clone(&failed_dependency);
22237    let claimant = std::thread::spawn(move || {
22238      let _owner = BlockingOwnerGuard::enter(Some(owner));
22239      claimant_executor.try_owned_blocking_over_cap(&claimant_dependency)
22240    });
22241    wait_until("the failed claimant to reserve the owner lane", || {
22242      !executor.active_blocking_owners.is_available(owner)
22243    });
22244    executor
22245      .parked_drivers
22246      .register_with_role(&parker, true, Some(Arc::clone(&parked_dependency)));
22247    failed_dependency.clear();
22248    drop(queue);
22249
22250    assert!(
22251      !claimant.join().unwrap(),
22252      "the cancelled dependency must not execute"
22253    );
22254    assert!(
22255      parker.consume_permit(),
22256      "releasing a failed exact claim must hand the owner lane to another parked dependency"
22257    );
22258  }
22259
22260  #[cfg(not(target_family = "wasm"))]
22261  #[test]
22262  fn exact_owner_lending_does_not_start_blocking_after_stop() {
22263    use std::sync::mpsc;
22264
22265    let options = RuntimeOptions {
22266      flavor: RuntimeFlavor::MultiThread,
22267      worker_threads: 2,
22268      max_blocking_tasks: 1,
22269      thread_name_prefix: "stopped-exact-owner".to_string(),
22270      park_deadline: None,
22271      drain_linger: DEFAULT_DRAIN_LINGER,
22272    };
22273    let executor =
22274      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22275    let ran = Arc::new(AtomicBool::new(false));
22276    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
22277    let (release_claim_tx, release_claim_rx) = mpsc::channel();
22278    let owner_executor = Arc::clone(&executor);
22279    let child_ran = Arc::clone(&ran);
22280    let owner = executor.schedule_blocking(move || {
22281      BEFORE_OWNED_BLOCKING_QUEUE_CLAIM_TEST_HOOK.with(|slot| {
22282        *slot.borrow_mut() = Some(Box::new(move || {
22283          claim_entered_tx.send(()).unwrap();
22284          release_claim_rx.recv().unwrap();
22285        }));
22286      });
22287      let child = owner_executor.schedule_blocking(move || {
22288        child_ran.store(true, Ordering::SeqCst);
22289      });
22290      let mut future = std::pin::pin!(async move {
22291        let _ = child.await;
22292      });
22293      owner_executor.block_on(future.as_mut())
22294    });
22295
22296    claim_entered_rx
22297      .recv_timeout(Duration::from_secs(2))
22298      .expect("the cooperative driver must pause before the exact-owner queue claim");
22299    assert_eq!(
22300      executor.blocking_queue.lock().unwrap().jobs.len(),
22301      1,
22302      "the exact dependency must remain queued before stop publication"
22303    );
22304
22305    // Pin the reported race: the cooperative driver already passed its loop's
22306    // stop check, then shutdown publishes stop before the exact queue claim.
22307    executor.stop.begin_shutdown();
22308    release_claim_tx.send(()).unwrap();
22309
22310    assert_eq!(
22311      futures::executor::block_on(owner).unwrap(),
22312      BlockOnOutcome::Stopped,
22313      "the cooperative driver must stop instead of executing its queued dependency"
22314    );
22315    assert!(
22316      !ran.load(Ordering::SeqCst),
22317      "exact-owner lending started blocking work after stop"
22318    );
22319    assert_eq!(
22320      executor.blocking_queue.lock().unwrap().jobs.len(),
22321      1,
22322      "the stopped exact-owner claim must leave the queued job for shutdown cancellation"
22323    );
22324
22325    executor.begin_shutdown();
22326    executor.wait_until_scheduler_idle();
22327    assert!(executor.blocking_queue.lock().unwrap().is_empty());
22328    assert!(!ran.load(Ordering::SeqCst));
22329  }
22330
22331  #[cfg(not(target_family = "wasm"))]
22332  #[test]
22333  fn exact_owner_lending_attempts_scale_linearly_with_topology() {
22334    const DEPENDENCY_COUNT: usize = 1024;
22335
22336    let options = RuntimeOptions {
22337      flavor: RuntimeFlavor::MultiThread,
22338      worker_threads: 2,
22339      max_blocking_tasks: 1,
22340      thread_name_prefix: "linear-owner-lending".to_string(),
22341      park_deadline: None,
22342      drain_linger: DEFAULT_DRAIN_LINGER,
22343    };
22344    let executor =
22345      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22346    let completed = Arc::new(AtomicUsize::new(0));
22347
22348    for index in 0..DEPENDENCY_COUNT {
22349      let owner = executor.fresh_owner_token();
22350      let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22351      let dependency = TaskDependency::new(u64::MAX, Some(owner));
22352      let blocking = BlockingDependency {
22353        job: BlockingJobId(u64::MAX - index as u64),
22354        owner: Some(owner),
22355      };
22356      dependency.set_owned(blocking);
22357      let completed = Arc::clone(&completed);
22358      executor
22359        .blocking_queue
22360        .lock()
22361        .unwrap()
22362        .push(QueuedBlocking {
22363          id: blocking.job,
22364          run: Box::new(move || {
22365            completed.fetch_add(1, Ordering::SeqCst);
22366          }),
22367        });
22368
22369      let _owner = BlockingOwnerGuard::enter(Some(owner));
22370      assert!(executor.try_owned_blocking_over_cap(&dependency));
22371    }
22372
22373    assert_eq!(completed.load(Ordering::SeqCst), DEPENDENCY_COUNT);
22374    assert_eq!(
22375      executor.owner_lending_attempts.load(Ordering::Relaxed),
22376      DEPENDENCY_COUNT,
22377      "targeted lending must perform one O(1) claim attempt per exact dependency"
22378    );
22379    let queue = executor.blocking_queue.lock().unwrap();
22380    assert!(queue.is_empty());
22381    assert_eq!(queue.head, None);
22382    assert_eq!(queue.tail, None);
22383  }
22384
22385  #[cfg(not(target_family = "wasm"))]
22386  #[test]
22387  fn one_dependency_interspersed_owner_publications_stays_linear() {
22388    const PUBLICATION_COUNT: usize = 1024;
22389
22390    let executor = multi_thread_executor(2, None, "linear-publication-lending");
22391    let owner = executor.fresh_owner_token();
22392    let wrong_owner = executor.fresh_owner_token();
22393    let dependency = TaskDependency::new(u64::MAX, Some(owner));
22394    let completed = Arc::new(AtomicUsize::new(0));
22395    for index in 0..PUBLICATION_COUNT {
22396      let cancelled_source = TaskDependency::default();
22397      cancelled_source.set_owned(BlockingDependency {
22398        job: BlockingJobId(u64::MAX - 100_000 - index as u64),
22399        owner: Some(owner),
22400      });
22401      dependency.set_borrowed(
22402        &cancelled_source
22403          .get()
22404          .expect("the source publication must initially be live"),
22405      );
22406      cancelled_source.clear();
22407
22408      dependency.set_owned(BlockingDependency {
22409        job: BlockingJobId(u64::MAX - 200_000 - index as u64),
22410        owner: Some(wrong_owner),
22411      });
22412      let blocking = BlockingDependency {
22413        job: BlockingJobId(u64::MAX - 1_000 - index as u64),
22414        owner: Some(owner),
22415      };
22416      dependency.set_owned(blocking);
22417      let completed = Arc::clone(&completed);
22418      executor
22419        .blocking_queue
22420        .lock()
22421        .unwrap()
22422        .push(QueuedBlocking {
22423          id: blocking.job,
22424          run: Box::new(move || {
22425            completed.fetch_add(1, Ordering::SeqCst);
22426          }),
22427        });
22428    }
22429    dependency.reset_entry_visits();
22430
22431    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
22432    let _owner = BlockingOwnerGuard::enter(Some(owner));
22433    for _ in 0..PUBLICATION_COUNT {
22434      assert!(
22435        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22436        "the owner-handoff predicate must find the next exact publication"
22437      );
22438      assert!(
22439        dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22440        "the park-availability predicate must find the next serviceable publication"
22441      );
22442      assert!(executor.try_owned_blocking_over_cap(&dependency));
22443    }
22444
22445    assert_eq!(completed.load(Ordering::SeqCst), PUBLICATION_COUNT);
22446    assert!(
22447      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22448      "wrong-owner publications must not satisfy an exact handoff"
22449    );
22450    assert!(
22451      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22452      "wrong-owner publications must not make the owner's lane appear serviceable"
22453    );
22454    assert!(
22455      dependency.entry_visits() <= PUBLICATION_COUNT * 9 + 8,
22456      "selection, handoff/availability predicates, stale retirement, and claims must visit O(n) entries across n publications, visits={}",
22457      dependency.entry_visits()
22458    );
22459    assert!(executor.blocking_queue.lock().unwrap().is_empty());
22460  }
22461
22462  #[cfg(not(target_family = "wasm"))]
22463  #[test]
22464  fn binding_unowned_dependency_updates_exact_owner_predicate() {
22465    let owner = BlockingOwnerToken {
22466      executor_id: u64::MAX - 200,
22467      frame: u64::MAX - 201,
22468    };
22469    let dependency = TaskDependency::default();
22470    let blocking = BlockingDependency {
22471      job: BlockingJobId(u64::MAX - 202),
22472      owner: None,
22473    };
22474    dependency.set_owned(blocking);
22475
22476    assert!(
22477      !dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22478      "an unowned publication must not satisfy an exact owner handoff"
22479    );
22480    assert!(
22481      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Available),
22482      "an unowned publication is available to the ambient owner"
22483    );
22484
22485    let selected = dependency
22486      .select_for_owner(owner)
22487      .expect("the owner must bind the unowned publication");
22488    assert_eq!(selected.publication.dependency.owner, Some(owner));
22489    assert!(
22490      dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact),
22491      "binding must immediately publish the entry to exact-owner predicates"
22492    );
22493    assert!(dependency.try_claim(&selected));
22494    assert!(!dependency.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
22495  }
22496
22497  #[cfg(not(target_family = "wasm"))]
22498  #[test]
22499  fn owner_selection_preserves_unowned_and_exact_publication_order() {
22500    let owner = BlockingOwnerToken {
22501      executor_id: u64::MAX - 210,
22502      frame: u64::MAX - 211,
22503    };
22504    let dependency = TaskDependency::default();
22505    let first = BlockingDependency {
22506      job: BlockingJobId(u64::MAX - 212),
22507      owner: None,
22508    };
22509    let second = BlockingDependency {
22510      job: BlockingJobId(u64::MAX - 213),
22511      owner: Some(owner),
22512    };
22513    dependency.set_owned(first);
22514    dependency.set_owned(second);
22515
22516    let selected = dependency
22517      .select_for_owner(owner)
22518      .expect("the first publication must be found");
22519    assert_eq!(
22520      selected.publication.dependency,
22521      BlockingDependency {
22522        owner: Some(owner),
22523        ..first
22524      }
22525    );
22526    assert!(dependency.try_claim(&selected));
22527
22528    let selected = dependency
22529      .select_for_owner(owner)
22530      .expect("the second publication must remain eligible");
22531    assert_eq!(selected.publication.dependency, second);
22532  }
22533
22534  #[cfg(not(target_family = "wasm"))]
22535  #[test]
22536  fn blocking_queue_exact_removal_reclaims_fifo_links() {
22537    const JOB_COUNT: u64 = 4096;
22538
22539    let mut queue = BlockingQueue {
22540      closed: false,
22541      head: None,
22542      tail: None,
22543      jobs: FxHashMap::default(),
22544    };
22545    for id in 1..=JOB_COUNT {
22546      queue.push(QueuedBlocking {
22547        id: BlockingJobId(id),
22548        run: Box::new(|| {}),
22549      });
22550    }
22551    for id in (2..=JOB_COUNT).step_by(2) {
22552      drop(
22553        queue
22554          .remove(BlockingJobId(id))
22555          .expect("even job must remain indexed"),
22556      );
22557    }
22558    for id in (1..=JOB_COUNT).step_by(2) {
22559      drop(
22560        queue
22561          .remove(BlockingJobId(id))
22562          .expect("odd job must remain indexed"),
22563      );
22564    }
22565    assert!(queue.jobs.is_empty());
22566    assert_eq!(queue.head, None);
22567    assert_eq!(queue.tail, None);
22568
22569    let order = Arc::new(Mutex::new(Vec::new()));
22570    for id in 1..=3 {
22571      let order = Arc::clone(&order);
22572      queue.push(QueuedBlocking {
22573        id: BlockingJobId(id),
22574        run: Box::new(move || order.lock().unwrap().push(id)),
22575      });
22576    }
22577    drop(
22578      queue
22579        .remove(BlockingJobId(2))
22580        .expect("middle job must be removable"),
22581    );
22582    queue.pop_front().expect("FIFO head must remain linked")();
22583    queue.pop_front().expect("FIFO tail must remain linked")();
22584    assert!(queue.pop_front().is_none());
22585    assert_eq!(*order.lock().unwrap(), vec![1, 3]);
22586    assert_eq!(queue.head, None);
22587    assert_eq!(queue.tail, None);
22588  }
22589
22590  #[cfg(not(target_family = "wasm"))]
22591  #[test]
22592  fn dependency_lending_keeps_public_blocking_metrics_within_cap() {
22593    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_LENDING_METRICS_CHILD";
22594
22595    if std::env::var_os(CHILD_ENV).is_some() {
22596      configure(RuntimeOptions {
22597        flavor: RuntimeFlavor::MultiThread,
22598        worker_threads: 2,
22599        max_blocking_tasks: 1,
22600        thread_name_prefix: "dependency-lending-metrics".to_string(),
22601        park_deadline: None,
22602        drain_linger: DEFAULT_DRAIN_LINGER,
22603      })
22604      .unwrap();
22605
22606      let outer = spawn_blocking(|| {
22607        let inner = spawn_blocking(|| 42usize);
22608        block_on(inner).unwrap()
22609      });
22610      assert_eq!(block_on(outer).unwrap(), 42);
22611
22612      // Result delivery wakes the joiner from inside the blocking closure,
22613      // immediately before the scheduler-owned metrics guard retires. Use
22614      // shutdown quiescence before sampling completed/live counters so this
22615      // test does not race that final bookkeeping.
22616      shutdown().unwrap();
22617      let snapshot = metrics();
22618      assert_eq!(snapshot.blocking_tasks_started, 2);
22619      assert_eq!(snapshot.blocking_tasks_completed, 2);
22620      assert_eq!(snapshot.active_blocking_tasks, 0);
22621      assert_eq!(
22622        snapshot.max_active_blocking_tasks, snapshot.max_blocking_tasks as u64,
22623        "dependency lending reuses the owner's admitted lane instead of increasing the active gauge"
22624      );
22625      return;
22626    }
22627
22628    let output = std::process::Command::new(std::env::current_exe().unwrap())
22629      .arg("--exact")
22630      .arg("async_runtime::tests::dependency_lending_keeps_public_blocking_metrics_within_cap")
22631      .arg("--nocapture")
22632      .env(CHILD_ENV, "1")
22633      .output()
22634      .expect("the dependency-lending metrics subprocess must start");
22635    assert!(
22636      output.status.success(),
22637      "dependency lending must preserve the public blocking-cap metric invariant; status={:?}\nstdout={}\nstderr={}",
22638      output.status.code(),
22639      String::from_utf8_lossy(&output.stdout),
22640      String::from_utf8_lossy(&output.stderr)
22641    );
22642  }
22643
22644  #[cfg(not(target_family = "wasm"))]
22645  #[test]
22646  fn nested_blocking_runs_exact_awaited_job_before_same_owner_sibling() {
22647    use std::sync::mpsc;
22648
22649    let (done_tx, done_rx) = mpsc::channel();
22650    let runner = std::thread::spawn(move || {
22651      let options = RuntimeOptions {
22652        flavor: RuntimeFlavor::MultiThread,
22653        worker_threads: 2,
22654        max_blocking_tasks: 1,
22655        thread_name_prefix: "exact-blocking-job".to_string(),
22656        park_deadline: None,
22657        drain_linger: DEFAULT_DRAIN_LINGER,
22658      };
22659      let executor =
22660        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
22661      let outer_exec = Arc::clone(&executor);
22662      let (sibling_started_tx, sibling_started_rx) = mpsc::channel();
22663      let (release_sibling_tx, release_sibling_rx) = mpsc::channel();
22664      let (awaited_ran_tx, awaited_ran_rx) = mpsc::channel();
22665      let outer = executor.schedule_blocking(move || {
22666        let sibling = outer_exec.schedule_blocking(move || {
22667          sibling_started_tx.send(()).unwrap();
22668          release_sibling_rx.recv().unwrap();
22669        });
22670        let awaited = outer_exec.schedule_blocking(move || {
22671          awaited_ran_tx.send(()).unwrap();
22672          17usize
22673        });
22674        let mut value = None;
22675        {
22676          let mut future = std::pin::pin!(async {
22677            value = Some(awaited.await.unwrap());
22678          });
22679          outer_exec.block_on(future.as_mut());
22680        }
22681        (sibling, value.unwrap())
22682      });
22683
22684      awaited_ran_rx
22685        .recv_timeout(Duration::from_secs(2))
22686        .expect("the exact awaited job must bypass the earlier detached sibling");
22687      let (sibling, value) = futures::executor::block_on(outer).unwrap();
22688      assert_eq!(value, 17);
22689      sibling_started_rx
22690        .recv_timeout(Duration::from_secs(2))
22691        .expect("the sibling must run after the owner releases its counted slot");
22692      release_sibling_tx.send(()).unwrap();
22693      futures::executor::block_on(sibling).unwrap();
22694      done_tx.send(()).unwrap();
22695    });
22696    done_rx
22697      .recv_timeout(Duration::from_secs(5))
22698      .expect("same-owner sibling selection deadlocked the exact awaited job");
22699    runner.join().unwrap();
22700  }
22701
22702  #[cfg(not(target_family = "wasm"))]
22703  #[test]
22704  fn blocking_dependency_propagates_through_async_join_lineage() {
22705    use std::sync::mpsc;
22706
22707    let (done_tx, done_rx) = mpsc::channel();
22708    let runner = std::thread::spawn(move || {
22709      let controller = Arc::new(multi_thread_controller("blocking-lineage", 2, 1));
22710      let owner_controller = Arc::clone(&controller);
22711      let (child_tx, child_rx) = mpsc::channel();
22712      let outer = controller
22713        .try_spawn_blocking(move || {
22714          let async_controller = Arc::clone(&owner_controller);
22715          let async_child = owner_controller
22716            .try_spawn(async move {
22717              async_controller
22718                .try_spawn_blocking(move || {
22719                  child_tx.send(()).unwrap();
22720                  23usize
22721                })
22722                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking job"))
22723                .await
22724                .unwrap()
22725            })
22726            .unwrap_or_else(|_| panic!("runtime must accept the async descendant"));
22727          let mut value = None;
22728          {
22729            let mut future = std::pin::pin!(async {
22730              value = Some(async_child.await.unwrap());
22731            });
22732            owner_controller.block_on(future.as_mut());
22733          }
22734          value.unwrap()
22735        })
22736        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22737
22738      child_rx
22739        .recv_timeout(Duration::from_secs(2))
22740        .expect("the async descendant's exact blocking dependency must inherit the owner lane");
22741      assert_eq!(futures::executor::block_on(outer).unwrap(), 23);
22742      controller.shutdown().unwrap();
22743      done_tx.send(()).unwrap();
22744    });
22745    done_rx
22746      .recv_timeout(Duration::from_secs(5))
22747      .expect("blocking dependency lineage deadlocked");
22748    runner.join().unwrap();
22749  }
22750
22751  #[cfg(not(target_family = "wasm"))]
22752  #[test]
22753  fn blocking_dependency_does_not_escape_a_serviced_runnable() {
22754    use std::sync::mpsc;
22755
22756    let controller = Arc::new(multi_thread_controller("blocking-lineage-isolation", 2, 1));
22757    let outer_controller = Arc::clone(&controller);
22758    let (child_queued_tx, child_queued_rx) = mpsc::channel();
22759    let (unrelated_ran_tx, unrelated_ran_rx) = mpsc::channel();
22760    let (release_outer_tx, release_outer_rx) = oneshot::channel();
22761    let outer = controller
22762      .try_spawn_blocking(move || {
22763        let child_controller = Arc::clone(&outer_controller);
22764        let child = outer_controller
22765          .try_spawn(async move {
22766            let blocking = child_controller
22767              .try_spawn_blocking(move || {
22768                unrelated_ran_tx.send(()).unwrap();
22769                31usize
22770              })
22771              .unwrap_or_else(|_| panic!("runtime must accept the unrelated blocking job"));
22772            child_queued_tx.send(()).unwrap();
22773            blocking.await.unwrap()
22774          })
22775          .unwrap_or_else(|_| panic!("runtime must accept the serviced async task"));
22776        let mut released = false;
22777        {
22778          let mut future = std::pin::pin!(async {
22779            release_outer_rx.await.unwrap();
22780            released = true;
22781          });
22782          outer_controller.block_on(future.as_mut());
22783        }
22784        assert!(released);
22785        child
22786      })
22787      .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
22788
22789    child_queued_rx
22790      .recv_timeout(Duration::from_secs(2))
22791      .expect("the cooperative driver must service the unrelated async runnable");
22792    assert!(
22793      unrelated_ran_rx
22794        .recv_timeout(Duration::from_millis(200))
22795        .is_err(),
22796      "an unrelated serviced runnable leaked its blocking job into the owner's over-cap lineage"
22797    );
22798
22799    release_outer_tx.send(()).unwrap();
22800    let child = futures::executor::block_on(outer).unwrap();
22801    unrelated_ran_rx
22802      .recv_timeout(Duration::from_secs(2))
22803      .expect("the unrelated blocking job must run after the owner releases its counted slot");
22804    assert_eq!(futures::executor::block_on(child).unwrap(), 31);
22805    controller.shutdown().unwrap();
22806  }
22807
22808  #[cfg(not(target_family = "wasm"))]
22809  #[test]
22810  fn dropping_pending_join_handle_clears_parent_blocking_dependency() {
22811    let parent = Arc::new(TaskDependency::default());
22812    let _context = TaskDependencyGuard::enter(&parent);
22813    let dependency = BlockingDependency {
22814      job: BlockingJobId(u64::MAX),
22815      owner: None,
22816    };
22817    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22818    let mut handle = JoinHandle(JoinHandleInner::Blocking {
22819      receiver,
22820      dependency,
22821      published: PublishedBlockingDependencies::default(),
22822    });
22823    let waker = futures::task::noop_waker();
22824    let mut cx = Context::from_waker(&waker);
22825
22826    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
22827    assert_eq!(parent.get_dependency(), Some(dependency));
22828    drop(handle);
22829    assert_eq!(
22830      parent.get_dependency(),
22831      None,
22832      "an abandoned JoinHandle must clear stale dependency lineage"
22833    );
22834    drop(sender);
22835  }
22836
22837  #[cfg(not(target_family = "wasm"))]
22838  #[test]
22839  fn dropping_owner_enriched_blocking_handle_clears_parent_dependency() {
22840    let owner = BlockingOwnerToken {
22841      executor_id: u64::MAX - 40,
22842      frame: u64::MAX - 41,
22843    };
22844    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22845    let _context = TaskDependencyGuard::enter(&parent);
22846    let dependency = BlockingDependency {
22847      job: BlockingJobId(u64::MAX - 42),
22848      owner: None,
22849    };
22850    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22851    let mut handle = JoinHandle(JoinHandleInner::Blocking {
22852      receiver,
22853      dependency,
22854      published: PublishedBlockingDependencies::default(),
22855    });
22856    let waker = futures::task::noop_waker();
22857    let mut cx = Context::from_waker(&waker);
22858
22859    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
22860    assert_eq!(
22861      parent.get_dependency(),
22862      Some(BlockingDependency {
22863        owner: Some(owner),
22864        ..dependency
22865      }),
22866      "the ambient task owner must enrich an untagged direct blocking dependency"
22867    );
22868    drop(handle);
22869    assert_eq!(
22870      parent.get_dependency(),
22871      None,
22872      "detach cleanup must match the stable job id after owner enrichment"
22873    );
22874    drop(sender);
22875  }
22876
22877  #[cfg(not(target_family = "wasm"))]
22878  #[test]
22879  fn one_poll_preserves_every_observed_blocking_dependency() {
22880    let parent = Arc::new(TaskDependency::default());
22881    let _context = TaskDependencyGuard::enter(&parent);
22882    let first = BlockingDependency {
22883      job: BlockingJobId(u64::MAX - 60),
22884      owner: None,
22885    };
22886    let second = BlockingDependency {
22887      job: BlockingJobId(u64::MAX - 61),
22888      owner: None,
22889    };
22890    let (first_sender, first_receiver) =
22891      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22892    let (second_sender, second_receiver) =
22893      oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22894    let mut first_handle = JoinHandle(JoinHandleInner::Blocking {
22895      receiver: first_receiver,
22896      dependency: first,
22897      published: PublishedBlockingDependencies::default(),
22898    });
22899    let mut second_handle = JoinHandle(JoinHandleInner::Blocking {
22900      receiver: second_receiver,
22901      dependency: second,
22902      published: PublishedBlockingDependencies::default(),
22903    });
22904    let waker = futures::task::noop_waker();
22905    let mut cx = Context::from_waker(&waker);
22906
22907    assert!(Pin::new(&mut first_handle).poll(&mut cx).is_pending());
22908    assert!(Pin::new(&mut second_handle).poll(&mut cx).is_pending());
22909    assert_eq!(
22910      parent.get_dependencies(),
22911      vec![first, second],
22912      "publishing a later dependency in one user poll must not replace an earlier dependency"
22913    );
22914
22915    drop(first_handle);
22916    drop(second_handle);
22917    drop(first_sender);
22918    drop(second_sender);
22919  }
22920
22921  #[cfg(not(target_family = "wasm"))]
22922  #[test]
22923  fn double_polled_then_detached_handle_does_not_outrank_a_live_dependency() {
22924    let owner = BlockingOwnerToken {
22925      executor_id: u64::MAX - 900,
22926      frame: u64::MAX - 901,
22927    };
22928    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22929    let _context = TaskDependencyGuard::enter(&parent);
22930    let waker = futures::task::noop_waker();
22931    let mut cx = Context::from_waker(&waker);
22932
22933    let j1 = BlockingDependency {
22934      job: BlockingJobId(u64::MAX - 910),
22935      owner: Some(owner),
22936    };
22937    let j2 = BlockingDependency {
22938      job: BlockingJobId(u64::MAX - 911),
22939      owner: Some(owner),
22940    };
22941
22942    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22943    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
22944
22945    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
22946      receiver: r1,
22947      dependency: j1,
22948      published: PublishedBlockingDependencies::default(),
22949    });
22950    // TWO Pending polls in one pass. Each publishes, so without coalescing the
22951    // handle leaves two live entries for J1 and can retire only the newer one.
22952    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
22953    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
22954    let entries_after_double_poll = parent
22955      .state
22956      .lock()
22957      .unwrap_or_else(std::sync::PoisonError::into_inner)
22958      .current
22959      .len();
22960    drop(h1);
22961
22962    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
22963      receiver: r2,
22964      dependency: j2,
22965      published: PublishedBlockingDependencies::default(),
22966    });
22967    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
22968
22969    assert_eq!(
22970      entries_after_double_poll, 1,
22971      "a repeat Pending poll must supersede the handle's own previous publication"
22972    );
22973    let selected = parent
22974      .select_for_owner(owner)
22975      .expect("a live dependency must be selectable");
22976    assert_eq!(
22977      selected.publication.dependency.job, j2.job,
22978      "a DETACHED job must not be selected for owner lending ahead of the live one"
22979    );
22980    drop((s1, s2));
22981  }
22982
22983  #[cfg(not(target_family = "wasm"))]
22984  #[test]
22985  fn handle_polled_under_a_nested_frame_retires_both_publications() {
22986    let owner = BlockingOwnerToken {
22987      executor_id: u64::MAX - 920,
22988      frame: u64::MAX - 921,
22989    };
22990    let outer = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22991    let inner = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
22992    let waker = futures::task::noop_waker();
22993    let mut cx = Context::from_waker(&waker);
22994
22995    let j1 = BlockingDependency {
22996      job: BlockingJobId(u64::MAX - 930),
22997      owner: Some(owner),
22998    };
22999    let j2 = BlockingDependency {
23000      job: BlockingJobId(u64::MAX - 931),
23001      owner: Some(owner),
23002    };
23003    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23004    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23005    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23006      receiver: r1,
23007      dependency: j1,
23008      published: Default::default(),
23009    });
23010
23011    {
23012      let _outer_ctx = TaskDependencyGuard::enter(&outer);
23013      // A handle can publish into more than one frame: once here, and again
23014      // under a nested re-entrant frame. Detaching must retire BOTH, or the
23015      // forgotten entry keeps a detached job selectable for owner lending.
23016      assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23017      {
23018        let _inner_ctx = TaskDependencyGuard::enter(&inner);
23019        assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23020      }
23021      drop(h1);
23022      assert!(
23023        outer.get_dependencies().is_empty(),
23024        "detaching must retire the publication made in the outer frame too"
23025      );
23026      let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23027        receiver: r2,
23028        dependency: j2,
23029        published: Default::default(),
23030      });
23031      assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23032      let selected = outer.select_for_owner(owner).expect("selectable");
23033      assert_eq!(
23034        selected.publication.dependency.job, j2.job,
23035        "a detached job published in an outer frame must not outrank a live one"
23036      );
23037      drop(h2);
23038    }
23039    drop((s1, s2));
23040  }
23041
23042  #[cfg(not(target_family = "wasm"))]
23043  #[test]
23044  fn a_pending_handle_does_not_retain_the_frames_it_has_left() {
23045    const FRAME_COUNT: usize = 64;
23046    let waker = futures::task::noop_waker();
23047    let mut cx = Context::from_waker(&waker);
23048    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23049    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23050      receiver,
23051      dependency: BlockingDependency {
23052        job: BlockingJobId(u64::MAX - 940),
23053        owner: None,
23054      },
23055      published: PublishedBlockingDependencies::default(),
23056    });
23057    let mut frames = Vec::new();
23058    for _ in 0..FRAME_COUNT {
23059      let frame = Arc::new(TaskDependency::default());
23060      frames.push(Arc::downgrade(&frame));
23061      let _context = TaskDependencyGuard::enter(&frame);
23062      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23063    }
23064    // A handle carried through a sequence of completed frames must not keep
23065    // any of them alive: it remembers each publication by `Weak`, and prunes
23066    // the expired tokens on its next publication, so the history stays at the
23067    // live frame count instead of growing with every frame ever seen.
23068    let retained = frames
23069      .iter()
23070      .filter(|frame| frame.strong_count() > 0)
23071      .count();
23072    assert_eq!(
23073      retained, 0,
23074      "a completed frame must not be kept alive by a pending handle's publication history"
23075    );
23076    drop(handle);
23077    drop(sender);
23078  }
23079
23080  #[cfg(not(target_family = "wasm"))]
23081  #[test]
23082  fn repolling_a_handle_does_not_reorder_it_behind_a_later_dependency() {
23083    // H1 -> H2 -> H1 in one poll pass. `select_for_owner` picks the LOWEST live
23084    // sequence, so H1 must keep the position its FIRST poll gave it: if
23085    // re-polling appended a replacement at the tail, H2 would be lent first,
23086    // and an H2 that waits on H1 would deadlock the owner's single lane.
23087    let owner = BlockingOwnerToken {
23088      executor_id: u64::MAX - 950,
23089      frame: u64::MAX - 951,
23090    };
23091    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
23092    let _context = TaskDependencyGuard::enter(&parent);
23093    let waker = futures::task::noop_waker();
23094    let mut cx = Context::from_waker(&waker);
23095
23096    let j1 = BlockingDependency {
23097      job: BlockingJobId(u64::MAX - 960),
23098      owner: Some(owner),
23099    };
23100    let j2 = BlockingDependency {
23101      job: BlockingJobId(u64::MAX - 961),
23102      owner: Some(owner),
23103    };
23104    let (s1, r1) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23105    let (s2, r2) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23106    let mut h1 = JoinHandle(JoinHandleInner::Blocking {
23107      receiver: r1,
23108      dependency: j1,
23109      published: PublishedBlockingDependencies::default(),
23110    });
23111    let mut h2 = JoinHandle(JoinHandleInner::Blocking {
23112      receiver: r2,
23113      dependency: j2,
23114      published: PublishedBlockingDependencies::default(),
23115    });
23116
23117    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23118    assert!(Pin::new(&mut h2).poll(&mut cx).is_pending());
23119    assert!(Pin::new(&mut h1).poll(&mut cx).is_pending());
23120
23121    let selected = parent
23122      .select_for_owner(owner)
23123      .expect("a live dependency must be selectable");
23124    assert_eq!(
23125      selected.publication.dependency.job, j1.job,
23126      "re-polling a handle must not push its dependency behind one published later"
23127    );
23128    drop((h1, h2, s1, s2));
23129  }
23130
23131  #[cfg(not(target_family = "wasm"))]
23132  #[test]
23133  fn a_handle_carried_through_reused_frames_keeps_a_bounded_history() {
23134    // Frames here are RETAINED (as a completed task's `JoinHandle` retains its
23135    // `TaskDependency`) but each drains `current` at the start of its poll,
23136    // which is what a real task poll does. A drained frame no longer holds the
23137    // entry, so its token stops standing and is dropped on the next
23138    // publication: the history tracks frames that still hold an entry, not
23139    // every frame the handle has ever been polled under.
23140    const FRAME_COUNT: usize = 256;
23141    let waker = futures::task::noop_waker();
23142    let mut cx = Context::from_waker(&waker);
23143    let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23144    let mut handle = JoinHandle(JoinHandleInner::Blocking {
23145      receiver,
23146      dependency: BlockingDependency {
23147        job: BlockingJobId(u64::MAX - 970),
23148        owner: None,
23149      },
23150      published: PublishedBlockingDependencies::default(),
23151    });
23152    let mut retained = Vec::new();
23153    for _ in 0..FRAME_COUNT {
23154      let frame = Arc::new(TaskDependency::default());
23155      retained.push(Arc::clone(&frame));
23156      let _context = TaskDependencyGuard::enter(&frame);
23157      assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23158      // The frame's next poll drains what the previous one published.
23159      frame.begin_poll();
23160    }
23161    let JoinHandle(JoinHandleInner::Blocking { published, .. }) = &handle else {
23162      panic!("the handle must still be a blocking handle");
23163    };
23164    let remembered = usize::from(published.first.is_some()) + published.rest.len();
23165    assert!(
23166      remembered <= 2,
23167      "a drained frame must not stay in the publication history, remembered={remembered}"
23168    );
23169    drop(handle);
23170    drop(sender);
23171  }
23172
23173  #[cfg(not(target_family = "wasm"))]
23174  #[test]
23175  fn mass_detach_of_pending_blocking_handles_stays_linear() {
23176    // A cancelled fan-out: N blocking handles are polled Pending inside ONE
23177    // poll pass -- so `begin_poll` never runs to drain `current` and all N
23178    // entries are simultaneously registered and still pending -- and are then
23179    // dropped together. Retiring a handle's own entry is O(1) and
23180    // `prune_stale_prefix` is amortised, so the whole detach is O(N).
23181    //
23182    // This is the shape that made an earlier attempt at this optimisation
23183    // quadratic: it replaced the job index with a scan of `current` on every
23184    // drop, which costs exactly N(N+1)/2 entry visits here, all of it under
23185    // the dependency mutex.
23186    const HANDLE_COUNT: usize = 512;
23187
23188    let parent = Arc::new(TaskDependency::default());
23189    let _context = TaskDependencyGuard::enter(&parent);
23190    let waker = futures::task::noop_waker();
23191    let mut cx = Context::from_waker(&waker);
23192
23193    let mut senders = Vec::with_capacity(HANDLE_COUNT);
23194    let mut handles = Vec::with_capacity(HANDLE_COUNT);
23195    for index in 0..HANDLE_COUNT {
23196      let (sender, receiver) = oneshot::channel::<Result<ContainedTaskOutput<()>, JoinError>>();
23197      senders.push(sender);
23198      handles.push(JoinHandle(JoinHandleInner::Blocking {
23199        receiver,
23200        dependency: BlockingDependency {
23201          job: BlockingJobId(u64::MAX - 300_000 - index as u64),
23202          owner: None,
23203        },
23204        published: PublishedBlockingDependencies::default(),
23205      }));
23206    }
23207    for handle in &mut handles {
23208      assert!(Pin::new(handle).poll(&mut cx).is_pending());
23209    }
23210    assert_eq!(
23211      parent
23212        .state
23213        .lock()
23214        .unwrap_or_else(std::sync::PoisonError::into_inner)
23215        .current
23216        .len(),
23217      HANDLE_COUNT,
23218      "every handle must be registered and still pending before the detach"
23219    );
23220
23221    parent.reset_entry_visits();
23222    drop(handles);
23223
23224    assert!(
23225      parent.entry_visits() <= HANDLE_COUNT * 4,
23226      "mass-detach must visit O(n) entries across n handles, visits={}",
23227      parent.entry_visits()
23228    );
23229    assert_eq!(
23230      parent.get_dependency(),
23231      None,
23232      "every detached handle must have retired its own publication"
23233    );
23234    drop(senders);
23235  }
23236
23237  #[cfg(not(target_family = "wasm"))]
23238  #[test]
23239  fn dependency_tls_borrow_is_released_before_reentrant_wake() {
23240    use std::sync::mpsc;
23241
23242    struct ReenterDependencyContext {
23243      dependency: Arc<TaskDependency>,
23244      woke: mpsc::Sender<()>,
23245    }
23246
23247    impl std::task::Wake for ReenterDependencyContext {
23248      fn wake(self: Arc<Self>) {
23249        let _context = TaskDependencyGuard::enter(&self.dependency);
23250        self.woke.send(()).unwrap();
23251      }
23252    }
23253
23254    let parent = Arc::new(TaskDependency::default());
23255    let nested = Arc::new(TaskDependency::default());
23256    let (woke_tx, woke_rx) = mpsc::channel();
23257    let waiter = Waker::from(Arc::new(ReenterDependencyContext {
23258      dependency: Arc::clone(&nested),
23259      woke: woke_tx,
23260    }));
23261    parent.register_waiter(&waiter);
23262    let blocking = BlockingDependency {
23263      job: BlockingJobId(u64::MAX - 62),
23264      owner: None,
23265    };
23266
23267    let _context = TaskDependencyGuard::enter(&parent);
23268    let mut published = PublishedBlockingDependencies::default();
23269    record_blocking_dependency(&mut published, blocking);
23270    woke_rx
23271      .recv_timeout(Duration::from_secs(1))
23272      .expect("dependency notification must permit reentrant TLS context access");
23273
23274    parent.register_waiter(&waiter);
23275    clear_blocking_dependency(published);
23276    woke_rx
23277      .recv_timeout(Duration::from_secs(1))
23278      .expect("dependency clearing must permit reentrant TLS context access");
23279  }
23280
23281  #[cfg(not(target_family = "wasm"))]
23282  #[test]
23283  fn dependency_free_begin_poll_does_not_lock_state() {
23284    use std::sync::mpsc;
23285
23286    let dependency = Arc::new(TaskDependency::default());
23287    let state = dependency.state.lock().unwrap();
23288    let (completed_tx, completed_rx) = mpsc::channel();
23289    let polling_dependency = Arc::clone(&dependency);
23290    let polling = std::thread::spawn(move || {
23291      polling_dependency.begin_poll();
23292      completed_tx.send(()).unwrap();
23293    });
23294
23295    let completed_without_lock = completed_rx
23296      .recv_timeout(Duration::from_millis(200))
23297      .is_ok();
23298    drop(state);
23299    polling.join().unwrap();
23300    assert!(
23301      completed_without_lock,
23302      "a task with no published dependency must skip the dependency-state mutex"
23303    );
23304  }
23305
23306  #[cfg(not(target_family = "wasm"))]
23307  #[test]
23308  fn deep_dependency_liveness_chain_is_iterative() {
23309    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEEP_DEPENDENCY_CHAIN_CHILD";
23310
23311    if std::env::var_os(CHILD_ENV).is_some() {
23312      const DEPTH: usize = 100_000;
23313
23314      let mut links = Vec::with_capacity(DEPTH + 1);
23315      links.push(DependencyLink::root());
23316      for _ in 0..DEPTH {
23317        links.push(DependencyLink::derived(links.last().unwrap()));
23318      }
23319      assert!(links.last().unwrap().is_live());
23320      links[0].cancel();
23321      assert!(!links.last().unwrap().is_live());
23322      return;
23323    }
23324
23325    let output = std::process::Command::new(std::env::current_exe().unwrap())
23326      .arg("--exact")
23327      .arg("async_runtime::tests::deep_dependency_liveness_chain_is_iterative")
23328      .arg("--nocapture")
23329      .env(CHILD_ENV, "1")
23330      .output()
23331      .expect("the subprocess test must start");
23332    assert!(
23333      output.status.success(),
23334      "deep dependency liveness traversal must not overflow the stack; status={:?}\nstdout={}\nstderr={}",
23335      output.status.code(),
23336      String::from_utf8_lossy(&output.stdout),
23337      String::from_utf8_lossy(&output.stderr)
23338    );
23339  }
23340
23341  #[cfg(not(target_family = "wasm"))]
23342  #[test]
23343  fn task_dependency_set_contains_panicking_waiter() {
23344    struct PanicWake;
23345    impl std::task::Wake for PanicWake {
23346      fn wake(self: Arc<Self>) {
23347        panic!("intentional TaskDependency::set waiter panic");
23348      }
23349    }
23350
23351    let dependency = TaskDependency::default();
23352    let blocking = BlockingDependency {
23353      job: BlockingJobId(u64::MAX),
23354      owner: None,
23355    };
23356    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23357
23358    let result = catch_unwind(AssertUnwindSafe(|| dependency.set_owned(blocking)));
23359    assert!(
23360      result.is_ok(),
23361      "set notification must not unwind its caller"
23362    );
23363    assert_eq!(
23364      dependency.get_dependency(),
23365      Some(blocking),
23366      "set must commit before notifying its waiter"
23367    );
23368  }
23369
23370  #[cfg(not(target_family = "wasm"))]
23371  #[test]
23372  fn task_dependency_clear_contains_panicking_waiter() {
23373    struct PanicWake;
23374    impl std::task::Wake for PanicWake {
23375      fn wake(self: Arc<Self>) {
23376        panic!("intentional TaskDependency::clear waiter panic");
23377      }
23378    }
23379
23380    let dependency = TaskDependency::default();
23381    dependency.set_owned(BlockingDependency {
23382      job: BlockingJobId(u64::MAX),
23383      owner: None,
23384    });
23385    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23386
23387    let result = catch_unwind(AssertUnwindSafe(|| dependency.clear()));
23388    assert!(
23389      result.is_ok(),
23390      "clear notification must not unwind its caller"
23391    );
23392    assert_eq!(
23393      dependency.get_dependency(),
23394      None,
23395      "clear must commit before notifying its waiter"
23396    );
23397  }
23398
23399  #[cfg(not(target_family = "wasm"))]
23400  #[test]
23401  fn task_dependency_clear_if_contains_panicking_waiter() {
23402    struct PanicWake;
23403    impl std::task::Wake for PanicWake {
23404      fn wake(self: Arc<Self>) {
23405        panic!("intentional TaskDependency::clear_if waiter panic");
23406      }
23407    }
23408
23409    let dependency = TaskDependency::default();
23410    let blocking = BlockingDependency {
23411      job: BlockingJobId(u64::MAX),
23412      owner: None,
23413    };
23414    let sequence = dependency.set_owned(blocking);
23415    dependency.register_waiter(&Waker::from(Arc::new(PanicWake)));
23416
23417    let result = catch_unwind(AssertUnwindSafe(|| dependency.retire(sequence)));
23418    assert!(
23419      result.is_ok(),
23420      "retire notification must not unwind its caller"
23421    );
23422    assert_eq!(
23423      dependency.get_dependency(),
23424      None,
23425      "retire must commit before notifying its waiter"
23426    );
23427  }
23428
23429  #[cfg(not(target_family = "wasm"))]
23430  #[test]
23431  fn task_dependency_waiter_replacement_contains_panicking_drop() {
23432    use std::sync::mpsc;
23433
23434    const GENERATION: u64 = u64::MAX - 20;
23435
23436    struct PanicOnDropWake {
23437      dropped: mpsc::Sender<Option<u64>>,
23438    }
23439
23440    #[expect(
23441      clippy::manual_noop_waker,
23442      reason = "the test observes the waker payload's Drop"
23443    )]
23444    impl std::task::Wake for PanicOnDropWake {
23445      fn wake(self: Arc<Self>) {}
23446    }
23447
23448    impl Drop for PanicOnDropWake {
23449      fn drop(&mut self) {
23450        self
23451          .dropped
23452          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
23453          .unwrap();
23454        panic!("intentional replaced dependency waiter destructor panic");
23455      }
23456    }
23457
23458    let dependency = TaskDependency::new(GENERATION, None);
23459    let (dropped_tx, dropped_rx) = mpsc::channel();
23460    let waiter = Waker::from(Arc::new(PanicOnDropWake {
23461      dropped: dropped_tx,
23462    }));
23463    dependency.register_waiter(&waiter);
23464    drop(waiter);
23465
23466    let replacement = futures::task::noop_waker();
23467    let result = catch_unwind(AssertUnwindSafe(|| {
23468      dependency.register_waiter(&replacement)
23469    }));
23470    assert!(
23471      result.is_ok(),
23472      "replacing a hostile waiter must not unwind registration"
23473    );
23474    assert_eq!(
23475      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
23476      Some(GENERATION),
23477      "the replaced waiter must be destroyed under its runtime generation"
23478    );
23479  }
23480
23481  #[cfg(not(target_family = "wasm"))]
23482  #[test]
23483  fn task_dependency_retirement_contains_stored_waiter_drop() {
23484    use std::sync::mpsc;
23485
23486    const GENERATION: u64 = u64::MAX - 21;
23487
23488    struct PanicOnDropWake {
23489      dropped: mpsc::Sender<Option<u64>>,
23490    }
23491
23492    #[expect(
23493      clippy::manual_noop_waker,
23494      reason = "the test observes the waker payload's Drop"
23495    )]
23496    impl std::task::Wake for PanicOnDropWake {
23497      fn wake(self: Arc<Self>) {}
23498    }
23499
23500    impl Drop for PanicOnDropWake {
23501      fn drop(&mut self) {
23502        self
23503          .dropped
23504          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
23505          .unwrap();
23506        panic!("intentional stored dependency waiter destructor panic");
23507      }
23508    }
23509
23510    let dependency = TaskDependency::new(GENERATION, None);
23511    let (dropped_tx, dropped_rx) = mpsc::channel();
23512    let waiter = Waker::from(Arc::new(PanicOnDropWake {
23513      dropped: dropped_tx,
23514    }));
23515    dependency.register_waiter(&waiter);
23516    drop(waiter);
23517
23518    let result = catch_unwind(AssertUnwindSafe(|| drop(dependency)));
23519    assert!(
23520      result.is_ok(),
23521      "retiring a hostile stored waiter must not unwind"
23522    );
23523    assert_eq!(
23524      dropped_rx.recv_timeout(Duration::from_secs(1)).unwrap(),
23525      Some(GENERATION),
23526      "the retained waiter must be destroyed under its runtime generation"
23527    );
23528  }
23529
23530  #[cfg(not(target_family = "wasm"))]
23531  #[test]
23532  fn detaching_task_clears_dependency_waiter_parent_retention() {
23533    struct RetainingWake {
23534      retained_parent: Arc<()>,
23535    }
23536
23537    impl std::task::Wake for RetainingWake {
23538      fn wake(self: Arc<Self>) {
23539        let _ = &self.retained_parent;
23540      }
23541    }
23542
23543    let dependency = Arc::new(TaskDependency::default());
23544    let retained_parent = Arc::new(());
23545    let retained_parent_weak = Arc::downgrade(&retained_parent);
23546    let waiter = Waker::from(Arc::new(RetainingWake {
23547      retained_parent: Arc::clone(&retained_parent),
23548    }));
23549    dependency.register_waiter(&waiter);
23550    drop(waiter);
23551    drop(retained_parent);
23552    assert!(
23553      retained_parent_weak.upgrade().is_some(),
23554      "the stored dependency waiter must retain its parent before detachment"
23555    );
23556
23557    let (runnable, task) = async_task::spawn(
23558      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
23559      |_| {},
23560    );
23561    drop(JoinHandle(JoinHandleInner::Task {
23562      task: task.fallible(),
23563      awaiter: None,
23564      dependency: Arc::clone(&dependency),
23565    }));
23566    assert!(
23567      retained_parent_weak.upgrade().is_none(),
23568      "detachment must clear the dependency waiter instead of retaining its parent task"
23569    );
23570    drop(runnable);
23571  }
23572
23573  #[cfg(not(target_family = "wasm"))]
23574  #[test]
23575  fn detaching_task_clears_many_parent_publications_linearly() {
23576    const PUBLICATION_COUNT: usize = 1024;
23577
23578    struct CountWake(Arc<AtomicUsize>);
23579
23580    impl std::task::Wake for CountWake {
23581      fn wake(self: Arc<Self>) {
23582        self.0.fetch_add(1, Ordering::SeqCst);
23583      }
23584    }
23585
23586    fn latest_publication(dependency: &TaskDependency) -> DependencyPublication {
23587      dependency
23588        .state
23589        .lock()
23590        .unwrap_or_else(std::sync::PoisonError::into_inner)
23591        .current
23592        .back()
23593        .expect("the sentinel publication must be appended")
23594        .publication
23595        .clone()
23596    }
23597
23598    let owner = BlockingOwnerToken {
23599      executor_id: u64::MAX - 300,
23600      frame: u64::MAX - 301,
23601    };
23602    let other_owner = BlockingOwnerToken {
23603      executor_id: u64::MAX - 302,
23604      frame: u64::MAX - 303,
23605    };
23606    let child = Arc::new(TaskDependency::default());
23607    for index in 0..PUBLICATION_COUNT {
23608      child.set_owned(BlockingDependency {
23609        job: BlockingJobId(1_000_000 + index as u64),
23610        owner: (index % 2 == 0).then_some(owner),
23611      });
23612    }
23613    let child_publications = child.snapshot_all_live();
23614    let source = child_publications[0].clone();
23615
23616    let parent = Arc::new(TaskDependency::default());
23617    let _context = TaskDependencyGuard::enter(&parent);
23618    let (runnable, task) = async_task::spawn(
23619      futures::future::pending::<Result<ContainedTaskOutput<()>, JoinError>>(),
23620      |_| {},
23621    );
23622    let mut handle = JoinHandle(JoinHandleInner::Task {
23623      task: task.fallible(),
23624      awaiter: None,
23625      dependency: Arc::clone(&child),
23626    });
23627    let waker = futures::task::noop_waker();
23628    let mut cx = Context::from_waker(&waker);
23629    assert!(Pin::new(&mut handle).poll(&mut cx).is_pending());
23630    assert_eq!(
23631      parent.get_dependencies().len(),
23632      PUBLICATION_COUNT,
23633      "polling the child handle must propagate every current publication"
23634    );
23635
23636    let mut wrong_claim_source = source.clone();
23637    wrong_claim_source.claim = Arc::new(DependencyClaim::new());
23638    parent.set_borrowed(&wrong_claim_source);
23639    let wrong_claim_publication = latest_publication(&parent);
23640
23641    let mut wrong_job_source = source.clone();
23642    wrong_job_source.dependency.job = BlockingJobId(u64::MAX - 304);
23643    parent.set_borrowed(&wrong_job_source);
23644    let wrong_job_publication = latest_publication(&parent);
23645
23646    let wrong_immediate_source = source.derived(None);
23647    parent.set_borrowed(&wrong_immediate_source);
23648    let wrong_immediate_publication = latest_publication(&parent);
23649
23650    let wakes = Arc::new(AtomicUsize::new(0));
23651    parent.register_waiter(&Waker::from(Arc::new(CountWake(Arc::clone(&wakes)))));
23652    parent.reset_entry_visits();
23653    drop(handle);
23654
23655    assert_eq!(
23656      wakes.load(Ordering::SeqCst),
23657      1,
23658      "one batched parent cleanup must emit one waiter notification"
23659    );
23660    assert!(
23661      parent.entry_visits() <= PUBLICATION_COUNT * 2 + 8,
23662      "detach must hash child sources once, scan the parent once, and reclaim at most one stale prefix; visits={}",
23663      parent.entry_visits()
23664    );
23665    assert!(
23666      wrong_claim_publication.is_live(),
23667      "claim identity must remain part of the match"
23668    );
23669    assert!(
23670      wrong_job_publication.is_live(),
23671      "job identity must remain part of the match"
23672    );
23673    assert!(
23674      wrong_immediate_publication.is_live(),
23675      "only an exact immediate-parent link may be retired"
23676    );
23677
23678    {
23679      let state = parent
23680        .state
23681        .lock()
23682        .unwrap_or_else(std::sync::PoisonError::into_inner);
23683      assert_eq!(state.current.len(), 3);
23684      // The `job_sequences` index is gone -- a blocking handle retires its own
23685      // entry by sequence -- so "the index retains no stale sequence" is
23686      // structurally impossible rather than something to assert. The
23687      // substantive property that assertion protected, that only the three
23688      // NON-matching sentinels survived the clear, is checked directly on the
23689      // authoritative structure.
23690      assert!(
23691        state
23692          .current
23693          .iter()
23694          .all(|entry| entry.publication.is_live()),
23695        "only the three non-matching sentinels may remain, all still live"
23696      );
23697      assert!(state.unowned_sequences.is_empty());
23698      assert_eq!(state.owner_sequences.len(), 1);
23699      assert_eq!(
23700        state.owner_sequences.get(&owner).map(VecDeque::len),
23701        Some(3)
23702      );
23703    }
23704    assert!(parent.has_live_owner_dependency(owner, OwnerDependencyMode::Exact));
23705    assert!(
23706      !parent.has_live_owner_dependency(other_owner, OwnerDependencyMode::Available),
23707      "cancelled unowned publications must not survive in availability predicates"
23708    );
23709    drop(runnable);
23710  }
23711
23712  #[cfg(not(target_family = "wasm"))]
23713  #[test]
23714  fn dependency_waker_and_buffered_result_drop_panics_are_independently_contained() {
23715    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEPENDENCY_WAKER_DOUBLE_PANIC_CHILD";
23716
23717    if std::env::var_os(CHILD_ENV).is_some() {
23718      struct PanicWake;
23719      impl std::task::Wake for PanicWake {
23720        fn wake(self: Arc<Self>) {
23721          panic!("intentional dependency waker panic");
23722        }
23723      }
23724
23725      struct PanicOnDrop;
23726      impl Drop for PanicOnDrop {
23727        fn drop(&mut self) {
23728          panic!("intentional buffered blocking result destructor panic");
23729        }
23730      }
23731
23732      let parent = Arc::new(TaskDependency::default());
23733      let dependency = BlockingDependency {
23734        job: BlockingJobId(u64::MAX - 1),
23735        owner: None,
23736      };
23737      // The handle is built by hand rather than by polling, so hand it the
23738      // publication token a Pending poll would have recorded; detach retires
23739      // the entry by sequence.
23740      let sequence = parent.set_owned(dependency);
23741      parent.register_waiter(&Waker::from(Arc::new(PanicWake)));
23742      let _context = TaskDependencyGuard::enter(&parent);
23743      let (sender, receiver) = oneshot::channel();
23744      let _ = sender.send(Ok::<ContainedTaskOutput<PanicOnDrop>, JoinError>(
23745        ContainedTaskOutput::new(PanicOnDrop, u64::MAX),
23746      ));
23747      drop(JoinHandle(JoinHandleInner::Blocking {
23748        receiver,
23749        dependency,
23750        published: PublishedBlockingDependencies {
23751          first: Some(PublishedBlockingDependency {
23752            state: Arc::downgrade(&parent),
23753            sequence,
23754          }),
23755          rest: Vec::new(),
23756        },
23757      }));
23758      assert_eq!(parent.get_dependency(), None);
23759      return;
23760    }
23761
23762    let output = std::process::Command::new(std::env::current_exe().unwrap())
23763      .arg("--exact")
23764      .arg(
23765        "async_runtime::tests::dependency_waker_and_buffered_result_drop_panics_are_independently_contained",
23766      )
23767      .arg("--nocapture")
23768      .env(CHILD_ENV, "1")
23769      .output()
23770      .expect("the subprocess test must start");
23771    assert!(
23772      output.status.success(),
23773      "dependency cleanup and buffered result destruction must not double-panic; status={:?}\nstdout={}\nstderr={}",
23774      output.status.code(),
23775      String::from_utf8_lossy(&output.stdout),
23776      String::from_utf8_lossy(&output.stderr)
23777    );
23778  }
23779
23780  #[cfg(not(target_family = "wasm"))]
23781  #[test]
23782  fn multi_thread_block_on_respects_blocking_cap_for_non_owner_driver() {
23783    // Non-owner blocking-cap invariant: a plain pool-worker runnable driver
23784    // that does NOT own a counted blocking slot and re-enters `block_on`
23785    // must NOT run queued blocking jobs over `max_blocking_tasks` via the
23786    // cooperative over-cap fallback. Two holder blocking jobs saturate the cap;
23787    // two driver tasks then `block_on(spawn_blocking(..))`. If a non-owner
23788    // driver could use the over-cap fallback, the inner jobs would run while
23789    // both holders still occupy slots and push peak active blocking above 2.
23790    // The cap must be honored AND everything must still complete.
23791    use std::sync::{Barrier, mpsc};
23792    use std::time::Duration;
23793
23794    let (done_tx, done_rx) = mpsc::channel();
23795    let peak_out = Arc::new(Mutex::new(None));
23796    let peak_slot = Arc::clone(&peak_out);
23797    let runner = std::thread::spawn(move || {
23798      let metrics = Arc::new(RuntimeMetrics::default());
23799      let options = RuntimeOptions {
23800        flavor: RuntimeFlavor::MultiThread,
23801        worker_threads: 4,
23802        max_blocking_tasks: 2,
23803        thread_name_prefix: "rd1-cap".to_string(),
23804        park_deadline: None,
23805        drain_linger: DEFAULT_DRAIN_LINGER,
23806      };
23807      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
23808
23809      let holder_count = 2usize; // saturates max_blocking_tasks
23810      let driver_count = 2usize;
23811
23812      // Holders signal once they hold a counted slot, then wait to be released.
23813      let (held_tx, held_rx) = mpsc::channel();
23814      let release = Arc::new(Barrier::new(holder_count + 1));
23815
23816      let mut holder_handles = Vec::new();
23817      for _ in 0..holder_count {
23818        let held_tx = held_tx.clone();
23819        let release = Arc::clone(&release);
23820        let handle = executor.schedule_blocking(move || {
23821          held_tx.send(()).unwrap();
23822          release.wait();
23823          0usize
23824        });
23825        holder_handles.push(handle);
23826      }
23827      // Wait until both holders hold their slots (cap fully saturated).
23828      for _ in 0..holder_count {
23829        held_rx.recv().unwrap();
23830      }
23831
23832      // Driver tasks: plain runnables (NOT blocking-slot owners) that block_on
23833      // an inner spawn_blocking.
23834      let mut driver_tasks = Vec::new();
23835      for _ in 0..driver_count {
23836        let exec = Arc::clone(&executor);
23837        let body = async move {
23838          let inner_exec = Arc::clone(&exec);
23839          let mut inner = std::pin::pin!(async move {
23840            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
23841            assert_eq!(v, 5);
23842          });
23843          exec.block_on(inner.as_mut());
23844        };
23845        let scheduler = Arc::clone(&executor);
23846        let (runnable, task) = async_task::spawn(body, move |r| scheduler.schedule(r));
23847        executor.schedule(runnable);
23848        driver_tasks.push(task);
23849      }
23850
23851      // Let the driver tasks reach block_on. They must park until a holder
23852      // frees a slot instead of running their inner jobs over the cap.
23853      std::thread::sleep(Duration::from_millis(300));
23854
23855      // Release the holders so the inner jobs can run within the cap.
23856      release.wait();
23857
23858      for task in driver_tasks {
23859        futures::executor::block_on(task);
23860      }
23861      for handle in holder_handles {
23862        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
23863      }
23864
23865      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
23866      let _ = done_tx.send(());
23867    });
23868
23869    match done_rx.recv_timeout(Duration::from_secs(10)) {
23870      Ok(()) => runner.join().unwrap(),
23871      Err(error) => panic!("non-owner block_on blocking-cap test deadlocked ({error})"),
23872    }
23873    let peak = peak_out.lock().unwrap().expect("peak captured");
23874    assert!(
23875      peak <= 2,
23876      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
23877    );
23878  }
23879
23880  #[cfg(not(target_family = "wasm"))]
23881  #[test]
23882  fn multi_thread_block_on_runnable_driven_by_owner_respects_cap() {
23883    // Blocking-owner isolation invariant: a counted blocking OWNER that
23884    // re-enters cooperative `block_on` can drive a plain queued runnable via
23885    // `run_one`. That runnable must NOT inherit the owner's over-cap blocking
23886    // privilege. If it re-enters `block_on` awaiting a `spawn_blocking` while
23887    // the cap is saturated, the inner job must wait for a slot instead of
23888    // borrowing the owner's token.
23889    //
23890    // Setup (worker_threads=2, max_blocking=2): owner O (a counted blocking closure)
23891    // holds slot 1; holder H2 holds slot 2 -> cap saturated. O spawns a plain task R
23892    // and drives it via cooperative `block_on`. With only 2 workers and 2 active
23893    // drainers, no replacement drainer can be spawned, so O itself runs R via
23894    // `run_one`. R re-enters `block_on` awaiting an inner `spawn_blocking` J.
23895    // R must not inherit O's slot flag: it parks until H2 frees a slot, so J
23896    // runs within the cap and the peak remains 2. Everything must still
23897    // complete.
23898    use std::sync::{Barrier, mpsc};
23899    use std::time::Duration;
23900
23901    let (done_tx, done_rx) = mpsc::channel();
23902    let peak_out = Arc::new(Mutex::new(None));
23903    let peak_slot = Arc::clone(&peak_out);
23904    let runner = std::thread::spawn(move || {
23905      let metrics = Arc::new(RuntimeMetrics::default());
23906      let options = RuntimeOptions {
23907        flavor: RuntimeFlavor::MultiThread,
23908        worker_threads: 2,
23909        max_blocking_tasks: 2,
23910        thread_name_prefix: "rd1-owner-runnable".to_string(),
23911        park_deadline: None,
23912        drain_linger: DEFAULT_DRAIN_LINGER,
23913      };
23914      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
23915
23916      let saturated = Arc::new(Barrier::new(2));
23917      let release = Arc::new(Barrier::new(2));
23918
23919      // Holder H2: holds the second counted slot until released. Not the driver.
23920      let h2_saturated = Arc::clone(&saturated);
23921      let h2_release = Arc::clone(&release);
23922      let h2 = executor.schedule_blocking(move || {
23923        h2_saturated.wait();
23924        h2_release.wait();
23925        0usize
23926      });
23927
23928      // Owner O: a counted blocking closure (slot 1). Once the cap is saturated it
23929      // spawns plain runnable R and drives it cooperatively via `block_on`.
23930      let o_exec = Arc::clone(&executor);
23931      let o_saturated = Arc::clone(&saturated);
23932      let o = executor.schedule_blocking(move || {
23933        o_saturated.wait(); // both slots held -> cap saturated
23934
23935        let r_exec = Arc::clone(&o_exec);
23936        let body = async move {
23937          let inner_exec = Arc::clone(&r_exec);
23938          let mut inner = std::pin::pin!(async move {
23939            let v = inner_exec.schedule_blocking(|| 5usize).await.unwrap();
23940            assert_eq!(v, 5);
23941          });
23942          r_exec.block_on(inner.as_mut());
23943        };
23944        let sched = Arc::clone(&o_exec);
23945        let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
23946        o_exec.schedule(runnable);
23947
23948        let mut wait_r = std::pin::pin!(async move {
23949          task.await;
23950        });
23951        o_exec.block_on(wait_r.as_mut());
23952        1usize
23953      });
23954
23955      // Give O time to drive R into its inner `block_on`; R must be parked
23956      // waiting for a slot.
23957      std::thread::sleep(Duration::from_millis(300));
23958      // Release H2 so the inner job can run within the cap.
23959      release.wait();
23960
23961      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
23962      assert_eq!(futures::executor::block_on(h2).unwrap(), 0usize);
23963
23964      *peak_slot.lock().unwrap() = Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
23965      let _ = done_tx.send(());
23966    });
23967
23968    match done_rx.recv_timeout(Duration::from_secs(10)) {
23969      Ok(()) => runner.join().unwrap(),
23970      Err(error) => panic!("owner-driven runnable blocking-cap test deadlocked ({error})"),
23971    }
23972    let peak = peak_out.lock().unwrap().expect("peak captured");
23973    assert!(
23974      peak <= 2,
23975      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
23976    );
23977  }
23978
23979  #[cfg(not(target_family = "wasm"))]
23980  #[test]
23981  fn multi_thread_over_cap_escape_runs_only_owner_job_not_unrelated() {
23982    // The over-cap fallback must select the exact pending JoinHandle job, not
23983    // the front of the shared blocking queue.
23984    //
23985    // Setup (worker_threads=2, max_blocking=2): owner O is a counted blocking
23986    // closure (slot 1); holder H holds slot 2 -> cap saturated. An unrelated job
23987    // U is queued ahead of O's descendant D. O re-enters `block_on` awaiting D.
23988    // The exact dependency must skip U and run D. Because U can only run once
23989    // a real slot frees after O completes, D must STRICTLY precede U.
23990    use std::sync::{Barrier, mpsc};
23991
23992    let (done_tx, done_rx) = mpsc::channel();
23993    let order_out = Arc::new(Mutex::new(Vec::<&'static str>::new()));
23994    let order_read = Arc::clone(&order_out);
23995    let runner = std::thread::spawn(move || {
23996      let metrics = Arc::new(RuntimeMetrics::default());
23997      let options = RuntimeOptions {
23998        flavor: RuntimeFlavor::MultiThread,
23999        worker_threads: 2,
24000        max_blocking_tasks: 2,
24001        thread_name_prefix: "rd1-unrelated".to_string(),
24002        park_deadline: None,
24003        drain_linger: DEFAULT_DRAIN_LINGER,
24004      };
24005      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
24006
24007      // O and H signal once they each hold a counted slot (cap saturated), then
24008      // wait so U is queued before O nests. H stays held until the very end.
24009      let (held_tx, held_rx) = mpsc::channel();
24010      let u_ready = Arc::new(Barrier::new(2)); // O <-> main: U is queued
24011      let h_release = Arc::new(Barrier::new(2)); // H <-> main: end of test
24012
24013      // Holder H: holds slot 2 for the whole test.
24014      let h_held = held_tx.clone();
24015      let h_rel = Arc::clone(&h_release);
24016      let h = executor.schedule_blocking(move || {
24017        h_held.send(()).unwrap();
24018        h_rel.wait();
24019        0usize
24020      });
24021
24022      // Owner O: holds slot 1, then (once U is queued) nests `block_on` awaiting
24023      // its OWN descendant D.
24024      let o_exec = Arc::clone(&executor);
24025      let o_ready = Arc::clone(&u_ready);
24026      let o_order = Arc::clone(&order_out);
24027      let o = executor.schedule_blocking(move || {
24028        held_tx.send(()).unwrap();
24029        o_ready.wait(); // wait until U is queued ahead of D
24030        let inner_exec = Arc::clone(&o_exec);
24031        let d_order = Arc::clone(&o_order);
24032        let mut inner = std::pin::pin!(async move {
24033          let v = inner_exec
24034            .schedule_blocking(move || {
24035              d_order.lock().unwrap().push("D");
24036              5usize
24037            })
24038            .await
24039            .unwrap();
24040          assert_eq!(v, 5);
24041        });
24042        o_exec.block_on(inner.as_mut());
24043        1usize
24044      });
24045
24046      // Wait until BOTH O and H hold their slots -> cap saturated.
24047      held_rx.recv().unwrap();
24048      held_rx.recv().unwrap();
24049
24050      // Queue U ahead of D; it must not be mistaken for the awaited dependency.
24051      let u_order = Arc::clone(&order_out);
24052      let u = executor.schedule_blocking(move || {
24053        u_order.lock().unwrap().push("U");
24054        2usize
24055      });
24056
24057      // Release O to nest now that U is queued ahead of D.
24058      u_ready.wait();
24059
24060      // O makes progress by running D over cap; U only runs after O frees a slot.
24061      assert_eq!(futures::executor::block_on(o).unwrap(), 1usize);
24062      assert_eq!(futures::executor::block_on(u).unwrap(), 2usize);
24063
24064      // Release H and finish.
24065      h_release.wait();
24066      assert_eq!(futures::executor::block_on(h).unwrap(), 0usize);
24067
24068      let _ = done_tx.send(());
24069    });
24070
24071    match done_rx.recv_timeout(std::time::Duration::from_secs(10)) {
24072      Ok(()) => runner.join().unwrap(),
24073      Err(error) => panic!("owner-scoped over-cap escape test deadlocked ({error})"),
24074    }
24075    let order = order_read.lock().unwrap().clone();
24076    assert_eq!(
24077      order,
24078      vec!["D", "U"],
24079      "owner must run its OWN descendant D over cap; unrelated U must wait for a real slot"
24080    );
24081  }
24082
24083  #[cfg(not(target_family = "wasm"))]
24084  #[test]
24085  fn multi_thread_over_cap_escape_is_dependency_and_executor_scoped() {
24086    use std::sync::{Barrier, mpsc};
24087
24088    let opts = RuntimeOptions {
24089      flavor: RuntimeFlavor::MultiThread,
24090      worker_threads: 2,
24091      max_blocking_tasks: 2,
24092      thread_name_prefix: "rd1-scope".to_string(),
24093      park_deadline: None,
24094      drain_linger: DEFAULT_DRAIN_LINGER,
24095    };
24096    let exec1 =
24097      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24098    let exec2 =
24099      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24100    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24101
24102    let holder_count = opts.max_blocking_tasks;
24103    let (held_tx, held_rx) = mpsc::channel();
24104    let release = Arc::new(Barrier::new(holder_count + 1));
24105    let mut holder_handles = Vec::new();
24106    for _ in 0..holder_count {
24107      let held_tx = held_tx.clone();
24108      let release = Arc::clone(&release);
24109      holder_handles.push(exec2.schedule_blocking(move || {
24110        held_tx.send(()).unwrap();
24111        release.wait();
24112        0usize
24113      }));
24114    }
24115    for _ in 0..holder_count {
24116      held_rx.recv().unwrap();
24117    }
24118    assert_eq!(
24119      exec2.active_blocking.load(Ordering::Acquire),
24120      opts.max_blocking_tasks
24121    );
24122
24123    let exact = BlockingJobId(u64::MAX);
24124    let ran = Arc::new(AtomicBool::new(false));
24125    let ran_job = Arc::clone(&ran);
24126    exec2.blocking_queue.lock().unwrap().push(QueuedBlocking {
24127      id: exact,
24128      run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24129    });
24130    let dependency = TaskDependency::default();
24131
24132    let token1 = exec1.fresh_owner_token();
24133    {
24134      let _owner = BlockingOwnerGuard::enter(Some(token1));
24135      dependency.set_owned(BlockingDependency {
24136        job: exact,
24137        owner: Some(token1),
24138      });
24139      assert!(
24140        !exec2.try_owned_blocking_over_cap(&dependency),
24141        "a foreign-executor ambient token must fail the executor gate"
24142      );
24143    }
24144
24145    let token2 = exec2.fresh_owner_token();
24146    {
24147      let _active_owner = ActiveBlockingOwnerGuard::enter(&exec2.active_blocking_owners, token2);
24148      let _owner = BlockingOwnerGuard::enter(Some(token2));
24149      dependency.set_owned(BlockingDependency {
24150        job: BlockingJobId(u64::MAX - 1),
24151        owner: Some(token2),
24152      });
24153      assert!(
24154        !exec2.try_owned_blocking_over_cap(&dependency),
24155        "a foreign or unrelated dependency id must fail the exact-job gate"
24156      );
24157      dependency.set_owned(BlockingDependency {
24158        job: BlockingJobId(exact.0 - 2),
24159        owner: Some(token2),
24160      });
24161      assert!(
24162        !exec2.try_owned_blocking_over_cap(&dependency),
24163        "a different job id must not consume the queued dependency"
24164      );
24165      dependency.set_owned(BlockingDependency {
24166        job: exact,
24167        owner: Some(token2),
24168      });
24169      assert!(
24170        exec2.try_owned_blocking_over_cap(&dependency),
24171        "the exact awaited job must run over cap"
24172      );
24173    }
24174    assert!(
24175      ran.load(Ordering::SeqCst),
24176      "the exact dependency must have run"
24177    );
24178
24179    release.wait();
24180    for handle in holder_handles {
24181      assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
24182    }
24183  }
24184
24185  #[cfg(not(target_family = "wasm"))]
24186  #[test]
24187  fn ambiguous_active_owners_do_not_authorize_untagged_dependency() {
24188    let executor = multi_thread_executor(2, None, "untagged-owner-dependency");
24189    let first_owner = executor.fresh_owner_token();
24190    let second_owner = executor.fresh_owner_token();
24191    let _first_owner =
24192      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, first_owner);
24193    let _second_owner =
24194      ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, second_owner);
24195    let exact = BlockingJobId(u64::MAX - 30);
24196    let dependency = TaskDependency::default();
24197    dependency.set_owned(BlockingDependency {
24198      job: exact,
24199      owner: None,
24200    });
24201    let ran = Arc::new(AtomicBool::new(false));
24202    let ran_job = Arc::clone(&ran);
24203    executor
24204      .blocking_queue
24205      .lock()
24206      .unwrap()
24207      .push(QueuedBlocking {
24208        id: exact,
24209        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24210      });
24211
24212    assert!(
24213      !executor.try_owned_blocking_over_cap(&dependency),
24214      "an untagged driver must not choose between multiple active owner frames"
24215    );
24216    assert!(!ran.load(Ordering::SeqCst));
24217    let queued = executor.blocking_queue.lock().unwrap().remove(exact);
24218    assert!(
24219      queued.is_some(),
24220      "ambiguous ownership must leave the exact job queued"
24221    );
24222  }
24223
24224  #[cfg(not(target_family = "wasm"))]
24225  #[test]
24226  fn owner_lending_claims_live_dependency_not_stale_snapshot() {
24227    let executor = multi_thread_executor(2, None, "live-parked-dependency");
24228    let owner = executor.fresh_owner_token();
24229    let dependency = TaskDependency::new(u64::MAX, Some(owner));
24230    let stale = BlockingJobId(u64::MAX - 10);
24231    let live = BlockingJobId(u64::MAX - 9);
24232    dependency.set_owned(BlockingDependency {
24233      job: stale,
24234      owner: Some(owner),
24235    });
24236    dependency.begin_poll();
24237    dependency.set_owned(BlockingDependency {
24238      job: live,
24239      owner: Some(owner),
24240    });
24241
24242    let stale_ran = Arc::new(AtomicBool::new(false));
24243    let stale_job_ran = Arc::clone(&stale_ran);
24244    let live_ran = Arc::new(AtomicBool::new(false));
24245    let live_job_ran = Arc::clone(&live_ran);
24246    {
24247      let mut queue = executor
24248        .blocking_queue
24249        .lock()
24250        .unwrap_or_else(std::sync::PoisonError::into_inner);
24251      queue.push(QueuedBlocking {
24252        id: stale,
24253        run: Box::new(move || stale_job_ran.store(true, Ordering::SeqCst)),
24254      });
24255      queue.push(QueuedBlocking {
24256        id: live,
24257        run: Box::new(move || live_job_ran.store(true, Ordering::SeqCst)),
24258      });
24259    }
24260
24261    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24262    let _owner = BlockingOwnerGuard::enter(Some(owner));
24263    assert!(
24264      executor.try_owned_blocking_over_cap(&dependency),
24265      "the owner lane must claim the currently published dependency"
24266    );
24267    assert!(
24268      live_ran.load(Ordering::SeqCst),
24269      "the live dependency must run"
24270    );
24271    assert!(
24272      !stale_ran.load(Ordering::SeqCst),
24273      "the stale registered id must remain queued"
24274    );
24275    let remaining = executor
24276      .blocking_queue
24277      .lock()
24278      .unwrap_or_else(std::sync::PoisonError::into_inner)
24279      .jobs
24280      .keys()
24281      .copied()
24282      .collect::<Vec<_>>();
24283    assert_eq!(remaining, vec![stale]);
24284  }
24285
24286  #[cfg(not(target_family = "wasm"))]
24287  #[test]
24288  fn cancelled_child_publication_cannot_be_claimed_after_parent_publish() {
24289    let executor = multi_thread_executor(2, None, "cancelled-child-publication");
24290    let owner = executor.fresh_owner_token();
24291    let child = TaskDependency::new_task(u64::MAX);
24292    let parent = TaskDependency::new(u64::MAX, Some(owner));
24293    let exact = BlockingDependency {
24294      job: BlockingJobId(u64::MAX - 31),
24295      owner: Some(owner),
24296    };
24297
24298    child.set_owned(exact);
24299    let stale = child
24300      .get()
24301      .expect("the child must publish its direct dependency");
24302    child.begin_poll();
24303    parent.set_borrowed(&stale);
24304
24305    let ran = Arc::new(AtomicBool::new(false));
24306    let ran_job = Arc::clone(&ran);
24307    executor
24308      .blocking_queue
24309      .lock()
24310      .unwrap()
24311      .push(QueuedBlocking {
24312        id: exact.job,
24313        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24314      });
24315    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24316    let _owner = BlockingOwnerGuard::enter(Some(owner));
24317    assert!(
24318      !executor.try_owned_blocking_over_cap(&parent),
24319      "a publication copied before child cancellation must remain cancelled after parent install"
24320    );
24321    assert!(!ran.load(Ordering::SeqCst));
24322
24323    child.set_owned(exact);
24324    parent.set_borrowed(
24325      &child
24326        .get()
24327        .expect("the replacement publication must be live"),
24328    );
24329    assert!(
24330      executor.try_owned_blocking_over_cap(&parent),
24331      "a fresh child publication must still authorize its exact queued job"
24332    );
24333    assert!(ran.load(Ordering::SeqCst));
24334  }
24335
24336  #[cfg(not(target_family = "wasm"))]
24337  #[test]
24338  fn intermediate_repoll_does_not_cancel_child_source_claim() {
24339    let executor = multi_thread_executor(2, None, "intermediate-repoll-source-claim");
24340    let owner = executor.fresh_owner_token();
24341    let child = TaskDependency::new_task(u64::MAX);
24342    let intermediate = TaskDependency::new_task(u64::MAX);
24343    let parent = TaskDependency::new(u64::MAX, Some(owner));
24344    let exact = BlockingDependency {
24345      job: BlockingJobId(u64::MAX - 43),
24346      owner: Some(owner),
24347    };
24348
24349    child.set_owned(exact);
24350    intermediate.set_borrowed(&child.get().expect("the child source must be live"));
24351    parent.set_borrowed(
24352      &intermediate
24353        .get()
24354        .expect("the intermediate publication must be live"),
24355    );
24356
24357    intermediate.begin_poll();
24358    assert!(
24359      child.get().is_some(),
24360      "withdrawing an intermediate hop must not cancel the child's source claim"
24361    );
24362    assert!(
24363      parent.get().is_none(),
24364      "the ancestor's old chain must become unclaimable when its intermediate hop is withdrawn"
24365    );
24366
24367    intermediate.set_borrowed(&child.get().expect("the child source must remain reusable"));
24368    parent.set_borrowed(
24369      &intermediate
24370        .get()
24371        .expect("the intermediate must republish the still-live source"),
24372    );
24373    let ran = Arc::new(AtomicBool::new(false));
24374    let ran_job = Arc::clone(&ran);
24375    executor
24376      .blocking_queue
24377      .lock()
24378      .unwrap()
24379      .push(QueuedBlocking {
24380        id: exact.job,
24381        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24382      });
24383
24384    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24385    let _owner = BlockingOwnerGuard::enter(Some(owner));
24386    assert!(
24387      executor.try_owned_blocking_over_cap(&parent),
24388      "a fresh chain through the repolled intermediate must retain the exact source claim"
24389    );
24390    assert!(ran.load(Ordering::SeqCst));
24391  }
24392
24393  #[cfg(not(target_family = "wasm"))]
24394  #[test]
24395  fn exact_claim_linearizes_before_concurrent_source_withdrawal() {
24396    use std::sync::mpsc;
24397
24398    let executor = multi_thread_executor(2, None, "claim-cancel-linearization");
24399    let owner = executor.fresh_owner_token();
24400    let _active_owner = ActiveBlockingOwnerGuard::enter(&executor.active_blocking_owners, owner);
24401    let child = Arc::new(TaskDependency::new_task(u64::MAX));
24402    let parent = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
24403    let exact = BlockingDependency {
24404      job: BlockingJobId(u64::MAX - 44),
24405      owner: Some(owner),
24406    };
24407    child.set_owned(exact);
24408    parent.set_borrowed(&child.get().expect("the child source must be live"));
24409
24410    let ran = Arc::new(AtomicBool::new(false));
24411    let ran_job = Arc::clone(&ran);
24412    executor
24413      .blocking_queue
24414      .lock()
24415      .unwrap()
24416      .push(QueuedBlocking {
24417        id: exact.job,
24418        run: Box::new(move || ran_job.store(true, Ordering::SeqCst)),
24419      });
24420
24421    let (claim_entered_tx, claim_entered_rx) = mpsc::channel();
24422    let (release_claim_tx, release_claim_rx) = mpsc::channel();
24423    let claimant_executor = Arc::clone(&executor);
24424    let claimant_parent = Arc::clone(&parent);
24425    let claimant = std::thread::spawn(move || {
24426      DEPENDENCY_CLAIM_TEST_HOOK.with(|slot| {
24427        *slot.borrow_mut() = Some(Box::new(move || {
24428          claim_entered_tx.send(()).unwrap();
24429          release_claim_rx.recv().unwrap();
24430        }));
24431      });
24432      let _owner = BlockingOwnerGuard::enter(Some(owner));
24433      claimant_executor.try_owned_blocking_over_cap(&claimant_parent)
24434    });
24435    claim_entered_rx
24436      .recv_timeout(Duration::from_secs(2))
24437      .expect("the exact claimant must hold the shared transition lock");
24438
24439    let (withdrawn_tx, withdrawn_rx) = mpsc::channel();
24440    let withdrawing_child = Arc::clone(&child);
24441    let withdrawal = std::thread::spawn(move || {
24442      withdrawing_child.begin_poll();
24443      withdrawn_tx.send(()).unwrap();
24444    });
24445    let withdrew_before_claim = withdrawn_rx
24446      .recv_timeout(Duration::from_millis(200))
24447      .is_ok();
24448    release_claim_tx.send(()).unwrap();
24449
24450    assert!(
24451      claimant.join().unwrap(),
24452      "the claim that linearized first must execute the exact job"
24453    );
24454    withdrawal.join().unwrap();
24455    assert!(
24456      !withdrew_before_claim,
24457      "source withdrawal must not interleave after claim validation but before claim commit"
24458    );
24459    assert!(ran.load(Ordering::SeqCst));
24460  }
24461
24462  #[cfg(not(target_family = "wasm"))]
24463  #[test]
24464  fn multi_thread_block_on_cooperative_branch_is_executor_scoped() {
24465    // Pool-worker-marker scoping invariant: `block_on` may take the cooperative
24466    // (queue-driving) branch ONLY for a re-entrant call OF THE SAME
24467    // executor whose worker set the marker. A worker of executor A that re-enters
24468    // `block_on` on executor B must take the PARKING branch instead -- it owns
24469    // none of B's accounting, so driving B's queue would make it an unaccounted
24470    // within-cap driver. This mirrors the `BlockingOwnerToken.executor_id` gate.
24471    //
24472    // Fully deterministic, no sleeps/threads: we set the ambient pool-worker
24473    // marker to exec1's id by entering the REAL `OnPoolWorkerGuard`, then call
24474    // `block_on` on each executor with the SAME probe shape and observe whether
24475    // the cooperative `run_one` driver actually ran a queued runnable.
24476    //
24477    // The probe future returns `Pending` once (self-waking so the parking branch
24478    // also makes progress) then `Ready`, guaranteeing the cooperative loop reaches
24479    // `run_one` at least once before the future completes. A single runnable is
24480    // pushed DIRECTLY onto the target executor's queue (bypassing `schedule`, so
24481    // no background drainer is ever spawned to steal it). After `block_on`
24482    // returns: the cooperative branch will have driven that runnable
24483    // (`runnable_polls == 1`, queue empty); the parking branch leaves it untouched
24484    // (`runnable_polls == 0`, still queued).
24485    struct PendThenReady {
24486      polls: usize,
24487    }
24488    impl Future for PendThenReady {
24489      type Output = ();
24490      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24491        self.polls += 1;
24492        if self.polls >= 2 {
24493          Poll::Ready(())
24494        } else {
24495          // Self-wake so the parking branch (`futures::executor::block_on`)
24496          // re-polls to completion without any external waker; the cooperative
24497          // branch re-polls unconditionally each loop iteration regardless.
24498          cx.waker().wake_by_ref();
24499          Poll::Pending
24500        }
24501      }
24502    }
24503
24504    // Push one already-resolvable runnable directly onto `target`'s queue (no
24505    // `schedule`, so no drainer is spawned) and detach its task.
24506    fn enqueue_runnable(target: &Arc<MultiThreadExecutor>) {
24507      let scheduler = Arc::clone(target);
24508      let (runnable, task) = async_task::spawn(async {}, move |r| scheduler.schedule(r));
24509      target.push_fifo_raw(runnable);
24510      task.detach();
24511    }
24512
24513    let opts = RuntimeOptions {
24514      flavor: RuntimeFlavor::MultiThread,
24515      worker_threads: 2,
24516      max_blocking_tasks: 2,
24517      thread_name_prefix: "rd-onpool-scope".to_string(),
24518      park_deadline: None,
24519      drain_linger: DEFAULT_DRAIN_LINGER,
24520    };
24521    let exec1 =
24522      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24523    let exec2 =
24524      Arc::new(MultiThreadExecutor::new(&opts, Arc::new(RuntimeMetrics::default())).unwrap());
24525    assert_ne!(exec1.id, exec2.id, "executors must get distinct ids");
24526
24527    // Simulate being on exec1's pool worker for the whole scenario.
24528    let _on_pool = OnPoolWorkerGuard::enter(exec1.id);
24529
24530    // FOREIGN call: on exec1's worker, drive exec2.block_on. The marker
24531    // (Some(exec1.id)) does NOT equal exec2.id, so exec2 must PARK and never
24532    // touch its queue.
24533    enqueue_runnable(&exec2);
24534    {
24535      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
24536      exec2.block_on(probe.as_mut());
24537    }
24538    assert_eq!(
24539      exec2.metrics.runnable_polls.load(Ordering::Relaxed),
24540      0,
24541      "a foreign-executor on-pool marker must NOT drive exec2's queue (must park)"
24542    );
24543    assert_eq!(
24544      exec2.injector.len(),
24545      1,
24546      "the foreign call must leave exec2's runnable untouched on its queue"
24547    );
24548
24549    // CONTROL call: on exec1's worker, drive exec1.block_on. The marker equals
24550    // exec1.id, so exec1 takes the COOPERATIVE branch and drives its queued
24551    // runnable via `run_one`.
24552    enqueue_runnable(&exec1);
24553    {
24554      let mut probe = std::pin::pin!(PendThenReady { polls: 0 });
24555      exec1.block_on(probe.as_mut());
24556    }
24557    assert_eq!(
24558      exec1.metrics.runnable_polls.load(Ordering::Relaxed),
24559      1,
24560      "a same-executor on-pool marker MUST drive exec1's queue cooperatively"
24561    );
24562    assert!(
24563      exec1.injector.is_empty(),
24564      "the cooperative branch must have drained exec1's queued runnable"
24565    );
24566
24567    // Tidy: drop the untouched exec2 runnable so its task does not leak a waker
24568    // (steal-drain replaces the former `queue.lock().clear()`).
24569    while exec2.steal_one().is_some() {}
24570  }
24571
24572  #[cfg(not(target_family = "wasm"))]
24573  #[test]
24574  fn multi_thread_rd2_long_lived_blocking_consumer_does_not_starve_async_producers() {
24575    // A long-lived blocking consumer may consume the entire configured
24576    // blocking allowance, but it must not consume the reserve lane required
24577    // by the async producers that feed it.
24578    use std::sync::mpsc;
24579    use std::time::Duration;
24580
24581    const PRODUCER_COUNT: usize = 8;
24582
24583    fn schedule_pool_workload(
24584      executor: &Arc<MultiThreadExecutor>,
24585      tx: &mpsc::Sender<Option<usize>>,
24586      count: usize,
24587    ) {
24588      for i in 0..count {
24589        let tx = tx.clone();
24590        let scheduler = Arc::clone(executor);
24591        let (runnable, task) =
24592          async_task::spawn(async move { tx.send(Some(i)).unwrap() }, move |r| {
24593            scheduler.schedule(r);
24594          });
24595        executor.schedule(runnable);
24596        task.detach();
24597      }
24598      let tx = tx.clone();
24599      let scheduler = Arc::clone(executor);
24600      let (runnable, task) = async_task::spawn(async move { tx.send(None).unwrap() }, move |r| {
24601        scheduler.schedule(r)
24602      });
24603      executor.schedule(runnable);
24604      task.detach();
24605    }
24606
24607    let expected_sum: usize = (0..PRODUCER_COUNT).sum();
24608    let (done_tx, done_rx) = mpsc::sync_channel(1);
24609    let runner = std::thread::spawn(move || {
24610      let options = RuntimeOptions {
24611        flavor: RuntimeFlavor::MultiThread,
24612        worker_threads: 1,
24613        max_blocking_tasks: 1,
24614        thread_name_prefix: "rd2-pre-fix".to_string(),
24615        park_deadline: None,
24616        drain_linger: DEFAULT_DRAIN_LINGER,
24617      }
24618      .validate()
24619      .expect("production MultiThread options must provide a reserve lane");
24620      let executor =
24621        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
24622
24623      let (tx, rx) = mpsc::channel::<Option<usize>>();
24624      let (started_tx, started_rx) = mpsc::sync_channel(1);
24625      let handle = executor.schedule_blocking(move || {
24626        started_tx.send(()).unwrap();
24627        let mut sum = 0usize;
24628        while let Ok(msg) = rx.recv() {
24629          match msg {
24630            Some(value) => sum += value,
24631            None => break,
24632          }
24633        }
24634        sum
24635      });
24636      started_rx.recv_timeout(Duration::from_secs(1)).unwrap();
24637      schedule_pool_workload(&executor, &tx, PRODUCER_COUNT);
24638      let sum = futures::executor::block_on(handle).unwrap();
24639      done_tx.send(sum).unwrap();
24640    });
24641
24642    match done_rx.recv_timeout(Duration::from_secs(10)) {
24643      Ok(sum) => {
24644        runner.join().unwrap();
24645        assert_eq!(sum, expected_sum);
24646      }
24647      Err(error) => {
24648        panic!("a saturated blocking lane starved the async producers that feed it ({error})")
24649      }
24650    }
24651  }
24652
24653  #[test]
24654  fn current_thread_blocking_work_completes_inline() {
24655    let metrics = Arc::new(RuntimeMetrics::default());
24656    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::new(
24657      CurrentThreadExecutor::new(Arc::clone(&metrics)),
24658    )));
24659    let registration = backend.work.try_register_work().unwrap();
24660
24661    let value =
24662      futures::executor::block_on(backend.spawn_registered_blocking(|| 7, registration, &metrics))
24663        .expect("blocking task should complete");
24664
24665    assert_eq!(value, 7);
24666    assert_eq!(metrics.blocking_tasks_started.load(Ordering::Relaxed), 1);
24667    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
24668  }
24669
24670  #[cfg(not(target_family = "wasm"))]
24671  #[test]
24672  fn current_thread_blocking_work_obeys_single_lane_across_callers() {
24673    use std::sync::mpsc;
24674
24675    let controller = Arc::new(current_thread_controller(
24676      "current-thread-blocking-admission",
24677    ));
24678    let backend = controller.backend();
24679    let (first_started_tx, first_started_rx) = mpsc::channel();
24680    let (release_first_tx, release_first_rx) = mpsc::channel();
24681    let first_controller = Arc::clone(&controller);
24682    let first = std::thread::spawn(move || {
24683      let handle = first_controller
24684        .try_spawn_blocking(move || {
24685          first_started_tx.send(()).unwrap();
24686          release_first_rx.recv().unwrap();
24687          1usize
24688        })
24689        .unwrap_or_else(|_| panic!("the first blocking call must be accepted"));
24690      futures::executor::block_on(handle).unwrap()
24691    });
24692    first_started_rx
24693      .recv_timeout(Duration::from_secs(1))
24694      .unwrap();
24695
24696    let (second_started_tx, second_started_rx) = mpsc::channel();
24697    let second_controller = Arc::clone(&controller);
24698    let second = std::thread::spawn(move || {
24699      let handle = second_controller
24700        .try_spawn_blocking(move || {
24701          second_started_tx.send(()).unwrap();
24702          2usize
24703        })
24704        .unwrap_or_else(|_| panic!("the second blocking call must be accepted"));
24705      futures::executor::block_on(handle).unwrap()
24706    });
24707
24708    wait_until("both CurrentThread blocking calls to be accepted", || {
24709      backend
24710        .work
24711        .state
24712        .lock()
24713        .unwrap_or_else(std::sync::PoisonError::into_inner)
24714        .active
24715        == 2
24716    });
24717    assert!(
24718      second_started_rx
24719        .recv_timeout(Duration::from_millis(100))
24720        .is_err(),
24721      "CurrentThread must not execute two blocking closures concurrently"
24722    );
24723
24724    release_first_tx.send(()).unwrap();
24725    second_started_rx
24726      .recv_timeout(Duration::from_secs(1))
24727      .expect("the waiting blocking call must acquire the released lane");
24728    assert_eq!(first.join().unwrap(), 1);
24729    assert_eq!(second.join().unwrap(), 2);
24730    assert_eq!(
24731      controller
24732        .metrics
24733        .blocking_tasks_started
24734        .load(Ordering::Relaxed),
24735      2
24736    );
24737    assert_eq!(
24738      controller
24739        .metrics
24740        .blocking_tasks_completed
24741        .load(Ordering::Relaxed),
24742      2
24743    );
24744    assert_eq!(
24745      controller
24746        .metrics
24747        .active_blocking_tasks
24748        .load(Ordering::Relaxed),
24749      0
24750    );
24751    assert_eq!(
24752      controller
24753        .metrics
24754        .max_active_blocking_tasks
24755        .load(Ordering::Relaxed),
24756      1,
24757      "the public active-lane metric must remain within CurrentThread's reported capacity"
24758    );
24759    controller.shutdown().unwrap();
24760  }
24761
24762  #[cfg(not(target_family = "wasm"))]
24763  #[test]
24764  fn current_thread_nested_blocking_reuses_the_active_lane() {
24765    use std::sync::mpsc;
24766
24767    let controller = Arc::new(current_thread_controller("current-thread-nested-blocking"));
24768    let (done_tx, done_rx) = mpsc::channel();
24769    let runner_controller = Arc::clone(&controller);
24770    let runner = std::thread::spawn(move || {
24771      let nested_controller = Arc::clone(&runner_controller);
24772      let outer = runner_controller
24773        .try_spawn_blocking(move || {
24774          let inner = nested_controller
24775            .try_spawn_blocking(|| 41usize)
24776            .unwrap_or_else(|_| panic!("the nested blocking call must be accepted"));
24777          futures::executor::block_on(inner).unwrap() + 1
24778        })
24779        .unwrap_or_else(|_| panic!("the outer blocking call must be accepted"));
24780      done_tx
24781        .send(futures::executor::block_on(outer).unwrap())
24782        .unwrap();
24783    });
24784
24785    assert_eq!(
24786      done_rx
24787        .recv_timeout(Duration::from_secs(2))
24788        .expect("a nested CurrentThread blocking call must not wait on its own lane"),
24789      42
24790    );
24791    runner.join().unwrap();
24792    assert_eq!(
24793      controller
24794        .metrics
24795        .blocking_tasks_started
24796        .load(Ordering::Relaxed),
24797      2
24798    );
24799    assert_eq!(
24800      controller
24801        .metrics
24802        .blocking_tasks_completed
24803        .load(Ordering::Relaxed),
24804      2
24805    );
24806    assert_eq!(
24807      controller
24808        .metrics
24809        .active_blocking_tasks
24810        .load(Ordering::Relaxed),
24811      0
24812    );
24813    assert_eq!(
24814      controller
24815        .metrics
24816        .max_active_blocking_tasks
24817        .load(Ordering::Relaxed),
24818      1,
24819      "nested work borrows the outer lane instead of reporting a second physical lane"
24820    );
24821    controller.shutdown().unwrap();
24822  }
24823
24824  #[cfg(not(target_family = "wasm"))]
24825  #[test]
24826  fn current_thread_hot_runnable_yields_to_exact_blocking_dependency() {
24827    use std::sync::mpsc;
24828
24829    struct HotFuture {
24830      stop: Arc<AtomicBool>,
24831    }
24832
24833    impl Future for HotFuture {
24834      type Output = ();
24835
24836      fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
24837        if self.stop.load(Ordering::SeqCst) {
24838          Poll::Ready(())
24839        } else {
24840          cx.waker().wake_by_ref();
24841          Poll::Pending
24842        }
24843      }
24844    }
24845
24846    let stop = Arc::new(AtomicBool::new(false));
24847    let (ready_tx, ready_rx) = mpsc::channel();
24848    let (exact_tx, exact_rx) = mpsc::channel();
24849    let (done_tx, done_rx) = mpsc::channel();
24850    let runner_stop = Arc::clone(&stop);
24851    let runner = std::thread::spawn(move || {
24852      let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
24853        RuntimeMetrics::default(),
24854      )));
24855      let owner = executor.fresh_owner_token();
24856      let job = BlockingJobId(u64::MAX - 70);
24857      let (result_tx, result_rx) = oneshot::channel();
24858      {
24859        let mut state = executor
24860          .blocking_admission
24861          .state
24862          .lock()
24863          .unwrap_or_else(std::sync::PoisonError::into_inner);
24864        state.owner = Some(owner);
24865        let exact_stop = Arc::clone(&runner_stop);
24866        state.queue.push(QueuedBlocking {
24867          id: job,
24868          run: Box::new(move || {
24869            exact_tx.send(!exact_stop.load(Ordering::SeqCst)).unwrap();
24870            let _ = result_tx.send(Ok(ContainedTaskOutput::new(41usize, u64::MAX)));
24871          }),
24872        });
24873      }
24874      let exact = JoinHandle(JoinHandleInner::Blocking {
24875        receiver: result_rx,
24876        dependency: BlockingDependency {
24877          job,
24878          owner: Some(owner),
24879        },
24880        published: PublishedBlockingDependencies::default(),
24881      });
24882      let _owner = executor.enter_blocking_owner(owner, false);
24883
24884      let scheduler = Arc::clone(&executor);
24885      let hot_stop = Arc::clone(&runner_stop);
24886      let (runnable, hot_task) = async_task::spawn(HotFuture { stop: hot_stop }, move |runnable| {
24887        scheduler.schedule(runnable);
24888      });
24889      executor.schedule(runnable);
24890      ready_tx.send(()).unwrap();
24891
24892      let mut value = None;
24893      {
24894        let mut future = std::pin::pin!(async {
24895          value = Some(exact.await.unwrap());
24896          hot_task.await;
24897        });
24898        assert_eq!(
24899          executor.block_on(future.as_mut()),
24900          BlockOnOutcome::Completed
24901        );
24902      }
24903      executor
24904        .blocking_admission
24905        .state
24906        .lock()
24907        .unwrap_or_else(std::sync::PoisonError::into_inner)
24908        .owner = None;
24909      done_tx.send(value.unwrap()).unwrap();
24910    });
24911
24912    ready_rx
24913      .recv_timeout(Duration::from_secs(2))
24914      .expect("the fairness driver must start");
24915    let exact_before_stop = exact_rx.recv_timeout(Duration::from_secs(2)).ok();
24916    stop.store(true, Ordering::SeqCst);
24917    assert_eq!(
24918      done_rx
24919        .recv_timeout(Duration::from_secs(2))
24920        .expect("the CurrentThread driver must retire"),
24921      41
24922    );
24923    runner.join().unwrap();
24924    let exact_before_stop = exact_before_stop.or_else(|| exact_rx.try_recv().ok());
24925    assert_eq!(
24926      exact_before_stop,
24927      Some(true),
24928      "a hot CurrentThread runnable must yield to the exact dependency before it is stopped"
24929    );
24930  }
24931
24932  #[cfg(not(target_family = "wasm"))]
24933  #[test]
24934  fn current_thread_cross_driver_dependency_reuses_the_owner_lane() {
24935    use std::sync::mpsc;
24936
24937    let controller = Arc::new(current_thread_controller(
24938      "current-thread-cross-driver-blocking",
24939    ));
24940    let backend = controller.backend();
24941    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
24942      panic!("configured CurrentThread backend must create a CurrentThread executor");
24943    };
24944    let executor = Arc::clone(executor);
24945    drop(backend);
24946
24947    let (release_driver_tx, release_driver_rx) = oneshot::channel();
24948    let (driver_started_tx, driver_started_rx) = mpsc::channel();
24949    let driver_controller = Arc::clone(&controller);
24950    let driver = std::thread::spawn(move || {
24951      driver_started_tx.send(std::thread::current().id()).unwrap();
24952      block_on_with_controller(&driver_controller, async {
24953        release_driver_rx
24954          .await
24955          .expect("driver release sender must remain alive");
24956      });
24957    });
24958    let driver_thread = driver_started_rx
24959      .recv_timeout(Duration::from_secs(1))
24960      .unwrap();
24961    wait_until("the secondary CurrentThread driver to park", || {
24962      executor
24963        .stop
24964        .parkers
24965        .lock()
24966        .unwrap_or_else(std::sync::PoisonError::into_inner)
24967        .iter()
24968        .filter_map(Weak::upgrade)
24969        .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
24970    });
24971
24972    let (child_polled_tx, child_polled_rx) = mpsc::channel();
24973    let (release_child_tx, release_child_rx) = mpsc::channel();
24974    let (owner_awaiting_tx, owner_awaiting_rx) = mpsc::channel();
24975    let (blocking_ran_tx, blocking_ran_rx) = mpsc::channel();
24976    let (owner_done_tx, owner_done_rx) = mpsc::channel();
24977    let owner_controller = Arc::clone(&controller);
24978    let owner = std::thread::spawn(move || {
24979      let owner_thread = std::thread::current().id();
24980      let await_controller = Arc::clone(&owner_controller);
24981      let child_controller = Arc::clone(&owner_controller);
24982      let outer = owner_controller
24983        .try_spawn_blocking(move || {
24984          let spawn_controller = Arc::clone(&child_controller);
24985          let child = spawn_controller
24986            .try_spawn(async move {
24987              child_polled_tx.send(std::thread::current().id()).unwrap();
24988              release_child_rx.recv().unwrap();
24989              child_controller
24990                .try_spawn_blocking(move || {
24991                  blocking_ran_tx.send(std::thread::current().id()).unwrap();
24992                  41usize
24993                })
24994                .unwrap_or_else(|_| panic!("runtime must accept the descendant blocking work"))
24995                .await
24996                .unwrap()
24997                + 1
24998            })
24999            .unwrap_or_else(|_| panic!("runtime must accept the cross-driver child"));
25000
25001          assert_eq!(
25002            child_polled_rx
25003              .recv_timeout(Duration::from_secs(1))
25004              .unwrap(),
25005            driver_thread,
25006            "the secondary driver must own the child poll that reaches blocking admission"
25007          );
25008          owner_awaiting_tx.send(owner_thread).unwrap();
25009          block_on_with_controller(&await_controller, child).unwrap()
25010        })
25011        .unwrap_or_else(|_| panic!("runtime must accept the blocking owner"));
25012      owner_done_tx
25013        .send(futures::executor::block_on(outer))
25014        .unwrap();
25015    });
25016
25017    let owner_thread = owner_awaiting_rx
25018      .recv_timeout(Duration::from_secs(1))
25019      .unwrap();
25020    wait_until(
25021      "the blocking owner to park while awaiting the cross-driver child",
25022      || {
25023        executor
25024          .stop
25025          .parkers
25026          .lock()
25027          .unwrap_or_else(std::sync::PoisonError::into_inner)
25028          .iter()
25029          .filter_map(Weak::upgrade)
25030          .any(|parker| parker.state.load(Ordering::SeqCst) == PARKER_SLEEPING)
25031      },
25032    );
25033    release_child_tx.send(()).unwrap();
25034
25035    assert_eq!(
25036      blocking_ran_rx
25037        .recv_timeout(Duration::from_secs(2))
25038        .unwrap(),
25039      owner_thread,
25040      "the exact descendant must be serviced by the lane-owning driver"
25041    );
25042    assert_eq!(
25043      owner_done_rx
25044        .recv_timeout(Duration::from_secs(2))
25045        .expect("cross-driver blocking dependency must not deadlock")
25046        .unwrap(),
25047      42
25048    );
25049    release_driver_tx.send(()).unwrap();
25050    owner.join().unwrap();
25051    driver.join().unwrap();
25052    controller.shutdown().unwrap();
25053    assert_eq!(
25054      controller
25055        .metrics
25056        .blocking_tasks_started
25057        .load(Ordering::Relaxed),
25058      2
25059    );
25060    assert_eq!(
25061      controller
25062        .metrics
25063        .blocking_tasks_completed
25064        .load(Ordering::Relaxed),
25065      2
25066    );
25067    assert_eq!(
25068      controller
25069        .metrics
25070        .active_blocking_tasks
25071        .load(Ordering::Relaxed),
25072      0
25073    );
25074    assert_eq!(
25075      controller
25076        .metrics
25077        .max_active_blocking_tasks
25078        .load(Ordering::Relaxed),
25079      1,
25080      "cross-driver dependency service must reuse the owner's physical lane"
25081    );
25082  }
25083
25084  #[cfg(not(target_family = "wasm"))]
25085  #[test]
25086  fn current_thread_blocking_admission_rechecks_stop_under_lock() {
25087    use std::sync::mpsc;
25088
25089    let controller = Arc::new(current_thread_controller(
25090      "current-thread-blocking-stop-race",
25091    ));
25092    let backend = controller.backend();
25093    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25094      panic!("configured CurrentThread backend must create a CurrentThread executor");
25095    };
25096    let executor = Arc::clone(executor);
25097    drop(backend);
25098
25099    let ran = Arc::new(AtomicBool::new(false));
25100    let blocking_ran = Arc::clone(&ran);
25101    let (admission_locked_tx, admission_locked_rx) = mpsc::channel();
25102    let (release_admission_tx, release_admission_rx) = mpsc::channel();
25103    let (blocking_result_tx, blocking_result_rx) = mpsc::channel();
25104    let submit_controller = Arc::clone(&controller);
25105    let submitter = std::thread::spawn(move || {
25106      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25107        *slot.borrow_mut() = Some(Box::new(move || {
25108          admission_locked_tx.send(()).unwrap();
25109          release_admission_rx.recv().unwrap();
25110        }));
25111      });
25112      let handle = submit_controller
25113        .try_spawn_blocking(move || {
25114          blocking_ran.store(true, Ordering::SeqCst);
25115        })
25116        .unwrap_or_else(|_| {
25117          panic!("the submission linearized before shutdown and must be accepted")
25118        });
25119      blocking_result_tx
25120        .send(futures::executor::block_on(handle))
25121        .unwrap();
25122    });
25123    admission_locked_rx
25124      .recv_timeout(Duration::from_secs(2))
25125      .expect("the blocking submitter must pause while holding the admission lock");
25126
25127    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
25128    let shutdown_controller = Arc::clone(&controller);
25129    let shutdown = std::thread::spawn(move || {
25130      shutdown_result_tx
25131        .send(shutdown_controller.shutdown())
25132        .unwrap();
25133    });
25134    wait_until("CurrentThread shutdown to publish generation stop", || {
25135      executor.stop.is_stopping()
25136    });
25137    release_admission_tx.send(()).unwrap();
25138
25139    let blocking_error = blocking_result_rx
25140      .recv_timeout(Duration::from_secs(2))
25141      .expect("the admitted blocking handle must resolve")
25142      .expect_err("stop publication must reject the blocking lane claim");
25143    assert_eq!(
25144      blocking_error.to_string(),
25145      "the async runtime stopped before the blocking task could start"
25146    );
25147    assert!(
25148      !ran.load(Ordering::SeqCst),
25149      "blocking work must not start after stop publication"
25150    );
25151    shutdown_result_rx
25152      .recv_timeout(Duration::from_secs(2))
25153      .expect("shutdown must finish after the rejected admission retires")
25154      .unwrap();
25155    submitter.join().unwrap();
25156    shutdown.join().unwrap();
25157  }
25158
25159  #[cfg(not(target_family = "wasm"))]
25160  #[test]
25161  fn current_thread_fifo_blocking_claim_rechecks_stop_under_lock() {
25162    use std::sync::mpsc;
25163
25164    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25165      RuntimeMetrics::default(),
25166    )));
25167    let ran = Arc::new(AtomicBool::new(false));
25168    let job_ran = Arc::clone(&ran);
25169    {
25170      let mut state = executor
25171        .blocking_admission
25172        .state
25173        .lock()
25174        .unwrap_or_else(std::sync::PoisonError::into_inner);
25175      state.queue.push(QueuedBlocking {
25176        id: BlockingJobId(u64::MAX - 71),
25177        run: Box::new(move || {
25178          job_ran.store(true, Ordering::SeqCst);
25179        }),
25180      });
25181    }
25182
25183    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25184    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25185    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25186    let claim_executor = Arc::clone(&executor);
25187    let claimant = std::thread::spawn(move || {
25188      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25189        *slot.borrow_mut() = Some(Box::new(move || {
25190          claim_locked_tx.send(()).unwrap();
25191          release_claim_rx.recv().unwrap();
25192        }));
25193      });
25194      claim_result_tx
25195        .send(claim_executor.run_one_blocking())
25196        .unwrap();
25197    });
25198    claim_locked_rx
25199      .recv_timeout(Duration::from_secs(2))
25200      .expect("the FIFO claimant must pause while holding the admission lock");
25201
25202    executor.stop.begin_shutdown();
25203    release_claim_tx.send(()).unwrap();
25204    assert!(
25205      !claim_result_rx
25206        .recv_timeout(Duration::from_secs(2))
25207        .expect("the stopped FIFO claim must finish"),
25208      "stop publication must reject the queued FIFO claim"
25209    );
25210    claimant.join().unwrap();
25211    assert!(
25212      !ran.load(Ordering::SeqCst),
25213      "queued FIFO work must not start after stop publication"
25214    );
25215    assert!(
25216      !executor
25217        .blocking_admission
25218        .state
25219        .lock()
25220        .unwrap_or_else(std::sync::PoisonError::into_inner)
25221        .queue
25222        .is_empty(),
25223      "the rejected FIFO job must remain queued for shutdown cancellation"
25224    );
25225
25226    executor.close_blocking_admission();
25227    assert!(
25228      executor
25229        .blocking_admission
25230        .state
25231        .lock()
25232        .unwrap_or_else(std::sync::PoisonError::into_inner)
25233        .queue
25234        .is_empty()
25235    );
25236  }
25237
25238  #[cfg(not(target_family = "wasm"))]
25239  #[test]
25240  fn current_thread_fifo_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25241    use std::sync::mpsc;
25242
25243    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25244      RuntimeMetrics::default(),
25245    )));
25246    let ran = Arc::new(AtomicBool::new(false));
25247    let job_ran = Arc::clone(&ran);
25248    {
25249      let mut state = executor
25250        .blocking_admission
25251        .state
25252        .lock()
25253        .unwrap_or_else(std::sync::PoisonError::into_inner);
25254      state.queue.push(QueuedBlocking {
25255        id: BlockingJobId(u64::MAX - 73),
25256        run: Box::new(move || {
25257          job_ran.store(true, Ordering::SeqCst);
25258        }),
25259      });
25260    }
25261
25262    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25263    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25264    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25265    let claim_executor = Arc::clone(&executor);
25266    let claimant = std::thread::spawn(move || {
25267      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25268        *slot.borrow_mut() = Some(Box::new(move || {
25269          claim_locked_tx.send(()).unwrap();
25270          release_claim_rx.recv().unwrap();
25271        }));
25272      });
25273      claim_result_tx
25274        .send(claim_executor.run_one_blocking())
25275        .unwrap();
25276    });
25277    claim_locked_rx
25278      .recv_timeout(Duration::from_secs(2))
25279      .expect("the FIFO claimant must pause while holding the admission lock");
25280
25281    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25282    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25283    let cancelling_executor = Arc::clone(&executor);
25284    let cancellation = std::thread::spawn(move || {
25285      let cancelling = cancelling_executor
25286        .begin_failed_dispatch_cancellation()
25287        .expect("the test must publish terminal dispatch cancellation");
25288      cancellation_published_tx.send(()).unwrap();
25289      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25290      cancellation_done_tx.send(()).unwrap();
25291    });
25292    cancellation_published_rx
25293      .recv_timeout(Duration::from_secs(2))
25294      .expect("terminal dispatch cancellation must publish before queue cancellation");
25295    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25296    assert!(
25297      matches!(
25298        cancellation_done_rx.try_recv(),
25299        Err(mpsc::TryRecvError::Empty)
25300      ),
25301      "terminal cancellation must wait for the blocking-admission lock"
25302    );
25303
25304    release_claim_tx.send(()).unwrap();
25305    assert!(
25306      !claim_result_rx
25307        .recv_timeout(Duration::from_secs(2))
25308        .expect("the cancelled FIFO claim must finish"),
25309      "terminal cancellation publication must reject the queued FIFO claim"
25310    );
25311    cancellation_done_rx
25312      .recv_timeout(Duration::from_secs(2))
25313      .expect("terminal cancellation must drain the rejected FIFO job");
25314    claimant.join().unwrap();
25315    cancellation.join().unwrap();
25316
25317    assert!(
25318      !ran.load(Ordering::SeqCst),
25319      "terminally cancelled FIFO work must not start"
25320    );
25321    assert!(
25322      executor
25323        .blocking_admission
25324        .state
25325        .lock()
25326        .unwrap_or_else(std::sync::PoisonError::into_inner)
25327        .queue
25328        .is_empty(),
25329      "terminal cancellation must drain the rejected FIFO job"
25330    );
25331    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25332  }
25333
25334  #[cfg(not(target_family = "wasm"))]
25335  #[test]
25336  fn current_thread_owned_blocking_claim_rechecks_stop_under_lock() {
25337    use std::sync::mpsc;
25338
25339    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25340      RuntimeMetrics::default(),
25341    )));
25342    let owner = executor.fresh_owner_token();
25343    let job = BlockingJobId(u64::MAX - 72);
25344    let ran = Arc::new(AtomicBool::new(false));
25345    let job_ran = Arc::clone(&ran);
25346    {
25347      let mut state = executor
25348        .blocking_admission
25349        .state
25350        .lock()
25351        .unwrap_or_else(std::sync::PoisonError::into_inner);
25352      state.owner = Some(owner);
25353      state.queue.push(QueuedBlocking {
25354        id: job,
25355        run: Box::new(move || {
25356          job_ran.store(true, Ordering::SeqCst);
25357        }),
25358      });
25359    }
25360    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25361    dependency.set_owned(BlockingDependency {
25362      job,
25363      owner: Some(owner),
25364    });
25365
25366    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25367    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25368    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25369    let claim_executor = Arc::clone(&executor);
25370    let claim_dependency = Arc::clone(&dependency);
25371    let claimant = std::thread::spawn(move || {
25372      let _owner = claim_executor.enter_blocking_owner(owner, false);
25373      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25374        *slot.borrow_mut() = Some(Box::new(move || {
25375          claim_locked_tx.send(()).unwrap();
25376          release_claim_rx.recv().unwrap();
25377        }));
25378      });
25379      claim_result_tx
25380        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
25381        .unwrap();
25382    });
25383    claim_locked_rx
25384      .recv_timeout(Duration::from_secs(2))
25385      .expect("the owner claimant must pause while holding the admission lock");
25386
25387    executor.stop.begin_shutdown();
25388    release_claim_tx.send(()).unwrap();
25389    assert!(
25390      !claim_result_rx
25391        .recv_timeout(Duration::from_secs(2))
25392        .expect("the stopped owner claim must finish"),
25393      "stop publication must reject the exact-owner claim"
25394    );
25395    claimant.join().unwrap();
25396    assert!(
25397      !ran.load(Ordering::SeqCst),
25398      "queued exact-owner work must not start after stop publication"
25399    );
25400    assert!(
25401      !executor
25402        .blocking_admission
25403        .state
25404        .lock()
25405        .unwrap_or_else(std::sync::PoisonError::into_inner)
25406        .queue
25407        .is_empty(),
25408      "the rejected exact-owner job must remain queued for shutdown cancellation"
25409    );
25410
25411    executor
25412      .blocking_admission
25413      .state
25414      .lock()
25415      .unwrap_or_else(std::sync::PoisonError::into_inner)
25416      .owner = None;
25417    executor.close_blocking_admission();
25418    assert!(
25419      executor
25420        .blocking_admission
25421        .state
25422        .lock()
25423        .unwrap_or_else(std::sync::PoisonError::into_inner)
25424        .queue
25425        .is_empty()
25426    );
25427  }
25428
25429  #[cfg(not(target_family = "wasm"))]
25430  #[test]
25431  fn current_thread_owned_blocking_claim_rechecks_terminal_cancellation_under_lock() {
25432    use std::sync::mpsc;
25433
25434    let executor = Arc::new(CurrentThreadExecutor::new(Arc::new(
25435      RuntimeMetrics::default(),
25436    )));
25437    let owner = executor.fresh_owner_token();
25438    let job = BlockingJobId(u64::MAX - 74);
25439    let ran = Arc::new(AtomicBool::new(false));
25440    let job_ran = Arc::clone(&ran);
25441    {
25442      let mut state = executor
25443        .blocking_admission
25444        .state
25445        .lock()
25446        .unwrap_or_else(std::sync::PoisonError::into_inner);
25447      state.owner = Some(owner);
25448      state.queue.push(QueuedBlocking {
25449        id: job,
25450        run: Box::new(move || {
25451          job_ran.store(true, Ordering::SeqCst);
25452        }),
25453      });
25454    }
25455    let dependency = Arc::new(TaskDependency::new(u64::MAX, Some(owner)));
25456    dependency.set_owned(BlockingDependency {
25457      job,
25458      owner: Some(owner),
25459    });
25460
25461    let (claim_locked_tx, claim_locked_rx) = mpsc::channel();
25462    let (release_claim_tx, release_claim_rx) = mpsc::channel();
25463    let (claim_result_tx, claim_result_rx) = mpsc::channel();
25464    let claim_executor = Arc::clone(&executor);
25465    let claim_dependency = Arc::clone(&dependency);
25466    let claimant = std::thread::spawn(move || {
25467      let _owner = claim_executor.enter_blocking_owner(owner, false);
25468      CURRENT_THREAD_BLOCKING_CLAIM_TEST_HOOK.with(|slot| {
25469        *slot.borrow_mut() = Some(Box::new(move || {
25470          claim_locked_tx.send(()).unwrap();
25471          release_claim_rx.recv().unwrap();
25472        }));
25473      });
25474      claim_result_tx
25475        .send(claim_executor.try_run_owned_blocking(&claim_dependency))
25476        .unwrap();
25477    });
25478    claim_locked_rx
25479      .recv_timeout(Duration::from_secs(2))
25480      .expect("the owner claimant must pause while holding the admission lock");
25481
25482    let (cancellation_published_tx, cancellation_published_rx) = mpsc::channel();
25483    let (cancellation_done_tx, cancellation_done_rx) = mpsc::channel();
25484    let cancelling_executor = Arc::clone(&executor);
25485    let cancellation = std::thread::spawn(move || {
25486      let cancelling = cancelling_executor
25487        .begin_failed_dispatch_cancellation()
25488        .expect("the test must publish terminal dispatch cancellation");
25489      cancellation_published_tx.send(()).unwrap();
25490      cancelling_executor.cancel_work_after_host_dispatch_failure(cancelling);
25491      cancellation_done_tx.send(()).unwrap();
25492    });
25493    cancellation_published_rx
25494      .recv_timeout(Duration::from_secs(2))
25495      .expect("terminal dispatch cancellation must publish before queue cancellation");
25496    assert!(executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25497    assert!(
25498      matches!(
25499        cancellation_done_rx.try_recv(),
25500        Err(mpsc::TryRecvError::Empty)
25501      ),
25502      "terminal cancellation must wait for the blocking-admission lock"
25503    );
25504
25505    release_claim_tx.send(()).unwrap();
25506    assert!(
25507      !claim_result_rx
25508        .recv_timeout(Duration::from_secs(2))
25509        .expect("the cancelled owner claim must finish"),
25510      "terminal cancellation publication must reject the exact-owner claim"
25511    );
25512    cancellation_done_rx
25513      .recv_timeout(Duration::from_secs(2))
25514      .expect("terminal cancellation must drain the rejected exact-owner job");
25515    claimant.join().unwrap();
25516    cancellation.join().unwrap();
25517
25518    assert!(
25519      !ran.load(Ordering::SeqCst),
25520      "terminally cancelled exact-owner work must not start"
25521    );
25522    assert!(
25523      executor
25524        .blocking_admission
25525        .state
25526        .lock()
25527        .unwrap_or_else(std::sync::PoisonError::into_inner)
25528        .queue
25529        .is_empty(),
25530      "terminal cancellation must drain the rejected exact-owner job"
25531    );
25532    assert!(!executor.cancelling_failed_dispatch.load(Ordering::Acquire));
25533  }
25534
25535  #[cfg(not(target_family = "wasm"))]
25536  #[test]
25537  fn current_thread_shutdown_cancels_waiting_blocking_work() {
25538    use std::sync::mpsc;
25539
25540    struct DropSignal(mpsc::Sender<()>);
25541
25542    impl Drop for DropSignal {
25543      fn drop(&mut self) {
25544        let _ = self.0.send(());
25545      }
25546    }
25547
25548    let controller = Arc::new(current_thread_controller(
25549      "current-thread-blocking-shutdown",
25550    ));
25551    let backend = controller.backend();
25552    let (first_started_tx, first_started_rx) = mpsc::channel();
25553    let (release_first_tx, release_first_rx) = mpsc::channel();
25554    let first_controller = Arc::clone(&controller);
25555    let first = std::thread::spawn(move || {
25556      let handle = first_controller
25557        .try_spawn_blocking(move || {
25558          first_started_tx.send(()).unwrap();
25559          release_first_rx.recv().unwrap();
25560        })
25561        .unwrap_or_else(|_| panic!("the running blocking call must be accepted"));
25562      futures::executor::block_on(handle)
25563    });
25564    first_started_rx
25565      .recv_timeout(Duration::from_secs(1))
25566      .unwrap();
25567
25568    let waiting_ran = Arc::new(AtomicBool::new(false));
25569    let waiting_ran_task = Arc::clone(&waiting_ran);
25570    let (waiting_dropped_tx, waiting_dropped_rx) = mpsc::channel();
25571    let waiting_drop = DropSignal(waiting_dropped_tx);
25572    let (waiting_result_tx, waiting_result_rx) = mpsc::channel();
25573    let waiting_controller = Arc::clone(&controller);
25574    let waiting = std::thread::spawn(move || {
25575      let handle = waiting_controller
25576        .try_spawn_blocking(move || {
25577          let _waiting_drop = waiting_drop;
25578          waiting_ran_task.store(true, Ordering::SeqCst);
25579        })
25580        .unwrap_or_else(|_| panic!("the waiting blocking call must be accepted"));
25581      waiting_result_tx
25582        .send(futures::executor::block_on(handle))
25583        .unwrap();
25584    });
25585    wait_until("both CurrentThread blocking calls to be accepted", || {
25586      backend
25587        .work
25588        .state
25589        .lock()
25590        .unwrap_or_else(std::sync::PoisonError::into_inner)
25591        .active
25592        == 2
25593    });
25594
25595    let (shutdown_tx, shutdown_rx) = mpsc::channel();
25596    let shutdown_controller = Arc::clone(&controller);
25597    let shutdown = std::thread::spawn(move || {
25598      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
25599    });
25600    waiting_dropped_rx
25601      .recv_timeout(Duration::from_secs(1))
25602      .expect("shutdown must destroy blocking work waiting for the CurrentThread lane");
25603    assert!(
25604      !waiting_ran.load(Ordering::SeqCst),
25605      "waiting blocking work must not start"
25606    );
25607    assert!(
25608      waiting_result_rx
25609        .recv_timeout(Duration::from_secs(1))
25610        .unwrap()
25611        .is_err(),
25612      "the waiting blocking handle must resolve as cancelled"
25613    );
25614    assert!(
25615      shutdown_rx
25616        .recv_timeout(Duration::from_millis(100))
25617        .is_err(),
25618      "shutdown must still wait for the blocking closure that already owns the lane"
25619    );
25620
25621    release_first_tx.send(()).unwrap();
25622    first.join().unwrap().unwrap();
25623    waiting.join().unwrap();
25624    shutdown_rx
25625      .recv_timeout(Duration::from_secs(1))
25626      .unwrap()
25627      .unwrap();
25628    shutdown.join().unwrap();
25629    assert_eq!(
25630      controller
25631        .metrics
25632        .blocking_tasks_started
25633        .load(Ordering::Relaxed),
25634      1
25635    );
25636    assert_eq!(
25637      controller
25638        .metrics
25639        .blocking_tasks_completed
25640        .load(Ordering::Relaxed),
25641      1
25642    );
25643    assert_eq!(
25644      controller
25645        .metrics
25646        .active_blocking_tasks
25647        .load(Ordering::Relaxed),
25648      0
25649    );
25650  }
25651
25652  #[cfg(not(target_family = "wasm"))]
25653  #[test]
25654  fn current_thread_blocking_result_panic_payload_retains_generation_identity() {
25655    use std::{panic::panic_any, sync::mpsc};
25656
25657    struct ReentrantPayload {
25658      controller: Arc<RuntimeController>,
25659      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
25660    }
25661
25662    impl Drop for ReentrantPayload {
25663      fn drop(&mut self) {
25664        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
25665        let shutdown = self.controller.shutdown();
25666        self.result.send((generation, shutdown)).unwrap();
25667      }
25668    }
25669
25670    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
25671
25672    impl Drop for PanicPayloadOnDrop {
25673      fn drop(&mut self) {
25674        panic_any(self.0.take().expect("the result must panic only once"));
25675      }
25676    }
25677
25678    let controller = Arc::new(current_thread_controller(
25679      "current-thread-panic-payload-generation",
25680    ));
25681    let backend = controller.backend();
25682    let generation = backend.generation();
25683    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
25684      panic!("configured CurrentThread backend must create a CurrentThread executor");
25685    };
25686    let executor = Arc::clone(executor);
25687    drop(backend);
25688
25689    // Hold the physical lane long enough to force scheduler-owned delivery.
25690    // Dropping the receiver then makes the queued sender destroy the result.
25691    let owner = executor.fresh_owner_token();
25692    executor
25693      .blocking_admission
25694      .state
25695      .lock()
25696      .unwrap_or_else(std::sync::PoisonError::into_inner)
25697      .owner = Some(owner);
25698    let (result_tx, result_rx) = mpsc::channel();
25699    let payload_controller = Arc::clone(&controller);
25700    let handle = controller
25701      .try_spawn_blocking(move || {
25702        PanicPayloadOnDrop(Some(ReentrantPayload {
25703          controller: payload_controller,
25704          result: result_tx,
25705        }))
25706      })
25707      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
25708    drop(handle);
25709    executor
25710      .blocking_admission
25711      .state
25712      .lock()
25713      .unwrap_or_else(std::sync::PoisonError::into_inner)
25714      .owner = None;
25715    let dispatch = current_thread_dispatch(&executor);
25716    controller.drive_current_thread_dispatch(dispatch, false);
25717
25718    let (observed_generation, shutdown) = result_rx
25719      .recv_timeout(Duration::from_secs(1))
25720      .expect("the caught result panic payload must be destroyed in the scheduler turn");
25721    assert_eq!(observed_generation, Some(generation));
25722    let error = shutdown.expect_err("payload destruction is still work in the active generation");
25723    assert!(error.to_string().contains("work running on that runtime"));
25724    controller.shutdown().unwrap();
25725  }
25726
25727  #[test]
25728  fn current_thread_detached_blocking_result_drop_is_contained() {
25729    use std::sync::mpsc;
25730
25731    struct PanicOnDrop(mpsc::Sender<()>);
25732
25733    impl Drop for PanicOnDrop {
25734      fn drop(&mut self) {
25735        self.0.send(()).unwrap();
25736        panic!("intentional CurrentThread blocking result destructor panic");
25737      }
25738    }
25739
25740    let controller = current_thread_controller("current-thread-blocking-result-drop");
25741    let (dropped_tx, dropped_rx) = mpsc::channel();
25742    let handle = controller
25743      .try_spawn_blocking(move || PanicOnDrop(dropped_tx))
25744      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept blocking work"));
25745
25746    let result = catch_unwind(AssertUnwindSafe(|| drop(handle)));
25747    dropped_rx
25748      .recv_timeout(Duration::from_secs(1))
25749      .expect("dropping the detached handle must reclaim its blocking result");
25750    assert!(
25751      result.is_ok(),
25752      "the blocking result destructor panic must be contained"
25753    );
25754    controller.shutdown().unwrap();
25755  }
25756
25757  // ---- Runtime option validation and configuration immutability -------------
25758  // These build `RuntimeOptions`/`RuntimeController` LOCALLY and never touch the
25759  // global `RUNTIME` singleton, so they stay deterministic and order-independent.
25760
25761  #[test]
25762  fn validate_rejects_zero_worker_threads() {
25763    let error = RuntimeOptions {
25764      flavor: RuntimeFlavor::MultiThread,
25765      worker_threads: 0,
25766      max_blocking_tasks: 1,
25767      thread_name_prefix: "rd8".to_string(),
25768      park_deadline: None,
25769      drain_linger: DEFAULT_DRAIN_LINGER,
25770    }
25771    .validate()
25772    .expect_err("worker_threads == 0 must be rejected");
25773    assert_eq!(
25774      error.to_string(),
25775      "worker_threads must be greater than zero"
25776    );
25777  }
25778
25779  #[test]
25780  fn validate_rejects_zero_max_blocking_tasks() {
25781    let error = RuntimeOptions {
25782      flavor: RuntimeFlavor::MultiThread,
25783      worker_threads: 2,
25784      max_blocking_tasks: 0,
25785      thread_name_prefix: "rd8".to_string(),
25786      park_deadline: None,
25787      drain_linger: DEFAULT_DRAIN_LINGER,
25788    }
25789    .validate()
25790    .expect_err("max_blocking_tasks == 0 must be rejected");
25791    assert_eq!(
25792      error.to_string(),
25793      "max_blocking_tasks must be greater than zero"
25794    );
25795  }
25796
25797  #[test]
25798  fn validate_current_thread_coerces_worker_threads_to_one() {
25799    // CurrentThread forces `worker_threads` to 1; `max_blocking_tasks` is then
25800    // clamped to `.min(worker_threads)` == 1.
25801    let validated = RuntimeOptions {
25802      flavor: RuntimeFlavor::CurrentThread,
25803      worker_threads: 8,
25804      max_blocking_tasks: 8,
25805      thread_name_prefix: "rd8".to_string(),
25806      park_deadline: None,
25807      drain_linger: DEFAULT_DRAIN_LINGER,
25808    }
25809    .validate()
25810    .expect("CurrentThread options must validate");
25811    assert_eq!(validated.flavor, RuntimeFlavor::CurrentThread);
25812    assert_eq!(validated.worker_threads, 1);
25813    assert_eq!(validated.max_blocking_tasks, 1);
25814  }
25815
25816  // Native-only: `validate()` rejects `MultiThread` under `cfg(target_family = "wasm")`
25817  // (after applying the clamp), so this success assertion is valid off-wasm only.
25818  #[cfg(not(target_family = "wasm"))]
25819  #[test]
25820  fn validate_reserves_one_worker_from_blocking_admission() {
25821    let validated = RuntimeOptions {
25822      flavor: RuntimeFlavor::MultiThread,
25823      worker_threads: 2,
25824      max_blocking_tasks: 8,
25825      thread_name_prefix: "rd8".to_string(),
25826      park_deadline: None,
25827      drain_linger: DEFAULT_DRAIN_LINGER,
25828    }
25829    .validate()
25830    .expect("MultiThread options must validate");
25831    assert_eq!(validated.worker_threads, 2);
25832    assert_eq!(validated.max_blocking_tasks, 1);
25833  }
25834
25835  #[cfg(not(target_family = "wasm"))]
25836  #[test]
25837  fn validate_promotes_multi_thread_one_to_truthful_two_worker_minimum() {
25838    let validated = RuntimeOptions {
25839      flavor: RuntimeFlavor::MultiThread,
25840      worker_threads: 1,
25841      max_blocking_tasks: 8,
25842      thread_name_prefix: "rd8-minimum".to_string(),
25843      park_deadline: None,
25844      drain_linger: DEFAULT_DRAIN_LINGER,
25845    }
25846    .validate()
25847    .expect("MultiThread options must validate");
25848    assert_eq!(validated.worker_threads, 2);
25849    assert_eq!(validated.max_blocking_tasks, 1);
25850  }
25851
25852  #[cfg(not(target_family = "wasm"))]
25853  #[test]
25854  fn validate_applies_worker_caps_before_blocking_reserve_and_reporting() {
25855    let simulated_32_bit = RuntimeOptions {
25856      flavor: RuntimeFlavor::MultiThread,
25857      worker_threads: 256,
25858      max_blocking_tasks: 256,
25859      thread_name_prefix: "rd8-rayon-cap-32".to_string(),
25860      park_deadline: None,
25861      drain_linger: DEFAULT_DRAIN_LINGER,
25862    }
25863    .validate_with_rayon_max_threads(Some(255))
25864    .expect("a 32-bit-sized request must clamp to Rayon's 255-thread cap");
25865    assert_eq!(simulated_32_bit.worker_threads, 255);
25866    assert_eq!(simulated_32_bit.max_blocking_tasks, 254);
25867
25868    let rayon_max_threads = max_num_threads();
25869    let requested_threads = rayon_max_threads
25870      .max(MAX_ASYNC_RUNTIME_WORKER_THREADS)
25871      .checked_add(1)
25872      .expect("supported worker counts must leave room for an oversized request");
25873    let controller = RuntimeController::new();
25874    controller
25875      .configure(RuntimeOptions {
25876        flavor: RuntimeFlavor::MultiThread,
25877        worker_threads: requested_threads,
25878        max_blocking_tasks: requested_threads,
25879        thread_name_prefix: "rd8-rayon-cap".to_string(),
25880        park_deadline: None,
25881        drain_linger: DEFAULT_DRAIN_LINGER,
25882      })
25883      .expect("an oversized request must clamp to the production worker ceiling");
25884
25885    let reported = controller.options();
25886    let expected_workers = rayon_max_threads.min(MAX_ASYNC_RUNTIME_WORKER_THREADS);
25887    assert_eq!(reported.worker_threads, expected_workers);
25888    assert_eq!(
25889      reported.max_blocking_tasks,
25890      expected_workers - 1,
25891      "the blocking cap must reserve a lane from the physically realizable worker count"
25892    );
25893  }
25894
25895  #[cfg(not(target_family = "wasm"))]
25896  #[test]
25897  fn drain_linger_extreme_values_are_clamped_and_zero_disables() {
25898    // u64::MAX microseconds (the exact value an unvalidated
25899    // `NAPI_RUNTIME_DRAIN_LINGER_US` typo used to freeze into the process
25900    // via the removed OnceLock) must clamp to the ceiling everywhere a
25901    // budget enters the system: full options validation, a partial patch
25902    // through the controller, and the executor's defensive constructor
25903    // clamp for direct unvalidated test construction.
25904    let extreme = Duration::from_micros(u64::MAX);
25905    let validated = RuntimeOptions {
25906      flavor: RuntimeFlavor::MultiThread,
25907      worker_threads: 2,
25908      max_blocking_tasks: 1,
25909      thread_name_prefix: "linger-clamp".to_string(),
25910      park_deadline: None,
25911      drain_linger: extreme,
25912    }
25913    .validate_with_rayon_max_threads(Some(2))
25914    .expect("an extreme linger budget must clamp, not fail validation");
25915    assert_eq!(validated.drain_linger, MAX_DRAIN_LINGER);
25916
25917    let controller = RuntimeController::new();
25918    controller
25919      .configure_partial(RuntimeOptionsPatch {
25920        drain_linger: Some(extreme),
25921        ..RuntimeOptionsPatch::default()
25922      })
25923      .expect("an extreme linger patch must clamp, not fail validation");
25924    assert_eq!(
25925      controller.options().drain_linger,
25926      MAX_DRAIN_LINGER,
25927      "introspection must report the clamped budget, not the requested one"
25928    );
25929    controller
25930      .configure_partial(RuntimeOptionsPatch {
25931        drain_linger: Some(Duration::ZERO),
25932        ..RuntimeOptionsPatch::default()
25933      })
25934      .expect("ZERO (lingering disabled) must remain a valid budget");
25935    assert_eq!(controller.options().drain_linger, Duration::ZERO);
25936
25937    let metrics = Arc::new(RuntimeMetrics::default());
25938    let unvalidated = RuntimeOptions {
25939      flavor: RuntimeFlavor::MultiThread,
25940      worker_threads: 2,
25941      max_blocking_tasks: 2,
25942      thread_name_prefix: "linger-clamp-exec".to_string(),
25943      park_deadline: None,
25944      drain_linger: extreme,
25945    };
25946    let executor = MultiThreadExecutor::new(&unvalidated, metrics)
25947      .expect("the executor must construct with an extreme unvalidated budget");
25948    assert_eq!(
25949      executor.drain_linger,
25950      Some(MAX_DRAIN_LINGER),
25951      "the constructor's defensive clamp must bound unvalidated budgets"
25952    );
25953
25954    let metrics = Arc::new(RuntimeMetrics::default());
25955    let disabled = RuntimeOptions {
25956      flavor: RuntimeFlavor::MultiThread,
25957      worker_threads: 2,
25958      max_blocking_tasks: 2,
25959      thread_name_prefix: "linger-clamp-off".to_string(),
25960      park_deadline: None,
25961      drain_linger: Duration::ZERO,
25962    };
25963    let executor = MultiThreadExecutor::new(&disabled, metrics)
25964      .expect("the executor must construct with lingering disabled");
25965    assert_eq!(executor.drain_linger, None, "ZERO must disable lingering");
25966  }
25967
25968  #[cfg(not(target_family = "wasm"))]
25969  #[test]
25970  fn runtime_controller_default_reserves_one_worker_from_blocking_admission() {
25971    let controller = RuntimeController::new();
25972    let options = controller.options();
25973    assert_eq!(options.flavor, RuntimeFlavor::MultiThread);
25974    assert_eq!(
25975      options.max_blocking_tasks,
25976      options.worker_threads.saturating_sub(1).max(1),
25977      "the unconfigured Rust API must use the same validated reserve as the binding"
25978    );
25979  }
25980
25981  #[cfg(not(target_family = "wasm"))]
25982  #[test]
25983  fn concurrent_partial_configuration_merges_from_latest_committed_options() {
25984    use std::{sync::mpsc, time::Duration};
25985
25986    let controller = Arc::new(multi_thread_controller("partial-config-race", 8, 1));
25987    let (first_merged_tx, first_merged_rx) = mpsc::sync_channel(1);
25988    let (release_first_tx, release_first_rx) = mpsc::sync_channel(1);
25989    let first_controller = Arc::clone(&controller);
25990    let first = std::thread::spawn(move || {
25991      first_controller
25992        .configure_partial_inner(
25993          RuntimeOptionsPatch {
25994            max_blocking_tasks: Some(3),
25995            ..RuntimeOptionsPatch::default()
25996          },
25997          |candidate| {
25998            assert_eq!(
25999              (candidate.worker_threads, candidate.max_blocking_tasks),
26000              (8, 3)
26001            );
26002            first_merged_tx
26003              .send(())
26004              .expect("the race coordinator must still be listening");
26005            release_first_rx
26006              .recv_timeout(Duration::from_secs(5))
26007              .expect("the first partial configuration must be released");
26008          },
26009        )
26010        .expect("the first partial configuration must commit");
26011    });
26012    first_merged_rx
26013      .recv_timeout(Duration::from_secs(5))
26014      .expect("the first partial configuration must hold the controller lock after merging");
26015
26016    let (second_started_tx, second_started_rx) = mpsc::sync_channel(1);
26017    let (second_candidate_tx, second_candidate_rx) = mpsc::sync_channel(1);
26018    let second_controller = Arc::clone(&controller);
26019    let second = std::thread::spawn(move || {
26020      second_started_tx
26021        .send(())
26022        .expect("the race coordinator must still be listening");
26023      second_controller
26024        .configure_partial_inner(
26025          RuntimeOptionsPatch {
26026            worker_threads: Some(6),
26027            ..RuntimeOptionsPatch::default()
26028          },
26029          |candidate| {
26030            second_candidate_tx
26031              .send((candidate.worker_threads, candidate.max_blocking_tasks))
26032              .expect("the race coordinator must still be listening");
26033          },
26034        )
26035        .expect("the second partial configuration must commit");
26036    });
26037    second_started_rx
26038      .recv_timeout(Duration::from_secs(5))
26039      .expect("the second partial configuration thread must start");
26040    release_first_tx
26041      .send(())
26042      .expect("the first partial configuration must still be waiting");
26043
26044    first
26045      .join()
26046      .expect("the first configuration thread must not panic");
26047    second
26048      .join()
26049      .expect("the second configuration thread must not panic");
26050    assert_eq!(
26051      second_candidate_rx
26052        .recv_timeout(Duration::from_secs(5))
26053        .expect("the second partial configuration must expose its merged candidate"),
26054      (6, 3),
26055      "the second caller must merge against the first caller's committed options"
26056    );
26057
26058    let options = controller.options();
26059    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 3));
26060    assert_eq!(options.thread_name_prefix, "partial-config-race");
26061  }
26062
26063  #[cfg(not(target_family = "wasm"))]
26064  #[test]
26065  fn invalid_concurrent_partial_configuration_cannot_overwrite_a_valid_peer() {
26066    use std::{sync::mpsc, time::Duration};
26067
26068    let controller = Arc::new(multi_thread_controller("partial-config-rollback", 6, 2));
26069    let (invalid_merged_tx, invalid_merged_rx) = mpsc::sync_channel(1);
26070    let (release_invalid_tx, release_invalid_rx) = mpsc::sync_channel(1);
26071    let invalid_controller = Arc::clone(&controller);
26072    let invalid = std::thread::spawn(move || {
26073      invalid_controller
26074        .configure_partial_inner(
26075          RuntimeOptionsPatch {
26076            worker_threads: Some(0),
26077            ..RuntimeOptionsPatch::default()
26078          },
26079          |candidate| {
26080            assert_eq!(
26081              (candidate.worker_threads, candidate.max_blocking_tasks),
26082              (0, 2)
26083            );
26084            invalid_merged_tx
26085              .send(())
26086              .expect("the race coordinator must still be listening");
26087            release_invalid_rx
26088              .recv_timeout(Duration::from_secs(5))
26089              .expect("the invalid partial configuration must be released");
26090          },
26091        )
26092        .expect_err("the invalid partial configuration must be rejected")
26093        .to_string()
26094    });
26095    invalid_merged_rx
26096      .recv_timeout(Duration::from_secs(5))
26097      .expect("the invalid partial configuration must hold the lock after merging");
26098
26099    let (valid_started_tx, valid_started_rx) = mpsc::sync_channel(1);
26100    let (valid_candidate_tx, valid_candidate_rx) = mpsc::sync_channel(1);
26101    let valid_controller = Arc::clone(&controller);
26102    let valid = std::thread::spawn(move || {
26103      valid_started_tx
26104        .send(())
26105        .expect("the race coordinator must still be listening");
26106      valid_controller
26107        .configure_partial_inner(
26108          RuntimeOptionsPatch {
26109            max_blocking_tasks: Some(4),
26110            ..RuntimeOptionsPatch::default()
26111          },
26112          |candidate| {
26113            valid_candidate_tx
26114              .send((candidate.worker_threads, candidate.max_blocking_tasks))
26115              .expect("the race coordinator must still be listening");
26116          },
26117        )
26118        .expect("the valid partial configuration must commit");
26119    });
26120    valid_started_rx
26121      .recv_timeout(Duration::from_secs(5))
26122      .expect("the valid partial configuration thread must start");
26123    release_invalid_tx
26124      .send(())
26125      .expect("the invalid partial configuration must still be waiting");
26126
26127    assert_eq!(
26128      invalid
26129        .join()
26130        .expect("the invalid configuration thread must not panic"),
26131      "worker_threads must be greater than zero"
26132    );
26133    valid
26134      .join()
26135      .expect("the valid configuration thread must not panic");
26136    assert_eq!(
26137      valid_candidate_rx
26138        .recv_timeout(Duration::from_secs(5))
26139        .expect("the valid partial configuration must expose its merged candidate"),
26140      (6, 4),
26141      "the valid caller must merge against the last committed options, not the rejected candidate"
26142    );
26143
26144    let options = controller.options();
26145    assert_eq!((options.worker_threads, options.max_blocking_tasks), (6, 4));
26146    assert_eq!(options.thread_name_prefix, "partial-config-rollback");
26147  }
26148
26149  #[test]
26150  fn configure_after_backend_started_is_rejected() {
26151    // A local controller, not the global `RUNTIME`. Use a CurrentThread backend
26152    // so starting it is cheap and spawns no OS threads.
26153    let controller = RuntimeController::new();
26154    controller
26155      .configure(RuntimeOptions {
26156        flavor: RuntimeFlavor::CurrentThread,
26157        worker_threads: 1,
26158        max_blocking_tasks: 1,
26159        thread_name_prefix: "rd8".to_string(),
26160        park_deadline: None,
26161        drain_linger: DEFAULT_DRAIN_LINGER,
26162      })
26163      .expect("first configure before the backend exists must succeed");
26164
26165    // Materialize the backend; further configuration must now be refused.
26166    let _backend = controller.backend();
26167
26168    let error = controller
26169      .configure(RuntimeOptions::default())
26170      .expect_err("configure after the backend started must be rejected");
26171    assert_eq!(
26172      error.to_string(),
26173      "the async runtime configuration is frozen; configure it before the first async call"
26174    );
26175  }
26176
26177  #[test]
26178  fn partial_configuration_after_backend_started_is_rejected() {
26179    let controller = current_thread_controller("partial-config-frozen");
26180    let _backend = controller.backend();
26181
26182    let error = controller
26183      .configure_partial(RuntimeOptionsPatch {
26184        worker_threads: Some(2),
26185        ..RuntimeOptionsPatch::default()
26186      })
26187      .expect_err("partial configuration after the backend started must be rejected");
26188    assert_eq!(
26189      error.to_string(),
26190      "the async runtime configuration is frozen; configure it before the first async call"
26191    );
26192  }
26193
26194  fn current_thread_controller(thread_name_prefix: &str) -> RuntimeController {
26195    let controller = RuntimeController::new();
26196    controller
26197      .configure(RuntimeOptions {
26198        flavor: RuntimeFlavor::CurrentThread,
26199        worker_threads: 1,
26200        max_blocking_tasks: 1,
26201        thread_name_prefix: thread_name_prefix.to_string(),
26202        park_deadline: None,
26203        drain_linger: DEFAULT_DRAIN_LINGER,
26204      })
26205      .expect("CurrentThread test configuration must be accepted");
26206    controller
26207  }
26208
26209  #[cfg(not(target_family = "wasm"))]
26210  fn multi_thread_controller(
26211    thread_name_prefix: &str,
26212    worker_threads: usize,
26213    max_blocking_tasks: usize,
26214  ) -> RuntimeController {
26215    let controller = RuntimeController::new();
26216    controller
26217      .configure(RuntimeOptions {
26218        flavor: RuntimeFlavor::MultiThread,
26219        worker_threads,
26220        max_blocking_tasks,
26221        thread_name_prefix: thread_name_prefix.to_string(),
26222        park_deadline: None,
26223        drain_linger: DEFAULT_DRAIN_LINGER,
26224      })
26225      .expect("MultiThread test configuration must be accepted");
26226    controller
26227  }
26228
26229  #[test]
26230  fn blocking_backend_creation_failure_preserves_diagnostic() {
26231    let controller = current_thread_controller("blocking-backend-creation-failure");
26232    let ran = Arc::new(AtomicBool::new(false));
26233    let work_ran = Arc::clone(&ran);
26234
26235    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26236    let Err((error, work)) = controller.try_spawn_blocking(move || {
26237      work_ran.store(true, Ordering::SeqCst);
26238      17usize
26239    }) else {
26240      panic!("the injected backend creation failure must reject blocking work");
26241    };
26242    assert_eq!(
26243      error.to_string(),
26244      "injected async runtime backend creation failure"
26245    );
26246    assert!(
26247      !ran.load(Ordering::SeqCst),
26248      "rejected blocking work must not run"
26249    );
26250    assert_eq!(
26251      work(),
26252      17,
26253      "the rejected blocking closure must remain usable"
26254    );
26255    assert!(ran.load(Ordering::SeqCst));
26256
26257    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26258    let error = futures::executor::block_on(controller.spawn_blocking(|| 23usize))
26259      .expect_err("infallible blocking submission must return a failed join handle");
26260    assert_eq!(
26261      error.to_string(),
26262      "injected async runtime backend creation failure"
26263    );
26264
26265    let handle = controller
26266      .try_spawn_blocking(|| 31usize)
26267      .unwrap_or_else(|_| panic!("a later blocking backend creation retry must succeed"));
26268    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26269    controller.shutdown().unwrap();
26270  }
26271
26272  #[cfg(not(target_family = "wasm"))]
26273  #[test]
26274  fn try_block_on_dyn_rejects_stopped_runtime_without_polling() {
26275    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_STOPPED_CHILD";
26276
26277    if std::env::var_os(CHILD_ENV).is_some() {
26278      struct PollProbe {
26279        polls: Arc<AtomicUsize>,
26280      }
26281
26282      impl Future for PollProbe {
26283        type Output = ();
26284
26285        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26286          self.polls.fetch_add(1, Ordering::SeqCst);
26287          Poll::Ready(())
26288        }
26289      }
26290
26291      shutdown().expect("the isolated runtime must stop before block_on admission");
26292      let polls = Arc::new(AtomicUsize::new(0));
26293      let mut future = std::pin::pin!(PollProbe {
26294        polls: Arc::clone(&polls)
26295      });
26296      let error = try_block_on_dyn(future.as_mut())
26297        .expect_err("the stopped runtime must reject the borrowed future");
26298      assert_eq!(
26299        error.to_string(),
26300        "the async runtime is stopped; call start before submitting work"
26301      );
26302      assert_eq!(
26303        polls.load(Ordering::SeqCst),
26304        0,
26305        "rejected block_on must not poll its future"
26306      );
26307
26308      start().expect("the isolated runtime must restart");
26309      try_block_on_dyn(future.as_mut()).expect("restart must accept the same borrowed future");
26310      assert_eq!(polls.load(Ordering::SeqCst), 1);
26311      shutdown().expect("the restarted isolated runtime must stop");
26312      return;
26313    }
26314
26315    let output = std::process::Command::new(std::env::current_exe().unwrap())
26316      .arg("--exact")
26317      .arg("async_runtime::tests::try_block_on_dyn_rejects_stopped_runtime_without_polling")
26318      .arg("--nocapture")
26319      .env(CHILD_ENV, "1")
26320      .output()
26321      .expect("the stopped block_on subprocess must start");
26322    assert!(
26323      output.status.success(),
26324      "stopped try_block_on_dyn must preserve the unpolled future; status={:?}\nstdout={}\nstderr={}",
26325      output.status.code(),
26326      String::from_utf8_lossy(&output.stdout),
26327      String::from_utf8_lossy(&output.stderr)
26328    );
26329  }
26330
26331  #[cfg(not(target_family = "wasm"))]
26332  #[test]
26333  fn try_block_on_dyn_reports_concurrent_shutdown() {
26334    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TRY_BLOCK_ON_DYN_SHUTDOWN_CHILD";
26335
26336    if std::env::var_os(CHILD_ENV).is_some() {
26337      use std::sync::mpsc;
26338
26339      struct PendingProbe {
26340        entered: Option<mpsc::Sender<()>>,
26341        complete: Arc<AtomicBool>,
26342      }
26343
26344      impl Future for PendingProbe {
26345        type Output = ();
26346
26347        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26348          if let Some(entered) = self.entered.take() {
26349            entered.send(()).unwrap();
26350          }
26351          if self.complete.load(Ordering::SeqCst) {
26352            Poll::Ready(())
26353          } else {
26354            Poll::Pending
26355          }
26356        }
26357      }
26358
26359      configure(RuntimeOptions {
26360        flavor: RuntimeFlavor::CurrentThread,
26361        worker_threads: 1,
26362        max_blocking_tasks: 1,
26363        thread_name_prefix: "try-block-on-dyn-shutdown".to_string(),
26364        park_deadline: None,
26365        drain_linger: DEFAULT_DRAIN_LINGER,
26366      })
26367      .expect("the isolated runtime must accept CurrentThread configuration");
26368      start().expect("the isolated runtime must start");
26369
26370      let (entered_tx, entered_rx) = mpsc::channel();
26371      let (error_tx, error_rx) = mpsc::channel();
26372      let (retry_tx, retry_rx) = mpsc::channel();
26373      let complete = Arc::new(AtomicBool::new(false));
26374      let future_complete = Arc::clone(&complete);
26375      let runner = std::thread::spawn(move || {
26376        let mut future = std::pin::pin!(PendingProbe {
26377          entered: Some(entered_tx),
26378          complete: future_complete
26379        });
26380        let error = try_block_on_dyn(future.as_mut())
26381          .expect_err("concurrent shutdown must interrupt the borrowed future")
26382          .to_string();
26383        error_tx.send(error).unwrap();
26384        retry_rx.recv().unwrap();
26385        try_block_on_dyn(future.as_mut())
26386          .expect("restart must accept the same interrupted borrowed future");
26387      });
26388      entered_rx
26389        .recv_timeout(Duration::from_secs(2))
26390        .expect("the borrowed future must be polled before shutdown");
26391      shutdown().expect("shutdown must wait for the active block_on scope to retire");
26392      assert_eq!(
26393        error_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
26394        "the async runtime stopped before block_on completed its future"
26395      );
26396      start().expect("the isolated runtime must restart after the interrupted drive");
26397      complete.store(true, Ordering::SeqCst);
26398      retry_tx.send(()).unwrap();
26399      runner.join().unwrap();
26400      shutdown().expect("the retried isolated runtime must stop");
26401      return;
26402    }
26403
26404    let output = std::process::Command::new(std::env::current_exe().unwrap())
26405      .arg("--exact")
26406      .arg("async_runtime::tests::try_block_on_dyn_reports_concurrent_shutdown")
26407      .arg("--nocapture")
26408      .env(CHILD_ENV, "1")
26409      .output()
26410      .expect("the concurrent block_on shutdown subprocess must start");
26411    assert!(
26412      output.status.success(),
26413      "try_block_on_dyn must return the shutdown diagnostic; status={:?}\nstdout={}\nstderr={}",
26414      output.status.code(),
26415      String::from_utf8_lossy(&output.stdout),
26416      String::from_utf8_lossy(&output.stderr)
26417    );
26418  }
26419
26420  #[test]
26421  fn initial_lifecycle_start_preserves_lazy_configuration() {
26422    let controller = RuntimeController::new();
26423    controller
26424      .start()
26425      .expect("initial lifecycle start must succeed");
26426    controller
26427      .configure(RuntimeOptions {
26428        flavor: RuntimeFlavor::CurrentThread,
26429        worker_threads: 1,
26430        max_blocking_tasks: 1,
26431        thread_name_prefix: "lifecycle-initial-start".to_string(),
26432        park_deadline: None,
26433        drain_linger: DEFAULT_DRAIN_LINGER,
26434      })
26435      .expect("module-registration start must not consume the configuration window");
26436
26437    let state = controller
26438      .state
26439      .lock()
26440      .unwrap_or_else(std::sync::PoisonError::into_inner);
26441    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26442  }
26443
26444  #[test]
26445  fn zero_work_lifecycle_cycles_preserve_lazy_configuration() {
26446    let controller = RuntimeController::new();
26447
26448    for _ in 0..3 {
26449      controller
26450        .start()
26451        .expect("zero-work lifecycle start must succeed");
26452      assert!(matches!(
26453        &controller
26454          .state
26455          .lock()
26456          .unwrap_or_else(std::sync::PoisonError::into_inner)
26457          .lifecycle,
26458        RuntimeLifecycle::Initial
26459      ));
26460
26461      controller
26462        .shutdown()
26463        .expect("zero-work lifecycle shutdown must succeed");
26464      assert!(matches!(
26465        &controller
26466          .state
26467          .lock()
26468          .unwrap_or_else(std::sync::PoisonError::into_inner)
26469          .lifecycle,
26470        RuntimeLifecycle::StoppedBeforeFirstUse
26471      ));
26472    }
26473
26474    controller
26475      .start()
26476      .expect("a later zero-work restart must remain lazy");
26477    controller
26478      .configure(RuntimeOptions {
26479        flavor: RuntimeFlavor::CurrentThread,
26480        worker_threads: 1,
26481        max_blocking_tasks: 1,
26482        thread_name_prefix: "lifecycle-zero-work-config".to_string(),
26483        park_deadline: None,
26484        drain_linger: DEFAULT_DRAIN_LINGER,
26485      })
26486      .expect("zero-work lifecycle cycles must not consume the configuration window");
26487
26488    let state = controller
26489      .state
26490      .lock()
26491      .unwrap_or_else(std::sync::PoisonError::into_inner);
26492    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26493  }
26494
26495  #[cfg(not(target_family = "wasm"))]
26496  #[test]
26497  fn failed_initial_submission_destruction_blocks_retry_and_shutdown() {
26498    use std::sync::mpsc;
26499
26500    struct BlockingDropFuture {
26501      entered: mpsc::Sender<()>,
26502      release: mpsc::Receiver<()>,
26503    }
26504
26505    impl Future for BlockingDropFuture {
26506      type Output = ();
26507
26508      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26509        Poll::Pending
26510      }
26511    }
26512
26513    impl Drop for BlockingDropFuture {
26514      fn drop(&mut self) {
26515        self.entered.send(()).unwrap();
26516        self.release.recv().unwrap();
26517      }
26518    }
26519
26520    let controller = Arc::new(current_thread_controller("failed-initial-submission-drop"));
26521    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26522    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26523    let submit_controller = Arc::clone(&controller);
26524    let submitter = std::thread::spawn(move || {
26525      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26526      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
26527        entered: drop_entered_tx,
26528        release: release_drop_rx,
26529      }));
26530      assert!(
26531        result.is_err(),
26532        "the injected backend creation failure must reject the submission"
26533      );
26534    });
26535    drop_entered_rx
26536      .recv_timeout(Duration::from_secs(2))
26537      .expect("the rejected future destructor must start");
26538
26539    let retry_ran = Arc::new(AtomicBool::new(false));
26540    let retry_ran_task = Arc::clone(&retry_ran);
26541    let Err(retry) = controller.try_spawn_detached(async move {
26542      retry_ran_task.store(true, Ordering::SeqCst);
26543    }) else {
26544      panic!("backend creation must remain closed during rejected destruction");
26545    };
26546    drop(retry);
26547    assert!(!retry_ran.load(Ordering::SeqCst));
26548
26549    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26550    let shutdown_controller = Arc::clone(&controller);
26551    let shutdown = std::thread::spawn(move || {
26552      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26553    });
26554    wait_until("initial shutdown to close runtime admission", || {
26555      matches!(
26556        controller
26557          .state
26558          .lock()
26559          .unwrap_or_else(std::sync::PoisonError::into_inner)
26560          .lifecycle,
26561        RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::StoppedBeforeFirstUse)
26562      )
26563    });
26564    assert!(
26565      shutdown_rx
26566        .recv_timeout(Duration::from_millis(200))
26567        .is_err(),
26568      "initial shutdown must wait for rejected user state destruction"
26569    );
26570    assert!(
26571      controller.try_spawn_detached(async {}).is_err(),
26572      "shutdown must keep first-generation admission closed while it waits"
26573    );
26574
26575    let (start_tx, start_rx) = mpsc::channel();
26576    let start_controller = Arc::clone(&controller);
26577    let starter = std::thread::spawn(move || {
26578      start_tx.send(start_controller.start()).unwrap();
26579    });
26580    assert!(
26581      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26582      "start must wait for the zero-backend shutdown transition to finish"
26583    );
26584
26585    release_drop_tx.send(()).unwrap();
26586    submitter.join().unwrap();
26587    shutdown_rx
26588      .recv_timeout(Duration::from_secs(2))
26589      .unwrap()
26590      .unwrap();
26591    shutdown.join().unwrap();
26592    start_rx
26593      .recv_timeout(Duration::from_secs(2))
26594      .unwrap()
26595      .unwrap();
26596    starter.join().unwrap();
26597
26598    let ran = Arc::new(AtomicBool::new(false));
26599    let ran_task = Arc::clone(&ran);
26600    controller
26601      .try_spawn_detached(async move {
26602        ran_task.store(true, Ordering::SeqCst);
26603      })
26604      .unwrap_or_else(|_| panic!("submission after rejected destruction must create the backend"));
26605    let dispatch = controller_current_thread_dispatch(&controller);
26606    controller.drive_current_thread_dispatch(dispatch, false);
26607    assert!(ran.load(Ordering::SeqCst));
26608    controller.shutdown().unwrap();
26609  }
26610
26611  #[cfg(not(target_family = "wasm"))]
26612  #[test]
26613  fn stopped_shutdown_blocks_restart_until_rejected_destruction_retires() {
26614    use std::sync::mpsc;
26615
26616    struct BlockingDropFuture {
26617      entered: mpsc::Sender<()>,
26618      release: mpsc::Receiver<()>,
26619    }
26620
26621    impl Future for BlockingDropFuture {
26622      type Output = ();
26623
26624      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26625        Poll::Pending
26626      }
26627    }
26628
26629    impl Drop for BlockingDropFuture {
26630      fn drop(&mut self) {
26631        self.entered.send(()).unwrap();
26632        self.release.recv().unwrap();
26633      }
26634    }
26635
26636    let controller = Arc::new(current_thread_controller("stopped-submission-drop"));
26637    let old_generation = controller.backend().generation();
26638    controller.shutdown().unwrap();
26639
26640    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26641    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26642    let submit_controller = Arc::clone(&controller);
26643    let submitter = std::thread::spawn(move || {
26644      let result = futures::executor::block_on(submit_controller.spawn(BlockingDropFuture {
26645        entered: drop_entered_tx,
26646        release: release_drop_rx,
26647      }));
26648      assert!(
26649        result.is_err(),
26650        "the stopped runtime must reject the submission"
26651      );
26652    });
26653    drop_entered_rx
26654      .recv_timeout(Duration::from_secs(2))
26655      .unwrap();
26656
26657    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26658    let shutdown_controller = Arc::clone(&controller);
26659    let shutdown = std::thread::spawn(move || {
26660      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26661    });
26662    wait_until(
26663      "stopped shutdown to enter its zero-backend transition",
26664      || {
26665        matches!(
26666          controller
26667            .state
26668            .lock()
26669            .unwrap_or_else(std::sync::PoisonError::into_inner)
26670            .lifecycle,
26671          RuntimeLifecycle::StoppingWithoutBackend(ZeroBackendShutdownTarget::Stopped)
26672        )
26673      },
26674    );
26675
26676    let (start_tx, start_rx) = mpsc::channel();
26677    let start_controller = Arc::clone(&controller);
26678    let starter = std::thread::spawn(move || {
26679      start_tx.send(start_controller.start()).unwrap();
26680    });
26681    assert!(
26682      shutdown_rx
26683        .recv_timeout(Duration::from_millis(200))
26684        .is_err(),
26685      "stopped shutdown must wait for rejected destruction"
26686    );
26687    assert!(
26688      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
26689      "restart must wait for the stopped zero-backend transition"
26690    );
26691
26692    release_drop_tx.send(()).unwrap();
26693    submitter.join().unwrap();
26694    shutdown_rx
26695      .recv_timeout(Duration::from_secs(2))
26696      .unwrap()
26697      .unwrap();
26698    shutdown.join().unwrap();
26699    start_rx
26700      .recv_timeout(Duration::from_secs(2))
26701      .unwrap()
26702      .unwrap();
26703    starter.join().unwrap();
26704
26705    let new_generation = controller.backend().generation();
26706    assert_ne!(new_generation, old_generation);
26707    controller.shutdown().unwrap();
26708  }
26709
26710  #[test]
26711  fn failed_initial_submission_destructor_cannot_reenter_shutdown() {
26712    use std::sync::mpsc;
26713
26714    struct ShutdownOnDrop {
26715      controller: Arc<RuntimeController>,
26716      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26717    }
26718
26719    impl Future for ShutdownOnDrop {
26720      type Output = ();
26721
26722      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26723        Poll::Pending
26724      }
26725    }
26726
26727    impl Drop for ShutdownOnDrop {
26728      fn drop(&mut self) {
26729        self.result.send(self.controller.shutdown()).unwrap();
26730      }
26731    }
26732
26733    let controller = Arc::new(current_thread_controller(
26734      "failed-initial-reentrant-shutdown",
26735    ));
26736    let (result_tx, result_rx) = mpsc::channel();
26737    FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26738    let rejected = futures::executor::block_on(controller.spawn(ShutdownOnDrop {
26739      controller: Arc::clone(&controller),
26740      result: result_tx,
26741    }));
26742    assert!(rejected.is_err());
26743
26744    let error = result_rx
26745      .recv_timeout(Duration::from_secs(1))
26746      .expect("the rejected destructor must not self-deadlock")
26747      .expect_err("rejected destruction cannot wait for its own shutdown");
26748    assert!(error.to_string().contains("rejected submission"));
26749    {
26750      let state = controller
26751        .state
26752        .lock()
26753        .unwrap_or_else(std::sync::PoisonError::into_inner);
26754      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26755      assert_eq!(state.rejected_drops, 0);
26756    }
26757
26758    let handle = controller
26759      .try_spawn(async { 23usize })
26760      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
26761    let dispatch = controller_current_thread_dispatch(&controller);
26762    controller.drive_current_thread_dispatch(dispatch, false);
26763    assert_eq!(futures::executor::block_on(handle).unwrap(), 23);
26764    controller.shutdown().unwrap();
26765  }
26766
26767  #[test]
26768  fn failed_initial_submission_destructor_cannot_reenter_start() {
26769    use std::sync::mpsc;
26770
26771    struct StartOnDrop {
26772      controller: Arc<RuntimeController>,
26773      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
26774      release: mpsc::Receiver<()>,
26775    }
26776
26777    impl Future for StartOnDrop {
26778      type Output = ();
26779
26780      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26781        Poll::Pending
26782      }
26783    }
26784
26785    impl Drop for StartOnDrop {
26786      fn drop(&mut self) {
26787        self.result.send(self.controller.start()).unwrap();
26788        self.release.recv().unwrap();
26789      }
26790    }
26791
26792    let controller = Arc::new(current_thread_controller("failed-initial-reentrant-start"));
26793    let (result_tx, result_rx) = mpsc::channel();
26794    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26795    let (submission_tx, submission_rx) = mpsc::channel();
26796    let submit_controller = Arc::clone(&controller);
26797    let submitter = std::thread::spawn(move || {
26798      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26799      let rejected = futures::executor::block_on(submit_controller.spawn(StartOnDrop {
26800        controller: Arc::clone(&submit_controller),
26801        result: result_tx,
26802        release: release_drop_rx,
26803      }));
26804      submission_tx.send(rejected).unwrap();
26805    });
26806
26807    let error = result_rx
26808      .recv_timeout(Duration::from_secs(1))
26809      .expect("the rejected destructor must not self-deadlock")
26810      .expect_err("rejected destruction must not report a successful runtime start");
26811    assert!(error.to_string().contains("rejected submission"));
26812    {
26813      let state = controller
26814        .state
26815        .lock()
26816        .unwrap_or_else(std::sync::PoisonError::into_inner);
26817      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26818      assert_eq!(state.rejected_drops, 1);
26819    }
26820
26821    let (wait_entered_tx, wait_entered_rx) = mpsc::channel();
26822    let (external_start_tx, external_start_rx) = mpsc::channel();
26823    let start_controller = Arc::clone(&controller);
26824    let starter = std::thread::spawn(move || {
26825      BEFORE_INITIAL_START_WAIT_TEST_HOOK.with(|slot| {
26826        *slot.borrow_mut() = Some(Box::new(move || {
26827          wait_entered_tx.send(()).unwrap();
26828        }));
26829      });
26830      external_start_tx.send(start_controller.start()).unwrap();
26831    });
26832    wait_entered_rx
26833      .recv_timeout(Duration::from_secs(1))
26834      .expect("external start must reach the Initial rejected-destruction wait");
26835    assert!(
26836      external_start_rx.try_recv().is_err(),
26837      "external start must not return while rejected destruction remains active"
26838    );
26839
26840    release_drop_tx.send(()).unwrap();
26841    assert!(
26842      submission_rx
26843        .recv_timeout(Duration::from_secs(2))
26844        .unwrap()
26845        .is_err()
26846    );
26847    submitter.join().unwrap();
26848    external_start_rx
26849      .recv_timeout(Duration::from_secs(2))
26850      .unwrap()
26851      .unwrap();
26852    starter.join().unwrap();
26853
26854    {
26855      let state = controller
26856        .state
26857        .lock()
26858        .unwrap_or_else(std::sync::PoisonError::into_inner);
26859      assert!(matches!(&state.lifecycle, RuntimeLifecycle::Initial));
26860      assert_eq!(state.rejected_drops, 0);
26861    }
26862
26863    let handle = controller
26864      .try_spawn(async { 31usize })
26865      .unwrap_or_else(|_| panic!("a later backend creation retry must succeed"));
26866    let dispatch = controller_current_thread_dispatch(&controller);
26867    controller.drive_current_thread_dispatch(dispatch, false);
26868    assert_eq!(futures::executor::block_on(handle).unwrap(), 31);
26869    controller.shutdown().unwrap();
26870  }
26871
26872  #[test]
26873  fn failed_initial_block_on_destruction_blocks_retry_and_shutdown() {
26874    use std::sync::mpsc;
26875
26876    struct BlockingDropFuture {
26877      controller: Arc<RuntimeController>,
26878      entered: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
26879      release: mpsc::Receiver<()>,
26880    }
26881
26882    impl Future for BlockingDropFuture {
26883      type Output = ();
26884
26885      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26886        Poll::Pending
26887      }
26888    }
26889
26890    impl Drop for BlockingDropFuture {
26891      fn drop(&mut self) {
26892        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
26893        self
26894          .entered
26895          .send((generation, self.controller.shutdown()))
26896          .unwrap();
26897        self.release.recv().unwrap();
26898      }
26899    }
26900
26901    let controller = Arc::new(current_thread_controller("failed-initial-block-on-drop"));
26902    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
26903    let (release_drop_tx, release_drop_rx) = mpsc::channel();
26904    let (runner_done_tx, runner_done_rx) = mpsc::channel();
26905    let runner_controller = Arc::clone(&controller);
26906    let runner = std::thread::spawn(move || {
26907      FAIL_NEXT_RUNTIME_BACKEND_CREATION.with(|fail| fail.set(true));
26908      let result = catch_unwind(AssertUnwindSafe(|| {
26909        block_on_with_controller(
26910          &runner_controller,
26911          BlockingDropFuture {
26912            controller: Arc::clone(&runner_controller),
26913            entered: drop_entered_tx,
26914            release: release_drop_rx,
26915          },
26916        );
26917      }));
26918      runner_done_tx.send(result.is_err()).unwrap();
26919    });
26920
26921    let (generation, reentrant_shutdown) = drop_entered_rx
26922      .recv_timeout(Duration::from_secs(2))
26923      .expect("failed block_on acquisition must destroy its owned future");
26924    assert_eq!(generation, None, "no runtime generation was created");
26925    let error = reentrant_shutdown
26926      .expect_err("rejected block_on destruction cannot wait for its own shutdown");
26927    assert!(error.to_string().contains("rejected submission"));
26928
26929    let Err(retry_error) = controller.try_backend() else {
26930      panic!("backend retry must remain closed during rejected block_on destruction");
26931    };
26932    assert!(retry_error.to_string().contains("rejected submission"));
26933
26934    let (shutdown_tx, shutdown_rx) = mpsc::channel();
26935    let shutdown_controller = Arc::clone(&controller);
26936    let shutdown = std::thread::spawn(move || {
26937      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
26938    });
26939    assert!(
26940      shutdown_rx
26941        .recv_timeout(Duration::from_millis(200))
26942        .is_err(),
26943      "initial shutdown must wait for rejected block_on destruction"
26944    );
26945
26946    release_drop_tx.send(()).unwrap();
26947    assert!(
26948      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
26949      "block_on must preserve its infallible panic contract after contained cleanup"
26950    );
26951    runner.join().unwrap();
26952    shutdown_rx
26953      .recv_timeout(Duration::from_secs(2))
26954      .unwrap()
26955      .unwrap();
26956    shutdown.join().unwrap();
26957
26958    controller
26959      .start()
26960      .expect("zero-generation shutdown must remain restartable");
26961    let ran = Arc::new(AtomicBool::new(false));
26962    let ran_task = Arc::clone(&ran);
26963    controller
26964      .try_spawn_detached(async move {
26965        ran_task.store(true, Ordering::SeqCst);
26966      })
26967      .unwrap_or_else(|_| panic!("backend creation must succeed after rejected cleanup retires"));
26968    let dispatch = controller_current_thread_dispatch(&controller);
26969    controller.drive_current_thread_dispatch(dispatch, false);
26970    assert!(ran.load(Ordering::SeqCst));
26971    controller.shutdown().unwrap();
26972  }
26973
26974  #[test]
26975  fn public_block_on_contains_poll_and_drop_double_panic() {
26976    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_BLOCK_ON_DOUBLE_PANIC_CHILD";
26977
26978    if std::env::var_os(CHILD_ENV).is_some() {
26979      struct PanicInPollAndDrop;
26980
26981      impl Future for PanicInPollAndDrop {
26982        type Output = ();
26983
26984        fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
26985          panic!("intentional block_on poll panic");
26986        }
26987      }
26988
26989      impl Drop for PanicInPollAndDrop {
26990        fn drop(&mut self) {
26991          panic!("intentional block_on future destructor panic");
26992        }
26993      }
26994
26995      let controller = current_thread_controller("block-on-double-panic");
26996      assert!(
26997        catch_unwind(AssertUnwindSafe(|| {
26998          block_on_with_controller(&controller, PanicInPollAndDrop);
26999        }))
27000        .is_err(),
27001        "the original poll panic must still reach the caller"
27002      );
27003      controller
27004        .shutdown()
27005        .expect("contained future destruction must not strand the runtime");
27006      return;
27007    }
27008
27009    let output = std::process::Command::new(std::env::current_exe().unwrap())
27010      .arg("--exact")
27011      .arg("async_runtime::tests::public_block_on_contains_poll_and_drop_double_panic")
27012      .arg("--nocapture")
27013      .env(CHILD_ENV, "1")
27014      .output()
27015      .expect("the subprocess test must start");
27016    assert!(
27017      output.status.success(),
27018      "a block_on poll panic plus future destructor panic must not abort the process; status={:?}\nstdout={}\nstderr={}",
27019      output.status.code(),
27020      String::from_utf8_lossy(&output.stdout),
27021      String::from_utf8_lossy(&output.stderr)
27022    );
27023  }
27024
27025  #[test]
27026  fn real_generation_after_zero_work_cycle_keeps_frozen_restart_semantics() {
27027    let controller = RuntimeController::new();
27028    controller
27029      .start()
27030      .expect("initial lifecycle start must succeed");
27031    controller
27032      .shutdown()
27033      .expect("zero-work lifecycle shutdown must succeed");
27034    controller
27035      .start()
27036      .expect("zero-work lifecycle restart must remain lazy");
27037    controller
27038      .configure(RuntimeOptions {
27039        flavor: RuntimeFlavor::CurrentThread,
27040        worker_threads: 1,
27041        max_blocking_tasks: 1,
27042        thread_name_prefix: "lifecycle-zero-work-real-generation".to_string(),
27043        park_deadline: None,
27044        drain_linger: DEFAULT_DRAIN_LINGER,
27045      })
27046      .expect("configuration before first real async use must succeed");
27047
27048    let first_generation = controller.backend().generation();
27049    controller
27050      .shutdown()
27051      .expect("the first real generation must stop");
27052    assert!(matches!(
27053      &controller
27054        .state
27055        .lock()
27056        .unwrap_or_else(std::sync::PoisonError::into_inner)
27057        .lifecycle,
27058      RuntimeLifecycle::Stopped
27059    ));
27060    let error = controller
27061      .configure(RuntimeOptions::default())
27062      .expect_err("a real generation must permanently freeze configuration");
27063    assert_eq!(
27064      error.to_string(),
27065      "the async runtime configuration is frozen; configure it before the first async call"
27066    );
27067
27068    controller
27069      .start()
27070      .expect("the stopped real generation must restart");
27071    let restarted_generation = {
27072      let state = controller
27073        .state
27074        .lock()
27075        .unwrap_or_else(std::sync::PoisonError::into_inner);
27076      let RuntimeLifecycle::Running(backend) = &state.lifecycle else {
27077        panic!("a real generation restart must eagerly create its replacement backend");
27078      };
27079      backend.generation()
27080    };
27081    assert_ne!(
27082      restarted_generation, first_generation,
27083      "a real generation restart must not reuse the retired backend"
27084    );
27085    controller
27086      .shutdown()
27087      .expect("the replacement generation must stop");
27088  }
27089
27090  #[test]
27091  fn runtime_controller_rejects_submissions_after_shutdown() {
27092    let controller = current_thread_controller("lifecycle-reject");
27093    controller.start().expect("runtime must start");
27094    drop(controller.backend());
27095    controller.shutdown().expect("runtime must stop");
27096
27097    let async_ran = Arc::new(AtomicBool::new(false));
27098    let async_ran_task = Arc::clone(&async_ran);
27099    let Err(rejected_future) = controller.try_spawn_detached(async move {
27100      async_ran_task.store(true, Ordering::SeqCst);
27101    }) else {
27102      panic!("shutdown must reject async work");
27103    };
27104    assert!(
27105      !async_ran.load(Ordering::SeqCst),
27106      "a rejected future must not be polled"
27107    );
27108    futures::executor::block_on(rejected_future);
27109    assert!(
27110      async_ran.load(Ordering::SeqCst),
27111      "the original future must remain usable"
27112    );
27113
27114    let blocking_ran = Arc::new(AtomicBool::new(false));
27115    let blocking_ran_task = Arc::clone(&blocking_ran);
27116    let Err((blocking_error, rejected_work)) = controller.try_spawn_blocking(move || {
27117      blocking_ran_task.store(true, Ordering::SeqCst);
27118      7
27119    }) else {
27120      panic!("shutdown must reject blocking work");
27121    };
27122    assert_eq!(
27123      blocking_error.to_string(),
27124      "the async runtime is stopped; call start before submitting work"
27125    );
27126    assert!(
27127      !blocking_ran.load(Ordering::SeqCst),
27128      "rejected blocking work must not run"
27129    );
27130    assert_eq!(
27131      rejected_work(),
27132      7,
27133      "the original closure must remain usable"
27134    );
27135
27136    let config_error = controller
27137      .configure(RuntimeOptions::default())
27138      .expect_err("shutdown must not make runtime configuration mutable again");
27139    assert_eq!(
27140      config_error.to_string(),
27141      "the async runtime configuration is frozen; configure it before the first async call"
27142    );
27143
27144    let state = controller
27145      .state
27146      .lock()
27147      .unwrap_or_else(std::sync::PoisonError::into_inner);
27148    assert!(matches!(&state.lifecycle, RuntimeLifecycle::Stopped));
27149  }
27150
27151  #[test]
27152  fn rejected_future_can_be_resubmitted_after_restart() {
27153    let controller = current_thread_controller("lifecycle-retry-future");
27154    controller.start().expect("runtime must start");
27155    drop(controller.backend());
27156    controller.shutdown().expect("runtime must stop");
27157
27158    let runs = Arc::new(AtomicUsize::new(0));
27159    let runs_task = Arc::clone(&runs);
27160    let future = async move {
27161      runs_task.fetch_add(1, Ordering::SeqCst);
27162      23
27163    };
27164    let Err((error, future)) = controller.try_spawn(future) else {
27165      panic!("stopped runtime must reject the future");
27166    };
27167    assert!(error.to_string().contains("runtime is stopped"));
27168    assert_eq!(runs.load(Ordering::SeqCst), 0);
27169
27170    controller.start().expect("runtime must restart");
27171    let handle = controller
27172      .try_spawn(future)
27173      .unwrap_or_else(|_| panic!("restart must accept retained future"));
27174    let dispatch = controller_current_thread_dispatch(&controller);
27175    controller.drive_current_thread_dispatch(dispatch, false);
27176    assert_eq!(
27177      futures::executor::block_on(handle).expect("retained future must complete"),
27178      23
27179    );
27180    assert_eq!(runs.load(Ordering::SeqCst), 1);
27181    controller
27182      .shutdown()
27183      .expect("replacement runtime must stop");
27184  }
27185
27186  #[test]
27187  fn runtime_controller_start_after_shutdown_accepts_work() {
27188    let controller = current_thread_controller("lifecycle-restart");
27189    controller.start().expect("runtime must start");
27190    drop(controller.backend());
27191    controller.shutdown().expect("runtime must stop");
27192    controller.start().expect("runtime must restart");
27193
27194    let async_ran = Arc::new(AtomicBool::new(false));
27195    let async_ran_task = Arc::clone(&async_ran);
27196    controller
27197      .try_spawn_detached(async move {
27198        async_ran_task.store(true, Ordering::SeqCst);
27199      })
27200      .unwrap_or_else(|_| panic!("restarted runtime must accept async work"));
27201    assert!(
27202      !async_ran.load(Ordering::SeqCst),
27203      "hostless CurrentThread submission must remain enqueue-only"
27204    );
27205    let dispatch = controller_current_thread_dispatch(&controller);
27206    controller.drive_current_thread_dispatch(dispatch, false);
27207    assert!(
27208      async_ran.load(Ordering::SeqCst),
27209      "restarted runtime must poll submitted work"
27210    );
27211
27212    let Ok(handle) = controller.try_spawn_blocking(|| 11) else {
27213      panic!("restarted runtime must accept blocking work");
27214    };
27215    assert_eq!(
27216      futures::executor::block_on(handle).expect("blocking work must complete after restart"),
27217      11
27218    );
27219  }
27220
27221  #[test]
27222  fn stale_current_thread_host_callback_cannot_drive_a_restarted_generation() {
27223    let controller = current_thread_controller("stale-host-generation");
27224    let old_generation = controller.backend().generation();
27225    let old_dispatch = controller_current_thread_dispatch(&controller);
27226    controller.shutdown().unwrap();
27227    controller.start().unwrap();
27228    let new_generation = controller.backend().generation();
27229    assert_ne!(new_generation, old_generation);
27230
27231    let ran = Arc::new(AtomicBool::new(false));
27232    let ran_task = Arc::clone(&ran);
27233    controller
27234      .try_spawn_detached(async move {
27235        ran_task.store(true, Ordering::SeqCst);
27236      })
27237      .unwrap_or_else(|_| panic!("the restarted runtime must accept work"));
27238    let new_dispatch = controller_current_thread_dispatch(&controller);
27239    assert_ne!(new_dispatch, old_dispatch);
27240
27241    controller.drive_current_thread_dispatch(old_dispatch, false);
27242    assert!(
27243      !ran.load(Ordering::SeqCst),
27244      "a callback from the retired generation must not touch the restarted executor"
27245    );
27246    controller.drive_current_thread_dispatch(new_dispatch, false);
27247    assert!(ran.load(Ordering::SeqCst));
27248    controller.shutdown().unwrap();
27249  }
27250
27251  #[test]
27252  fn stale_current_thread_dispatch_cancellation_cannot_clear_a_restarted_generation() {
27253    let controller = current_thread_controller("stale-host-cancellation");
27254    let old_generation = controller.backend().generation();
27255    let old_dispatch = controller_current_thread_dispatch(&controller);
27256    controller.shutdown().unwrap();
27257    controller.start().unwrap();
27258    let backend = controller.backend();
27259    let new_generation = backend.generation();
27260    assert_ne!(new_generation, old_generation);
27261    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27262      panic!("configured CurrentThread backend must create a CurrentThread executor");
27263    };
27264    let new_dispatch = current_thread_dispatch(executor);
27265    assert_ne!(new_dispatch, old_dispatch);
27266
27267    controller.cancel_current_thread_dispatch_for_test(old_dispatch);
27268    assert_eq!(
27269      executor.dispatch_pending.load(Ordering::Acquire),
27270      new_dispatch,
27271      "a delayed failure from the retired generation must not release the replacement latch"
27272    );
27273
27274    controller.cancel_current_thread_dispatch_for_test(new_dispatch);
27275    assert_eq!(
27276      executor.dispatch_pending.load(Ordering::Acquire),
27277      0,
27278      "the live generation must be able to cancel its accepted dispatch"
27279    );
27280    drop(backend);
27281    controller.shutdown().unwrap();
27282  }
27283
27284  #[cfg(not(target_family = "wasm"))]
27285  #[test]
27286  fn retained_current_thread_outputs_cannot_access_a_restarted_generation() {
27287    use std::sync::mpsc;
27288
27289    type ProbeResult = (
27290      &'static str,
27291      Option<u64>,
27292      Result<(), String>,
27293      Result<(), String>,
27294      Result<(), String>,
27295    );
27296
27297    struct StaleGenerationProbe {
27298      label: &'static str,
27299      controller: Arc<RuntimeController>,
27300      result: mpsc::Sender<ProbeResult>,
27301    }
27302
27303    impl Drop for StaleGenerationProbe {
27304      fn drop(&mut self) {
27305        let generation = active_runtime_generation();
27306        let submission = match self.controller.try_spawn(async {}) {
27307          Ok(handle) => {
27308            drop(handle);
27309            Ok(())
27310          }
27311          Err((error, future)) => {
27312            drop(future);
27313            Err(error.to_string())
27314          }
27315        };
27316        let start = self.controller.start().map_err(|error| error.to_string());
27317        let shutdown = self
27318          .controller
27319          .shutdown()
27320          .map_err(|error| error.to_string());
27321        self
27322          .result
27323          .send((self.label, generation, submission, start, shutdown))
27324          .unwrap();
27325      }
27326    }
27327
27328    let controller = Arc::new(current_thread_controller("retained-current-thread-output"));
27329    let old_generation = controller.backend().generation();
27330    let (result_tx, result_rx) = mpsc::channel();
27331
27332    let async_controller = Arc::clone(&controller);
27333    let async_result = result_tx.clone();
27334    let async_handle = controller
27335      .try_spawn(async move {
27336        StaleGenerationProbe {
27337          label: "async",
27338          controller: async_controller,
27339          result: async_result,
27340        }
27341      })
27342      .unwrap_or_else(|_| panic!("the first generation must accept the async output"));
27343    let old_dispatch = controller_current_thread_dispatch(&controller);
27344    controller.drive_current_thread_dispatch(old_dispatch, false);
27345
27346    let ready_controller = Arc::clone(&controller);
27347    let ready_handle = controller
27348      .try_spawn_blocking(move || StaleGenerationProbe {
27349        label: "ready",
27350        controller: ready_controller,
27351        result: result_tx,
27352      })
27353      .unwrap_or_else(|_| panic!("the first generation must accept the immediate output"));
27354
27355    controller.shutdown().unwrap();
27356    controller.start().unwrap();
27357    let new_generation = controller.backend().generation();
27358    assert_ne!(new_generation, old_generation);
27359
27360    drop(async_handle);
27361    let async_result = result_rx
27362      .recv_timeout(Duration::from_secs(2))
27363      .expect("the retained async output must be destroyed without waiting on the replacement");
27364    drop(ready_handle);
27365    let ready_result = result_rx
27366      .recv_timeout(Duration::from_secs(2))
27367      .expect("the retained immediate output must be destroyed without touching the replacement");
27368
27369    for (expected_label, result) in [("async", async_result), ("ready", ready_result)] {
27370      let (label, generation, submission, start, shutdown) = result;
27371      assert_eq!(label, expected_label);
27372      assert_eq!(generation, Some(old_generation));
27373      for error in [submission, start, shutdown] {
27374        assert!(
27375          error
27376            .expect_err("retired-generation output destruction must be rejected")
27377            .contains("retired generation")
27378        );
27379      }
27380    }
27381
27382    let survivor = controller
27383      .try_spawn(async { 29usize })
27384      .unwrap_or_else(|_| panic!("the replacement generation must remain usable"));
27385    let new_dispatch = controller_current_thread_dispatch(&controller);
27386    controller.drive_current_thread_dispatch(new_dispatch, false);
27387    assert_eq!(futures::executor::block_on(survivor).unwrap(), 29);
27388    controller.shutdown().unwrap();
27389  }
27390
27391  #[cfg(not(target_family = "wasm"))]
27392  #[test]
27393  fn retained_blocking_output_cannot_wait_on_replacement_shutdown() {
27394    use std::sync::mpsc;
27395
27396    struct ShutdownOnDrop {
27397      controller: Arc<RuntimeController>,
27398      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
27399    }
27400
27401    impl Drop for ShutdownOnDrop {
27402      fn drop(&mut self) {
27403        self
27404          .result
27405          .send((active_runtime_generation(), self.controller.shutdown()))
27406          .unwrap();
27407      }
27408    }
27409
27410    let controller = Arc::new(multi_thread_controller("retained-blocking-output", 2, 1));
27411    let old_generation = controller.backend().generation();
27412    let (result_tx, result_rx) = mpsc::channel();
27413    let old_handle = controller
27414      .try_spawn_blocking({
27415        let controller = Arc::clone(&controller);
27416        move || ShutdownOnDrop {
27417          controller,
27418          result: result_tx,
27419        }
27420      })
27421      .unwrap_or_else(|_| panic!("the first generation must accept the blocking output"));
27422    wait_until(
27423      "the first-generation blocking result to be buffered",
27424      || {
27425        controller
27426          .metrics
27427          .blocking_tasks_completed
27428          .load(Ordering::Acquire)
27429          == 1
27430      },
27431    );
27432    controller.shutdown().unwrap();
27433    controller.start().unwrap();
27434    let new_generation = controller.backend().generation();
27435    assert_ne!(new_generation, old_generation);
27436
27437    let (holder_started_tx, holder_started_rx) = mpsc::channel();
27438    let (release_holder_tx, release_holder_rx) = mpsc::channel();
27439    let holder = controller
27440      .try_spawn_blocking(move || {
27441        holder_started_tx.send(()).unwrap();
27442        release_holder_rx.recv().unwrap();
27443      })
27444      .unwrap_or_else(|_| panic!("the replacement generation must accept the blocking holder"));
27445    holder_started_rx
27446      .recv_timeout(Duration::from_secs(2))
27447      .unwrap();
27448
27449    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27450    let shutdown_controller = Arc::clone(&controller);
27451    let shutdown = std::thread::spawn(move || {
27452      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27453    });
27454    wait_until("the replacement generation to enter Stopping", || {
27455      matches!(
27456        controller.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner).lifecycle,
27457        RuntimeLifecycle::Stopping(identity) if identity.generation == new_generation
27458      )
27459    });
27460
27461    let dropper = std::thread::spawn(move || drop(old_handle));
27462    let (observed_generation, reentrant_shutdown) =
27463      match result_rx.recv_timeout(Duration::from_secs(2)) {
27464        Ok(result) => result,
27465        Err(error) => {
27466          release_holder_tx.send(()).unwrap();
27467          let _ = shutdown_rx.recv_timeout(Duration::from_secs(2));
27468          let _ = dropper.join();
27469          let _ = shutdown.join();
27470          panic!("retained first-generation output waited on replacement shutdown: {error}");
27471        }
27472      };
27473    assert_eq!(observed_generation, Some(old_generation));
27474    let error = reentrant_shutdown
27475      .expect_err("a retained first-generation output must not control replacement shutdown");
27476    assert!(error.to_string().contains("retired generation"));
27477
27478    release_holder_tx.send(()).unwrap();
27479    shutdown_rx
27480      .recv_timeout(Duration::from_secs(2))
27481      .unwrap()
27482      .unwrap();
27483    dropper.join().unwrap();
27484    shutdown.join().unwrap();
27485    futures::executor::block_on(holder).unwrap();
27486  }
27487
27488  #[cfg(not(target_family = "wasm"))]
27489  #[test]
27490  fn admitted_current_thread_host_turn_holds_shutdown_until_it_retires() {
27491    use std::sync::mpsc;
27492
27493    let controller = Arc::new(current_thread_controller("host-admission-shutdown"));
27494    let generation = controller.backend().generation();
27495    let dispatch = controller_current_thread_dispatch(&controller);
27496    let (admitted_tx, admitted_rx) = mpsc::channel();
27497    let (release_tx, release_rx) = mpsc::channel();
27498    let driver_controller = Arc::clone(&controller);
27499    let driver = std::thread::spawn(move || {
27500      AFTER_CURRENT_THREAD_HOST_ADMISSION_TEST_HOOK.with(|slot| {
27501        *slot.borrow_mut() = Some(Box::new(move || {
27502          admitted_tx.send(()).unwrap();
27503          release_rx.recv().unwrap();
27504        }));
27505      });
27506      driver_controller.drive_current_thread_dispatch(dispatch, false);
27507    });
27508    admitted_rx
27509      .recv_timeout(Duration::from_secs(2))
27510      .expect("the host turn must claim its scheduler role before execution");
27511
27512    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27513    let shutdown_controller = Arc::clone(&controller);
27514    let shutdown = std::thread::spawn(move || {
27515      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27516    });
27517    wait_until(
27518      "shutdown to close the admitted CurrentThread generation",
27519      || {
27520        let state = controller
27521          .state
27522          .lock()
27523          .unwrap_or_else(std::sync::PoisonError::into_inner);
27524        matches!(
27525          &state.lifecycle,
27526          RuntimeLifecycle::Stopping(identity) if identity.generation == generation
27527        )
27528      },
27529    );
27530    assert!(
27531      shutdown_rx
27532        .recv_timeout(Duration::from_millis(200))
27533        .is_err(),
27534      "shutdown must wait for an admitted host callback even before it starts draining"
27535    );
27536
27537    release_tx.send(()).unwrap();
27538    driver.join().unwrap();
27539    shutdown_rx
27540      .recv_timeout(Duration::from_secs(2))
27541      .unwrap()
27542      .unwrap();
27543    shutdown.join().unwrap();
27544  }
27545
27546  #[test]
27547  fn spawn_racing_shutdown_cannot_recreate_backend() {
27548    use std::sync::mpsc;
27549
27550    let controller = Arc::new(current_thread_controller("lifecycle-race"));
27551    controller.start().expect("runtime must start");
27552    drop(controller.backend());
27553
27554    let (at_hook_tx, at_hook_rx) = mpsc::channel();
27555    let (release_tx, release_rx) = mpsc::channel();
27556    let ran = Arc::new(AtomicBool::new(false));
27557    let ran_task = Arc::clone(&ran);
27558    let worker_controller = Arc::clone(&controller);
27559    let worker = std::thread::spawn(move || {
27560      BEFORE_RUNTIME_SUBMISSION_LOCK_TEST_HOOK.with(|slot| {
27561        *slot.borrow_mut() = Some(Box::new(move || {
27562          at_hook_tx
27563            .send(())
27564            .expect("test coordinator must still be listening");
27565          release_rx
27566            .recv()
27567            .expect("test coordinator must release the submission");
27568        }));
27569      });
27570
27571      let future = async move {
27572        ran_task.store(true, Ordering::SeqCst);
27573      };
27574      match worker_controller.try_spawn_detached(future) {
27575        Ok(()) => panic!("a submission linearized after shutdown must be rejected"),
27576        Err(future) => drop(future),
27577      }
27578    });
27579
27580    at_hook_rx
27581      .recv_timeout(Duration::from_secs(5))
27582      .expect("submitting thread must stop before taking the controller lock");
27583    controller.shutdown().expect("runtime must stop");
27584    release_tx
27585      .send(())
27586      .expect("submitting thread must still be waiting");
27587    worker.join().expect("submitting thread must not panic");
27588
27589    assert!(
27590      !ran.load(Ordering::SeqCst),
27591      "the rejected task must never run"
27592    );
27593    let state = controller
27594      .state
27595      .lock()
27596      .unwrap_or_else(std::sync::PoisonError::into_inner);
27597    assert!(
27598      matches!(&state.lifecycle, RuntimeLifecycle::Stopped),
27599      "the rejected submission must not lazily recreate a backend"
27600    );
27601  }
27602
27603  #[cfg(not(target_family = "wasm"))]
27604  #[test]
27605  fn accepted_current_thread_submission_destruction_blocks_shutdown_and_restart() {
27606    use std::sync::mpsc;
27607
27608    struct BlockingDropFuture {
27609      work: Arc<GenerationWork>,
27610      entered: mpsc::Sender<(Option<u64>, usize)>,
27611      release: mpsc::Receiver<()>,
27612    }
27613
27614    impl Future for BlockingDropFuture {
27615      type Output = ();
27616
27617      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
27618        Poll::Pending
27619      }
27620    }
27621
27622    impl Drop for BlockingDropFuture {
27623      fn drop(&mut self) {
27624        let active = self
27625          .work
27626          .state
27627          .lock()
27628          .unwrap_or_else(std::sync::PoisonError::into_inner)
27629          .active;
27630        self
27631          .entered
27632          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
27633          .unwrap();
27634        self.release.recv().unwrap();
27635      }
27636    }
27637
27638    let controller = Arc::new(current_thread_controller("accepted-submission-drop"));
27639    let backend = controller.backend();
27640    let old_generation = backend.generation();
27641    let work = Arc::clone(&backend.work);
27642    let RuntimeExecutor::CurrentThread(executor) = &backend.executor else {
27643      panic!("configured CurrentThread backend must create a CurrentThread executor");
27644    };
27645    let executor = Arc::clone(executor);
27646    drop(backend);
27647
27648    let (registered_tx, registered_rx) = mpsc::channel();
27649    let (release_submission_tx, release_submission_rx) = mpsc::channel();
27650    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27651    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27652    let submit_controller = Arc::clone(&controller);
27653    let submit_work = Arc::clone(&work);
27654    let submitter = std::thread::spawn(move || {
27655      AFTER_ASYNC_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
27656        *slot.borrow_mut() = Some(Box::new(move || {
27657          registered_tx.send(()).unwrap();
27658          release_submission_rx.recv().unwrap();
27659        }));
27660      });
27661      submit_controller
27662        .try_spawn_detached(BlockingDropFuture {
27663          work: submit_work,
27664          entered: drop_entered_tx,
27665          release: release_drop_rx,
27666        })
27667        .unwrap_or_else(|_| {
27668          panic!("the submission linearized before shutdown and must be accepted")
27669        });
27670    });
27671    registered_rx
27672      .recv_timeout(Duration::from_secs(2))
27673      .expect("the submitting thread must register generation work before shutdown");
27674
27675    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27676    let shutdown_controller = Arc::clone(&controller);
27677    let shutdown = std::thread::spawn(move || {
27678      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27679    });
27680    wait_until("CurrentThread shutdown to close the runnable queue", || {
27681      executor
27682        .queue
27683        .lock()
27684        .unwrap_or_else(std::sync::PoisonError::into_inner)
27685        .closed
27686    });
27687    release_submission_tx.send(()).unwrap();
27688
27689    let (drop_generation, active_during_drop) = drop_entered_rx
27690      .recv_timeout(Duration::from_secs(2))
27691      .expect("the closed queue must destroy the accepted unpolled future");
27692    assert_eq!(drop_generation, Some(old_generation));
27693    assert_eq!(
27694      active_during_drop, 1,
27695      "accepted generation work must remain registered through user future destruction"
27696    );
27697
27698    let (start_tx, start_rx) = mpsc::channel();
27699    let start_controller = Arc::clone(&controller);
27700    let starter = std::thread::spawn(move || {
27701      start_tx.send(start_controller.start()).unwrap();
27702    });
27703    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
27704    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
27705    let shutdown_overlapped = early_shutdown.is_some();
27706    let restart_overlapped = early_start.is_some();
27707
27708    release_drop_tx.send(()).unwrap();
27709    submitter.join().unwrap();
27710    let shutdown_result = early_shutdown.unwrap_or_else(|| {
27711      shutdown_rx
27712        .recv_timeout(Duration::from_secs(2))
27713        .expect("shutdown must finish after drop")
27714    });
27715    shutdown_result.unwrap();
27716    shutdown.join().unwrap();
27717    let start_result = early_start.unwrap_or_else(|| {
27718      start_rx
27719        .recv_timeout(Duration::from_secs(2))
27720        .expect("restart must follow shutdown")
27721    });
27722    start_result.unwrap();
27723    starter.join().unwrap();
27724
27725    assert!(
27726      !shutdown_overlapped,
27727      "shutdown must not publish Stopped while an accepted old-generation future is being destroyed"
27728    );
27729    assert!(
27730      !restart_overlapped,
27731      "restart must not create a new generation while an accepted old-generation future is being destroyed"
27732    );
27733    assert_ne!(controller.backend().generation(), old_generation);
27734    controller.shutdown().unwrap();
27735  }
27736
27737  #[cfg(not(target_family = "wasm"))]
27738  #[test]
27739  fn accepted_multi_thread_blocking_destruction_blocks_shutdown_and_restart() {
27740    use std::sync::mpsc;
27741
27742    struct BlockingDrop {
27743      work: Arc<GenerationWork>,
27744      entered: mpsc::Sender<(Option<u64>, usize)>,
27745      release: mpsc::Receiver<()>,
27746    }
27747
27748    impl Drop for BlockingDrop {
27749      fn drop(&mut self) {
27750        let active = self
27751          .work
27752          .state
27753          .lock()
27754          .unwrap_or_else(std::sync::PoisonError::into_inner)
27755          .active;
27756        self
27757          .entered
27758          .send((ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get), active))
27759          .unwrap();
27760        self.release.recv().unwrap();
27761      }
27762    }
27763
27764    let controller = Arc::new(multi_thread_controller(
27765      "accepted-blocking-submission-drop",
27766      2,
27767      1,
27768    ));
27769    let backend = controller.backend();
27770    let old_generation = backend.generation();
27771    let work = Arc::clone(&backend.work);
27772    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27773      panic!("configured MultiThread backend must create a MultiThread executor");
27774    };
27775    let executor = Arc::downgrade(executor);
27776    drop(backend);
27777
27778    let (registered_tx, registered_rx) = mpsc::channel();
27779    let (release_submission_tx, release_submission_rx) = mpsc::channel();
27780    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
27781    let (release_drop_tx, release_drop_rx) = mpsc::channel();
27782    let submit_controller = Arc::clone(&controller);
27783    let submitter = std::thread::spawn(move || {
27784      AFTER_BLOCKING_WORK_REGISTRATION_TEST_HOOK.with(|slot| {
27785        *slot.borrow_mut() = Some(Box::new(move || {
27786          registered_tx.send(()).unwrap();
27787          release_submission_rx.recv().unwrap();
27788        }));
27789      });
27790      let drop_signal = BlockingDrop {
27791        work,
27792        entered: drop_entered_tx,
27793        release: release_drop_rx,
27794      };
27795      let handle = submit_controller
27796        .try_spawn_blocking(move || {
27797          let _drop_signal = drop_signal;
27798        })
27799        .unwrap_or_else(|_| {
27800          panic!("the submission linearized before shutdown and must be accepted")
27801        });
27802      assert!(
27803        futures::executor::block_on(handle).is_err(),
27804        "the executor must cancel work that reaches its queue after shutdown closed it"
27805      );
27806    });
27807    registered_rx
27808      .recv_timeout(Duration::from_secs(2))
27809      .expect("the submitting thread must register generation work before shutdown");
27810
27811    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27812    let shutdown_controller = Arc::clone(&controller);
27813    let shutdown = std::thread::spawn(move || {
27814      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
27815    });
27816    wait_until("MultiThread shutdown to close the blocking queue", || {
27817      executor
27818        .upgrade()
27819        .expect("the retiring executor must remain alive until shutdown completes")
27820        .blocking_queue
27821        .lock()
27822        .unwrap_or_else(std::sync::PoisonError::into_inner)
27823        .closed
27824    });
27825    release_submission_tx.send(()).unwrap();
27826
27827    let (drop_generation, active_during_drop) = drop_entered_rx
27828      .recv_timeout(Duration::from_secs(2))
27829      .expect("the closed queue must destroy the accepted blocking closure");
27830    assert_eq!(drop_generation, Some(old_generation));
27831    assert_eq!(
27832      active_during_drop, 1,
27833      "accepted generation work must remain registered through blocking closure destruction"
27834    );
27835
27836    let (start_tx, start_rx) = mpsc::channel();
27837    let start_controller = Arc::clone(&controller);
27838    let starter = std::thread::spawn(move || {
27839      start_tx.send(start_controller.start()).unwrap();
27840    });
27841    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
27842    let early_start = start_rx.recv_timeout(Duration::from_millis(200)).ok();
27843    let shutdown_overlapped = early_shutdown.is_some();
27844    let restart_overlapped = early_start.is_some();
27845
27846    release_drop_tx.send(()).unwrap();
27847    submitter.join().unwrap();
27848    let shutdown_result = early_shutdown.unwrap_or_else(|| {
27849      shutdown_rx
27850        .recv_timeout(Duration::from_secs(2))
27851        .expect("shutdown must finish after drop")
27852    });
27853    shutdown_result.unwrap();
27854    shutdown.join().unwrap();
27855    let start_result = early_start.unwrap_or_else(|| {
27856      start_rx
27857        .recv_timeout(Duration::from_secs(2))
27858        .expect("restart must follow shutdown")
27859    });
27860    start_result.unwrap();
27861    starter.join().unwrap();
27862
27863    assert!(
27864      !shutdown_overlapped,
27865      "shutdown must not publish Stopped while accepted old-generation blocking captures are being destroyed"
27866    );
27867    assert!(
27868      !restart_overlapped,
27869      "restart must not create a new generation while accepted old-generation blocking captures are being destroyed"
27870    );
27871    assert_ne!(controller.backend().generation(), old_generation);
27872    controller.shutdown().unwrap();
27873  }
27874
27875  #[cfg(not(target_family = "wasm"))]
27876  #[test]
27877  fn multi_thread_controller_reports_its_exact_physical_worker_count() {
27878    let controller = multi_thread_controller("lifecycle-physical", 1, 8);
27879    let options = controller.options();
27880    assert_eq!((options.worker_threads, options.max_blocking_tasks), (2, 1));
27881
27882    let backend = controller.backend();
27883    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27884      panic!("configured MultiThread backend must create a Rayon executor");
27885    };
27886    assert_eq!(executor.pool.current_num_threads(), options.worker_threads);
27887    let lifecycle = Arc::clone(&executor.worker_lifecycle);
27888    drop(backend);
27889
27890    controller.shutdown().expect("runtime must stop");
27891    assert_eq!(
27892      lifecycle.remaining(),
27893      0,
27894      "shutdown must observe every physical worker exit"
27895    );
27896  }
27897
27898  #[cfg(not(target_family = "wasm"))]
27899  #[test]
27900  fn multi_thread_shutdown_waits_for_worker_tls_destructors() {
27901    // Invariant: `shutdown()`'s worker retirement is a JOIN barrier that
27902    // includes worker TLS-destructor completion -- `wait_for_all_workers`
27903    // joins the OS threads, and an OS thread only becomes joinable after its
27904    // TLS destructors ran. Proven by RECORDING (an atomic bump) from each
27905    // worker's destructor and sampling the counter on the shutdown thread at
27906    // the very instant `shutdown()` returns: a barrier that excluded the
27907    // destructors would race that sample and fail loudly.
27908    //
27909    // The destructor must never do more than record. The previous shape
27910    // parked it on a condvar until the harness released it -- on Windows TLS
27911    // destructors run under the process loader lock, so the first parked
27912    // worker blocked the second from even ENTERING its destructor, the
27913    // release condition (both entered) became unsatisfiable, and the parked
27914    // destructor froze every thread spawn/exit in the process: the
27915    // whole-suite silent CI wedge (see the LOADER-LOCK RULE at
27916    // `WorkerTlsDropAction`).
27917    use std::sync::mpsc;
27918
27919    let controller = Arc::new(multi_thread_controller("lifecycle-worker-tls", 2, 1));
27920    let backend = controller.backend();
27921    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27922      panic!("configured MultiThread backend must create a Rayon executor");
27923    };
27924    let worker_count = executor.pool.current_num_threads();
27925    let completed = Arc::new(AtomicUsize::new(0));
27926    let probe_completed = Arc::clone(&completed);
27927    executor.pool.broadcast(move |_| {
27928      WORKER_TLS_DROP_PROBE.with(|slot| {
27929        let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
27930          action: Some(WorkerTlsDropAction::Record {
27931            completed: Arc::clone(&probe_completed),
27932          }),
27933        });
27934        assert!(
27935          previous.is_none(),
27936          "each worker must install exactly one TLS drop probe"
27937        );
27938      });
27939    });
27940    drop(backend);
27941
27942    assert_eq!(
27943      completed.load(Ordering::SeqCst),
27944      0,
27945      "no worker TLS destructor may run before shutdown retires the workers"
27946    );
27947
27948    let (shutdown_tx, shutdown_rx) = mpsc::channel();
27949    let shutdown_controller = Arc::clone(&controller);
27950    let sample_completed = Arc::clone(&completed);
27951    let shutdown = std::thread::spawn(move || {
27952      let result = shutdown_controller.shutdown();
27953      // Sample on the SAME thread, before any further synchronization could
27954      // mask a barrier gap: at this instant every destructor must already
27955      // have recorded itself.
27956      let seen_at_return = sample_completed.load(Ordering::SeqCst);
27957      shutdown_tx.send((result, seen_at_return)).unwrap();
27958    });
27959    let (shutdown_result, seen_at_return) = shutdown_rx
27960      .recv_timeout(Duration::from_secs(10))
27961      .expect("shutdown must finish once worker TLS destructors have run");
27962    shutdown_result.unwrap();
27963    assert_eq!(
27964      seen_at_return, worker_count,
27965      "every worker TLS destructor must have completed before shutdown() returned: \
27966       the worker-join barrier must include TLS-destructor completion"
27967    );
27968    join_within(
27969      "the shutdown thread exits after teardown",
27970      shutdown,
27971      Duration::from_secs(30),
27972    );
27973  }
27974
27975  #[cfg(not(target_family = "wasm"))]
27976  #[test]
27977  fn worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join() {
27978    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_WORKER_TLS_LIFECYCLE_REENTRY_CHILD";
27979
27980    if std::env::var_os(CHILD_ENV).is_some() {
27981      use std::sync::mpsc;
27982
27983      let controller = Arc::new(multi_thread_controller("worker-tls-reentry", 2, 1));
27984      let backend = controller.backend();
27985      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
27986        panic!("configured MultiThread backend must create a Rayon executor");
27987      };
27988      let installed = Arc::new(AtomicBool::new(false));
27989      let (completed_tx, completed_rx) = mpsc::channel();
27990      let probe_controller = Arc::clone(&controller);
27991      let probe_installed = Arc::clone(&installed);
27992      executor.pool.broadcast(move |_| {
27993        if probe_installed.swap(true, Ordering::SeqCst) {
27994          return;
27995        }
27996        WORKER_TLS_DROP_PROBE.with(|slot| {
27997          let previous = slot.borrow_mut().replace(WorkerTlsDropProbe {
27998            action: Some(WorkerTlsDropAction::ReenterLifecycle {
27999              controller: Arc::clone(&probe_controller),
28000              completed: completed_tx.clone(),
28001            }),
28002          });
28003          assert!(
28004            previous.is_none(),
28005            "the selected worker must have no prior TLS probe"
28006          );
28007        });
28008      });
28009      assert!(
28010        installed.load(Ordering::SeqCst),
28011        "one worker must install the lifecycle probe"
28012      );
28013      drop(backend);
28014
28015      controller
28016        .shutdown()
28017        .expect("external shutdown must join the retiring workers");
28018      let (start, shutdown) = completed_rx
28019        .recv_timeout(Duration::from_secs(2))
28020        .expect("the TLS destructor must complete both lifecycle reentry attempts");
28021      assert_eq!(
28022        start.unwrap_err(),
28023        "cannot restart the async runtime from work in the generation being stopped"
28024      );
28025      assert_eq!(
28026        shutdown.unwrap_err(),
28027        "cannot wait for async runtime shutdown from work in the generation being stopped"
28028      );
28029      return;
28030    }
28031
28032    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
28033      .arg("--exact")
28034      .arg(
28035        "async_runtime::tests::worker_tls_destructor_lifecycle_reentry_fails_fast_without_self_join",
28036      )
28037      .arg("--nocapture")
28038      .env(CHILD_ENV, "1")
28039      .stdout(std::process::Stdio::piped())
28040      .stderr(std::process::Stdio::piped())
28041      .spawn()
28042      .expect("the TLS lifecycle-reentry subprocess must start");
28043    let deadline = Instant::now() + Duration::from_secs(5);
28044    let status = loop {
28045      if let Some(status) = child
28046        .try_wait()
28047        .expect("the subprocess status must be readable")
28048      {
28049        break status;
28050      }
28051      if Instant::now() >= deadline {
28052        let _ = child.kill();
28053        let output = child
28054          .wait_with_output()
28055          .expect("the timed-out subprocess must be reaped");
28056        panic!(
28057          "worker TLS lifecycle reentry deadlocked shutdown\nstdout={}\nstderr={}",
28058          String::from_utf8_lossy(&output.stdout),
28059          String::from_utf8_lossy(&output.stderr)
28060        );
28061      }
28062      std::thread::sleep(Duration::from_millis(10));
28063    };
28064    let output = child
28065      .wait_with_output()
28066      .expect("the completed subprocess output must be read");
28067    assert!(
28068      status.success(),
28069      "worker TLS lifecycle reentry must fail fast; status={:?}\nstdout={}\nstderr={}",
28070      status.code(),
28071      String::from_utf8_lossy(&output.stdout),
28072      String::from_utf8_lossy(&output.stderr)
28073    );
28074  }
28075
28076  #[cfg(not(target_family = "wasm"))]
28077  #[test]
28078  fn multi_thread_shutdown_cancels_an_accepted_pending_task() {
28079    use std::sync::mpsc;
28080
28081    struct DropSignal(mpsc::Sender<()>);
28082    impl Drop for DropSignal {
28083      fn drop(&mut self) {
28084        let _ = self.0.send(());
28085      }
28086    }
28087
28088    let controller = multi_thread_controller("lifecycle-cancel", 2, 1);
28089    let (polled_tx, polled_rx) = mpsc::channel();
28090    let (dropped_tx, dropped_rx) = mpsc::channel();
28091    controller
28092      .try_spawn_detached(async move {
28093        let _drop_signal = DropSignal(dropped_tx);
28094        polled_tx.send(()).unwrap();
28095        std::future::pending::<()>().await;
28096      })
28097      .unwrap_or_else(|_| panic!("running runtime must accept the pending task"));
28098    polled_rx
28099      .recv_timeout(Duration::from_secs(5))
28100      .expect("the pending task must be polled before shutdown");
28101
28102    controller
28103      .shutdown()
28104      .expect("shutdown must cancel accepted async work");
28105    dropped_rx
28106      .recv_timeout(Duration::from_secs(5))
28107      .expect("shutdown must drop the accepted task future before returning");
28108  }
28109
28110  #[cfg(not(target_family = "wasm"))]
28111  #[test]
28112  fn current_thread_shutdown_outranks_perpetual_self_wake() {
28113    use std::sync::mpsc;
28114
28115    struct SelfWakingPending {
28116      entered: Option<mpsc::Sender<()>>,
28117    }
28118
28119    impl Future for SelfWakingPending {
28120      type Output = ();
28121
28122      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28123        if let Some(entered) = self.entered.take() {
28124          entered.send(()).unwrap();
28125        }
28126        cx.waker().wake_by_ref();
28127        Poll::Pending
28128      }
28129    }
28130
28131    let controller = Arc::new(current_thread_controller(
28132      "current-thread-self-wake-shutdown",
28133    ));
28134    let (entered_tx, entered_rx) = mpsc::channel();
28135    let (outcome_tx, outcome_rx) = mpsc::channel();
28136    let block_on_controller = Arc::clone(&controller);
28137    let runner = std::thread::spawn(move || {
28138      let mut future = std::pin::pin!(SelfWakingPending {
28139        entered: Some(entered_tx)
28140      });
28141      outcome_tx
28142        .send(block_on_controller.block_on(future.as_mut()))
28143        .unwrap();
28144    });
28145    entered_rx
28146      .recv_timeout(Duration::from_secs(2))
28147      .expect("the self-waking future must be polled before shutdown");
28148
28149    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28150    let shutdown_controller = Arc::clone(&controller);
28151    let shutdown = std::thread::spawn(move || {
28152      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28153    });
28154    shutdown_rx
28155      .recv_timeout(Duration::from_secs(2))
28156      .expect("shutdown must outrank a stored self-wake permit")
28157      .unwrap();
28158    assert_eq!(
28159      outcome_rx
28160        .recv_timeout(Duration::from_secs(2))
28161        .expect("the raw block_on driver must report shutdown"),
28162      BlockOnOutcome::Stopped
28163    );
28164    runner.join().unwrap();
28165    shutdown.join().unwrap();
28166  }
28167
28168  #[cfg(not(target_family = "wasm"))]
28169  #[test]
28170  fn multi_thread_fifo_claim_after_stop_publication_cancels_without_polling() {
28171    use std::sync::mpsc;
28172
28173    let controller = multi_thread_controller("stopped-fifo-claim", 2, 1);
28174    let backend = controller.backend();
28175    let executor = match &backend.executor {
28176      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28177      RuntimeExecutor::CurrentThread(_) => unreachable!(),
28178    };
28179    let release_workers = Arc::new((Mutex::new(false), Condvar::new()));
28180    let (worker_ready_tx, worker_ready_rx) = mpsc::channel();
28181    for _ in 0..2 {
28182      let worker_ready_tx = worker_ready_tx.clone();
28183      let release_workers = Arc::clone(&release_workers);
28184      executor.pool.spawn(move || {
28185        worker_ready_tx.send(()).unwrap();
28186        let (released, changed) = &*release_workers;
28187        let mut released = released
28188          .lock()
28189          .unwrap_or_else(std::sync::PoisonError::into_inner);
28190        while !*released {
28191          released = changed
28192            .wait(released)
28193            .unwrap_or_else(std::sync::PoisonError::into_inner);
28194        }
28195      });
28196    }
28197    drop(worker_ready_tx);
28198    for _ in 0..2 {
28199      worker_ready_rx
28200        .recv_timeout(Duration::from_secs(5))
28201        .expect("both workers must be occupied before the FIFO runnable is submitted");
28202    }
28203    drop(executor);
28204    drop(backend);
28205
28206    let polls = Arc::new(AtomicUsize::new(0));
28207    let handle = controller.spawn(StopPollProbe {
28208      polls: Arc::clone(&polls),
28209      first_poll: None,
28210    });
28211    wait_until("the FIFO runnable to be queued", || {
28212      controller.metrics.queued_runnables.load(Ordering::SeqCst) == 1
28213    });
28214
28215    let observed_queue = Arc::new(Mutex::new(None));
28216    let hook_observed_queue = Arc::clone(&observed_queue);
28217    let hook_metrics = Arc::clone(&controller.metrics);
28218    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28219      *slot.borrow_mut() = Some(Box::new(move || {
28220        let (released, changed) = &*release_workers;
28221        *released
28222          .lock()
28223          .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
28224        changed.notify_all();
28225        wait_until("the stopped FIFO runnable claim", || {
28226          hook_metrics.queued_runnables.load(Ordering::SeqCst) == 0
28227        });
28228        *hook_observed_queue
28229          .lock()
28230          .unwrap_or_else(std::sync::PoisonError::into_inner) =
28231          Some(hook_metrics.queued_runnables.load(Ordering::SeqCst));
28232      }));
28233    });
28234
28235    controller
28236      .shutdown()
28237      .expect("shutdown must cancel the stopped FIFO claim");
28238    assert_eq!(polls.load(Ordering::SeqCst), 0);
28239    assert_eq!(
28240      *observed_queue
28241        .lock()
28242        .unwrap_or_else(std::sync::PoisonError::into_inner),
28243      Some(0)
28244    );
28245    assert_eq!(
28246      controller.metrics.queued_runnables.load(Ordering::SeqCst),
28247      0
28248    );
28249    drop(handle);
28250  }
28251
28252  #[cfg(not(target_family = "wasm"))]
28253  #[test]
28254  fn multi_thread_lifo_claim_after_stop_publication_cancels_without_polling() {
28255    use std::sync::mpsc;
28256
28257    let controller = multi_thread_controller("stopped-lifo-claim", 2, 1);
28258    let backend = controller.backend();
28259    let executor = match &backend.executor {
28260      RuntimeExecutor::MultiThread(executor) => Arc::clone(executor),
28261      RuntimeExecutor::CurrentThread(_) => unreachable!(),
28262    };
28263    let polls = Arc::new(AtomicUsize::new(0));
28264    let scheduler = Arc::downgrade(&executor);
28265    let (runnable, task) = async_task::spawn(
28266      StopPollProbe {
28267        polls: Arc::clone(&polls),
28268        first_poll: None,
28269      },
28270      move |runnable| {
28271        if let Some(executor) = scheduler.upgrade() {
28272          executor.schedule(runnable);
28273        }
28274      },
28275    );
28276    let (slot_ready_tx, slot_ready_rx) = mpsc::channel();
28277    let (release_slot_tx, release_slot_rx) = mpsc::channel();
28278    let (claim_done_tx, claim_done_rx) = mpsc::channel();
28279    let worker_executor = Arc::clone(&executor);
28280    let worker_metrics = Arc::clone(&controller.metrics);
28281    executor.pool.spawn(move || {
28282      let _driver = OnPoolWorkerGuard::enter(worker_executor.id);
28283      worker_executor.schedule(runnable);
28284      slot_ready_tx.send(()).unwrap();
28285      release_slot_rx.recv().unwrap();
28286      let claimed = worker_executor.run_one_runnable();
28287      claim_done_tx
28288        .send((
28289          claimed,
28290          worker_metrics.queued_runnables.load(Ordering::SeqCst),
28291        ))
28292        .unwrap();
28293    });
28294    slot_ready_rx
28295      .recv_timeout(Duration::from_secs(5))
28296      .expect("the worker must publish the LIFO runnable before shutdown");
28297    assert_eq!(
28298      controller.metrics.queued_runnables.load(Ordering::SeqCst),
28299      1
28300    );
28301    drop(executor);
28302    drop(backend);
28303
28304    let observed_claim = Arc::new(Mutex::new(None));
28305    let hook_observed_claim = Arc::clone(&observed_claim);
28306    AFTER_GENERATION_STOP_PUBLICATION_TEST_HOOK.with(|slot| {
28307      *slot.borrow_mut() = Some(Box::new(move || {
28308        release_slot_tx.send(()).unwrap();
28309        let result = claim_done_rx
28310          .recv_timeout(Duration::from_secs(5))
28311          .expect("the stopped LIFO runnable claim must complete before abort");
28312        *hook_observed_claim
28313          .lock()
28314          .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
28315      }));
28316    });
28317
28318    controller
28319      .shutdown()
28320      .expect("shutdown must cancel the stopped LIFO claim");
28321    assert_eq!(polls.load(Ordering::SeqCst), 0);
28322    assert_eq!(
28323      *observed_claim
28324        .lock()
28325        .unwrap_or_else(std::sync::PoisonError::into_inner),
28326      Some((true, 0))
28327    );
28328    assert_eq!(
28329      controller.metrics.queued_runnables.load(Ordering::SeqCst),
28330      0
28331    );
28332    drop(task);
28333  }
28334
28335  #[cfg(not(target_family = "wasm"))]
28336  #[test]
28337  fn current_thread_shutdown_publishes_stop_before_queued_destructor_cleanup() {
28338    use std::sync::mpsc;
28339
28340    struct BlockingDrop {
28341      entered: mpsc::Sender<()>,
28342      release: mpsc::Receiver<()>,
28343    }
28344
28345    impl Drop for BlockingDrop {
28346      fn drop(&mut self) {
28347        self.entered.send(()).unwrap();
28348        self.release.recv().unwrap();
28349      }
28350    }
28351
28352    struct TwoPollProbe {
28353      first_poll: Option<mpsc::Sender<()>>,
28354      release_first_poll: mpsc::Receiver<()>,
28355      second_poll: mpsc::Sender<()>,
28356    }
28357
28358    impl Future for TwoPollProbe {
28359      type Output = ();
28360
28361      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
28362        if let Some(first_poll) = self.first_poll.take() {
28363          first_poll.send(()).unwrap();
28364          self.release_first_poll.recv().unwrap();
28365        } else {
28366          self.second_poll.send(()).unwrap();
28367        }
28368        cx.waker().wake_by_ref();
28369        Poll::Pending
28370      }
28371    }
28372
28373    let controller = Arc::new(current_thread_controller("shutdown-stop-before-cleanup"));
28374    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28375    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28376    let blocking_drop = BlockingDrop {
28377      entered: drop_entered_tx,
28378      release: release_drop_rx,
28379    };
28380    controller
28381      .try_spawn_detached(async move {
28382        let _drop = blocking_drop;
28383        std::future::pending::<()>().await;
28384      })
28385      .unwrap_or_else(|_| panic!("the cleanup probe must be accepted"));
28386
28387    let (first_poll_tx, first_poll_rx) = mpsc::channel();
28388    let (release_first_poll_tx, release_first_poll_rx) = mpsc::channel();
28389    let (second_poll_tx, second_poll_rx) = mpsc::channel();
28390    let (outcome_tx, outcome_rx) = mpsc::channel();
28391    let runner_controller = Arc::clone(&controller);
28392    let runner = std::thread::spawn(move || {
28393      let mut future = std::pin::pin!(TwoPollProbe {
28394        first_poll: Some(first_poll_tx),
28395        release_first_poll: release_first_poll_rx,
28396        second_poll: second_poll_tx,
28397      });
28398      outcome_tx
28399        .send(runner_controller.block_on(future.as_mut()))
28400        .unwrap();
28401    });
28402    first_poll_rx
28403      .recv_timeout(Duration::from_secs(2))
28404      .expect("the active block_on poll must enter before shutdown");
28405
28406    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28407    let shutdown_controller = Arc::clone(&controller);
28408    let shutdown = std::thread::spawn(move || {
28409      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28410    });
28411    drop_entered_rx
28412      .recv_timeout(Duration::from_secs(2))
28413      .expect("shutdown must begin destroying queued work");
28414
28415    release_first_poll_tx.send(()).unwrap();
28416    let polled_again = second_poll_rx
28417      .recv_timeout(Duration::from_millis(200))
28418      .is_ok();
28419    release_drop_tx.send(()).unwrap();
28420
28421    assert!(
28422      !polled_again,
28423      "an active block_on driver must observe generation stop before queued user cleanup blocks"
28424    );
28425    assert_eq!(
28426      outcome_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28427      BlockOnOutcome::Stopped
28428    );
28429    shutdown_rx
28430      .recv_timeout(Duration::from_secs(2))
28431      .unwrap()
28432      .unwrap();
28433    runner.join().unwrap();
28434    shutdown.join().unwrap();
28435  }
28436
28437  #[cfg(not(target_family = "wasm"))]
28438  #[test]
28439  fn shutdown_cancels_nested_block_on_within_deadline() {
28440    use std::sync::mpsc;
28441
28442    let controller = Arc::new(multi_thread_controller("nested-block-on-shutdown", 2, 1));
28443    let task_controller = Arc::clone(&controller);
28444    let (entered_tx, entered_rx) = mpsc::channel();
28445    let task = controller
28446      .try_spawn(async move {
28447        entered_tx.send(()).unwrap();
28448        block_on_with_controller(&task_controller, std::future::pending::<()>());
28449      })
28450      .unwrap_or_else(|_| panic!("runtime must accept the nested block_on task"));
28451    entered_rx.recv_timeout(Duration::from_secs(5)).unwrap();
28452
28453    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28454    let shutdown_controller = Arc::clone(&controller);
28455    let shutdown = std::thread::spawn(move || {
28456      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28457    });
28458    shutdown_rx
28459      .recv_timeout(Duration::from_secs(2))
28460      .expect("shutdown must wake and cancel an active nested block_on")
28461      .unwrap();
28462    shutdown.join().unwrap();
28463    assert!(
28464      futures::executor::block_on(task).is_err(),
28465      "the cancelled nested block_on task must resolve as stopped"
28466    );
28467  }
28468
28469  #[cfg(not(target_family = "wasm"))]
28470  #[test]
28471  fn stopped_public_block_on_drops_future_before_generation_retirement() {
28472    use std::sync::mpsc;
28473
28474    struct PendingDrop {
28475      entered: Option<mpsc::Sender<()>>,
28476      dropped: mpsc::Sender<Option<u64>>,
28477      release: mpsc::Receiver<()>,
28478    }
28479
28480    impl Future for PendingDrop {
28481      type Output = ();
28482
28483      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28484        if let Some(entered) = self.entered.take() {
28485          entered.send(()).unwrap();
28486        }
28487        Poll::Pending
28488      }
28489    }
28490
28491    impl Drop for PendingDrop {
28492      fn drop(&mut self) {
28493        self
28494          .dropped
28495          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
28496          .unwrap();
28497        self.release.recv().unwrap();
28498      }
28499    }
28500
28501    let controller = Arc::new(current_thread_controller(
28502      "public-block-on-generation-destruction",
28503    ));
28504    let generation = controller.backend().generation();
28505    let (entered_tx, entered_rx) = mpsc::channel();
28506    let (dropped_tx, dropped_rx) = mpsc::channel();
28507    let (release_tx, release_rx) = mpsc::channel();
28508    let (runner_done_tx, runner_done_rx) = mpsc::channel();
28509    let block_controller = Arc::clone(&controller);
28510    let runner = std::thread::spawn(move || {
28511      let result = catch_unwind(AssertUnwindSafe(|| {
28512        block_on_with_controller(
28513          &block_controller,
28514          PendingDrop {
28515            entered: Some(entered_tx),
28516            dropped: dropped_tx,
28517            release: release_rx,
28518          },
28519        );
28520      }));
28521      runner_done_tx.send(result.is_err()).unwrap();
28522    });
28523    entered_rx
28524      .recv_timeout(Duration::from_secs(2))
28525      .expect("the public block_on future must be polled before shutdown");
28526
28527    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28528    let shutdown_controller = Arc::clone(&controller);
28529    let shutdown = std::thread::spawn(move || {
28530      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28531    });
28532
28533    assert_eq!(
28534      dropped_rx
28535        .recv_timeout(Duration::from_secs(2))
28536        .expect("shutdown must wake block_on and begin destroying its future"),
28537      Some(generation),
28538      "the stopped future must be destroyed under its retiring generation identity"
28539    );
28540    assert!(
28541      shutdown_rx
28542        .recv_timeout(Duration::from_millis(200))
28543        .is_err(),
28544      "shutdown must not publish Stopped while the public block_on future destructor is running"
28545    );
28546
28547    release_tx.send(()).unwrap();
28548    assert!(
28549      runner_done_rx.recv_timeout(Duration::from_secs(2)).unwrap(),
28550      "the public helper must retain its existing early-stop panic behavior"
28551    );
28552    shutdown_rx
28553      .recv_timeout(Duration::from_secs(2))
28554      .unwrap()
28555      .unwrap();
28556    runner.join().unwrap();
28557    shutdown.join().unwrap();
28558  }
28559
28560  #[cfg(not(target_family = "wasm"))]
28561  #[test]
28562  fn shutdown_contains_panicking_nested_block_on_future_drop() {
28563    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_NESTED_BLOCK_ON_DROP_PANIC_CHILD";
28564
28565    if std::env::var_os(CHILD_ENV).is_some() {
28566      use std::sync::mpsc;
28567
28568      struct PendingPanicOnDrop {
28569        entered: Option<mpsc::Sender<()>>,
28570      }
28571
28572      impl Future for PendingPanicOnDrop {
28573        type Output = ();
28574
28575        fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
28576          if let Some(entered) = self.entered.take() {
28577            entered.send(()).unwrap();
28578          }
28579          Poll::Pending
28580        }
28581      }
28582
28583      impl Drop for PendingPanicOnDrop {
28584        fn drop(&mut self) {
28585          panic!("intentional nested block_on future destructor panic");
28586        }
28587      }
28588
28589      let controller = Arc::new(multi_thread_controller("nested-block-on-drop-panic", 2, 1));
28590      let task_controller = Arc::clone(&controller);
28591      let (entered_tx, entered_rx) = mpsc::channel();
28592      let task = controller
28593        .try_spawn(async move {
28594          block_on_with_controller(
28595            &task_controller,
28596            PendingPanicOnDrop {
28597              entered: Some(entered_tx),
28598            },
28599          );
28600        })
28601        .unwrap_or_else(|_| panic!("the runtime must accept the nested block_on task"));
28602      entered_rx
28603        .recv_timeout(Duration::from_secs(2))
28604        .expect("the nested block_on future must be polled before shutdown");
28605
28606      controller
28607        .shutdown()
28608        .expect("shutdown must contain nested future destruction");
28609      assert!(
28610        futures::executor::block_on(task).is_err(),
28611        "shutdown cancellation must still surface as a task error"
28612      );
28613      return;
28614    }
28615
28616    let output = std::process::Command::new(std::env::current_exe().unwrap())
28617      .arg("--exact")
28618      .arg("async_runtime::tests::shutdown_contains_panicking_nested_block_on_future_drop")
28619      .arg("--nocapture")
28620      .env(CHILD_ENV, "1")
28621      .output()
28622      .expect("the subprocess test must start");
28623    assert!(
28624      output.status.success(),
28625      "shutdown must not abort while destroying a hostile nested block_on future; status={:?}\nstdout={}\nstderr={}",
28626      output.status.code(),
28627      String::from_utf8_lossy(&output.stdout),
28628      String::from_utf8_lossy(&output.stderr)
28629    );
28630  }
28631
28632  #[test]
28633  fn panicking_future_drop_is_contained_before_async_task_drop() {
28634    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_FUTURE_DROP_CHILD";
28635
28636    if std::env::var_os(CHILD_ENV).is_some() {
28637      struct PanicOnDrop;
28638      impl Drop for PanicOnDrop {
28639        fn drop(&mut self) {
28640          panic!("intentional future destructor panic");
28641        }
28642      }
28643
28644      let controller = current_thread_controller("future-drop-subprocess");
28645      let bomb = PanicOnDrop;
28646      controller
28647        .try_spawn_detached(async move {
28648          let _bomb = bomb;
28649          std::future::pending::<()>().await;
28650        })
28651        .unwrap_or_else(|_| panic!("runtime must accept the pending task"));
28652      controller
28653        .shutdown()
28654        .expect("shutdown must contain the future destructor panic");
28655      return;
28656    }
28657
28658    let output = std::process::Command::new(std::env::current_exe().unwrap())
28659      .arg("--exact")
28660      .arg("async_runtime::tests::panicking_future_drop_is_contained_before_async_task_drop")
28661      .arg("--nocapture")
28662      .env(CHILD_ENV, "1")
28663      .output()
28664      .expect("the subprocess test must start");
28665    assert!(
28666      output.status.success(),
28667      "panicking future destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
28668      output.status.code(),
28669      String::from_utf8_lossy(&output.stdout),
28670      String::from_utf8_lossy(&output.stderr)
28671    );
28672  }
28673
28674  #[test]
28675  fn panicking_detached_output_drop_is_contained_before_async_task_drop() {
28676    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_PANICKING_TASK_OUTPUT_CHILD";
28677
28678    if std::env::var_os(CHILD_ENV).is_some() {
28679      struct PanicOnDrop;
28680      impl Drop for PanicOnDrop {
28681        fn drop(&mut self) {
28682          panic!("intentional detached output destructor panic");
28683        }
28684      }
28685
28686      let controller = current_thread_controller("output-drop-subprocess");
28687      let handle = controller
28688        .try_spawn(async { PanicOnDrop })
28689        .unwrap_or_else(|_| panic!("runtime must accept the task"));
28690      drop(handle);
28691      let dispatch = controller_current_thread_dispatch(&controller);
28692      controller.drive_current_thread_dispatch(dispatch, false);
28693      controller.shutdown().unwrap();
28694      return;
28695    }
28696
28697    let output = std::process::Command::new(std::env::current_exe().unwrap())
28698      .arg("--exact")
28699      .arg(
28700        "async_runtime::tests::panicking_detached_output_drop_is_contained_before_async_task_drop",
28701      )
28702      .arg("--nocapture")
28703      .env(CHILD_ENV, "1")
28704      .output()
28705      .expect("the subprocess test must start");
28706    assert!(
28707      output.status.success(),
28708      "panicking detached output destruction must not trigger async-task exit 134; status={:?}\nstdout={}\nstderr={}",
28709      output.status.code(),
28710      String::from_utf8_lossy(&output.stdout),
28711      String::from_utf8_lossy(&output.stderr)
28712    );
28713  }
28714
28715  #[cfg(not(target_family = "wasm"))]
28716  #[test]
28717  fn current_thread_shutdown_waits_for_host_driven_detached_output_drop() {
28718    use std::sync::mpsc;
28719
28720    struct BlockingDrop {
28721      controller: Arc<RuntimeController>,
28722      entered: mpsc::Sender<Option<u64>>,
28723      release: mpsc::Receiver<()>,
28724      reentrant_shutdown: mpsc::Sender<Result<(), RuntimeConfigError>>,
28725    }
28726
28727    impl Drop for BlockingDrop {
28728      fn drop(&mut self) {
28729        self
28730          .entered
28731          .send(ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get))
28732          .unwrap();
28733        self.release.recv().unwrap();
28734        self
28735          .reentrant_shutdown
28736          .send(self.controller.shutdown())
28737          .unwrap();
28738      }
28739    }
28740
28741    let controller = Arc::new(current_thread_controller(
28742      "current-thread-host-drain-output-retirement",
28743    ));
28744    let old_generation = controller.backend().generation();
28745    let (entered_tx, entered_rx) = mpsc::channel();
28746    let (release_tx, release_rx) = mpsc::channel();
28747    let (reentrant_tx, reentrant_rx) = mpsc::channel();
28748    let output_controller = Arc::clone(&controller);
28749    let handle = controller
28750      .try_spawn(async move {
28751        BlockingDrop {
28752          controller: output_controller,
28753          entered: entered_tx,
28754          release: release_rx,
28755          reentrant_shutdown: reentrant_tx,
28756        }
28757      })
28758      .unwrap_or_else(|_| panic!("the CurrentThread runtime must accept the detached task"));
28759    drop(handle);
28760    let dispatch = controller_current_thread_dispatch(&controller);
28761
28762    let driver_controller = Arc::clone(&controller);
28763    let driver =
28764      std::thread::spawn(move || driver_controller.drive_current_thread_dispatch(dispatch, false));
28765    assert_eq!(
28766      entered_rx
28767        .recv_timeout(Duration::from_secs(2))
28768        .expect("the host turn must begin destroying the detached output"),
28769      Some(old_generation),
28770      "detached output destruction must retain the generation that produced it"
28771    );
28772
28773    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28774    let shutdown_controller = Arc::clone(&controller);
28775    let shutdown = std::thread::spawn(move || {
28776      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28777    });
28778    wait_until(
28779      "CurrentThread shutdown to enter the old generation's stopping state",
28780      || {
28781        let state = controller
28782          .state
28783          .lock()
28784          .unwrap_or_else(std::sync::PoisonError::into_inner);
28785        matches!(
28786          &state.lifecycle,
28787          RuntimeLifecycle::Stopping(identity) if identity.generation == old_generation
28788        )
28789      },
28790    );
28791
28792    let (restart_tx, restart_rx) = mpsc::channel();
28793    let restart_controller = Arc::clone(&controller);
28794    let restart = std::thread::spawn(move || {
28795      restart_controller.start().unwrap();
28796      restart_tx
28797        .send(restart_controller.backend().generation())
28798        .unwrap();
28799    });
28800
28801    let early_shutdown = shutdown_rx.recv_timeout(Duration::from_millis(200)).ok();
28802    let early_restart = restart_rx.recv_timeout(Duration::from_millis(200)).ok();
28803    let shutdown_overlapped = early_shutdown.is_some();
28804    let restart_overlapped = early_restart.is_some();
28805    release_tx.send(()).unwrap();
28806
28807    let reentrant_error = reentrant_rx
28808      .recv_timeout(Duration::from_secs(2))
28809      .expect("the retiring output destructor must finish its lifecycle check")
28810      .expect_err("old-generation output destruction must not shut down a replacement runtime");
28811    assert!(
28812      reentrant_error
28813        .to_string()
28814        .contains("generation being stopped"),
28815      "the destructor must still observe its old generation as Stopping: {reentrant_error}"
28816    );
28817
28818    let shutdown_result = early_shutdown.unwrap_or_else(|| {
28819      shutdown_rx
28820        .recv_timeout(Duration::from_secs(2))
28821        .expect("shutdown must finish after detached output destruction retires")
28822    });
28823    shutdown_result.unwrap();
28824    let new_generation = early_restart.unwrap_or_else(|| {
28825      restart_rx
28826        .recv_timeout(Duration::from_secs(2))
28827        .expect("restart must finish after the old host turn retires")
28828    });
28829
28830    driver.join().unwrap();
28831    shutdown.join().unwrap();
28832    restart.join().unwrap();
28833    assert!(
28834      !shutdown_overlapped,
28835      "shutdown must not publish Stopped while a host turn is destroying detached output"
28836    );
28837    assert!(
28838      !restart_overlapped,
28839      "restart must not overlap a host turn from the retiring generation"
28840    );
28841    assert_ne!(new_generation, old_generation);
28842    controller.shutdown().unwrap();
28843  }
28844
28845  #[cfg(not(target_family = "wasm"))]
28846  #[test]
28847  fn queued_destructor_reentrant_shutdown_is_generation_rejected() {
28848    use std::sync::mpsc;
28849
28850    struct ShutdownOnDrop {
28851      controller: Arc<RuntimeController>,
28852      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
28853    }
28854
28855    impl Drop for ShutdownOnDrop {
28856      fn drop(&mut self) {
28857        let _ = self.result.send(self.controller.shutdown());
28858      }
28859    }
28860
28861    let controller = Arc::new(multi_thread_controller("reentrant-drop-shutdown", 2, 1));
28862    let (running_tx, running_rx) = mpsc::channel();
28863    let (release_tx, release_rx) = mpsc::channel();
28864    let running = controller
28865      .try_spawn_blocking(move || {
28866        running_tx.send(()).unwrap();
28867        release_rx.recv().unwrap();
28868      })
28869      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
28870    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
28871
28872    let (reentrant_tx, reentrant_rx) = mpsc::channel();
28873    let bomb = ShutdownOnDrop {
28874      controller: Arc::clone(&controller),
28875      result: reentrant_tx,
28876    };
28877    let queued = controller
28878      .try_spawn_blocking(move || {
28879        let _bomb = &bomb;
28880      })
28881      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
28882
28883    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28884    let shutdown_controller = Arc::clone(&controller);
28885    let shutdown = std::thread::spawn(move || {
28886      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28887    });
28888    let error = reentrant_rx
28889      .recv_timeout(Duration::from_secs(2))
28890      .expect("queued destruction must not self-deadlock")
28891      .expect_err("the retiring generation must reject reentrant shutdown");
28892    assert!(error.to_string().contains("generation being stopped"));
28893    release_tx.send(()).unwrap();
28894    shutdown_rx
28895      .recv_timeout(Duration::from_secs(2))
28896      .unwrap()
28897      .unwrap();
28898    shutdown.join().unwrap();
28899    futures::executor::block_on(running).unwrap();
28900    assert!(futures::executor::block_on(queued).is_err());
28901  }
28902
28903  #[cfg(not(target_family = "wasm"))]
28904  #[test]
28905  fn rejected_destructor_holds_stopping_generation_until_drop_finishes() {
28906    use std::sync::mpsc;
28907
28908    struct ShutdownAfterRelease {
28909      controller: Arc<RuntimeController>,
28910      entered: mpsc::Sender<()>,
28911      release: mpsc::Receiver<()>,
28912      result: mpsc::Sender<Result<(), RuntimeConfigError>>,
28913    }
28914
28915    impl Drop for ShutdownAfterRelease {
28916      fn drop(&mut self) {
28917        self.entered.send(()).unwrap();
28918        self.release.recv().unwrap();
28919        self.result.send(self.controller.shutdown()).unwrap();
28920      }
28921    }
28922
28923    let controller = Arc::new(multi_thread_controller("rejected-drop-generation", 2, 1));
28924    let (holder_started_tx, holder_started_rx) = mpsc::channel();
28925    let (release_holder_tx, release_holder_rx) = mpsc::channel();
28926    let holder = controller
28927      .try_spawn_blocking(move || {
28928        holder_started_tx.send(()).unwrap();
28929        release_holder_rx.recv().unwrap();
28930      })
28931      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
28932    holder_started_rx
28933      .recv_timeout(Duration::from_secs(2))
28934      .unwrap();
28935
28936    let (shutdown_tx, shutdown_rx) = mpsc::channel();
28937    let shutdown_controller = Arc::clone(&controller);
28938    let shutdown = std::thread::spawn(move || {
28939      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
28940    });
28941    wait_until("the runtime entered Stopping", || {
28942      matches!(
28943        controller
28944          .state
28945          .lock()
28946          .unwrap_or_else(std::sync::PoisonError::into_inner)
28947          .lifecycle,
28948        RuntimeLifecycle::Stopping(_)
28949      )
28950    });
28951
28952    let (drop_entered_tx, drop_entered_rx) = mpsc::channel();
28953    let (release_drop_tx, release_drop_rx) = mpsc::channel();
28954    let (reentrant_tx, reentrant_rx) = mpsc::channel();
28955    let rejected_controller = Arc::clone(&controller);
28956    let (rejected_done_tx, rejected_done_rx) = mpsc::channel();
28957    let rejected = std::thread::spawn(move || {
28958      let bomb = ShutdownAfterRelease {
28959        controller: Arc::clone(&rejected_controller),
28960        entered: drop_entered_tx,
28961        release: release_drop_rx,
28962        result: reentrant_tx,
28963      };
28964      let handle = rejected_controller.spawn(async move {
28965        let _bomb = bomb;
28966        std::future::pending::<()>().await;
28967      });
28968      rejected_done_tx
28969        .send(futures::executor::block_on(handle))
28970        .unwrap();
28971    });
28972    drop_entered_rx
28973      .recv_timeout(Duration::from_secs(2))
28974      .unwrap();
28975
28976    release_holder_tx.send(()).unwrap();
28977    assert!(
28978      shutdown_rx
28979        .recv_timeout(Duration::from_millis(200))
28980        .is_err(),
28981      "shutdown crossed into Stopped before rejected destruction finished"
28982    );
28983
28984    let (start_tx, start_rx) = mpsc::channel();
28985    let start_controller = Arc::clone(&controller);
28986    let starter = std::thread::spawn(move || {
28987      start_tx.send(start_controller.start()).unwrap();
28988    });
28989    assert!(
28990      start_rx.recv_timeout(Duration::from_millis(200)).is_err(),
28991      "restart crossed into a new generation before rejected destruction finished"
28992    );
28993
28994    release_drop_tx.send(()).unwrap();
28995    let error = reentrant_rx
28996      .recv_timeout(Duration::from_secs(2))
28997      .expect("the rejected destructor must complete")
28998      .expect_err("the retiring generation must reject the destructor's shutdown");
28999    assert!(error.to_string().contains("generation being stopped"));
29000    assert!(
29001      rejected_done_rx
29002        .recv_timeout(Duration::from_secs(2))
29003        .unwrap()
29004        .is_err()
29005    );
29006    rejected.join().unwrap();
29007
29008    shutdown_rx
29009      .recv_timeout(Duration::from_secs(2))
29010      .unwrap()
29011      .unwrap();
29012    shutdown.join().unwrap();
29013    start_rx
29014      .recv_timeout(Duration::from_secs(2))
29015      .unwrap()
29016      .unwrap();
29017    starter.join().unwrap();
29018    futures::executor::block_on(holder).unwrap();
29019    controller.shutdown().unwrap();
29020  }
29021
29022  #[cfg(not(target_family = "wasm"))]
29023  #[test]
29024  fn shutdown_contains_hostile_caught_panic_payload() {
29025    use std::{panic::panic_any, sync::mpsc};
29026
29027    struct PanicAgain;
29028    impl Drop for PanicAgain {
29029      fn drop(&mut self) {
29030        panic!("caught panic payload destructor escaped");
29031      }
29032    }
29033
29034    struct PanicPayloadOnDrop(mpsc::Sender<()>);
29035    impl Drop for PanicPayloadOnDrop {
29036      fn drop(&mut self) {
29037        let _ = self.0.send(());
29038        panic_any(PanicAgain);
29039      }
29040    }
29041
29042    let controller = Arc::new(multi_thread_controller("hostile-panic-payload", 2, 1));
29043    let (running_tx, running_rx) = mpsc::channel();
29044    let (release_tx, release_rx) = mpsc::channel();
29045    let running = controller
29046      .try_spawn_blocking(move || {
29047        running_tx.send(()).unwrap();
29048        release_rx.recv().unwrap();
29049      })
29050      .unwrap_or_else(|_| panic!("runtime must accept the blocking holder"));
29051    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29052
29053    let (drop_tx, drop_rx) = mpsc::channel();
29054    let payload = PanicPayloadOnDrop(drop_tx);
29055    let queued = controller
29056      .try_spawn_blocking(move || {
29057        let _payload = &payload;
29058      })
29059      .unwrap_or_else(|_| panic!("runtime must accept queued blocking work"));
29060
29061    let (shutdown_tx, shutdown_rx) = mpsc::channel();
29062    let shutdown_controller = Arc::clone(&controller);
29063    let shutdown = std::thread::spawn(move || {
29064      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
29065    });
29066    drop_rx.recv_timeout(Duration::from_secs(2)).unwrap();
29067    release_tx.send(()).unwrap();
29068    shutdown_rx
29069      .recv_timeout(Duration::from_secs(2))
29070      .expect("hostile panic payload destruction must remain contained")
29071      .unwrap();
29072    shutdown
29073      .join()
29074      .expect("the caught payload must not panic during destruction");
29075    futures::executor::block_on(running).unwrap();
29076    assert!(futures::executor::block_on(queued).is_err());
29077  }
29078
29079  #[cfg(not(target_family = "wasm"))]
29080  #[test]
29081  fn multi_thread_shutdown_drops_queued_blocking_and_waits_for_running_blocking() {
29082    use std::sync::mpsc;
29083
29084    struct DropSignal(mpsc::Sender<()>);
29085    impl Drop for DropSignal {
29086      fn drop(&mut self) {
29087        let _ = self.0.send(());
29088      }
29089    }
29090
29091    let controller = Arc::new(multi_thread_controller("lifecycle-blocking", 2, 1));
29092    let (running_tx, running_rx) = mpsc::channel();
29093    let (release_tx, release_rx) = mpsc::channel();
29094    let running = controller
29095      .try_spawn_blocking(move || {
29096        running_tx.send(()).unwrap();
29097        release_rx.recv().unwrap();
29098        7usize
29099      })
29100      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29101    running_rx
29102      .recv_timeout(Duration::from_secs(5))
29103      .expect("the first blocking job must hold the only blocking slot");
29104
29105    let queued_ran = Arc::new(AtomicBool::new(false));
29106    let queued_ran_work = Arc::clone(&queued_ran);
29107    let (queued_dropped_tx, queued_dropped_rx) = mpsc::channel();
29108    let queued_drop_signal = DropSignal(queued_dropped_tx);
29109    let queued = controller
29110      .try_spawn_blocking(move || {
29111        let _drop_signal = queued_drop_signal;
29112        queued_ran_work.store(true, Ordering::SeqCst);
29113        11usize
29114      })
29115      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29116
29117    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29118    let shutdown_controller = Arc::clone(&controller);
29119    let shutdown = std::thread::spawn(move || {
29120      shutdown_controller.shutdown().unwrap();
29121      shutdown_done_tx.send(()).unwrap();
29122    });
29123
29124    queued_dropped_rx
29125      .recv_timeout(Duration::from_secs(5))
29126      .expect("shutdown must drop queued blocking work");
29127    assert!(
29128      !queued_ran.load(Ordering::SeqCst),
29129      "queued blocking work must not execute"
29130    );
29131    assert!(
29132      shutdown_done_rx.try_recv().is_err(),
29133      "shutdown must wait while an accepted blocking closure is still running"
29134    );
29135
29136    release_tx.send(()).unwrap();
29137    shutdown_done_rx
29138      .recv_timeout(Duration::from_secs(5))
29139      .expect("shutdown must finish after running blocking work retires");
29140    shutdown.join().unwrap();
29141    assert_eq!(futures::executor::block_on(running).unwrap(), 7);
29142    assert!(
29143      futures::executor::block_on(queued).is_err(),
29144      "the queued blocking handle must resolve as cancelled"
29145    );
29146  }
29147
29148  #[cfg(not(target_family = "wasm"))]
29149  #[test]
29150  fn multi_thread_shutdown_contains_panicking_queued_blocking_destructor_and_restarts() {
29151    use std::sync::mpsc;
29152
29153    struct PanicOnDrop(mpsc::Sender<()>);
29154    impl Drop for PanicOnDrop {
29155      fn drop(&mut self) {
29156        let _ = self.0.send(());
29157        panic!("intentional queued blocking destructor panic");
29158      }
29159    }
29160
29161    let controller = Arc::new(multi_thread_controller("lifecycle-drop-panic", 2, 1));
29162    let (running_tx, running_rx) = mpsc::channel();
29163    let (release_tx, release_rx) = mpsc::channel();
29164    let running = controller
29165      .try_spawn_blocking(move || {
29166        running_tx.send(()).unwrap();
29167        release_rx.recv().unwrap();
29168      })
29169      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29170    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29171
29172    let (drop_tx, drop_rx) = mpsc::channel();
29173    let panic_on_drop = PanicOnDrop(drop_tx);
29174    let queued = controller
29175      .try_spawn_blocking(move || {
29176        let _keep_captured_until_run = &panic_on_drop;
29177      })
29178      .unwrap_or_else(|_| panic!("queued blocking work must be accepted"));
29179
29180    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29181    let shutdown_controller = Arc::clone(&controller);
29182    let shutdown = std::thread::spawn(move || {
29183      shutdown_controller.shutdown().unwrap();
29184      shutdown_done_tx.send(()).unwrap();
29185    });
29186
29187    drop_rx
29188      .recv_timeout(Duration::from_secs(5))
29189      .expect("shutdown must attempt to drop the queued closure");
29190    release_tx.send(()).unwrap();
29191    shutdown_done_rx
29192      .recv_timeout(Duration::from_secs(5))
29193      .expect("a panicking queued destructor must not strand shutdown");
29194    shutdown
29195      .join()
29196      .expect("shutdown must contain the destructor panic");
29197    futures::executor::block_on(running).unwrap();
29198    assert!(futures::executor::block_on(queued).is_err());
29199
29200    controller
29201      .start()
29202      .expect("the stopped controller must restart");
29203    let restarted = controller
29204      .try_spawn_blocking(|| 17usize)
29205      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29206    assert_eq!(futures::executor::block_on(restarted).unwrap(), 17);
29207    controller.shutdown().unwrap();
29208  }
29209
29210  #[cfg(not(target_family = "wasm"))]
29211  #[test]
29212  fn multi_thread_dropped_blocking_result_panic_does_not_strand_shutdown() {
29213    use std::{panic::panic_any, sync::mpsc};
29214
29215    struct ReentrantPayload {
29216      controller: Arc<RuntimeController>,
29217      result: mpsc::Sender<(Option<u64>, Result<(), RuntimeConfigError>)>,
29218    }
29219
29220    impl Drop for ReentrantPayload {
29221      fn drop(&mut self) {
29222        let generation = ACTIVE_RUNTIME_GENERATION.with(std::cell::Cell::get);
29223        let shutdown = self.controller.shutdown();
29224        self.result.send((generation, shutdown)).unwrap();
29225      }
29226    }
29227
29228    struct PanicPayloadOnDrop(Option<ReentrantPayload>);
29229
29230    impl Drop for PanicPayloadOnDrop {
29231      fn drop(&mut self) {
29232        panic_any(self.0.take().expect("the result must panic only once"));
29233      }
29234    }
29235
29236    let controller = Arc::new(multi_thread_controller("blocking-result-drop-panic", 2, 1));
29237    let generation = controller.backend().generation();
29238    let (started_tx, started_rx) = mpsc::channel();
29239    let (release_tx, release_rx) = mpsc::channel();
29240    let (result_tx, result_rx) = mpsc::channel();
29241    let payload_controller = Arc::clone(&controller);
29242    let handle = controller
29243      .try_spawn_blocking(move || {
29244        started_tx.send(()).unwrap();
29245        release_rx.recv().unwrap();
29246        PanicPayloadOnDrop(Some(ReentrantPayload {
29247          controller: payload_controller,
29248          result: result_tx,
29249        }))
29250      })
29251      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29252    started_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29253
29254    // Detach the receiver before the result is delivered. Sending then drops
29255    // the result on the worker, whose destructor intentionally panics.
29256    drop(handle);
29257    release_tx.send(()).unwrap();
29258    let (observed_generation, reentrant_shutdown) = result_rx
29259      .recv_timeout(Duration::from_secs(5))
29260      .expect("the dropped result panic payload must not self-deadlock");
29261    assert_eq!(observed_generation, Some(generation));
29262    let error =
29263      reentrant_shutdown.expect_err("payload destruction is still work in the active generation");
29264    assert!(error.to_string().contains("work running on that runtime"));
29265
29266    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29267    let shutdown_controller = Arc::clone(&controller);
29268    let shutdown = std::thread::spawn(move || {
29269      shutdown_controller.shutdown().unwrap();
29270      shutdown_done_tx.send(()).unwrap();
29271    });
29272    shutdown_done_rx
29273      .recv_timeout(Duration::from_secs(5))
29274      .expect("a dropped-result destructor panic must not strand shutdown");
29275    shutdown
29276      .join()
29277      .expect("shutdown must contain the destructor panic");
29278
29279    controller
29280      .start()
29281      .expect("the stopped controller must restart");
29282    let restarted = controller
29283      .try_spawn_blocking(|| 19usize)
29284      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29285    assert_eq!(futures::executor::block_on(restarted).unwrap(), 19);
29286    controller.shutdown().unwrap();
29287  }
29288
29289  #[cfg(not(target_family = "wasm"))]
29290  #[test]
29291  fn multi_thread_completed_blocking_result_drop_panic_is_contained() {
29292    use std::sync::mpsc;
29293
29294    struct PanicOnDrop(mpsc::Sender<()>);
29295
29296    impl Drop for PanicOnDrop {
29297      fn drop(&mut self) {
29298        self.0.send(()).unwrap();
29299        panic!("intentional buffered blocking result destructor panic");
29300      }
29301    }
29302
29303    let options = RuntimeOptions {
29304      flavor: RuntimeFlavor::MultiThread,
29305      worker_threads: 2,
29306      max_blocking_tasks: 1,
29307      thread_name_prefix: "buffered-blocking-result-drop".to_string(),
29308      park_deadline: None,
29309      drain_linger: DEFAULT_DRAIN_LINGER,
29310    };
29311    let executor =
29312      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
29313    let (started_tx, started_rx) = mpsc::channel();
29314    let (dropped_tx, dropped_rx) = mpsc::channel();
29315    let handle = executor.schedule_blocking(move || {
29316      started_tx.send(()).unwrap();
29317      PanicOnDrop(dropped_tx)
29318    });
29319    started_rx
29320      .recv_timeout(Duration::from_secs(2))
29321      .expect("the blocking closure must run");
29322    wait_until("the completed blocking result to be buffered", || {
29323      executor.active_blocking.load(Ordering::Acquire) == 0
29324    });
29325    assert!(
29326      dropped_rx.try_recv().is_err(),
29327      "the buffered result must remain owned by the live receiver"
29328    );
29329
29330    let dropped = catch_unwind(AssertUnwindSafe(|| drop(handle)));
29331    assert!(
29332      dropped.is_ok(),
29333      "dropping the buffered result must contain its destructor panic"
29334    );
29335    dropped_rx
29336      .recv_timeout(Duration::from_secs(2))
29337      .expect("dropping the receiver must reclaim the buffered result");
29338  }
29339
29340  #[cfg(not(target_family = "wasm"))]
29341  #[test]
29342  fn multi_thread_closed_queue_contains_panicking_blocking_destructor() {
29343    use std::sync::mpsc;
29344
29345    struct PanicOnDrop(mpsc::Sender<()>);
29346    impl Drop for PanicOnDrop {
29347      fn drop(&mut self) {
29348        let _ = self.0.send(());
29349        panic!("intentional late blocking destructor panic");
29350      }
29351    }
29352
29353    let executor = multi_thread_executor(2, None, "closed-queue-drop");
29354    executor.begin_shutdown();
29355
29356    let (drop_tx, drop_rx) = mpsc::channel();
29357    let panic_on_drop = PanicOnDrop(drop_tx);
29358    let handle = executor.schedule_blocking(move || {
29359      let _keep_captured_until_run = &panic_on_drop;
29360    });
29361
29362    drop_rx
29363      .recv_timeout(Duration::from_secs(5))
29364      .expect("the closed queue must retire the rejected closure");
29365    assert!(
29366      futures::executor::block_on(handle).is_err(),
29367      "the rejected blocking handle must resolve as cancelled"
29368    );
29369  }
29370
29371  #[cfg(not(target_family = "wasm"))]
29372  #[test]
29373  fn multi_thread_shutdown_contains_panicking_timer_waker_and_restarts() {
29374    struct PanicWake;
29375    impl std::task::Wake for PanicWake {
29376      fn wake(self: Arc<Self>) {
29377        panic!("intentional timer waker panic");
29378      }
29379    }
29380
29381    let controller = multi_thread_controller("lifecycle-waker-panic", 2, 1);
29382    let backend = controller.backend();
29383    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29384      panic!("configured MultiThread backend must create a Rayon executor");
29385    };
29386    let mut sleep = heap_sleep(executor, Instant::now() + Duration::from_secs(60));
29387    let waker = Waker::from(Arc::new(PanicWake));
29388    let mut cx = Context::from_waker(&waker);
29389    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
29390    drop(backend);
29391
29392    controller
29393      .shutdown()
29394      .expect("shutdown must contain the timer waker panic");
29395    controller
29396      .start()
29397      .expect("the stopped controller must restart");
29398    let restarted = controller
29399      .try_spawn_blocking(|| 23usize)
29400      .unwrap_or_else(|_| panic!("the restarted controller must accept work"));
29401    assert_eq!(futures::executor::block_on(restarted).unwrap(), 23);
29402    controller.shutdown().unwrap();
29403  }
29404
29405  #[cfg(not(target_family = "wasm"))]
29406  #[test]
29407  fn multi_thread_restart_waits_for_old_generation_worker_exit() {
29408    use std::sync::mpsc;
29409
29410    let controller = Arc::new(multi_thread_controller("lifecycle-generation", 2, 1));
29411    let backend = controller.backend();
29412    let old_generation = backend.generation();
29413    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29414      panic!("configured MultiThread backend must create a Rayon executor");
29415    };
29416    let old_workers = Arc::clone(&executor.worker_lifecycle);
29417    drop(backend);
29418
29419    let (running_tx, running_rx) = mpsc::channel();
29420    let (release_tx, release_rx) = mpsc::channel();
29421    let running = controller
29422      .try_spawn_blocking(move || {
29423        running_tx.send(()).unwrap();
29424        release_rx.recv().unwrap();
29425      })
29426      .unwrap_or_else(|_| panic!("running blocking work must be accepted"));
29427    running_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29428
29429    let (shutdown_done_tx, shutdown_done_rx) = mpsc::channel();
29430    let shutdown_controller = Arc::clone(&controller);
29431    let shutdown = std::thread::spawn(move || {
29432      shutdown_controller.shutdown().unwrap();
29433      shutdown_done_tx.send(()).unwrap();
29434    });
29435    wait_until("controller entered Stopping", || {
29436      let state = controller
29437        .state
29438        .lock()
29439        .unwrap_or_else(std::sync::PoisonError::into_inner);
29440      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
29441    });
29442
29443    let (start_done_tx, start_done_rx) = mpsc::channel();
29444    let start_controller = Arc::clone(&controller);
29445    let starter = std::thread::spawn(move || {
29446      start_controller.start().unwrap();
29447      start_done_tx.send(()).unwrap();
29448    });
29449    assert!(
29450      start_done_rx.try_recv().is_err(),
29451      "start must wait while shutdown is quiescing"
29452    );
29453    assert!(
29454      old_workers.remaining() > 0,
29455      "the held old generation must still own workers"
29456    );
29457
29458    release_tx.send(()).unwrap();
29459    futures::executor::block_on(running).unwrap();
29460    shutdown_done_rx
29461      .recv_timeout(Duration::from_secs(5))
29462      .unwrap();
29463    start_done_rx.recv_timeout(Duration::from_secs(5)).unwrap();
29464    shutdown.join().unwrap();
29465    starter.join().unwrap();
29466    assert_eq!(
29467      old_workers.remaining(),
29468      0,
29469      "restart may begin only after every old worker exits"
29470    );
29471
29472    let backend = controller.backend();
29473    assert_ne!(backend.generation(), old_generation);
29474    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
29475      panic!("restarted backend must remain MultiThread");
29476    };
29477    assert_eq!(
29478      executor.pool.current_num_threads(),
29479      controller.options().worker_threads
29480    );
29481    drop(backend);
29482    controller.shutdown().unwrap();
29483  }
29484
29485  #[cfg(not(target_family = "wasm"))]
29486  #[test]
29487  fn shutdown_from_runtime_work_is_rejected_instead_of_self_deadlocking() {
29488    let controller = Arc::new(multi_thread_controller("lifecycle-self-stop", 2, 1));
29489    let task_controller = Arc::clone(&controller);
29490    let handle = controller
29491      .try_spawn(async move {
29492        task_controller
29493          .shutdown()
29494          .expect_err("runtime work must not wait for its own generation")
29495          .to_string()
29496      })
29497      .unwrap_or_else(|_| panic!("running runtime must accept the task"));
29498    let message = futures::executor::block_on(handle).unwrap();
29499    assert!(message.contains("cannot shut down"));
29500    controller.shutdown().unwrap();
29501  }
29502
29503  // ---- MultiThread blocking cap, drain budget, and panic propagation ---------
29504  // All three construct a `MultiThreadExecutor`/`RuntimeMetrics` LOCALLY (never the
29505  // global `RUNTIME`) and run the workload on a child thread guarded by a
29506  // `recv_timeout`, so a regression that hangs is reported as a failure instead of
29507  // wedging the whole suite.
29508
29509  #[cfg(not(target_family = "wasm"))]
29510  #[test]
29511  fn multi_thread_blocking_active_never_exceeds_max_blocking() {
29512    // Blocking-cap invariant: `take_blocking` refuses to pop or run a queued
29513    // blocking job while `active_blocking >= max_blocking`. The guard is
29514    // exercised deterministically (no sleeps, no incidental worker overlap):
29515    //
29516    //   1. EXACTLY `max_blocking_tasks` HELD holders saturate the cap. Each holder
29517    //      occupies a counted slot (`take_blocking` increments `active_blocking`
29518    //      BEFORE running the closure), signals `held`, then parks on a release
29519    //      barrier -- so all slots are provably occupied before we proceed.
29520    //   2. While the cap is saturated, we queue MORE blocking jobs ("extras").
29521    //      With the cap intact `take_blocking` returns `None` for them, so the
29522    //      idle workers cannot pop them: no extra may signal `extra_started`.
29523    //   3. We release the holders, then assert EVERY job (holders + extras)
29524    //      completes with the right result and the recorded peak never exceeded
29525    //      the cap.
29526    //
29527    // Decisive property: if the `active >= self.max_blocking` guard were removed,
29528    // the two idle workers would pop the extras while both holders are still
29529    // parked, driving the recorded peak (`max_active_blocking_tasks`, a monotonic
29530    // fetch_max set at job entry) above the cap -- so this test fails on EVERY run.
29531    // The release barrier is sized `holder_count + 1` (<= max_blocking + 1), so it
29532    // can never self-deadlock on the over-cap extras.
29533    use std::sync::{Barrier, mpsc};
29534    use std::time::Duration;
29535
29536    let (done_tx, done_rx) = mpsc::channel();
29537    let peak_out = Arc::new(Mutex::new(None));
29538    let peak_slot = Arc::clone(&peak_out);
29539    let runner = std::thread::spawn(move || {
29540      let metrics = Arc::new(RuntimeMetrics::default());
29541      let options = RuntimeOptions {
29542        flavor: RuntimeFlavor::MultiThread,
29543        worker_threads: 4,
29544        max_blocking_tasks: 2,
29545        thread_name_prefix: "rd9-blk-cap".to_string(),
29546        park_deadline: None,
29547        drain_linger: DEFAULT_DRAIN_LINGER,
29548      };
29549      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
29550
29551      let holder_count = options.max_blocking_tasks; // saturates the cap exactly
29552      let extra_count = 4usize; // beyond-cap jobs that must NOT run while held
29553
29554      // Holders: signal once they hold a counted slot, then park until released.
29555      let (held_tx, held_rx) = mpsc::channel();
29556      let release = Arc::new(Barrier::new(holder_count + 1));
29557      let mut holder_handles = Vec::new();
29558      for _ in 0..holder_count {
29559        let held_tx = held_tx.clone();
29560        let release = Arc::clone(&release);
29561        holder_handles.push(executor.schedule_blocking(move || {
29562          held_tx.send(()).unwrap();
29563          release.wait();
29564          0usize
29565        }));
29566      }
29567      // Wait until every slot is provably occupied (cap fully saturated).
29568      for _ in 0..holder_count {
29569        held_rx.recv().unwrap();
29570      }
29571
29572      // Queue the beyond-cap extras. Each signals the instant it begins running.
29573      let (extra_started_tx, extra_started_rx) = mpsc::channel();
29574      let mut extra_handles = Vec::new();
29575      for i in 0..extra_count {
29576        let extra_started_tx = extra_started_tx.clone();
29577        extra_handles.push(executor.schedule_blocking(move || {
29578          extra_started_tx.send(i).unwrap();
29579          i * 2
29580        }));
29581      }
29582
29583      // With the cap intact, `take_blocking` returns `None` while both slots are
29584      // held, so no extra can have started running yet.
29585      assert!(
29586        matches!(extra_started_rx.try_recv(), Err(mpsc::TryRecvError::Empty)),
29587        "a beyond-cap blocking job ran before the cap was released (cap check broken)"
29588      );
29589
29590      // Release the holders so the queued extras can run within the cap.
29591      release.wait();
29592
29593      for handle in holder_handles {
29594        assert_eq!(futures::executor::block_on(handle).unwrap(), 0usize);
29595      }
29596      // Each handle owns its own oneshot receiver, so awaiting in order returns
29597      // each extra's own result regardless of the order the pool ran them.
29598      for (i, handle) in extra_handles.into_iter().enumerate() {
29599        assert_eq!(futures::executor::block_on(handle).unwrap(), i * 2);
29600      }
29601
29602      // `blocking_tasks_started` is incremented at job entry, so once every result
29603      // is in, all holder + extra starts are recorded.
29604      assert_eq!(
29605        metrics.blocking_tasks_started.load(Ordering::Relaxed),
29606        (holder_count + extra_count) as u64
29607      );
29608
29609      *peak_slot
29610        .lock()
29611        .unwrap_or_else(std::sync::PoisonError::into_inner) =
29612        Some(metrics.max_active_blocking_tasks.load(Ordering::Relaxed));
29613      let _ = done_tx.send(());
29614    });
29615
29616    match done_rx.recv_timeout(Duration::from_secs(10)) {
29617      Ok(()) => runner.join().unwrap(),
29618      Err(error) => panic!("blocking-cap test did not complete ({error})"),
29619    }
29620    let peak = peak_out
29621      .lock()
29622      .unwrap_or_else(std::sync::PoisonError::into_inner)
29623      .expect("peak captured");
29624    assert!(
29625      peak >= 1,
29626      "at least one blocking job must have run (peak {peak})"
29627    );
29628    assert!(
29629      peak <= 2,
29630      "peak active blocking tasks {peak} exceeded max_blocking_tasks 2"
29631    );
29632  }
29633
29634  #[cfg(not(target_family = "wasm"))]
29635  #[test]
29636  fn multi_thread_drain_budget_completes_large_runnable_batch() {
29637    // `drain` processes at most `RUNNABLE_BUDGET` (64) runnables per pass, then
29638    // yields and re-drains via `finish_draining` -> `ensure_drainer`. A batch of
29639    // MORE than 64 runnables must therefore exercise the budget yield/re-drain and
29640    // still complete every task, with the runnable counters settling back to 0.
29641    use std::sync::mpsc;
29642    use std::time::{Duration, Instant};
29643
29644    let (done_tx, done_rx) = mpsc::channel();
29645    let runner = std::thread::spawn(move || {
29646      let metrics = Arc::new(RuntimeMetrics::default());
29647      let options = RuntimeOptions {
29648        flavor: RuntimeFlavor::MultiThread,
29649        worker_threads: 2,
29650        max_blocking_tasks: 2,
29651        thread_name_prefix: "rd9-budget".to_string(),
29652        park_deadline: None,
29653        drain_linger: DEFAULT_DRAIN_LINGER,
29654      };
29655      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
29656
29657      let task_count = 200usize; // > RUNNABLE_BUDGET (64): forces budget re-drain
29658      let completed = Arc::new(AtomicU64::new(0));
29659      let mut tasks = Vec::new();
29660      for _ in 0..task_count {
29661        let completed = Arc::clone(&completed);
29662        let scheduler = Arc::clone(&executor);
29663        let (runnable, task) = async_task::spawn(
29664          async move {
29665            completed.fetch_add(1, Ordering::Relaxed);
29666          },
29667          move |r| scheduler.schedule(r),
29668        );
29669        executor.schedule(runnable);
29670        tasks.push(task);
29671      }
29672      for task in tasks {
29673        futures::executor::block_on(task);
29674      }
29675
29676      // Every immediately-ready future is polled exactly once; `runnable_polls`
29677      // (incremented at poll entry, before completion) is therefore exact here.
29678      assert_eq!(completed.load(Ordering::Relaxed), task_count as u64);
29679      assert_eq!(
29680        metrics.runnable_polls.load(Ordering::Relaxed),
29681        task_count as u64
29682      );
29683
29684      // The active-runnable guard for the LAST task drops just after its Task
29685      // resolves (i.e. just after `block_on` returns), so give the counters a
29686      // brief, bounded moment to settle to 0 rather than asserting them racily.
29687      let deadline = Instant::now() + Duration::from_secs(5);
29688      loop {
29689        let active = metrics.active_runnables.load(Ordering::Relaxed);
29690        let queued = metrics.queued_runnables.load(Ordering::Relaxed);
29691        if active == 0 && queued == 0 {
29692          break;
29693        }
29694        assert!(
29695          Instant::now() < deadline,
29696          "runnable counters did not settle to 0: active={active} queued={queued}"
29697        );
29698        std::thread::yield_now();
29699      }
29700      let _ = done_tx.send(());
29701    });
29702
29703    match done_rx.recv_timeout(Duration::from_secs(15)) {
29704      Ok(()) => runner.join().unwrap(),
29705      Err(error) => panic!("drain-budget test did not complete ({error})"),
29706    }
29707  }
29708
29709  #[cfg(not(target_family = "wasm"))]
29710  #[test]
29711  fn multi_thread_spawned_panic_surfaces_as_join_error() {
29712    // A panicking spawned future must surface as `Err(JoinError)` carrying the
29713    // panic payload's message. We mirror the production `spawn` wrapper LOCALLY
29714    // (catch_unwind the future, map the panic via `JoinError::from_panic`) and
29715    // drive the resulting `JoinHandle` on this executor -- without touching the
29716    // global `RUNTIME`. `JoinError::from_panic` downcasts a `&str` payload, so the
29717    // `panic!("x")` message round-trips as `"x"`.
29718    use std::sync::mpsc;
29719    use std::time::Duration;
29720
29721    let (done_tx, done_rx) = mpsc::channel();
29722    let runner = std::thread::spawn(move || {
29723      let metrics = Arc::new(RuntimeMetrics::default());
29724      let options = RuntimeOptions {
29725        flavor: RuntimeFlavor::MultiThread,
29726        worker_threads: 2,
29727        max_blocking_tasks: 2,
29728        thread_name_prefix: "rd9-panic".to_string(),
29729        park_deadline: None,
29730        drain_linger: DEFAULT_DRAIN_LINGER,
29731      };
29732      let executor = Arc::new(MultiThreadExecutor::new(&options, metrics).unwrap());
29733
29734      let scheduler = Arc::clone(&executor);
29735      let wrapped = async {
29736        AssertUnwindSafe(async { panic!("x") })
29737          .catch_unwind()
29738          .await
29739          .map(|()| ContainedTaskOutput::new((), u64::MAX))
29740          .map_err(panic_payload_to_join_error)
29741      };
29742      let (runnable, task) = async_task::spawn(wrapped, move |r| scheduler.schedule(r));
29743      executor.schedule(runnable);
29744
29745      let result: Result<(), JoinError> =
29746        futures::executor::block_on(JoinHandle(JoinHandleInner::Task {
29747          task: task.fallible(),
29748          awaiter: None,
29749          dependency: Arc::new(TaskDependency::default()),
29750        }));
29751      let _ = done_tx.send(result);
29752    });
29753
29754    let result = match done_rx.recv_timeout(Duration::from_secs(10)) {
29755      Ok(result) => {
29756        runner.join().unwrap();
29757        result
29758      }
29759      Err(error) => panic!("panic-to-JoinError test did not complete ({error})"),
29760    };
29761    let error = result.expect_err("a panicking spawned future must surface as Err(JoinError)");
29762    assert_eq!(error.to_string(), "x");
29763  }
29764
29765  #[test]
29766  fn concurrent_runtime_metrics_reset_preserves_live_and_high_water_gauges() {
29767    use std::sync::mpsc;
29768
29769    let metrics = Arc::new(RuntimeMetrics::default());
29770    metrics.tasks_spawned.store(1, Ordering::Relaxed);
29771    metrics.tasks_completed.store(2, Ordering::Relaxed);
29772    metrics.tasks_panicked.store(3, Ordering::Relaxed);
29773    metrics.runnable_schedules.store(4, Ordering::Relaxed);
29774    metrics.runnable_polls.store(5, Ordering::Relaxed);
29775    metrics
29776      .blocking_tasks_scheduled
29777      .store(14, Ordering::Relaxed);
29778    metrics.blocking_tasks_started.store(10, Ordering::Relaxed);
29779    metrics
29780      .blocking_tasks_completed
29781      .store(11, Ordering::Relaxed);
29782    metrics.runnable_scheduled();
29783    let (guards_live_tx, guards_live_rx) = mpsc::channel();
29784    let (release_tx, release_rx) = mpsc::channel();
29785    let guard_metrics = Arc::clone(&metrics);
29786    let guard_thread = std::thread::spawn(move || {
29787      let runnable_guard = guard_metrics.runnable_started();
29788      let blocking_guard = guard_metrics.blocking_started(true);
29789      guards_live_tx.send(()).unwrap();
29790      release_rx.recv().unwrap();
29791      drop(runnable_guard);
29792      drop(blocking_guard);
29793    });
29794    guards_live_rx.recv().unwrap();
29795    let fingerprint_before_reset = metrics.progress_fingerprint();
29796
29797    metrics.reset();
29798
29799    assert!(
29800      metrics.progress_fingerprint() != fingerprint_before_reset,
29801      "a reset must advance the deadlock detector generation even if counters later repeat"
29802    );
29803    assert_eq!(
29804      metrics.tasks_spawned.load(Ordering::Relaxed),
29805      0,
29806      "tasks_spawned"
29807    );
29808    assert_eq!(
29809      metrics.tasks_completed.load(Ordering::Relaxed),
29810      0,
29811      "tasks_completed"
29812    );
29813    assert_eq!(
29814      metrics.tasks_panicked.load(Ordering::Relaxed),
29815      0,
29816      "tasks_panicked"
29817    );
29818    assert_eq!(
29819      metrics.runnable_schedules.load(Ordering::Relaxed),
29820      0,
29821      "runnable_schedules"
29822    );
29823    assert_eq!(
29824      metrics.runnable_polls.load(Ordering::Relaxed),
29825      0,
29826      "runnable_polls"
29827    );
29828    assert_eq!(metrics.queued_runnables.load(Ordering::Relaxed), 0);
29829    assert_eq!(metrics.max_queued_runnables.load(Ordering::Relaxed), 1);
29830    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 1);
29831    assert_eq!(metrics.max_active_runnables.load(Ordering::Relaxed), 1);
29832    assert_eq!(
29833      metrics.blocking_tasks_scheduled.load(Ordering::Relaxed),
29834      0,
29835      "blocking_tasks_scheduled"
29836    );
29837    assert_eq!(
29838      metrics.blocking_tasks_started.load(Ordering::Relaxed),
29839      0,
29840      "blocking_tasks_started"
29841    );
29842    assert_eq!(
29843      metrics.blocking_tasks_completed.load(Ordering::Relaxed),
29844      0,
29845      "blocking_tasks_completed"
29846    );
29847    assert_eq!(metrics.active_blocking_tasks.load(Ordering::Relaxed), 1);
29848    assert_eq!(metrics.max_active_blocking_tasks.load(Ordering::Relaxed), 1);
29849
29850    release_tx.send(()).unwrap();
29851    guard_thread.join().unwrap();
29852    assert_eq!(metrics.active_runnables.load(Ordering::Relaxed), 0);
29853    assert_eq!(
29854      metrics.active_blocking_tasks.load(Ordering::Relaxed),
29855      0,
29856      "a live blocking guard completed after reset without unsigned underflow"
29857    );
29858    assert_eq!(metrics.blocking_tasks_completed.load(Ordering::Relaxed), 1);
29859  }
29860
29861  #[test]
29862  fn runtime_metrics_snapshot_clamps_high_water_marks_to_loaded_live_gauges() {
29863    use std::sync::mpsc;
29864
29865    fn pause_next_high_water_update(entered: mpsc::Sender<()>, release: mpsc::Receiver<()>) {
29866      RUNTIME_METRICS_HIGH_WATER_TEST_HOOK.with(|slot| {
29867        *slot.borrow_mut() = Some(Box::new(move || {
29868          entered.send(()).unwrap();
29869          release.recv().unwrap();
29870        }));
29871      });
29872    }
29873
29874    let options = RuntimeOptions {
29875      flavor: RuntimeFlavor::CurrentThread,
29876      worker_threads: 1,
29877      max_blocking_tasks: 1,
29878      thread_name_prefix: "metrics-snapshot".to_string(),
29879      park_deadline: None,
29880      drain_linger: DEFAULT_DRAIN_LINGER,
29881    };
29882
29883    let queued_metrics = Arc::new(RuntimeMetrics::default());
29884    let (queued_entered_tx, queued_entered_rx) = mpsc::channel();
29885    let (queued_release_tx, queued_release_rx) = mpsc::channel();
29886    let queued_writer_metrics = Arc::clone(&queued_metrics);
29887    let queued_writer = std::thread::spawn(move || {
29888      pause_next_high_water_update(queued_entered_tx, queued_release_rx);
29889      queued_writer_metrics.runnable_scheduled();
29890    });
29891    queued_entered_rx.recv().unwrap();
29892    assert_eq!(
29893      queued_metrics.max_queued_runnables.load(Ordering::Relaxed),
29894      0
29895    );
29896    let snapshot = queued_metrics.snapshot(&options);
29897    assert_eq!(snapshot.queued_runnables, 1);
29898    assert_eq!(snapshot.max_queued_runnables, 1);
29899    queued_release_tx.send(()).unwrap();
29900    queued_writer.join().unwrap();
29901    queued_metrics.runnable_cancelled();
29902
29903    let active_metrics = Arc::new(RuntimeMetrics::default());
29904    active_metrics.queued_runnables.store(1, Ordering::Relaxed);
29905    let (active_entered_tx, active_entered_rx) = mpsc::channel();
29906    let (active_release_tx, active_release_rx) = mpsc::channel();
29907    let active_writer_metrics = Arc::clone(&active_metrics);
29908    let active_writer = std::thread::spawn(move || {
29909      pause_next_high_water_update(active_entered_tx, active_release_rx);
29910      let _active = active_writer_metrics.runnable_started();
29911    });
29912    active_entered_rx.recv().unwrap();
29913    assert_eq!(
29914      active_metrics.max_active_runnables.load(Ordering::Relaxed),
29915      0
29916    );
29917    let snapshot = active_metrics.snapshot(&options);
29918    assert_eq!(snapshot.active_runnables, 1);
29919    assert_eq!(snapshot.max_active_runnables, 1);
29920    active_release_tx.send(()).unwrap();
29921    active_writer.join().unwrap();
29922
29923    let blocking_metrics = Arc::new(RuntimeMetrics::default());
29924    let (blocking_entered_tx, blocking_entered_rx) = mpsc::channel();
29925    let (blocking_release_tx, blocking_release_rx) = mpsc::channel();
29926    let blocking_writer_metrics = Arc::clone(&blocking_metrics);
29927    let blocking_writer = std::thread::spawn(move || {
29928      pause_next_high_water_update(blocking_entered_tx, blocking_release_rx);
29929      let _active = blocking_writer_metrics.blocking_started(true);
29930    });
29931    blocking_entered_rx.recv().unwrap();
29932    assert_eq!(
29933      blocking_metrics
29934        .max_active_blocking_tasks
29935        .load(Ordering::Relaxed),
29936      0
29937    );
29938    let snapshot = blocking_metrics.snapshot(&options);
29939    assert_eq!(snapshot.active_blocking_tasks, 1);
29940    assert_eq!(snapshot.max_active_blocking_tasks, 1);
29941    blocking_release_tx.send(()).unwrap();
29942    blocking_writer.join().unwrap();
29943  }
29944
29945  #[test]
29946  fn runtime_metrics_reset_fails_before_generation_wraparound() {
29947    let metrics = RuntimeMetrics::default();
29948    metrics
29949      .reset_generation
29950      .store(u64::MAX - 1, Ordering::SeqCst);
29951    metrics.tasks_spawned.store(7, Ordering::Relaxed);
29952
29953    let exhausted = catch_unwind(AssertUnwindSafe(|| metrics.reset()));
29954    assert!(exhausted.is_err());
29955    assert!(!metrics.reset_lock.is_poisoned());
29956    assert_eq!(
29957      metrics.reset_generation.load(Ordering::SeqCst),
29958      u64::MAX - 1
29959    );
29960    assert_eq!(metrics.tasks_spawned.load(Ordering::Relaxed), 7);
29961  }
29962
29963  // ---- Self-detecting block_on deadlocks -------------------------------------
29964  // Five deadlock-detection shapes are pinned here: (1) a threadless CurrentThread
29965  // park decision with no pending wake panics with the typed diagnostic; (2) a
29966  // self-waking future (the PendThenReady shape) must NOT panic -- the panic
29967  // lives at the PARK DECISION, not at Pending-return; (3) an
29968  // armed deadline fires on a genuinely dead cooperative MT park; (4) runtime
29969  // progress RESETS the deadline instead of firing; (5) the
29970  // foreign/napi whole-build park is excluded from the deadline entirely.
29971
29972  /// A future that never completes and never wakes anyone: once its driver
29973  /// parks, no wake can ever arrive. The deadlock-detection probe shape.
29974  struct NeverReady;
29975  impl Future for NeverReady {
29976    type Output = ();
29977    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29978      Poll::Pending
29979    }
29980  }
29981
29982  #[cfg(not(target_family = "wasm"))]
29983  struct ReadyAfterOwnerLanePublication {
29984    dependency: BlockingDependency,
29985    ready: Arc<AtomicBool>,
29986  }
29987
29988  #[cfg(not(target_family = "wasm"))]
29989  impl Future for ReadyAfterOwnerLanePublication {
29990    type Output = ();
29991
29992    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
29993      if self.ready.load(Ordering::SeqCst) {
29994        Poll::Ready(())
29995      } else {
29996        // This shape publishes on every pending poll and never retires; the
29997        // owner-lane assertions read the state, not the token.
29998        let mut published = PublishedBlockingDependencies::default();
29999        record_blocking_dependency(&mut published, self.dependency);
30000        Poll::Pending
30001      }
30002    }
30003  }
30004
30005  #[test]
30006  fn current_thread_threadless_park_with_no_pending_wake_panics_with_typed_diagnostic() {
30007    // Shape (1): on a threadless build a CT park decision with an empty queue
30008    // and no wake token pending is a PROVABLE deadlock (no other thread exists
30009    // to deliver the wake) -- `block_on` must panic immediately with the typed
30010    // `BlockOnDeadlock` diagnostic instead of parking forever. The workload
30011    // runs on a child thread bounded by recv_timeout so a missing detection
30012    // (a hang) fails the test rather than wedging the suite.
30013    use std::sync::mpsc;
30014
30015    let (done_tx, done_rx) = mpsc::channel();
30016    let runner = std::thread::spawn(move || {
30017      let metrics = Arc::new(RuntimeMetrics::default());
30018      // Native stand-in for the threadless wasm build; no deadline because the
30019      // certain case is not timing-based.
30020      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30021
30022      let payload = catch_unwind(AssertUnwindSafe(|| {
30023        let mut future = std::pin::pin!(NeverReady);
30024        executor.block_on(future.as_mut());
30025      }))
30026      .expect_err("a threadless park with an empty queue and no pending wake must panic");
30027      let diagnostic = payload
30028        .downcast_ref::<BlockOnDeadlock>()
30029        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
30030      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
30031      assert_eq!(
30032        diagnostic.park_deadline, None,
30033        "the certain case carries no deadline"
30034      );
30035      let message = diagnostic.to_string();
30036      assert!(
30037        message.contains("block_on") && message.contains("JS"),
30038        "the diagnostic must name the block_on-awaiting-JS hazard, got: {message}"
30039      );
30040      let _ = done_tx.send(());
30041    });
30042
30043    match done_rx.recv_timeout(Duration::from_secs(10)) {
30044      Ok(()) => runner.join().unwrap(),
30045      Err(error) => panic!(
30046        "threadless CurrentThread park did not self-detect ({error}): block_on parked (or failed) instead of panicking with the diagnostic"
30047      ),
30048    }
30049  }
30050
30051  #[test]
30052  fn current_thread_threadless_self_waking_pending_future_does_not_panic() {
30053    // Shape (2), pinning panic-at-PARK-DECISION: a future that returns
30054    // `Pending` but wakes itself synchronously during the poll leaves
30055    // a wake token pending, so the loop must consume the token and re-poll --
30056    // NOT panic -- even on a threadless build. "Pending returned" alone is
30057    // never a deadlock.
30058    use std::sync::mpsc;
30059
30060    struct PendThenReady {
30061      polls: usize,
30062    }
30063    impl Future for PendThenReady {
30064      type Output = ();
30065      fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
30066        self.polls += 1;
30067        if self.polls >= 2 {
30068          Poll::Ready(())
30069        } else {
30070          cx.waker().wake_by_ref();
30071          Poll::Pending
30072        }
30073      }
30074    }
30075
30076    let (done_tx, done_rx) = mpsc::channel();
30077    let runner = std::thread::spawn(move || {
30078      let metrics = Arc::new(RuntimeMetrics::default());
30079      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
30080      let mut future = std::pin::pin!(PendThenReady { polls: 0 });
30081      executor.block_on(future.as_mut());
30082      assert_eq!(
30083        future.polls, 2,
30084        "the self-woken future must have been re-polled to completion"
30085      );
30086      let _ = done_tx.send(());
30087    });
30088
30089    match done_rx.recv_timeout(Duration::from_secs(10)) {
30090      Ok(()) => runner.join().unwrap(),
30091      Err(error) => panic!(
30092        "a self-waking Pending future must complete without panicking on a threadless build ({error})"
30093      ),
30094    }
30095  }
30096
30097  #[cfg(not(target_family = "wasm"))]
30098  #[test]
30099  fn current_thread_native_park_deadline_fires_with_typed_diagnostic() {
30100    // Threaded CT is NOT provably dead at the park decision (another OS thread
30101    // could deliver a wake later), so detection there is deadline-based and
30102    // opt-in. A park that outlives the armed deadline with ZERO runtime
30103    // progress must panic with the typed diagnostic.
30104    use std::sync::mpsc;
30105
30106    let (done_tx, done_rx) = mpsc::channel();
30107    let runner = std::thread::spawn(move || {
30108      let metrics = Arc::new(RuntimeMetrics::default());
30109      let executor = Arc::new(CurrentThreadExecutor::with_detection(
30110        metrics,
30111        false,
30112        Some(Duration::from_millis(50)),
30113      ));
30114
30115      let payload = catch_unwind(AssertUnwindSafe(|| {
30116        let mut future = std::pin::pin!(NeverReady);
30117        executor.block_on(future.as_mut());
30118      }))
30119      .expect_err("a dead native CT park must fire the armed deadline");
30120      let diagnostic = payload
30121        .downcast_ref::<BlockOnDeadlock>()
30122        .expect("typed payload");
30123      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadDeadline);
30124      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
30125      // Teardown inside the bounded window: a wedged executor drop must fail
30126      // the recv below loudly, not hang the final join silently.
30127      drop(executor);
30128      let _ = done_tx.send(());
30129    });
30130
30131    match done_rx.recv_timeout(Duration::from_secs(10)) {
30132      Ok(()) => join_within(
30133        "the CT deadline-fire runner exits after teardown",
30134        runner,
30135        Duration::from_secs(30),
30136      ),
30137      Err(error) => panic!("native CurrentThread park deadline did not fire ({error})"),
30138    }
30139  }
30140
30141  #[cfg(not(target_family = "wasm"))]
30142  #[test]
30143  fn current_thread_native_park_deadline_resets_on_runtime_progress() {
30144    // PROGRESS-BASED RESET, CT side: metrics advancing while parked means the
30145    // runtime is alive (e.g. a slow JS plugin with other
30146    // work proceeding), so the deadline must re-arm instead of firing.
30147    // Progress is injected by advancing the very counter the reset reads --
30148    // the exact seam, isolated from wake delivery (production advances it via
30149    // real runnable polls).
30150    //
30151    // Deadline/cadence ratio: the reset is only exercised when at least one
30152    // tick lands inside EVERY armed window, so the window must dwarf the
30153    // injector thread's worst-case scheduling stall. On starved CI hosts
30154    // (GitHub macOS runners: ~3 heavily oversubscribed cores) a 30ms sleep
30155    // can overshoot past a 60ms window, leaving one window with zero
30156    // progress -- which IS a deadline fire by design. Ticks every ~25ms
30157    // against a 1s window tolerate a ~975ms stall while the park is still
30158    // held across multiple full windows, so the repeated RE-ARM (not merely
30159    // the first arm) is what keeps the park alive.
30160    use std::sync::mpsc;
30161
30162    let deadline = Duration::from_millis(1000);
30163    let metrics = Arc::new(RuntimeMetrics::default());
30164    let executor = Arc::new(CurrentThreadExecutor::with_detection(
30165      Arc::clone(&metrics),
30166      false,
30167      Some(deadline),
30168    ));
30169
30170    let (value_tx, value_rx) = oneshot::channel::<usize>();
30171    let (done_tx, done_rx) = mpsc::channel();
30172    let runner = std::thread::spawn(move || {
30173      let mut output = None;
30174      {
30175        let mut future = std::pin::pin!(async {
30176          output = Some(value_rx.await.unwrap());
30177        });
30178        executor.block_on(future.as_mut());
30179      }
30180      assert_eq!(output, Some(9));
30181      // Teardown inside the bounded window (see `join_within`).
30182      drop(executor);
30183      let _ = done_tx.send(());
30184    });
30185
30186    // Hold the park across several full deadline windows, every one
30187    // containing many injected progress ticks, then deliver the (legitimately
30188    // late) wake. The loop is elapsed-based so an oversleeping iteration
30189    // extends the hold (more windows) instead of thinning the tick supply,
30190    // and the final tick lands at most one iteration before the wake.
30191    let hold_until = Instant::now() + deadline * 5 / 2;
30192    while Instant::now() < hold_until {
30193      std::thread::sleep(Duration::from_millis(25));
30194      metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
30195    }
30196    value_tx.send(9).unwrap();
30197
30198    match done_rx.recv_timeout(Duration::from_secs(30)) {
30199      Ok(()) => join_within(
30200        "the CT deadline-reset runner exits after teardown",
30201        runner,
30202        Duration::from_secs(30),
30203      ),
30204      Err(error) => panic!(
30205        "a CT park with ongoing runtime progress fired its deadline or lost its wake ({error})"
30206      ),
30207    }
30208  }
30209
30210  #[cfg(not(target_family = "wasm"))]
30211  #[test]
30212  fn multi_thread_cooperative_park_past_deadline_with_no_progress_panics_with_typed_diagnostic() {
30213    // Shape (3): the MT deadline applies to the DriverParker inside
30214    // `cooperative_block_on`. Run the cooperative branch directly on this
30215    // thread with the test-only on-pool marker so the panic is observable as
30216    // a typed payload without rayon in between.
30217    use std::sync::mpsc;
30218
30219    let (done_tx, done_rx) = mpsc::channel();
30220    let runner = std::thread::spawn(move || {
30221      let options = RuntimeOptions {
30222        flavor: RuntimeFlavor::MultiThread,
30223        worker_threads: 1,
30224        max_blocking_tasks: 1,
30225        thread_name_prefix: "deadline-fire".to_string(),
30226        park_deadline: Some(Duration::from_millis(50)),
30227        drain_linger: DEFAULT_DRAIN_LINGER,
30228      };
30229      let executor =
30230        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30231
30232      let payload = {
30233        let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30234        catch_unwind(AssertUnwindSafe(|| {
30235          let mut future = std::pin::pin!(NeverReady);
30236          executor.block_on(future.as_mut());
30237        }))
30238        .expect_err("a dead cooperative park must fire the armed deadline")
30239      };
30240      let diagnostic = payload
30241        .downcast_ref::<BlockOnDeadlock>()
30242        .expect("typed payload");
30243      assert_eq!(
30244        diagnostic.kind,
30245        BlockOnDeadlockKind::MultiThreadCooperativeDeadline
30246      );
30247      assert_eq!(diagnostic.park_deadline, Some(Duration::from_millis(50)));
30248      let message = diagnostic.to_string();
30249      assert!(
30250        message.contains("block_on"),
30251        "diagnostic must name the hazard, got: {message}"
30252      );
30253      assert_eq!(
30254        executor.parked_drivers.count.load(Ordering::SeqCst),
30255        0,
30256        "the firing driver must deregister itself before panicking"
30257      );
30258      // Teardown inside the bounded window (see `join_within`).
30259      drop(executor);
30260      let _ = done_tx.send(());
30261    });
30262
30263    match done_rx.recv_timeout(Duration::from_secs(10)) {
30264      Ok(()) => join_within(
30265        "the MT deadline-fire runner exits after executor teardown",
30266        runner,
30267        Duration::from_secs(30),
30268      ),
30269      Err(error) => panic!("MT cooperative park deadline did not fire ({error})"),
30270    }
30271  }
30272
30273  #[cfg(not(target_family = "wasm"))]
30274  #[test]
30275  fn multi_thread_cooperative_park_deadline_resets_on_executor_progress() {
30276    // Shape (4), MT side of the progress reset: a legitimately slow wake under
30277    // an armed deadline must NOT fire while the executor's
30278    // metrics keep advancing. Topology: the sole worker is parked as a
30279    // cooperative driver (real pool thread, real DriverParker park); the test
30280    // injects progress ticks across several full deadline windows, then
30281    // delivers the late wake.
30282    //
30283    // Deadline/cadence ratio: one tick must land inside EVERY armed window,
30284    // so the window must dwarf the injector thread's worst-case scheduling
30285    // stall -- on starved CI hosts (GitHub macOS runners: ~3 heavily
30286    // oversubscribed cores) a 30ms sleep can overshoot past a 60ms window,
30287    // and a full window with zero progress IS a deadline fire by design.
30288    // Ticks every ~25ms against a 1s window tolerate a ~975ms stall while
30289    // still holding the park across multiple full windows, proving the
30290    // repeated RE-ARM rather than a single lucky first window.
30291    use std::sync::mpsc;
30292
30293    let (done_tx, done_rx) = mpsc::channel();
30294    let runner = std::thread::spawn(move || {
30295      let deadline = Duration::from_millis(1000);
30296      let metrics = Arc::new(RuntimeMetrics::default());
30297      let options = RuntimeOptions {
30298        flavor: RuntimeFlavor::MultiThread,
30299        worker_threads: 1,
30300        max_blocking_tasks: 1,
30301        thread_name_prefix: "deadline-reset".to_string(),
30302        park_deadline: Some(deadline),
30303        drain_linger: DEFAULT_DRAIN_LINGER,
30304      };
30305      let executor = Arc::new(MultiThreadExecutor::new(&options, Arc::clone(&metrics)).unwrap());
30306
30307      // Park the sole worker as a cooperative driver awaiting a gate (same
30308      // topology as the miss-compensation test).
30309      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30310      let td_exec = Arc::clone(&executor);
30311      let completed = Arc::new(AtomicBool::new(false));
30312      let td_flag = Arc::clone(&completed);
30313      let td_body = async move {
30314        let mut inner = std::pin::pin!(async move {
30315          assert_eq!(gate_rx.await.unwrap(), 9);
30316        });
30317        td_exec.block_on(inner.as_mut());
30318        td_flag.store(true, Ordering::SeqCst);
30319      };
30320      let sched = Arc::clone(&executor);
30321      let (td_runnable, td_task) = async_task::spawn(td_body, move |r| sched.schedule(r));
30322      executor.schedule(td_runnable);
30323      wait_until("the sole driver parked", || {
30324        executor.parked_drivers.count.load(Ordering::SeqCst) == 1
30325      });
30326
30327      // Outlive the armed deadline several times over, with every window
30328      // containing many injected progress ticks (the counter the reset
30329      // reads). Elapsed-based so an oversleeping iteration extends the hold
30330      // instead of thinning the tick supply, and the final tick lands at
30331      // most one iteration before the late wake below.
30332      let hold_until = Instant::now() + deadline * 5 / 2;
30333      while Instant::now() < hold_until {
30334        std::thread::sleep(Duration::from_millis(25));
30335        metrics.runnable_polls.fetch_add(1, Ordering::Relaxed);
30336      }
30337      assert!(
30338        !completed.load(Ordering::SeqCst),
30339        "the driver must still be parked awaiting its late wake"
30340      );
30341      gate_tx.send(9).unwrap();
30342      futures::executor::block_on(td_task);
30343      assert!(
30344        completed.load(Ordering::SeqCst),
30345        "the late wake must complete the parked driver"
30346      );
30347      // Teardown inside the bounded window (see `join_within`).
30348      drop(executor);
30349      let _ = done_tx.send(());
30350    });
30351
30352    match done_rx.recv_timeout(Duration::from_secs(30)) {
30353      Ok(()) => join_within(
30354        "the MT deadline-reset runner exits after executor teardown",
30355        runner,
30356        Duration::from_secs(30),
30357      ),
30358      Err(error) => panic!(
30359        "an MT cooperative park with ongoing executor progress fired its deadline or lost its wake ({error})"
30360      ),
30361    }
30362  }
30363
30364  #[cfg(not(target_family = "wasm"))]
30365  #[test]
30366  fn multi_thread_foreign_block_on_long_park_is_excluded_from_deadline() {
30367    // Shape (5): the foreign/napi caller thread parks in
30368    // `futures::executor::block_on` for the ENTIRE build -- the normal
30369    // production shape. Even with an aggressively short deadline armed and
30370    // ZERO executor progress for many windows, that park must never fire.
30371    use std::sync::mpsc;
30372
30373    let (done_tx, done_rx) = mpsc::channel();
30374    let runner = std::thread::spawn(move || {
30375      let options = RuntimeOptions {
30376        flavor: RuntimeFlavor::MultiThread,
30377        worker_threads: 2,
30378        max_blocking_tasks: 2,
30379        thread_name_prefix: "foreign-exempt".to_string(),
30380        park_deadline: Some(Duration::from_millis(40)),
30381        drain_linger: DEFAULT_DRAIN_LINGER,
30382      };
30383      let executor =
30384        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30385
30386      let (value_tx, value_rx) = oneshot::channel::<usize>();
30387      let waker_thread = std::thread::spawn(move || {
30388        // Well past several deadline windows, with zero executor progress.
30389        std::thread::sleep(Duration::from_millis(300));
30390        let _ = value_tx.send(7);
30391      });
30392
30393      let started = Instant::now();
30394      let mut output = None;
30395      {
30396        let mut future = std::pin::pin!(async {
30397          output = Some(value_rx.await.unwrap());
30398        });
30399        // This (non-pool) thread takes the parking branch: exempt by design.
30400        executor.block_on(future.as_mut());
30401      }
30402      assert_eq!(output, Some(7));
30403      assert!(
30404        started.elapsed() >= Duration::from_millis(250),
30405        "the park must genuinely have outlived the 40ms deadline many times over"
30406      );
30407      waker_thread.join().unwrap();
30408      // Teardown inside the bounded window: this test was one of the silent
30409      // wedges on the Windows CI runners -- every assertion above had
30410      // passed, so the hang lived in the unbounded residue (executor drop /
30411      // thread teardown). Keep that phase inside the loud bounds.
30412      drop(executor);
30413      let _ = done_tx.send(());
30414    });
30415
30416    match done_rx.recv_timeout(Duration::from_secs(10)) {
30417      Ok(()) => join_within(
30418        "the exempt foreign-park runner exits after executor teardown",
30419        runner,
30420        Duration::from_secs(30),
30421      ),
30422      Err(error) => {
30423        panic!("the exempt foreign-thread whole-build park fired or lost its wake ({error})")
30424      }
30425    }
30426  }
30427
30428  #[cfg(not(target_family = "wasm"))]
30429  #[test]
30430  fn multi_thread_wake_delivered_at_deadline_expiry_edge_is_not_reported_as_deadlock() {
30431    // Deadline-expiry wake race, pinned DETERMINISTICALLY via the expiry test
30432    // hook instead of a wall-clock lottery. Interleaving:
30433    //   (1) the cooperative park's deadline expires with genuinely zero
30434    //       progress (nothing else touches the executor before the park);
30435    //   (2) BEFORE the driver's expiry decision runs -- parker STILL
30436    //       REGISTERED -- a scheduler queues a blocking job; its `wake_one`
30437    //       pops this parker, stores the permit and reports the wake as
30438    //       DELIVERED (so no drainer is spawned). The wake was counted as
30439    //       delivered TO US, so the post-deregister permit re-check is the
30440    //       first-line catch; queued work and the enqueue counters in the
30441    //       fingerprint are further backstops;
30442    //   (3) the expiry decision runs. It must treat the permit as a wake, run
30443    //       the job (which sends the gate), and complete the awaited future
30444    //       instead of reporting `BlockOnDeadlock`.
30445    use std::sync::mpsc;
30446
30447    let (done_tx, done_rx) = mpsc::channel();
30448    let runner = std::thread::spawn(move || {
30449      let options = RuntimeOptions {
30450        flavor: RuntimeFlavor::MultiThread,
30451        worker_threads: 1,
30452        max_blocking_tasks: 1,
30453        thread_name_prefix: "edge-wake".to_string(),
30454        park_deadline: Some(Duration::from_millis(40)),
30455        drain_linger: DEFAULT_DRAIN_LINGER,
30456      };
30457      let executor =
30458        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30459
30460      // Until the racing job sends this gate, the awaited future is Pending.
30461      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30462      // (registered_before_wake, registered_after_wake) observed by the hook.
30463      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(usize, usize)>();
30464
30465      let hook_exec = Arc::clone(&executor);
30466      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
30467        *slot.borrow_mut() = Some(Box::new(move || {
30468          let before = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
30469          // The racing schedule: pushes the job, then `wake_one` pops the
30470          // still-registered parker (permit stored, wake counted as
30471          // delivered, no drainer).
30472          let handle = hook_exec.schedule_blocking(move || {
30473            let _ = gate_tx.send(5usize);
30474            0usize
30475          });
30476          drop(handle); // result unobserved; the gate send is the signal
30477          let after = hook_exec.parked_drivers.count.load(Ordering::SeqCst);
30478          hook_saw_tx.send((before, after)).unwrap();
30479        }));
30480      });
30481
30482      // The test-only on-pool marker runs the cooperative branch and its
30483      // thread-local hook on THIS thread.
30484      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30485      let mut output = None;
30486      {
30487        let mut future = std::pin::pin!(async {
30488          output = Some(gate_rx.await.unwrap());
30489        });
30490        executor.block_on(future.as_mut());
30491      }
30492      // Clear the hook slot BEFORE any assert can panic: a hook left
30493      // installed captures an executor Arc, and dropping the last executor
30494      // Arc from a TLS destructor at thread exit is loader-lock hostile on
30495      // Windows (see the LOADER-LOCK RULE at `WorkerTlsDropAction`).
30496      DEADLINE_EXPIRY_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
30497      assert_eq!(
30498        output,
30499        Some(5),
30500        "the edge-delivered wake's job must have run and completed the future"
30501      );
30502
30503      let (before, after) = hook_saw_rx.recv().expect("the expiry hook must have fired");
30504      assert_eq!(
30505        before, 1,
30506        "the hook must observe the parker STILL registered (race window)"
30507      );
30508      assert_eq!(
30509        after, 0,
30510        "the racing wake_one must have popped the parker (wake DELIVERED)"
30511      );
30512      // Teardown inside the bounded window (see `join_within`).
30513      drop(executor);
30514      let _ = done_tx.send(());
30515    });
30516
30517    match done_rx.recv_timeout(Duration::from_secs(10)) {
30518      Ok(()) => join_within(
30519        "the expiry-edge-wake runner exits after executor teardown",
30520        runner,
30521        Duration::from_secs(30),
30522      ),
30523      Err(error) => panic!(
30524        "a wake delivered at the deadline-expiry edge was reported as a deadlock or lost ({error})"
30525      ),
30526    }
30527  }
30528
30529  #[cfg(not(target_family = "wasm"))]
30530  #[test]
30531  fn multi_thread_blocking_enqueue_between_queue_recheck_and_verdict_is_not_reported_as_deadlock() {
30532    // Enqueue/fingerprint invariant: both runnable and blocking submissions
30533    // must advance the progress fingerprint at ENQUEUE. A blocking submission
30534    // landing after `has_queued_work()` but before the final fingerprint read
30535    // has already pushed deliverable work, while its wake targets an EMPTY
30536    // parked-driver registry. Only `blocking_tasks_scheduled` makes that work
30537    // visible to the verdict. DEADLINE_VERDICT_TEST_HOOK deterministically
30538    // fires in this window on the driver thread:
30539    //   * `active_drainers` is saturated so `ensure_drainer` cannot start the
30540    //     job on the real worker -- `blocking_tasks_started` provably cannot
30541    //     advance behind our back;
30542    //   * the enqueue counter must therefore save the healthy park.
30543    use std::sync::mpsc;
30544
30545    let (done_tx, done_rx) = mpsc::channel();
30546    let runner = std::thread::spawn(move || {
30547      let options = RuntimeOptions {
30548        flavor: RuntimeFlavor::MultiThread,
30549        worker_threads: 1,
30550        max_blocking_tasks: 1,
30551        thread_name_prefix: "verdict-blocking".to_string(),
30552        park_deadline: Some(Duration::from_millis(40)),
30553        drain_linger: DEFAULT_DRAIN_LINGER,
30554      };
30555      let executor =
30556        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30557      executor
30558        .active_drainers
30559        .store(executor.max_drainers, Ordering::SeqCst);
30560
30561      // Until the racing job sends this gate, the awaited future is Pending.
30562      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
30563      // Registry size observed by the hook (must be 0: already deregistered,
30564      // so no permit can possibly be delivered for this enqueue).
30565      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<usize>();
30566
30567      let hook_exec = Arc::clone(&executor);
30568      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
30569        *slot.borrow_mut() = Some(Box::new(move || {
30570          let handle = hook_exec.schedule_blocking(move || {
30571            let _ = gate_tx.send(11usize);
30572            0usize
30573          });
30574          drop(handle); // result unobserved; the gate send is the signal
30575          hook_saw_tx
30576            .send(hook_exec.parked_drivers.count.load(Ordering::SeqCst))
30577            .unwrap();
30578        }));
30579      });
30580
30581      // The test-only on-pool marker runs the cooperative branch and its
30582      // thread-local hook on THIS thread.
30583      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30584      let mut output = None;
30585      {
30586        let mut future = std::pin::pin!(async {
30587          output = Some(gate_rx.await.unwrap());
30588        });
30589        executor.block_on(future.as_mut());
30590      }
30591      assert_eq!(
30592        output,
30593        Some(11),
30594        "the enqueued job must have been run by the surviving driver via run_one"
30595      );
30596
30597      let registered_at_hook = hook_saw_rx
30598        .recv()
30599        .expect("the verdict hook must have fired");
30600      assert_eq!(
30601        registered_at_hook, 0,
30602        "the hook fires post-deregister: no permit can be delivered, only the fingerprint's          enqueue counter can catch this submission"
30603      );
30604      executor.active_drainers.store(0, Ordering::SeqCst);
30605      let _ = done_tx.send(());
30606    });
30607
30608    match done_rx.recv_timeout(Duration::from_secs(10)) {
30609      Ok(()) => runner.join().unwrap(),
30610      Err(error) => panic!(
30611        "a blocking enqueue landing between the queue re-check and the verdict was reported as a deadlock or lost ({error})"
30612      ),
30613    }
30614  }
30615
30616  #[cfg(not(target_family = "wasm"))]
30617  #[test]
30618  fn multi_thread_cross_thread_publication_epoch_at_final_verdict_prevents_deadlock() {
30619    // Publish only through the deadlock-admission protocol after every legacy
30620    // final check. No runnable, blocking job, permit, timer, or metrics
30621    // fingerprint changes, so only the synchronized publication epoch can
30622    // prevent the false verdict. The future observes a separate test flag on
30623    // its next poll; that flag is deliberately not a scheduler wake source.
30624    use std::sync::mpsc;
30625
30626    struct ReadyAfterPublication(Arc<AtomicBool>);
30627
30628    impl Future for ReadyAfterPublication {
30629      type Output = ();
30630
30631      fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
30632        if self.0.load(Ordering::SeqCst) {
30633          Poll::Ready(())
30634        } else {
30635          Poll::Pending
30636        }
30637      }
30638    }
30639
30640    let (done_tx, done_rx) = mpsc::channel();
30641    let runner = std::thread::spawn(move || {
30642      let options = RuntimeOptions {
30643        flavor: RuntimeFlavor::MultiThread,
30644        worker_threads: 1,
30645        max_blocking_tasks: 1,
30646        thread_name_prefix: "final-verdict-publication".to_string(),
30647        park_deadline: Some(Duration::from_millis(40)),
30648        drain_linger: DEFAULT_DRAIN_LINGER,
30649      };
30650      let executor =
30651        Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
30652
30653      let ready = Arc::new(AtomicBool::new(false));
30654      let baseline_fingerprint = executor.metrics.progress_fingerprint();
30655      let baseline_publications = executor.deadlock_state.publications();
30656      let (hook_saw_tx, hook_saw_rx) = mpsc::channel::<(u64, bool, bool, bool, bool, usize)>();
30657      let hook_exec = Arc::clone(&executor);
30658      let hook_ready = Arc::clone(&ready);
30659      DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
30660        *slot.borrow_mut() = Some(Box::new(move || {
30661          let publication_executor = Arc::clone(&hook_exec);
30662          std::thread::spawn(move || {
30663            let mut admission = publication_executor
30664              .begin_deadlock_admission()
30665              .expect("the deadline detector must enable admission accounting");
30666            hook_ready.store(true, Ordering::SeqCst);
30667            admission.mark_published();
30668          })
30669          .join()
30670          .unwrap();
30671          hook_saw_tx
30672            .send((
30673              hook_exec.deadlock_state.publications(),
30674              !hook_exec.has_queued_runnable(),
30675              hook_exec
30676                .blocking_queue
30677                .lock()
30678                .unwrap_or_else(std::sync::PoisonError::into_inner)
30679                .is_empty(),
30680              hook_exec.next_timer_deadline().is_none(),
30681              hook_exec.metrics.progress_fingerprint() == baseline_fingerprint,
30682              hook_exec.parked_drivers.count.load(Ordering::SeqCst),
30683            ))
30684            .unwrap();
30685        }));
30686      });
30687
30688      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30689      let mut future = std::pin::pin!(ReadyAfterPublication(ready));
30690      assert_eq!(
30691        executor.block_on(future.as_mut()),
30692        BlockOnOutcome::Completed,
30693        "the publication epoch must veto the first verdict and permit the next poll"
30694      );
30695      let (
30696        publications,
30697        runnable_queue_empty,
30698        blocking_queue_empty,
30699        timer_heap_empty,
30700        fingerprint_unchanged,
30701        parked_drivers,
30702      ) = hook_saw_rx
30703        .recv()
30704        .expect("the final-verdict hook must have fired");
30705      assert_eq!(
30706        publications,
30707        baseline_publications
30708          .checked_add(1)
30709          .expect("test publication epoch must not overflow"),
30710        "the cross-thread admission must publish exactly one epoch"
30711      );
30712      assert!(
30713        runnable_queue_empty,
30714        "the publication test must not queue a runnable"
30715      );
30716      assert!(
30717        blocking_queue_empty,
30718        "the publication test must not queue blocking work"
30719      );
30720      assert!(
30721        timer_heap_empty,
30722        "the publication test must not register a timer"
30723      );
30724      assert!(
30725        fingerprint_unchanged,
30726        "ordinary metrics progress must not veto the publication-only verdict"
30727      );
30728      assert_eq!(
30729        parked_drivers, 0,
30730        "the publication must land after the cooperative parker was deregistered"
30731      );
30732      let _ = done_tx.send(());
30733    });
30734
30735    match done_rx.recv_timeout(Duration::from_secs(10)) {
30736      Ok(()) => runner.join().unwrap(),
30737      Err(error) => panic!(
30738        "a publication-only cross-thread admission was not observed by the final verdict ({error})"
30739      ),
30740    }
30741  }
30742
30743  #[cfg(not(target_family = "wasm"))]
30744  #[test]
30745  fn multi_thread_selected_owner_handoff_before_unpark_vetoes_gated_deadlock() {
30746    // The exact owner lane is saturated and reserved while the cooperative
30747    // driver parks. At expiry, release selects that driver and publishes its
30748    // handoff, then pauses before unpark. The driver reaches the gated verdict
30749    // with no queue, timer, metric, or ordinary publication change. The
30750    // exact-owner publication boundary must serialize the final check. The
30751    // hook then consumes the ordinary permit and renews the lane reservation,
30752    // leaving only the pending-handoff predicate to veto the false deadlock.
30753    use std::sync::mpsc;
30754
30755    let executor = multi_thread_executor(
30756      1,
30757      Some(Duration::from_millis(40)),
30758      "deadline-selected-handoff",
30759    );
30760    let owner = executor.fresh_owner_token();
30761    executor.active_blocking_owners.register(owner);
30762    executor
30763      .active_blocking
30764      .store(executor.max_blocking, Ordering::SeqCst);
30765
30766    let ready = Arc::new(AtomicBool::new(false));
30767    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
30768    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
30769    let (selected_tx, selected_rx) = mpsc::channel();
30770    let (allow_unpark_tx, allow_unpark_rx) = mpsc::channel();
30771    let (released_tx, released_rx) = mpsc::channel();
30772    let (renewed_tx, renewed_rx) = mpsc::channel();
30773    let release_executor = Arc::clone(&executor);
30774    let renewal_executor = Arc::clone(&executor);
30775    let release_ready = Arc::clone(&ready);
30776    let releaser = std::thread::spawn(move || {
30777      let reservation =
30778        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
30779          .expect("the synthetic exact-owner lane must start reserved");
30780      reservation_ready_tx.send(()).unwrap();
30781      OWNER_HANDOFF_SELECTED_TEST_HOOK.with(|slot| {
30782        *slot.borrow_mut() = Some(Box::new(move || {
30783          selected_tx.send(()).unwrap();
30784          allow_unpark_rx.recv().unwrap();
30785          let renewed = renewal_executor
30786            .active_blocking_owners
30787            .reserve(owner)
30788            .expect("the released exact-owner lane must be reservable before unpark");
30789          release_ready.store(true, Ordering::SeqCst);
30790          renewed_tx.send(renewed).unwrap();
30791        }));
30792      });
30793      release_reservation_rx.recv().unwrap();
30794      released_tx
30795        .send(release_executor.release_owner_lane_and_wake(reservation))
30796        .unwrap();
30797    });
30798    reservation_ready_rx
30799      .recv_timeout(Duration::from_secs(2))
30800      .expect("the exact-owner reservation must be established before the park");
30801
30802    let baseline_fingerprint = executor.metrics.progress_fingerprint();
30803    let baseline_publications = executor.deadlock_state.publications();
30804    let expiry_executor = Arc::clone(&executor);
30805    DEADLINE_EXPIRY_TEST_HOOK.with(|slot| {
30806      *slot.borrow_mut() = Some(Box::new(move || {
30807        release_reservation_tx.send(()).unwrap();
30808        selected_rx
30809          .recv_timeout(Duration::from_secs(2))
30810          .expect("reservation release must select the still-registered exact-owner driver");
30811        assert_eq!(
30812          expiry_executor.parked_drivers.count.load(Ordering::SeqCst),
30813          0,
30814          "handoff selection must remove the driver before pausing ahead of unpark"
30815        );
30816      }));
30817    });
30818
30819    let verdict_executor = Arc::clone(&executor);
30820    let verdict_ready = Arc::clone(&ready);
30821    DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
30822      *slot.borrow_mut() = Some(Box::new(move || {
30823        assert!(
30824          verdict_executor.active_blocking_owners.is_available(owner),
30825          "reservation release must publish lane availability before handoff selection"
30826        );
30827        assert!(!verdict_executor.has_queued_runnable());
30828        assert!(
30829          verdict_executor
30830            .blocking_queue
30831            .lock()
30832            .unwrap_or_else(std::sync::PoisonError::into_inner)
30833            .is_empty()
30834        );
30835        assert!(verdict_executor.next_timer_deadline().is_none());
30836        assert!(
30837          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
30838          "ordinary metrics progress must not save the selected-handoff verdict"
30839        );
30840        assert_eq!(
30841          verdict_executor.deadlock_state.publications(),
30842          baseline_publications
30843        );
30844        assert!(
30845          !verdict_ready.load(Ordering::SeqCst),
30846          "the selected handoff must still be paused before unpark at the gated verdict"
30847        );
30848        allow_unpark_tx.send(()).unwrap();
30849      }));
30850    });
30851    let exact_owner_executor = Arc::clone(&executor);
30852    DEADLINE_EXACT_OWNER_VERDICT_TEST_HOOK.with(|slot| {
30853      *slot.borrow_mut() = Some(Box::new(move |parker| {
30854        assert!(
30855          parker.consume_permit(),
30856          "the selected handoff must unpark before exact-owner final predicates run"
30857        );
30858        assert!(
30859          parker.has_owner_handoff(),
30860          "consuming the ordinary permit must leave the exact-owner handoff pending"
30861        );
30862        assert!(
30863          !exact_owner_executor
30864            .active_blocking_owners
30865            .is_available(owner),
30866          "the renewed reservation must prevent lane availability from saving the verdict"
30867        );
30868      }));
30869    });
30870
30871    {
30872      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30873      let _owner = BlockingOwnerGuard::enter(Some(owner));
30874      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
30875        dependency: BlockingDependency {
30876          job: BlockingJobId(u64::MAX - 80),
30877          owner: Some(owner)
30878        },
30879        ready: Arc::clone(&ready),
30880      });
30881      assert_eq!(
30882        executor.block_on(future.as_mut()),
30883        BlockOnOutcome::Completed
30884      );
30885    }
30886    assert!(
30887      released_rx
30888        .recv_timeout(Duration::from_secs(2))
30889        .expect("the selected handoff publication must finish"),
30890      "reservation release must have selected the cooperative driver"
30891    );
30892    let renewed = renewed_rx
30893      .recv_timeout(Duration::from_secs(2))
30894      .expect("the selected handoff must renew the owner reservation before unpark");
30895    releaser.join().unwrap();
30896
30897    executor.active_blocking_owners.release(owner, renewed);
30898    executor.active_blocking.store(0, Ordering::SeqCst);
30899    executor.active_blocking_owners.unregister(owner);
30900    executor.begin_shutdown();
30901    executor.wait_until_scheduler_idle();
30902  }
30903
30904  #[cfg(not(target_family = "wasm"))]
30905  #[test]
30906  fn multi_thread_owner_release_after_parker_deregistration_vetoes_gated_deadlock() {
30907    // Release begins only after the deadline path has deregistered its parker
30908    // and completed every legacy final check. No targeted wake or ordinary
30909    // scheduler publication can save the verdict; the synchronized exact-owner
30910    // availability predicate must observe the released lane.
30911    use std::sync::mpsc;
30912
30913    let executor = multi_thread_executor(
30914      1,
30915      Some(Duration::from_millis(40)),
30916      "deadline-owner-no-parker",
30917    );
30918    let owner = executor.fresh_owner_token();
30919    executor.active_blocking_owners.register(owner);
30920    executor
30921      .active_blocking
30922      .store(executor.max_blocking, Ordering::SeqCst);
30923
30924    let ready = Arc::new(AtomicBool::new(false));
30925    let (reservation_ready_tx, reservation_ready_rx) = mpsc::channel();
30926    let (release_reservation_tx, release_reservation_rx) = mpsc::channel();
30927    let (released_tx, released_rx) = mpsc::channel();
30928    let release_executor = Arc::clone(&executor);
30929    let release_ready = Arc::clone(&ready);
30930    let releaser = std::thread::spawn(move || {
30931      let reservation =
30932        OwnerLaneReservation::try_acquire(&release_executor.active_blocking_owners, owner)
30933          .expect("the synthetic exact-owner lane must start reserved");
30934      reservation_ready_tx.send(()).unwrap();
30935      release_reservation_rx.recv().unwrap();
30936      let woke = release_executor.release_owner_lane_and_wake(reservation);
30937      release_ready.store(true, Ordering::SeqCst);
30938      released_tx.send(woke).unwrap();
30939    });
30940    reservation_ready_rx
30941      .recv_timeout(Duration::from_secs(2))
30942      .expect("the exact-owner reservation must be established before the park");
30943
30944    let baseline_fingerprint = executor.metrics.progress_fingerprint();
30945    let baseline_publications = executor.deadlock_state.publications();
30946    let verdict_executor = Arc::clone(&executor);
30947    DEADLINE_FINAL_VERDICT_TEST_HOOK.with(|slot| {
30948      *slot.borrow_mut() = Some(Box::new(move || {
30949        assert_eq!(
30950          verdict_executor.parked_drivers.count.load(Ordering::SeqCst),
30951          0,
30952          "the release must begin only after deadline deregistration"
30953        );
30954        release_reservation_tx.send(()).unwrap();
30955        assert!(
30956          !released_rx
30957            .recv_timeout(Duration::from_secs(2))
30958            .expect("the no-parker owner release must finish"),
30959          "no registered parker may receive the released owner lane"
30960        );
30961        assert!(verdict_executor.active_blocking_owners.is_available(owner));
30962        assert!(!verdict_executor.has_queued_runnable());
30963        assert!(
30964          verdict_executor
30965            .blocking_queue
30966            .lock()
30967            .unwrap_or_else(std::sync::PoisonError::into_inner)
30968            .is_empty()
30969        );
30970        assert!(verdict_executor.next_timer_deadline().is_none());
30971        assert!(
30972          verdict_executor.metrics.progress_fingerprint() == baseline_fingerprint,
30973          "ordinary metrics progress must not save the no-parker release verdict"
30974        );
30975        assert_eq!(
30976          verdict_executor.deadlock_state.publications(),
30977          baseline_publications
30978        );
30979      }));
30980    });
30981
30982    {
30983      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
30984      let _owner = BlockingOwnerGuard::enter(Some(owner));
30985      let mut future = std::pin::pin!(ReadyAfterOwnerLanePublication {
30986        dependency: BlockingDependency {
30987          job: BlockingJobId(u64::MAX - 81),
30988          owner: Some(owner)
30989        },
30990        ready: Arc::clone(&ready),
30991      });
30992      assert_eq!(
30993        executor.block_on(future.as_mut()),
30994        BlockOnOutcome::Completed
30995      );
30996    }
30997    releaser.join().unwrap();
30998
30999    executor.active_blocking.store(0, Ordering::SeqCst);
31000    executor.active_blocking_owners.unregister(owner);
31001    executor.begin_shutdown();
31002    executor.wait_until_scheduler_idle();
31003  }
31004
31005  #[cfg(not(target_family = "wasm"))]
31006  #[test]
31007  fn multi_thread_shutdown_stop_precedes_abort_wake_at_gated_deadlock_verdict() {
31008    // Pin the shutdown race after the final verdict gate has closed:
31009    //   1. an accepted task is pending, so shutdown abort must wake it;
31010    //   2. a cooperative block_on reaches its deadline and pauses while
31011    //      holding the closed verdict gate;
31012    //   3. shutdown publishes generation stop, transitions
31013    //      Running -> Stopping, then its abort wake blocks entering scheduler
31014    //      admission behind that gate;
31015    //   4. the gated final verdict must observe stop and return Stopped,
31016    //      reopening the gate so the abort wake and shutdown can finish.
31017    //
31018    // Before stop was published by the controller transition, step 3 blocked
31019    // inside abort before executor shutdown could set it. The driver then saw
31020    // empty queues and emitted a false BlockOnDeadlock.
31021    use std::sync::mpsc;
31022
31023    let controller = Arc::new(RuntimeController::new());
31024    controller
31025      .configure(RuntimeOptions {
31026        flavor: RuntimeFlavor::MultiThread,
31027        worker_threads: 2,
31028        max_blocking_tasks: 1,
31029        thread_name_prefix: "shutdown-gated-verdict".to_string(),
31030        park_deadline: Some(Duration::from_millis(40)),
31031        drain_linger: DEFAULT_DRAIN_LINGER,
31032      })
31033      .expect("the shutdown race test configuration must be accepted");
31034
31035    let backend = controller.backend();
31036    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31037      panic!("the configured backend must be MultiThread");
31038    };
31039    let executor_id = executor.id;
31040    let weak_executor = Arc::downgrade(executor);
31041    drop(backend);
31042
31043    let (task_polled_tx, task_polled_rx) = mpsc::channel();
31044    let task = controller
31045      .try_spawn(async move {
31046        task_polled_tx.send(()).unwrap();
31047        std::future::pending::<()>().await;
31048      })
31049      .unwrap_or_else(|_| panic!("the pending task must be accepted before shutdown"));
31050    task_polled_rx
31051      .recv_timeout(Duration::from_secs(2))
31052      .expect("the accepted task must publish an abortable pending waker");
31053
31054    let (gate_closed_tx, gate_closed_rx) = mpsc::channel();
31055    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
31056    let (driver_result_tx, driver_result_rx) = mpsc::channel();
31057    let driver_controller = Arc::clone(&controller);
31058    let driver = std::thread::spawn(move || {
31059      DEADLINE_GATED_VERDICT_TEST_HOOK.with(|slot| {
31060        *slot.borrow_mut() = Some(Box::new(move || {
31061          gate_closed_tx.send(()).unwrap();
31062          release_verdict_rx.recv().unwrap();
31063        }));
31064      });
31065
31066      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
31067      let mut pending = std::pin::pin!(NeverReady);
31068      let result = catch_unwind(AssertUnwindSafe(|| {
31069        driver_controller.block_on(pending.as_mut())
31070      }));
31071      let result = match result {
31072        Ok(outcome) => Ok(outcome),
31073        Err(payload) => {
31074          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
31075          discard_panic_payload(payload);
31076          Err(was_deadlock)
31077        }
31078      };
31079      driver_result_tx.send(result).unwrap();
31080    });
31081
31082    gate_closed_rx
31083      .recv_timeout(Duration::from_secs(2))
31084      .expect("the cooperative driver must pause with the verdict gate closed");
31085
31086    let (abort_blocked_tx, abort_blocked_rx) = mpsc::channel();
31087    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
31088    let shutdown_controller = Arc::clone(&controller);
31089    let shutdown = std::thread::spawn(move || {
31090      DEADLOCK_CLOSED_GATE_ADMISSION_TEST_HOOK.with(|slot| {
31091        *slot.borrow_mut() = Some(Box::new(move || {
31092          abort_blocked_tx.send(()).unwrap();
31093        }));
31094      });
31095      shutdown_result_tx
31096        .send(shutdown_controller.shutdown())
31097        .unwrap();
31098    });
31099
31100    abort_blocked_rx
31101      .recv_timeout(Duration::from_secs(2))
31102      .expect("the accepted task's abort wake must reach the closed verdict gate");
31103    let lifecycle_is_stopping = {
31104      let state = controller
31105        .state
31106        .lock()
31107        .unwrap_or_else(std::sync::PoisonError::into_inner);
31108      matches!(&state.lifecycle, RuntimeLifecycle::Stopping(_))
31109    };
31110    let stop_was_published = weak_executor
31111      .upgrade()
31112      .is_some_and(|executor| executor.stop.publication_guard().is_stopping());
31113
31114    release_verdict_tx.send(()).unwrap();
31115
31116    let driver_result = driver_result_rx
31117      .recv_timeout(Duration::from_secs(2))
31118      .expect("the gated driver must resolve after observing shutdown");
31119    let shutdown_result = shutdown_result_rx
31120      .recv_timeout(Duration::from_secs(5))
31121      .expect("shutdown must finish after the verdict gate reopens");
31122    // Both threads have sent their results; what remains is teardown (and on
31123    // the shutdown thread, the controller's `wait_for_all_workers` already
31124    // completed inside `shutdown()`). This test was one of the silent wedges
31125    // on the Windows CI runners -- bound the residue loudly.
31126    join_within(
31127      "the gated cooperative driver exits after teardown",
31128      driver,
31129      Duration::from_secs(30),
31130    );
31131    join_within(
31132      "the shutdown thread exits after teardown",
31133      shutdown,
31134      Duration::from_secs(30),
31135    );
31136
31137    assert!(
31138      lifecycle_is_stopping,
31139      "the abort wake must occur after Running -> Stopping"
31140    );
31141    assert!(
31142      stop_was_published,
31143      "generation stop must be visible before shutdown invokes accepted-task aborts"
31144    );
31145    assert_eq!(
31146      driver_result,
31147      Ok(BlockOnOutcome::Stopped),
31148      "shutdown must not be reported as a gated BlockOnDeadlock"
31149    );
31150    shutdown_result.unwrap();
31151    assert!(
31152      futures::executor::block_on(task).is_err(),
31153      "the accepted pending task must resolve as cancelled"
31154    );
31155  }
31156
31157  #[cfg(not(target_family = "wasm"))]
31158  #[test]
31159  fn multi_thread_gated_verdict_linearizes_before_shutdown_publication() {
31160    // Complement the publication-wins race above. The verdict owns both its
31161    // admission gate and the stop-publication mutex before shutdown starts.
31162    // Shutdown reaches the lifecycle -> publication edge but cannot mutate
31163    // Running -> Stopping until the verdict finishes its synchronized checks.
31164    use std::sync::mpsc;
31165
31166    let controller = Arc::new(RuntimeController::new());
31167    controller
31168      .configure(RuntimeOptions {
31169        flavor: RuntimeFlavor::MultiThread,
31170        worker_threads: 2,
31171        max_blocking_tasks: 1,
31172        thread_name_prefix: "verdict-before-shutdown".to_string(),
31173        park_deadline: Some(Duration::from_millis(40)),
31174        drain_linger: DEFAULT_DRAIN_LINGER,
31175      })
31176      .expect("the verdict-first test configuration must be accepted");
31177
31178    let backend = controller.backend();
31179    let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
31180      panic!("the configured backend must be MultiThread");
31181    };
31182    let executor_id = executor.id;
31183    drop(backend);
31184
31185    let (verdict_locked_tx, verdict_locked_rx) = mpsc::channel();
31186    let (release_verdict_tx, release_verdict_rx) = mpsc::channel();
31187    let (driver_result_tx, driver_result_rx) = mpsc::channel();
31188    let driver_controller = Arc::clone(&controller);
31189    let driver = std::thread::spawn(move || {
31190      DEADLINE_SYNCHRONIZED_STOP_TEST_HOOK.with(|slot| {
31191        *slot.borrow_mut() = Some(Box::new(move || {
31192          verdict_locked_tx.send(()).unwrap();
31193          release_verdict_rx.recv().unwrap();
31194        }));
31195      });
31196
31197      let _on_pool = OnPoolWorkerGuard::enter(executor_id);
31198      let mut pending = std::pin::pin!(NeverReady);
31199      let result = catch_unwind(AssertUnwindSafe(|| {
31200        driver_controller.block_on(pending.as_mut())
31201      }));
31202      let result = match result {
31203        Ok(outcome) => Ok(outcome),
31204        Err(payload) => {
31205          let was_deadlock = payload.downcast_ref::<BlockOnDeadlock>().is_some();
31206          discard_panic_payload(payload);
31207          Err(was_deadlock)
31208        }
31209      };
31210      driver_result_tx.send(result).unwrap();
31211    });
31212
31213    verdict_locked_rx
31214      .recv_timeout(Duration::from_secs(2))
31215      .expect("the verdict must hold the synchronized stop-publication guard");
31216
31217    let (shutdown_waiting_tx, shutdown_waiting_rx) = mpsc::channel();
31218    let (shutdown_result_tx, shutdown_result_rx) = mpsc::channel();
31219    let shutdown_controller = Arc::clone(&controller);
31220    let shutdown = std::thread::spawn(move || {
31221      BEFORE_GENERATION_STOP_PUBLICATION_LOCK_TEST_HOOK.with(|slot| {
31222        *slot.borrow_mut() = Some(Box::new(move || {
31223          shutdown_waiting_tx.send(()).unwrap();
31224        }));
31225      });
31226      shutdown_result_tx
31227        .send(shutdown_controller.shutdown())
31228        .unwrap();
31229    });
31230
31231    shutdown_waiting_rx
31232      .recv_timeout(Duration::from_secs(2))
31233      .expect("shutdown must reach the lifecycle-to-publication lock edge");
31234    let early_shutdown = shutdown_result_rx
31235      .recv_timeout(Duration::from_millis(100))
31236      .ok();
31237    let shutdown_was_blocked = early_shutdown.is_none();
31238    release_verdict_tx.send(()).unwrap();
31239
31240    let driver_result = driver_result_rx
31241      .recv_timeout(Duration::from_secs(2))
31242      .expect("the verdict-first driver must finish after release");
31243    let shutdown_result = early_shutdown.unwrap_or_else(|| {
31244      shutdown_result_rx
31245        .recv_timeout(Duration::from_secs(5))
31246        .expect("shutdown must finish after the verdict releases publication")
31247    });
31248    driver.join().unwrap();
31249    shutdown.join().unwrap();
31250
31251    assert!(
31252      shutdown_was_blocked,
31253      "shutdown must not mutate and retire the Running lifecycle while the verdict owns publication"
31254    );
31255    assert_eq!(
31256      driver_result,
31257      Err(true),
31258      "a verdict that linearizes before shutdown must retain the typed deadlock"
31259    );
31260    shutdown_result.unwrap();
31261  }
31262
31263  #[cfg(not(target_family = "wasm"))]
31264  #[test]
31265  fn multi_thread_deadlock_panic_hook_can_submit_scheduler_work() {
31266    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_DEADLOCK_PANIC_HOOK_CHILD";
31267
31268    if std::env::var_os(CHILD_ENV).is_some() {
31269      let executor =
31270        multi_thread_executor(1, Some(Duration::from_millis(40)), "deadlock-panic-hook");
31271      // Keep the synthetic cooperative driver from arming a Rayon drainer. The
31272      // hook only needs to prove that scheduler admission returns.
31273      executor
31274        .active_drainers
31275        .store(executor.max_drainers, Ordering::SeqCst);
31276
31277      let hook_returned = Arc::new(AtomicBool::new(false));
31278      let hook_flag = Arc::clone(&hook_returned);
31279      let hook_executor = Arc::clone(&executor);
31280      let previous_hook = std::panic::take_hook();
31281      std::panic::set_hook(Box::new(move |_| {
31282        let handle = hook_executor.schedule_blocking(|| ());
31283        drop(handle);
31284        hook_flag.store(true, Ordering::SeqCst);
31285      }));
31286
31287      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
31288      let result = catch_unwind(AssertUnwindSafe(|| {
31289        let mut pending = std::pin::pin!(std::future::pending::<()>());
31290        executor.block_on(pending.as_mut());
31291      }));
31292      std::panic::set_hook(previous_hook);
31293
31294      let payload = result.expect_err("the synthetic cooperative park must reach its deadline");
31295      assert!(
31296        payload.downcast_ref::<BlockOnDeadlock>().is_some(),
31297        "the deadline must retain its structured diagnostic"
31298      );
31299      assert!(
31300        hook_returned.load(Ordering::SeqCst),
31301        "the panic hook's scheduler submission must return before unwind"
31302      );
31303
31304      executor.active_drainers.store(0, Ordering::SeqCst);
31305      executor.begin_shutdown();
31306      executor.wait_until_scheduler_idle();
31307      return;
31308    }
31309
31310    let mut child = std::process::Command::new(std::env::current_exe().unwrap())
31311      .arg("--exact")
31312      .arg("async_runtime::tests::multi_thread_deadlock_panic_hook_can_submit_scheduler_work")
31313      .arg("--nocapture")
31314      .env(CHILD_ENV, "1")
31315      .stdout(std::process::Stdio::piped())
31316      .stderr(std::process::Stdio::piped())
31317      .spawn()
31318      .expect("the panic-hook subprocess must start");
31319    let deadline = Instant::now() + Duration::from_secs(5);
31320    let status = loop {
31321      if let Some(status) = child
31322        .try_wait()
31323        .expect("the subprocess status must be readable")
31324      {
31325        break status;
31326      }
31327      if Instant::now() >= deadline {
31328        let _ = child.kill();
31329        let output = child
31330          .wait_with_output()
31331          .expect("the timed-out subprocess must be reaped");
31332        panic!(
31333          "a panic-hook scheduler submission deadlocked the verdict gate\nstdout={}\nstderr={}",
31334          String::from_utf8_lossy(&output.stdout),
31335          String::from_utf8_lossy(&output.stderr)
31336        );
31337      }
31338      std::thread::sleep(Duration::from_millis(10));
31339    };
31340    let output = child
31341      .wait_with_output()
31342      .expect("the completed subprocess output must be read");
31343    assert!(
31344      status.success(),
31345      "the panic-hook admission subprocess failed; status={:?}\nstdout={}\nstderr={}",
31346      status.code(),
31347      String::from_utf8_lossy(&output.stdout),
31348      String::from_utf8_lossy(&output.stderr)
31349    );
31350  }
31351
31352  // The PARK_DEADLINE_ENV parse (unset/zero/garbage => disabled) lives in
31353  // the embedding host's env-resolution pipeline together with the read
31354  // itself; its tests live there.
31355
31356  // ---- Timer facility: heap driver + timekeeper (MT), host delegation (CT) --
31357  // The tests cover both deadlock-detection interactions. All tests build
31358  // executors LOCALLY (never the global RUNTIME or the process-global host
31359  // driver) and run hang-prone workloads on a child thread bounded by
31360  // `recv_timeout`, so a regression fails loudly instead of wedging the suite.
31361
31362  #[test]
31363  fn contained_waker_separates_wake_and_drop_panics() {
31364    struct PanicWakeAndDrop;
31365
31366    impl std::task::Wake for PanicWakeAndDrop {
31367      fn wake(self: Arc<Self>) {
31368        panic!("intentional consuming wake panic");
31369      }
31370
31371      fn wake_by_ref(self: &Arc<Self>) {
31372        panic!("intentional borrowed wake panic");
31373      }
31374    }
31375
31376    impl Drop for PanicWakeAndDrop {
31377      fn drop(&mut self) {
31378        panic!("intentional RawWaker destructor panic");
31379      }
31380    }
31381
31382    wake_waker_contained(Waker::from(Arc::new(PanicWakeAndDrop)));
31383  }
31384
31385  struct TestHostileRawWakerState {
31386    clones: AtomicUsize,
31387    drop_panics: AtomicUsize,
31388    panic_on_clone: Option<usize>,
31389  }
31390
31391  struct TestHostileRawWakerToken {
31392    state: Arc<TestHostileRawWakerState>,
31393    panic_on_drop: bool,
31394  }
31395
31396  unsafe fn clone_test_hostile_waker(data: *const ()) -> std::task::RawWaker {
31397    let token = unsafe { &*data.cast::<TestHostileRawWakerToken>() };
31398    let clone = token.state.clones.fetch_add(1, Ordering::SeqCst) + 1;
31399    assert_ne!(
31400      token.state.panic_on_clone,
31401      Some(clone),
31402      "intentional RawWaker clone panic"
31403    );
31404    let clone = Box::new(TestHostileRawWakerToken {
31405      state: Arc::clone(&token.state),
31406      panic_on_drop: true,
31407    });
31408    std::task::RawWaker::new(Box::into_raw(clone).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE)
31409  }
31410
31411  unsafe fn wake_test_hostile_waker(data: *const ()) {
31412    unsafe { drop_test_hostile_waker(data) };
31413  }
31414
31415  unsafe fn wake_test_hostile_waker_by_ref(_data: *const ()) {}
31416
31417  unsafe fn drop_test_hostile_waker(data: *const ()) {
31418    let token = unsafe { Box::from_raw(data.cast_mut().cast::<TestHostileRawWakerToken>()) };
31419    if token.panic_on_drop {
31420      token.state.drop_panics.fetch_add(1, Ordering::SeqCst);
31421      panic!("intentional cloned RawWaker destructor panic");
31422    }
31423  }
31424
31425  static TEST_HOSTILE_RAW_WAKER_VTABLE: std::task::RawWakerVTable = std::task::RawWakerVTable::new(
31426    clone_test_hostile_waker,
31427    wake_test_hostile_waker,
31428    wake_test_hostile_waker_by_ref,
31429    drop_test_hostile_waker,
31430  );
31431
31432  fn test_hostile_waker(state: Arc<TestHostileRawWakerState>) -> Waker {
31433    let token = Box::new(TestHostileRawWakerToken {
31434      state,
31435      panic_on_drop: false,
31436    });
31437    let raw = std::task::RawWaker::new(Box::into_raw(token).cast(), &TEST_HOSTILE_RAW_WAKER_VTABLE);
31438    unsafe { Waker::from_raw(raw) }
31439  }
31440
31441  /// Waker that only counts. For polling a raw `Sleep` without an executor.
31442  struct CountingWake(AtomicUsize);
31443  impl std::task::Wake for CountingWake {
31444    fn wake(self: Arc<Self>) {
31445      self.0.fetch_add(1, Ordering::SeqCst);
31446    }
31447  }
31448
31449  /// One armed stub timer: the latest waker plus a cancellation flag.
31450  type StubTimerCell = Arc<(Mutex<Waker>, AtomicBool)>;
31451
31452  /// Test-local host driver: fires each registered timer from a helper OS
31453  /// thread at its deadline (re-registration under the same id refreshes the
31454  /// waker without arming a second thread, per the `TimerDriver` contract).
31455  struct StubHostTimerDriver {
31456    timers: Mutex<FxHashMap<TimerId, StubTimerCell>>,
31457  }
31458
31459  impl StubHostTimerDriver {
31460    fn new() -> Self {
31461      Self {
31462        timers: Mutex::new(FxHashMap::default()),
31463      }
31464    }
31465  }
31466
31467  impl TimerDriver for StubHostTimerDriver {
31468    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
31469      let mut timers = self.timers.lock().unwrap();
31470      if let Some(cell) = timers.get(&id) {
31471        *cell.0.lock().unwrap() = waker;
31472        return;
31473      }
31474      let cell = Arc::new((Mutex::new(waker), AtomicBool::new(false)));
31475      timers.insert(id, Arc::clone(&cell));
31476      std::thread::spawn(move || {
31477        let now = Instant::now();
31478        if deadline > now {
31479          std::thread::sleep(deadline - now);
31480        }
31481        if !cell.1.load(Ordering::SeqCst) {
31482          cell.0.lock().unwrap().wake_by_ref();
31483        }
31484      });
31485    }
31486
31487    fn cancel(&self, id: TimerId) {
31488      let removed = self.timers.lock().unwrap().remove(&id);
31489      if let Some(cell) = removed {
31490        cell.1.store(true, Ordering::SeqCst);
31491      }
31492    }
31493  }
31494
31495  /// Manual stub for registry-lifetime tests: records `register`/`cancel`
31496  /// calls, keeps the binding-shaped pending-waker bookkeeping (latest waker
31497  /// per timer id), never fires on its own, and reports the controllable
31498  /// `live` flag (a killed stub plays a driver whose owning host context --
31499  /// napi env in the binding -- was torn down).
31500  struct ManualStubDriver {
31501    live: AtomicBool,
31502    registers: Mutex<Vec<(TimerId, Instant)>>,
31503    cancels: Mutex<Vec<TimerId>>,
31504    pending: Mutex<FxHashMap<TimerId, Waker>>,
31505    swept: AtomicBool,
31506  }
31507
31508  impl ManualStubDriver {
31509    fn new() -> Arc<Self> {
31510      Arc::new(Self {
31511        live: AtomicBool::new(true),
31512        registers: Mutex::new(Vec::new()),
31513        cancels: Mutex::new(Vec::new()),
31514        pending: Mutex::new(FxHashMap::default()),
31515        swept: AtomicBool::new(false),
31516      })
31517    }
31518
31519    fn kill(&self) {
31520      self.live.store(false, Ordering::SeqCst);
31521    }
31522  }
31523
31524  impl TimerDriver for ManualStubDriver {
31525    fn register(&self, id: TimerId, deadline: Instant, waker: Waker) {
31526      self.registers.lock().unwrap().push((id, deadline));
31527      self.pending.lock().unwrap().insert(id, waker);
31528    }
31529
31530    fn cancel(&self, id: TimerId) {
31531      self.cancels.lock().unwrap().push(id);
31532      self.pending.lock().unwrap().remove(&id);
31533    }
31534
31535    fn is_live(&self) -> bool {
31536      self.live.load(Ordering::SeqCst)
31537    }
31538
31539    fn on_swept(&self) {
31540      // The binding shape: drain the pending map and wake everything armed
31541      // here so those sleeps re-poll onto the next live registrant.
31542      self.swept.store(true, Ordering::SeqCst);
31543      let wakers: Vec<Waker> = self
31544        .pending
31545        .lock()
31546        .unwrap()
31547        .drain()
31548        .map(|(_, w)| w)
31549        .collect();
31550      for waker in wakers {
31551        waker.wake();
31552      }
31553    }
31554  }
31555
31556  struct PanickingTimerDriver {
31557    swept: AtomicBool,
31558  }
31559
31560  impl TimerDriver for PanickingTimerDriver {
31561    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31562
31563    fn cancel(&self, _id: TimerId) {}
31564
31565    fn is_live(&self) -> bool {
31566      panic!("intentional timer-driver liveness panic");
31567    }
31568
31569    fn on_swept(&self) {
31570      self.swept.store(true, Ordering::SeqCst);
31571      panic!("intentional timer-driver sweep panic");
31572    }
31573  }
31574
31575  struct PanickingRegisterTimerDriver {
31576    registers: AtomicUsize,
31577    cancels: Mutex<Vec<TimerId>>,
31578    pending: Mutex<FxHashMap<TimerId, Waker>>,
31579  }
31580
31581  impl PanickingRegisterTimerDriver {
31582    fn new() -> Arc<Self> {
31583      Arc::new(Self {
31584        registers: AtomicUsize::new(0),
31585        cancels: Mutex::new(Vec::new()),
31586        pending: Mutex::new(FxHashMap::default()),
31587      })
31588    }
31589  }
31590
31591  impl TimerDriver for PanickingRegisterTimerDriver {
31592    fn register(&self, id: TimerId, _deadline: Instant, waker: Waker) {
31593      self.registers.fetch_add(1, Ordering::SeqCst);
31594      self.pending.lock().unwrap().insert(id, waker);
31595      panic!("intentional timer-driver register panic after arming");
31596    }
31597
31598    fn cancel(&self, id: TimerId) {
31599      self.cancels.lock().unwrap().push(id);
31600      self.pending.lock().unwrap().remove(&id);
31601    }
31602  }
31603
31604  struct PanicBeforeRetainingTimerDriver {
31605    registers: AtomicUsize,
31606  }
31607
31608  impl TimerDriver for PanicBeforeRetainingTimerDriver {
31609    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
31610      self.registers.fetch_add(1, Ordering::SeqCst);
31611      panic!("intentional timer-driver register panic before retaining its waker");
31612    }
31613
31614    fn cancel(&self, _id: TimerId) {}
31615  }
31616
31617  struct PanickingCancelDropTimerDriver {
31618    cancels: Arc<AtomicUsize>,
31619    drops: Arc<AtomicUsize>,
31620  }
31621
31622  impl TimerDriver for PanickingCancelDropTimerDriver {
31623    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31624
31625    fn cancel(&self, _id: TimerId) {
31626      self.cancels.fetch_add(1, Ordering::SeqCst);
31627      panic!("intentional timer-driver cancel panic");
31628    }
31629  }
31630
31631  impl Drop for PanickingCancelDropTimerDriver {
31632    fn drop(&mut self) {
31633      self.drops.fetch_add(1, Ordering::SeqCst);
31634      panic!("intentional timer-driver destructor panic");
31635    }
31636  }
31637
31638  struct UnregisterLaterThenPanicTimerDriver {
31639    registry: Weak<TimerDriverRegistry>,
31640    later: Mutex<Option<TimerDriverId>>,
31641    registers: AtomicUsize,
31642  }
31643
31644  impl TimerDriver for UnregisterLaterThenPanicTimerDriver {
31645    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {
31646      self.registers.fetch_add(1, Ordering::SeqCst);
31647      let later = *self.later.lock().unwrap();
31648      if let (Some(registry), Some(later)) = (self.registry.upgrade(), later) {
31649        registry.unregister(later);
31650      }
31651      panic!("intentional earlier timer-driver panic after unregistering a later driver");
31652    }
31653
31654    fn cancel(&self, _id: TimerId) {}
31655  }
31656
31657  struct ReentrantLivenessDriver {
31658    registry: std::sync::Weak<TimerDriverRegistry>,
31659    registration: Mutex<Option<TimerDriverId>>,
31660  }
31661
31662  impl TimerDriver for ReentrantLivenessDriver {
31663    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31664
31665    fn cancel(&self, _id: TimerId) {}
31666
31667    fn is_live(&self) -> bool {
31668      let registration = *self.registration.lock().unwrap();
31669      if let (Some(registry), Some(id)) = (self.registry.upgrade(), registration) {
31670        registry.unregister(id);
31671      }
31672      false
31673    }
31674  }
31675
31676  struct ReentrantMultiUnregisterTimerDriver {
31677    registry: std::sync::Weak<TimerDriverRegistry>,
31678    registrations: Mutex<Vec<TimerDriverId>>,
31679  }
31680
31681  impl TimerDriver for ReentrantMultiUnregisterTimerDriver {
31682    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31683
31684    fn cancel(&self, _id: TimerId) {}
31685
31686    fn is_live(&self) -> bool {
31687      if let Some(registry) = self.registry.upgrade() {
31688        for registration in self.registrations.lock().unwrap().iter().copied() {
31689          registry.unregister(registration);
31690        }
31691      }
31692      false
31693    }
31694  }
31695
31696  struct ReentrantSweepTimerDriver {
31697    registry: std::sync::Weak<TimerDriverRegistry>,
31698    registration: Mutex<Option<TimerDriverId>>,
31699    sweeps: AtomicUsize,
31700    removed_before_sweep: AtomicBool,
31701  }
31702
31703  impl TimerDriver for ReentrantSweepTimerDriver {
31704    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31705
31706    fn cancel(&self, _id: TimerId) {}
31707
31708    fn is_live(&self) -> bool {
31709      false
31710    }
31711
31712    fn on_swept(&self) {
31713      self.sweeps.fetch_add(1, Ordering::SeqCst);
31714      let registration = self.registration.lock().unwrap().take();
31715      if let (Some(registry), Some(registration)) = (self.registry.upgrade(), registration) {
31716        let state = registry
31717          .state
31718          .try_lock()
31719          .expect("the sweep callback must run outside the timer-driver registry lock");
31720        assert!(
31721          !state.entries.contains_key(&registration),
31722          "the dead driver must be unlinked before its sweep callback runs"
31723        );
31724        self.removed_before_sweep.store(true, Ordering::SeqCst);
31725        drop(state);
31726        // Mirrors JsTimerHostInner::evict: the sweep already removed this
31727        // registration, so explicit host cleanup must remain a cheap no-op.
31728        registry.unregister(registration);
31729      }
31730    }
31731  }
31732
31733  struct ReentrantDropDriver {
31734    registry: std::sync::Weak<TimerDriverRegistry>,
31735    fallback: TimerDriverId,
31736  }
31737
31738  impl TimerDriver for ReentrantDropDriver {
31739    fn register(&self, _id: TimerId, _deadline: Instant, _waker: Waker) {}
31740
31741    fn cancel(&self, _id: TimerId) {}
31742  }
31743
31744  impl Drop for ReentrantDropDriver {
31745    fn drop(&mut self) {
31746      if let Some(registry) = self.registry.upgrade() {
31747        registry.unregister(self.fallback);
31748      }
31749    }
31750  }
31751
31752  /// Waker whose `wake` just latches a flag -- observable evidence that a
31753  /// wake was DELIVERED (not merely that eviction bookkeeping ran).
31754  #[derive(Default)]
31755  struct WakeFlag(AtomicBool);
31756
31757  impl std::task::Wake for WakeFlag {
31758    fn wake(self: Arc<Self>) {
31759      self.0.store(true, Ordering::SeqCst);
31760    }
31761  }
31762
31763  #[cfg(not(target_family = "wasm"))]
31764  fn multi_thread_executor(
31765    workers: usize,
31766    park_deadline: Option<Duration>,
31767    prefix: &str,
31768  ) -> Arc<MultiThreadExecutor> {
31769    let options = RuntimeOptions {
31770      flavor: RuntimeFlavor::MultiThread,
31771      worker_threads: workers,
31772      max_blocking_tasks: workers,
31773      thread_name_prefix: prefix.to_string(),
31774      park_deadline,
31775      drain_linger: DEFAULT_DRAIN_LINGER,
31776    };
31777    Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap())
31778  }
31779
31780  /// Local driver registry holding exactly `driver` (the common single-host
31781  /// shape of the ported pre-registry tests).
31782  fn registry_with(driver: Arc<dyn TimerDriver>) -> Arc<TimerDriverRegistry> {
31783    let registry = Arc::new(TimerDriverRegistry::default());
31784    registry.register(driver);
31785    registry
31786  }
31787
31788  #[cfg(not(target_family = "wasm"))]
31789  fn heap_sleep(executor: &Arc<MultiThreadExecutor>, deadline: Instant) -> Sleep {
31790    // The MultiThread arm never touches the driver registry; empty is fine.
31791    make_sleep(
31792      &RuntimeBackend::from_executor(RuntimeExecutor::MultiThread(Arc::clone(executor))),
31793      &Arc::new(TimerDriverRegistry::default()),
31794      deadline,
31795    )
31796  }
31797
31798  #[cfg(not(target_family = "wasm"))]
31799  #[test]
31800  fn multi_thread_timer_registration_holds_deadlock_admission_until_heap_publication() {
31801    use std::sync::mpsc;
31802
31803    let executor = multi_thread_executor(
31804      1,
31805      Some(Duration::from_secs(1)),
31806      "timer-registration-admission",
31807    );
31808    let (entered_tx, entered_rx) = mpsc::channel();
31809    let (release_tx, release_rx) = mpsc::channel();
31810    let registration_executor = Arc::clone(&executor);
31811    let registration = std::thread::spawn(move || {
31812      TIMER_REGISTRATION_ADMISSION_TEST_HOOK.with(|slot| {
31813        *slot.borrow_mut() = Some(Box::new(move || {
31814          entered_tx.send(()).unwrap();
31815          release_rx.recv().unwrap();
31816        }));
31817      });
31818      registration_executor.register_timer(
31819        1,
31820        Instant::now() + Duration::from_secs(60),
31821        futures::task::noop_waker(),
31822        &Arc::new(AtomicBool::new(false)),
31823      );
31824    });
31825
31826    entered_rx
31827      .recv_timeout(Duration::from_secs(2))
31828      .expect("timer registration must pause after entering verdict admission");
31829    let verdict = executor.try_begin_deadlock_verdict();
31830    let admission_blocked_verdict = verdict.is_none();
31831    drop(verdict);
31832    release_tx.send(()).unwrap();
31833    registration.join().unwrap();
31834
31835    assert!(
31836      admission_blocked_verdict,
31837      "a timer registration must remain announced until its heap entry is visible"
31838    );
31839    assert!(
31840      executor.next_timer_deadline().is_some(),
31841      "the admitted registration must publish its timer after release"
31842    );
31843    executor.begin_shutdown();
31844    executor.wait_until_scheduler_idle();
31845  }
31846
31847  #[cfg(not(target_family = "wasm"))]
31848  #[derive(Clone, Copy, Debug)]
31849  enum TimerFireAdmissionPath {
31850    Timekeeper,
31851    Cooperative,
31852    Shutdown,
31853  }
31854
31855  #[cfg(not(target_family = "wasm"))]
31856  struct ScheduleAfterTimerWakeRelease {
31857    executor: Arc<MultiThreadExecutor>,
31858    runnable: Mutex<Option<Runnable>>,
31859    entered: std::sync::mpsc::Sender<()>,
31860    release: Mutex<std::sync::mpsc::Receiver<()>>,
31861    scheduled: std::sync::mpsc::Sender<()>,
31862  }
31863
31864  #[cfg(not(target_family = "wasm"))]
31865  impl ScheduleAfterTimerWakeRelease {
31866    fn publish(&self) {
31867      self.entered.send(()).unwrap();
31868      self
31869        .release
31870        .lock()
31871        .unwrap_or_else(std::sync::PoisonError::into_inner)
31872        .recv()
31873        .unwrap();
31874      let runnable = self
31875        .runnable
31876        .lock()
31877        .unwrap_or_else(std::sync::PoisonError::into_inner)
31878        .take();
31879      if let Some(runnable) = runnable {
31880        self.executor.schedule(runnable);
31881      }
31882      self.scheduled.send(()).unwrap();
31883    }
31884  }
31885
31886  #[cfg(not(target_family = "wasm"))]
31887  impl std::task::Wake for ScheduleAfterTimerWakeRelease {
31888    fn wake(self: Arc<Self>) {
31889      self.publish();
31890    }
31891
31892    fn wake_by_ref(self: &Arc<Self>) {
31893      self.publish();
31894    }
31895  }
31896
31897  #[cfg(not(target_family = "wasm"))]
31898  fn insert_timer_for_fire_admission_test(
31899    executor: &MultiThreadExecutor,
31900    id: TimerId,
31901    deadline: Instant,
31902    waker: Waker,
31903    fired: Arc<AtomicBool>,
31904  ) {
31905    let mut inner = executor
31906      .timers
31907      .inner
31908      .lock()
31909      .unwrap_or_else(std::sync::PoisonError::into_inner);
31910    inner.entries.insert(id, HeapTimerEntry { waker, fired });
31911    inner.queue.push(deadline, id);
31912  }
31913
31914  #[cfg(not(target_family = "wasm"))]
31915  fn assert_timer_fire_path_holds_deadlock_admission(path: TimerFireAdmissionPath) {
31916    use std::sync::mpsc;
31917
31918    let executor = multi_thread_executor(1, Some(Duration::from_secs(1)), "timer-fire-admission");
31919    let (runnable, task) = async_task::spawn(async {}, |_| {});
31920    task.detach();
31921    let (entered_tx, entered_rx) = mpsc::channel();
31922    let (release_tx, release_rx) = mpsc::channel();
31923    let (scheduled_tx, scheduled_rx) = mpsc::channel();
31924    let fired = Arc::new(AtomicBool::new(false));
31925    let waker = Waker::from(Arc::new(ScheduleAfterTimerWakeRelease {
31926      executor: Arc::clone(&executor),
31927      runnable: Mutex::new(Some(runnable)),
31928      entered: entered_tx,
31929      release: Mutex::new(release_rx),
31930      scheduled: scheduled_tx,
31931    }));
31932
31933    let fire_thread = match path {
31934      TimerFireAdmissionPath::Timekeeper => {
31935        executor.register_timer(1, Instant::now() + Duration::from_millis(20), waker, &fired);
31936        None
31937      }
31938      TimerFireAdmissionPath::Cooperative => {
31939        insert_timer_for_fire_admission_test(
31940          &executor,
31941          1,
31942          Instant::now(),
31943          waker,
31944          Arc::clone(&fired),
31945        );
31946        let fire_executor = Arc::clone(&executor);
31947        Some(std::thread::spawn(move || fire_executor.fire_due_timers()))
31948      }
31949      TimerFireAdmissionPath::Shutdown => {
31950        insert_timer_for_fire_admission_test(
31951          &executor,
31952          1,
31953          Instant::now() + Duration::from_secs(60),
31954          waker,
31955          Arc::clone(&fired),
31956        );
31957        let fire_executor = Arc::clone(&executor);
31958        Some(std::thread::spawn(move || fire_executor.shutdown_timers()))
31959      }
31960    };
31961
31962    // Hang detector on a guaranteed event that needs a freshly spawned
31963    // thread (timekeeper / fire thread) to get CPU -- generous for starved
31964    // CI runners.
31965    entered_rx
31966      .recv_timeout(Duration::from_secs(10))
31967      .unwrap_or_else(|error| panic!("{path:?} timer waker was not invoked: {error}"));
31968    assert!(
31969      executor.next_timer_deadline().is_none(),
31970      "{path:?} must exercise the remove-before-wake window"
31971    );
31972    assert!(
31973      executor.deadlock_state.admissions.load(Ordering::SeqCst) != 0,
31974      "{path:?} must retain admission while its waker is publishing"
31975    );
31976    let verdict = executor.try_begin_deadlock_verdict();
31977    let admission_blocked_verdict = verdict.is_none();
31978    drop(verdict);
31979
31980    release_tx.send(()).unwrap();
31981    scheduled_rx
31982      .recv_timeout(Duration::from_secs(10))
31983      .unwrap_or_else(|error| panic!("{path:?} timer wake did not publish its runnable: {error}"));
31984    if let Some(fire_thread) = fire_thread {
31985      fire_thread.join().unwrap();
31986    }
31987    assert!(
31988      admission_blocked_verdict,
31989      "{path:?} allowed a final verdict while the timer was invisible and its wake unpublished"
31990    );
31991
31992    executor.begin_shutdown();
31993    executor.wait_until_scheduler_idle();
31994  }
31995
31996  #[cfg(not(target_family = "wasm"))]
31997  #[test]
31998  fn multi_thread_timer_fire_paths_hold_deadlock_admission_through_wake_publication() {
31999    for path in [
32000      TimerFireAdmissionPath::Timekeeper,
32001      TimerFireAdmissionPath::Cooperative,
32002      TimerFireAdmissionPath::Shutdown,
32003    ] {
32004      assert_timer_fire_path_holds_deadlock_admission(path);
32005    }
32006  }
32007
32008  #[cfg(not(target_family = "wasm"))]
32009  #[test]
32010  fn multi_thread_sleep_fires_from_foreign_block_on() {
32011    // The napi-caller shape: a NON-pool thread parks in
32012    // `futures::executor::block_on` awaiting a sleep. Nothing else runs on
32013    // the executor, so ONLY the dedicated timekeeper can fire the timer --
32014    // this is the timekeeper's existence test.
32015    use std::sync::mpsc;
32016
32017    let (done_tx, done_rx) = mpsc::channel();
32018    let runner = std::thread::spawn(move || {
32019      let executor = multi_thread_executor(2, None, "timer-foreign");
32020      let started = Instant::now();
32021      let mut future = std::pin::pin!(async {
32022        heap_sleep(&executor, Instant::now() + Duration::from_millis(80)).await;
32023      });
32024      executor.block_on(future.as_mut());
32025      assert!(
32026        started.elapsed() >= Duration::from_millis(80),
32027        "the sleep must not resolve before its deadline"
32028      );
32029      let _ = done_tx.send(());
32030    });
32031
32032    match done_rx.recv_timeout(Duration::from_secs(10)) {
32033      Ok(()) => runner.join().unwrap(),
32034      Err(error) => {
32035        panic!("a sleep awaited from a foreign block_on never fired ({error}): no timekeeper")
32036      }
32037    }
32038  }
32039
32040  #[cfg(not(target_family = "wasm"))]
32041  #[test]
32042  fn pending_timer_does_not_reduce_single_worker_scheduler_capacity() {
32043    // A long timer waits on the dedicated timer thread, leaving the sole
32044    // low-level test worker available for ordinary scheduler work.
32045    use std::sync::mpsc;
32046
32047    let (done_tx, done_rx) = mpsc::channel();
32048    let runner = std::thread::spawn(move || {
32049      let executor = multi_thread_executor(1, None, "timer-drainer");
32050
32051      // Arm a long timer through a real spawned task so the sole worker
32052      // returns to rayon afterwards (the watch-idle Debouncing shape).
32053      let sleep_exec = Arc::clone(&executor);
32054      let sleep_done = Arc::new(AtomicBool::new(false));
32055      let sleep_flag = Arc::clone(&sleep_done);
32056      let sched = Arc::clone(&executor);
32057      let (sleep_runnable, sleep_task) = async_task::spawn(
32058        async move {
32059          heap_sleep(&sleep_exec, Instant::now() + Duration::from_secs(60)).await;
32060          sleep_flag.store(true, Ordering::SeqCst);
32061        },
32062        move |r| sched.schedule(r),
32063      );
32064      executor.schedule(sleep_runnable);
32065      wait_until("the timekeeper parked on the 60s deadline", || {
32066        executor
32067          .timers
32068          .inner
32069          .lock()
32070          .unwrap()
32071          .timekeeper_parker
32072          .is_some()
32073      });
32074
32075      // Queued work must run NOW, not in 60s.
32076      let counter = Arc::new(AtomicUsize::new(0));
32077      let mut tasks = Vec::new();
32078      for _ in 0..32 {
32079        let counter = Arc::clone(&counter);
32080        let sched = Arc::clone(&executor);
32081        let (runnable, task) = async_task::spawn(
32082          async move {
32083            counter.fetch_add(1, Ordering::SeqCst);
32084          },
32085          move |r| sched.schedule(r),
32086        );
32087        executor.schedule(runnable);
32088        tasks.push(task);
32089      }
32090      for task in tasks {
32091        futures::executor::block_on(task);
32092      }
32093      assert_eq!(counter.load(Ordering::SeqCst), 32);
32094      assert!(
32095        !sleep_done.load(Ordering::SeqCst),
32096        "the 60s timer must still be pending: the work ran DURING the timer wait"
32097      );
32098      // Cancel the long sleep so the executor tears down promptly.
32099      executor.shutdown_timers();
32100      futures::executor::block_on(sleep_task);
32101      let _ = done_tx.send(());
32102    });
32103
32104    match done_rx.recv_timeout(Duration::from_secs(15)) {
32105      Ok(()) => runner.join().unwrap(),
32106      Err(error) => {
32107        panic!("queued work lost the only Rayon worker while a timer was pending ({error})")
32108      }
32109    }
32110  }
32111
32112  #[cfg(not(target_family = "wasm"))]
32113  #[test]
32114  fn multi_thread_timekeeper_starts_only_after_a_timer_is_polled() {
32115    let executor = multi_thread_executor(2, None, "timer-lazy-start");
32116    assert!(
32117      executor.timers.thread.lock().unwrap().is_none(),
32118      "constructing a runtime without timers must not create an extra OS thread"
32119    );
32120
32121    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32122    assert!(
32123      executor.timers.thread.lock().unwrap().is_none(),
32124      "an unpolled sleep has not registered a deadline"
32125    );
32126    let waker = futures::task::noop_waker();
32127    let mut cx = Context::from_waker(&waker);
32128    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32129    assert!(
32130      executor.timers.thread.lock().unwrap().is_some(),
32131      "the first timer registration must start the dedicated service"
32132    );
32133  }
32134
32135  #[cfg(not(target_family = "wasm"))]
32136  #[test]
32137  fn closed_timer_heap_does_not_wait_for_the_thread_handle_mutex() {
32138    use std::sync::mpsc;
32139
32140    let executor = multi_thread_executor(1, None, "timer-closed-start");
32141    executor.shutdown_timers();
32142    let handle_guard = executor.timers.thread.lock().unwrap();
32143    let ensure_executor = Arc::clone(&executor);
32144    let (completed_tx, completed_rx) = mpsc::channel();
32145    let ensure = std::thread::spawn(move || {
32146      ensure_executor.timers.ensure_started().unwrap();
32147      completed_tx.send(()).unwrap();
32148    });
32149
32150    // Hang detector on a freshly spawned thread getting CPU -- generous for
32151    // starved CI runners (a real regression blocks forever on the held lock).
32152    completed_rx.recv_timeout(Duration::from_secs(10)).expect(
32153      "ensure_started consulted or joined the timer handle before observing the closed heap",
32154    );
32155    drop(handle_guard);
32156    ensure.join().unwrap();
32157  }
32158
32159  #[cfg(not(target_family = "wasm"))]
32160  #[test]
32161  fn cancelling_non_earliest_timers_keeps_the_deadline_heap_bounded() {
32162    const TIMERS: u64 = 4096;
32163
32164    let executor = multi_thread_executor(1, None, "timer-indexed-cancel");
32165    let base = Instant::now() + Duration::from_secs(3600);
32166    for id in 0..TIMERS {
32167      executor.register_timer(
32168        id,
32169        base + Duration::from_secs(id),
32170        futures::task::noop_waker(),
32171        &Arc::new(AtomicBool::new(false)),
32172      );
32173    }
32174    {
32175      let inner = executor.timers.inner.lock().unwrap();
32176      assert_eq!(inner.queue.len(), usize::try_from(TIMERS).unwrap());
32177      assert_eq!(inner.queue.len(), inner.entries.len());
32178    }
32179
32180    // Preserve the earliest node while removing every later node. A lazy
32181    // BinaryHeap would retain all 4095 tombstones until that first deadline.
32182    for id in 1..TIMERS {
32183      executor.cancel_timer(id);
32184    }
32185    {
32186      let inner = executor.timers.inner.lock().unwrap();
32187      assert_eq!(inner.entries.len(), 1);
32188      assert_eq!(inner.queue.len(), 1);
32189      assert_eq!(inner.queue.peek().map(|(_, id)| id), Some(0));
32190    }
32191
32192    executor.cancel_timer(0);
32193    {
32194      let inner = executor.timers.inner.lock().unwrap();
32195      assert!(inner.entries.is_empty());
32196      assert!(inner.queue.is_empty());
32197    }
32198    executor.shutdown_timers();
32199    executor.timers.join();
32200  }
32201
32202  #[cfg(not(target_family = "wasm"))]
32203  #[test]
32204  fn timer_registration_is_transactional_when_timekeeper_start_fails() {
32205    let executor = multi_thread_executor(2, None, "timer-start-failure");
32206    executor
32207      .timers
32208      .fail_next_start
32209      .store(true, Ordering::SeqCst);
32210
32211    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32212    let waker = Waker::from(Arc::clone(&wake_count));
32213    let baseline_waker_refs = Arc::strong_count(&wake_count);
32214    let mut cx = Context::from_waker(&waker);
32215    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32216
32217    let failure = catch_unwind(AssertUnwindSafe(|| Pin::new(&mut sleep).poll(&mut cx)));
32218    assert!(
32219      failure.is_err(),
32220      "the injected timer-thread start failure must panic the poll"
32221    );
32222    {
32223      let inner = executor.timers.inner.lock().unwrap();
32224      assert!(
32225        inner.entries.is_empty(),
32226        "a failed start must not retain a timer entry"
32227      );
32228      assert!(
32229        inner.queue.is_empty(),
32230        "a failed start must not retain a stale heap node"
32231      );
32232      assert!(
32233        inner.timekeeper_parker.is_none(),
32234        "a failed start must not publish a timekeeper parker"
32235      );
32236    }
32237    assert!(
32238      executor.timers.thread.lock().unwrap().is_none(),
32239      "a failed start must not retain a thread handle"
32240    );
32241    assert_eq!(
32242      Arc::strong_count(&wake_count),
32243      baseline_waker_refs,
32244      "a failed start must release the cloned timer waker"
32245    );
32246
32247    assert!(
32248      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
32249      "the same Sleep must be safely retryable after a transient start failure"
32250    );
32251    {
32252      let inner = executor.timers.inner.lock().unwrap();
32253      assert_eq!(
32254        inner.entries.len(),
32255        1,
32256        "the retry must publish exactly one timer entry"
32257      );
32258      assert_eq!(
32259        inner.queue.len(),
32260        1,
32261        "the retry must publish exactly one heap node"
32262      );
32263    }
32264    assert!(
32265      executor.timers.thread.lock().unwrap().is_some(),
32266      "the retry must start the dedicated timekeeper"
32267    );
32268
32269    drop(sleep);
32270    assert!(
32271      executor.timers.inner.lock().unwrap().entries.is_empty(),
32272      "dropping the retried Sleep must cancel its registration"
32273    );
32274    executor.shutdown_timers();
32275    executor.wait_until_scheduler_idle();
32276    assert!(
32277      executor.timers.thread.lock().unwrap().is_none(),
32278      "shutdown after a recovered start failure must join the timekeeper"
32279    );
32280  }
32281
32282  #[cfg(not(target_family = "wasm"))]
32283  #[test]
32284  fn pending_timer_preserves_every_configured_rayon_worker() {
32285    use std::sync::mpsc;
32286
32287    let executor = multi_thread_executor(2, None, "timer-rayon-capacity");
32288    let waker = futures::task::noop_waker();
32289    let mut cx = Context::from_waker(&waker);
32290    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32291    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32292    wait_until("the dedicated timer thread to park", || {
32293      executor
32294        .timers
32295        .inner
32296        .lock()
32297        .unwrap()
32298        .timekeeper_parker
32299        .is_some()
32300    });
32301
32302    let released = Arc::new((Mutex::new(false), Condvar::new()));
32303    let (started_tx, started_rx) = mpsc::channel();
32304    let (completed_tx, completed_rx) = mpsc::channel();
32305    for _ in 0..2 {
32306      let released = Arc::clone(&released);
32307      let started_tx = started_tx.clone();
32308      let completed_tx = completed_tx.clone();
32309      executor.pool.spawn(move || {
32310        started_tx.send(()).unwrap();
32311        let (lock, changed) = &*released;
32312        let mut released = lock
32313          .lock()
32314          .unwrap_or_else(std::sync::PoisonError::into_inner);
32315        while !*released {
32316          released = changed
32317            .wait(released)
32318            .unwrap_or_else(std::sync::PoisonError::into_inner);
32319        }
32320        completed_tx.send(()).unwrap();
32321      });
32322    }
32323    drop(started_tx);
32324    drop(completed_tx);
32325
32326    let first = started_rx.recv_timeout(Duration::from_secs(2));
32327    let second = started_rx.recv_timeout(Duration::from_secs(2));
32328    {
32329      let (lock, changed) = &*released;
32330      *lock
32331        .lock()
32332        .unwrap_or_else(std::sync::PoisonError::into_inner) = true;
32333      changed.notify_all();
32334    }
32335    drop(sleep);
32336    for _ in 0..2 {
32337      completed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
32338    }
32339
32340    assert!(
32341      first.is_ok(),
32342      "the first configured Rayon worker did not start"
32343    );
32344    assert!(
32345      second.is_ok(),
32346      "a pending timer consumed one of the two configured Rayon workers"
32347    );
32348  }
32349
32350  #[cfg(not(target_family = "wasm"))]
32351  #[test]
32352  fn timekeeper_never_enters_a_blocking_closure() {
32353    use std::{
32354      sync::{Barrier, mpsc},
32355      time::Duration,
32356    };
32357
32358    let executor = multi_thread_executor(1, None, "timer-blocking-isolation");
32359    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32360    let waker = Waker::from(Arc::clone(&wake_count));
32361    let mut cx = Context::from_waker(&waker);
32362    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(100));
32363    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32364    wait_until(
32365      "the timekeeper parked before blocking work was submitted",
32366      || {
32367        executor
32368          .timers
32369          .inner
32370          .lock()
32371          .unwrap()
32372          .timekeeper_parker
32373          .is_some()
32374      },
32375    );
32376
32377    let release = Arc::new(Barrier::new(2));
32378    let blocker_release = Arc::clone(&release);
32379    let (started_tx, started_rx) = mpsc::sync_channel(1);
32380    let blocking = executor.schedule_blocking(move || {
32381      started_tx.send(()).unwrap();
32382      blocker_release.wait();
32383    });
32384    // Hang detector (the closure start needs a worker to get CPU) --
32385    // generous for starved CI runners.
32386    started_rx.recv_timeout(Duration::from_secs(10)).unwrap();
32387
32388    wait_until(
32389      "the timer waker to fire while the Rayon worker is blocked",
32390      || wake_count.0.load(Ordering::SeqCst) != 0,
32391    );
32392    release.wait();
32393    futures::executor::block_on(blocking).unwrap();
32394    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
32395  }
32396
32397  #[cfg(not(target_family = "wasm"))]
32398  #[test]
32399  fn nested_block_on_services_timer_wake_while_other_worker_is_blocked() {
32400    use std::sync::mpsc;
32401
32402    struct PendingEntered(Option<mpsc::Sender<()>>);
32403
32404    impl Future for PendingEntered {
32405      type Output = ();
32406
32407      fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32408        if let Some(entered) = self.0.take() {
32409          entered.send(()).unwrap();
32410        }
32411        Poll::Pending
32412      }
32413    }
32414
32415    let options = RuntimeOptions {
32416      flavor: RuntimeFlavor::MultiThread,
32417      worker_threads: 2,
32418      max_blocking_tasks: 1,
32419      thread_name_prefix: "nested-timer-wake".to_string(),
32420      park_deadline: None,
32421      drain_linger: DEFAULT_DRAIN_LINGER,
32422    };
32423    let executor =
32424      Arc::new(MultiThreadExecutor::new(&options, Arc::new(RuntimeMetrics::default())).unwrap());
32425
32426    let timer_exec = Arc::clone(&executor);
32427    let (timer_tx, timer_rx) = mpsc::channel();
32428    let timer_scheduler = Arc::clone(&executor);
32429    let (timer_runnable, timer_task) = async_task::spawn(
32430      async move {
32431        heap_sleep(&timer_exec, Instant::now() + Duration::from_millis(150)).await;
32432        timer_tx.send(()).unwrap();
32433      },
32434      move |runnable| timer_scheduler.schedule(runnable),
32435    );
32436    executor.schedule(timer_runnable);
32437    wait_until(
32438      "the dedicated timekeeper parked before nested block_on",
32439      || {
32440        executor
32441          .timers
32442          .inner
32443          .lock()
32444          .unwrap()
32445          .timekeeper_parker
32446          .is_some()
32447      },
32448    );
32449
32450    let (entered_tx, entered_rx) = mpsc::channel();
32451    let nested_exec = Arc::clone(&executor);
32452    let nested_scheduler = Arc::clone(&executor);
32453    let (nested_runnable, nested_task) = async_task::spawn(
32454      async move {
32455        let mut pending = Box::pin(PendingEntered(Some(entered_tx)));
32456        let outcome = nested_exec.block_on(pending.as_mut());
32457        drop(pending);
32458        assert_eq!(
32459          outcome,
32460          BlockOnOutcome::Stopped,
32461          "shutdown must stop the raw nested block_on driver"
32462        );
32463      },
32464      move |runnable| nested_scheduler.schedule(runnable),
32465    );
32466    executor.schedule(nested_runnable);
32467    entered_rx.recv_timeout(Duration::from_secs(2)).unwrap();
32468
32469    let (blocking_started_tx, blocking_started_rx) = mpsc::channel();
32470    let (release_tx, release_rx) = mpsc::channel();
32471    let blocking = executor.schedule_blocking(move || {
32472      blocking_started_tx.send(()).unwrap();
32473      release_rx.recv().unwrap();
32474    });
32475    blocking_started_rx
32476      .recv_timeout(Duration::from_secs(2))
32477      .expect("a general worker must service blocking work");
32478    timer_rx
32479      .recv_timeout(Duration::from_secs(2))
32480      .expect("the timer wake must reach the nested cooperative driver");
32481
32482    release_tx.send(()).unwrap();
32483    futures::executor::block_on(blocking).unwrap();
32484    executor.begin_shutdown();
32485    assert_eq!(
32486      futures::executor::block_on(nested_task.fallible()),
32487      Some(())
32488    );
32489    futures::executor::block_on(timer_task);
32490  }
32491
32492  #[cfg(not(target_family = "wasm"))]
32493  #[test]
32494  fn multi_thread_rearming_to_earlier_deadline_rewakes_timekeeper() {
32495    // Task requirement: a NEW earliest deadline (heap peek changes) must
32496    // re-arm the already-parked timekeeper. The 60s sentinel timer parks the
32497    // timekeeper far in the future; the 60ms timer must still fire on time.
32498    use std::sync::mpsc;
32499
32500    let (done_tx, done_rx) = mpsc::channel();
32501    let runner = std::thread::spawn(move || {
32502      let executor = multi_thread_executor(2, None, "timer-rearm");
32503
32504      let long_exec = Arc::clone(&executor);
32505      let long_done = Arc::new(AtomicBool::new(false));
32506      let long_flag = Arc::clone(&long_done);
32507      let sched = Arc::clone(&executor);
32508      let (long_runnable, long_task) = async_task::spawn(
32509        async move {
32510          heap_sleep(&long_exec, Instant::now() + Duration::from_secs(60)).await;
32511          long_flag.store(true, Ordering::SeqCst);
32512        },
32513        move |r| sched.schedule(r),
32514      );
32515      executor.schedule(long_runnable);
32516      wait_until("the timekeeper parked on the 60s sentinel", || {
32517        executor
32518          .timers
32519          .inner
32520          .lock()
32521          .unwrap()
32522          .timekeeper_parker
32523          .is_some()
32524      });
32525
32526      let started = Instant::now();
32527      let mut future = std::pin::pin!(async {
32528        heap_sleep(&executor, Instant::now() + Duration::from_millis(60)).await;
32529      });
32530      executor.block_on(future.as_mut());
32531      let elapsed = started.elapsed();
32532      assert!(
32533        elapsed >= Duration::from_millis(60),
32534        "fired before its deadline: {elapsed:?}"
32535      );
32536      assert!(
32537        !long_done.load(Ordering::SeqCst),
32538        "the 60s sentinel must still be pending: the short timer was fired by a re-armed \
32539         timekeeper, not by the sentinel expiring"
32540      );
32541      executor.shutdown_timers();
32542      futures::executor::block_on(long_task);
32543      let _ = done_tx.send(());
32544    });
32545
32546    match done_rx.recv_timeout(Duration::from_secs(15)) {
32547      Ok(()) => runner.join().unwrap(),
32548      Err(error) => panic!(
32549        "a timer earlier than the parked timekeeper's deadline never fired ({error}): re-arm-to-earlier is broken"
32550      ),
32551    }
32552  }
32553
32554  #[cfg(not(target_family = "wasm"))]
32555  #[test]
32556  fn multi_thread_sleep_cancels_registration_on_drop() {
32557    // `tokio::select!` losing-arm semantics: dropping the Sleep must retire
32558    // its heap registration (no waker fired later, heap drains empty).
32559    let executor = multi_thread_executor(1, None, "timer-cancel");
32560    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
32561    let waker = Waker::from(Arc::clone(&wake_count));
32562    let mut cx = Context::from_waker(&waker);
32563
32564    // The deadline's pre-poll budget is one statement; 250ms dwarfs it on a
32565    // starved runner (a past-due Sleep would resolve inline instead of
32566    // registering, failing the assert below for the wrong reason).
32567    let deadline = Instant::now() + Duration::from_millis(250);
32568    let mut sleep = heap_sleep(&executor, deadline);
32569    assert!(
32570      Pin::new(&mut sleep).poll(&mut cx).is_pending(),
32571      "must register, not resolve"
32572    );
32573    assert!(
32574      executor.next_timer_deadline().is_some(),
32575      "the registration must be in the heap"
32576    );
32577    drop(sleep);
32578    assert!(
32579      executor.next_timer_deadline().is_none(),
32580      "cancel-on-drop must retire the heap registration"
32581    );
32582    // Outlive the would-be deadline (event-exact, not a fixed sleep): the
32583    // cancelled timer must not wake. The negative assert is structural once
32584    // the heap is empty; waiting until the wall clock is genuinely past the
32585    // deadline keeps the ghost-fire probe honest.
32586    while Instant::now() < deadline + Duration::from_millis(25) {
32587      std::thread::sleep(Duration::from_millis(10));
32588    }
32589    executor.fire_due_timers();
32590    assert_eq!(
32591      wake_count.0.load(Ordering::SeqCst),
32592      0,
32593      "a dropped Sleep's waker must never fire"
32594    );
32595  }
32596
32597  #[cfg(not(target_family = "wasm"))]
32598  #[test]
32599  fn cancelling_last_timer_rearms_timekeeper_to_idle_immediately() {
32600    let executor = multi_thread_executor(2, None, "timer-cancel-retire");
32601    let waker = futures::task::noop_waker();
32602    let mut cx = Context::from_waker(&waker);
32603    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(60));
32604
32605    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32606    wait_until(
32607      "the timekeeper parked on the timer that will be cancelled",
32608      || {
32609        executor
32610          .timers
32611          .inner
32612          .lock()
32613          .unwrap()
32614          .timekeeper_parker
32615          .is_some()
32616      },
32617    );
32618
32619    drop(sleep);
32620    wait_until(
32621      "the cancelled timer's timekeeper returned to its idle wait",
32622      || {
32623        executor.next_timer_deadline().is_none()
32624          && executor
32625            .timers
32626            .inner
32627            .lock()
32628            .unwrap()
32629            .timekeeper_parker
32630            .is_none()
32631      },
32632    );
32633  }
32634
32635  #[cfg(not(target_family = "wasm"))]
32636  #[test]
32637  fn timer_waker_replacement_drops_outside_heap_lock() {
32638    use std::sync::mpsc;
32639
32640    struct DropOtherTimer(Mutex<Option<Sleep>>);
32641
32642    #[expect(
32643      clippy::manual_noop_waker,
32644      reason = "the no-op wake type has a reentrant Drop used by this regression"
32645    )]
32646    impl std::task::Wake for DropOtherTimer {
32647      fn wake(self: Arc<Self>) {}
32648    }
32649
32650    impl Drop for DropOtherTimer {
32651      fn drop(&mut self) {
32652        drop(self.0.lock().unwrap().take());
32653      }
32654    }
32655
32656    let (done_tx, done_rx) = mpsc::channel();
32657    let runner = std::thread::spawn(move || {
32658      let executor = multi_thread_executor(1, None, "timer-waker-drop-lock");
32659      let deadline = Instant::now() + Duration::from_secs(120);
32660      let mut other = heap_sleep(&executor, deadline);
32661      let noop = futures::task::noop_waker();
32662      let mut noop_cx = Context::from_waker(&noop);
32663      assert!(Pin::new(&mut other).poll(&mut noop_cx).is_pending());
32664
32665      let old = Waker::from(Arc::new(DropOtherTimer(Mutex::new(Some(other)))));
32666      let mut primary = heap_sleep(&executor, deadline);
32667      let mut old_cx = Context::from_waker(&old);
32668      assert!(Pin::new(&mut primary).poll(&mut old_cx).is_pending());
32669      drop(old);
32670
32671      let replacement = futures::task::noop_waker();
32672      let mut replacement_cx = Context::from_waker(&replacement);
32673      assert!(
32674        Pin::new(&mut primary)
32675          .poll(&mut replacement_cx)
32676          .is_pending()
32677      );
32678      drop(primary);
32679      executor.shutdown_timers();
32680      done_tx.send(()).unwrap();
32681    });
32682    // Hang detector on a freshly spawned runner thread -- generous for
32683    // starved CI runners (a real regression deadlocks forever).
32684    done_rx
32685      .recv_timeout(Duration::from_secs(10))
32686      .expect("RawWaker destruction re-entered cancellation while the heap lock was held");
32687    runner.join().unwrap();
32688  }
32689
32690  #[cfg(not(target_family = "wasm"))]
32691  #[test]
32692  fn timer_thread_can_drop_the_final_executor_arc_from_a_waker() {
32693    use std::sync::mpsc;
32694
32695    struct DropFinalExecutorOnWake {
32696      executor: Mutex<Option<Arc<MultiThreadExecutor>>>,
32697      completed: mpsc::Sender<()>,
32698    }
32699
32700    impl std::task::Wake for DropFinalExecutorOnWake {
32701      fn wake(self: Arc<Self>) {
32702        let executor = self
32703          .executor
32704          .lock()
32705          .unwrap_or_else(std::sync::PoisonError::into_inner)
32706          .take()
32707          .expect("the timer waker must own the final executor reference");
32708        assert_eq!(
32709          ON_TIMER_THREAD.with(std::cell::Cell::get),
32710          Some(executor.id),
32711          "the dedicated timer thread must deliver the timer wake"
32712        );
32713        drop(executor);
32714        self
32715          .completed
32716          .send(())
32717          .expect("the test receiver must outlive the timer-thread callback");
32718      }
32719    }
32720
32721    let executor = multi_thread_executor(1, None, "timer-final-arc");
32722    let (completed_tx, completed_rx) = mpsc::channel();
32723    let mut sleep = heap_sleep(&executor, Instant::now() + Duration::from_millis(80));
32724    {
32725      let waker = Waker::from(Arc::new(DropFinalExecutorOnWake {
32726        executor: Mutex::new(Some(Arc::clone(&executor))),
32727        completed: completed_tx,
32728      }));
32729      let mut cx = Context::from_waker(&waker);
32730      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32731    }
32732    wait_until(
32733      "the timer thread parked before owning the final executor Arc",
32734      || {
32735        executor
32736          .timers
32737          .inner
32738          .lock()
32739          .unwrap()
32740          .timekeeper_parker
32741          .is_some()
32742      },
32743    );
32744
32745    // `Sleep` retains only a Weak reference. Once this local Arc is gone, the
32746    // heap waker owns the final direct executor Arc and releases it from the
32747    // timer thread while `TimerHeap::drop` still owns that thread's handle.
32748    drop(executor);
32749    // Hang detector: the fire needs the (already spawned) timer thread to
32750    // reach its 80ms deadline -- generous for starved CI runners.
32751    completed_rx
32752      .recv_timeout(Duration::from_secs(10))
32753      .expect("dropping the final executor Arc on the timer thread attempted to join itself");
32754  }
32755
32756  #[cfg(not(target_family = "wasm"))]
32757  #[test]
32758  fn multi_thread_shutdown_with_pending_timers_drain_fires() {
32759    // Shutdown invariant: `shutdown()` with armed timers must drain-fire the
32760    // pending wakers -- a `watcher.close()` awaiting a
32761    // debounce sleep must complete, not hang. Also pins fire-on-register for
32762    // a Sleep polled after shutdown.
32763    use std::sync::mpsc;
32764
32765    let (done_tx, done_rx) = mpsc::channel();
32766    let runner = std::thread::spawn(move || {
32767      let controller = Arc::new(RuntimeController::new());
32768      // Default options: MultiThread on native.
32769      let backend = controller.backend();
32770      let RuntimeExecutor::MultiThread(executor) = &backend.executor else {
32771        panic!("default native backend must be MultiThread");
32772      };
32773      let executor = Arc::downgrade(executor);
32774      let mut late = heap_sleep(
32775        &executor
32776          .upgrade()
32777          .expect("the initial executor must be live"),
32778        Instant::now() + Duration::from_secs(120),
32779      );
32780      drop(backend);
32781
32782      let (parked_tx, parked_rx) = mpsc::channel();
32783      let block_controller = Arc::clone(&controller);
32784      let block_exec = Weak::clone(&executor);
32785      let blocker = std::thread::spawn(move || {
32786        let mut future = std::pin::pin!(async {
32787          let executor = block_exec
32788            .upgrade()
32789            .expect("the runtime must remain live while blocking");
32790          let sleep = heap_sleep(&executor, Instant::now() + Duration::from_secs(120));
32791          drop(executor);
32792          parked_tx.send(()).unwrap();
32793          sleep.await;
32794        });
32795        block_controller.block_on(future.as_mut());
32796      });
32797      parked_rx.recv().unwrap();
32798      wait_until("the 120s sleep registered in the heap", || {
32799        executor
32800          .upgrade()
32801          .is_some_and(|executor| executor.next_timer_deadline().is_some())
32802      });
32803
32804      // The acceptance surface: controller shutdown while a sleep is armed.
32805      controller
32806        .shutdown()
32807        .expect("runtime shutdown must succeed");
32808      blocker
32809        .join()
32810        .expect("the blocked sleep must be drain-fired by shutdown, not hang");
32811
32812      // A Sleep polled AFTER shutdown resolves immediately instead of
32813      // registering with a dead heap.
32814      let waker = Waker::from(Arc::new(CountingWake(AtomicUsize::new(0))));
32815      let mut cx = Context::from_waker(&waker);
32816      assert!(
32817        Pin::new(&mut late).poll(&mut cx).is_ready(),
32818        "a Sleep polled after shutdown must fire early, never park a closing runtime"
32819      );
32820      let _ = done_tx.send(());
32821    });
32822
32823    match done_rx.recv_timeout(Duration::from_secs(10)) {
32824      Ok(()) => runner.join().unwrap(),
32825      Err(error) => {
32826        panic!("shutdown with pending timers hung a blocked close ({error}): drain-fire missing")
32827      }
32828    }
32829  }
32830
32831  #[cfg(not(target_family = "wasm"))]
32832  #[test]
32833  fn multi_thread_pending_timer_longer_than_park_deadline_does_not_panic() {
32834    // Timer-wait/deadline invariant: the park deadline is armed
32835    // SHORTER than the next timer deadline. The cooperative driver awaiting
32836    // the sleep sits with ZERO
32837    // executor progress for several full deadline windows -- a timer wait is
32838    // a legitimate park (guaranteed wall-clock wake), so it must NOT be
32839    // declared a BlockOnDeadlock; the timer must still fire. worker_threads=1
32840    // also allows the parked driver to race the dedicated timekeeper and fire
32841    // the timer itself, pinning the cooperative timer-bounded park.
32842    use std::sync::mpsc;
32843
32844    let (done_tx, done_rx) = mpsc::channel();
32845    let runner = std::thread::spawn(move || {
32846      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-vs-deadline");
32847
32848      let task_exec = Arc::clone(&executor);
32849      let body = async move {
32850        let started = Instant::now();
32851        let mut inner = std::pin::pin!(async {
32852          heap_sleep(&task_exec, Instant::now() + Duration::from_millis(250)).await;
32853        });
32854        // Re-entrant block_on from the pool worker: the cooperative park.
32855        task_exec.block_on(inner.as_mut());
32856        assert!(started.elapsed() >= Duration::from_millis(250));
32857      };
32858      let sched = Arc::clone(&executor);
32859      let (runnable, task) = async_task::spawn(body, move |r| sched.schedule(r));
32860      executor.schedule(runnable);
32861
32862      // A spurious BlockOnDeadlock panic would unwind the task body (swallowed
32863      // by run_runnable's catch_unwind), so the task would never complete and
32864      // the bounded harness below would report the failure.
32865      futures::executor::block_on(task);
32866      // Teardown inside the bounded window: this test was one of the silent
32867      // wedges on the Windows CI runners with every assertion already
32868      // passed. Its teardown includes `TimerHeap::drop`'s timekeeper JOIN
32869      // (a timer was polled, so the timekeeper thread is live) -- exactly
32870      // the kind of phase that must fail loudly rather than hang.
32871      drop(executor);
32872      let _ = done_tx.send(());
32873    });
32874
32875    match done_rx.recv_timeout(Duration::from_secs(10)) {
32876      Ok(()) => join_within(
32877        "the timer-vs-deadline runner exits after executor (and timekeeper) teardown",
32878        runner,
32879        Duration::from_secs(30),
32880      ),
32881      Err(error) => {
32882        panic!("the deadline-armed timer wait neither completed nor failed cleanly ({error})")
32883      }
32884    }
32885  }
32886
32887  #[cfg(not(target_family = "wasm"))]
32888  #[test]
32889  fn multi_thread_timer_registered_at_verdict_edge_is_not_reported_as_deadlock() {
32890    // Timer-registration/verdict race, deterministic via the verdict hook: a
32891    // timer registered AFTER a deadline park was committed bumps no progress
32892    // counter by design, lands after the permit and queued-work re-checks, and
32893    // is invisible to the fingerprint. The heap re-check must recognize that
32894    // guaranteed future wake, veto the panic, and let the driver re-park
32895    // timer-bounded until the fire's task-wake completes the future.
32896    use std::sync::mpsc;
32897
32898    // A raw Sleep the hook registers is woken by this waker, which releases
32899    // the awaited future's gate.
32900    struct GateWake {
32901      gate: Arc<Mutex<Option<oneshot::Sender<usize>>>>,
32902    }
32903    impl std::task::Wake for GateWake {
32904      fn wake(self: Arc<Self>) {
32905        let gate = self.gate.lock().unwrap().take();
32906        if let Some(gate) = gate {
32907          let _ = gate.send(23);
32908        }
32909      }
32910    }
32911
32912    let (done_tx, done_rx) = mpsc::channel();
32913    let runner = std::thread::spawn(move || {
32914      let executor = multi_thread_executor(1, Some(Duration::from_millis(40)), "timer-verdict");
32915      // Saturate drainer accounting so `wake_one` fallbacks cannot start a
32916      // replacement drainer behind our back; only the verdict's heap re-check
32917      // can save this park.
32918      executor
32919        .active_drainers
32920        .store(executor.max_drainers, Ordering::SeqCst);
32921
32922      // Completion condition: the timer registered inside the hook fires and
32923      // its waker (the gate) releases the awaited future.
32924      let (gate_tx, gate_rx) = oneshot::channel::<usize>();
32925      let gate_tx = Arc::new(Mutex::new(Some(gate_tx)));
32926
32927      let hook_exec = Arc::clone(&executor);
32928      let hook_gate = Arc::clone(&gate_tx);
32929      // The Sleep must outlive the hook (drop cancels): park it in a slot the
32930      // runner thread later cleans up.
32931      let parked_sleep: Arc<Mutex<Option<Sleep>>> = Arc::new(Mutex::new(None));
32932      let hook_sleep_slot = Arc::clone(&parked_sleep);
32933      DEADLINE_VERDICT_TEST_HOOK.with(|slot| {
32934        *slot.borrow_mut() = Some(Box::new(move || {
32935          let mut sleep = heap_sleep(&hook_exec, Instant::now() + Duration::from_millis(120));
32936          let waker = Waker::from(Arc::new(GateWake { gate: hook_gate }));
32937          let mut cx = Context::from_waker(&waker);
32938          assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
32939          *hook_sleep_slot.lock().unwrap() = Some(sleep);
32940        }));
32941      });
32942
32943      // The test-only on-pool marker runs the cooperative branch and its
32944      // thread-local hook on THIS thread.
32945      let _on_pool = OnPoolWorkerGuard::enter(executor.id);
32946      let mut output = None;
32947      {
32948        let mut future = std::pin::pin!(async {
32949          output = Some(gate_rx.await.unwrap());
32950        });
32951        executor.block_on(future.as_mut());
32952      }
32953      // Clear the hook slot BEFORE any assert can panic: this hook captures
32954      // an executor Arc and THIS executor's timekeeper is live (a timer was
32955      // polled), so a leftover hook dropped by a TLS destructor would join
32956      // the timekeeper under the Windows loader lock (see the LOADER-LOCK
32957      // RULE at `WorkerTlsDropAction`).
32958      DEADLINE_VERDICT_TEST_HOOK.with(|slot| drop(slot.borrow_mut().take()));
32959      assert_eq!(
32960        output,
32961        Some(23),
32962        "the edge-registered timer must have fired and released the gate"
32963      );
32964      executor.active_drainers.store(0, Ordering::SeqCst);
32965      // Teardown inside the bounded window; includes the timekeeper join
32966      // (a timer was polled). See `join_within`.
32967      drop(executor);
32968      let _ = done_tx.send(());
32969    });
32970
32971    match done_rx.recv_timeout(Duration::from_secs(10)) {
32972      Ok(()) => join_within(
32973        "the timer-verdict-edge runner exits after executor (and timekeeper) teardown",
32974        runner,
32975        Duration::from_secs(30),
32976      ),
32977      Err(error) => panic!(
32978        "a timer registered between the queued-work re-check and the verdict was reported as a deadlock or its fire was lost ({error})"
32979      ),
32980    }
32981  }
32982
32983  #[test]
32984  fn current_thread_sleep_without_host_driver_fails_loud() {
32985    // Missing-driver invariant: a CT runtime without a registered host driver
32986    // must fail LOUD with this exact diagnostic -- never a silent never-firing
32987    // debounce or a misleading certain-deadlock panic later.
32988    let metrics = Arc::new(RuntimeMetrics::default());
32989    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
32990    let backend = RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(executor));
32991
32992    let payload = catch_unwind(AssertUnwindSafe(|| {
32993      let _sleep = make_sleep(
32994        &backend,
32995        &Arc::new(TimerDriverRegistry::default()),
32996        Instant::now() + Duration::from_millis(10),
32997      );
32998    }))
32999    .expect_err("sleep_until on CurrentThread without a driver must panic");
33000    let message = payload
33001      .downcast_ref::<String>()
33002      .cloned()
33003      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
33004      .expect("panic payload must be a message");
33005    assert!(
33006      message.contains("CurrentThread runtime has no live timer driver registered"),
33007      "the diagnostic must name the missing driver, got: {message}"
33008    );
33009  }
33010
33011  #[test]
33012  fn timer_driver_registry_selects_newest_live_and_evicts_dead() {
33013    // Registry semantics: all-live snapshots with newest-live compatibility
33014    // selection, dead entries swept on contact, explicit unregister, and a
33015    // dead-only registry reading as NO driver.
33016    let registry = TimerDriverRegistry::default();
33017    let a = ManualStubDriver::new();
33018    let b = ManualStubDriver::new();
33019    let a_id = registry.register(Arc::clone(&a) as Arc<dyn TimerDriver>);
33020    let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
33021    assert_ne!(a_id, b_id, "registration handles must be distinct");
33022    assert_eq!(
33023      registry.current().map(|(id, _)| id),
33024      Some(b_id),
33025      "newest live registrant serves"
33026    );
33027    assert!(registry.has_live_driver());
33028
33029    // A dead newest (stub for: its owning env torn down) is skipped AND
33030    // swept; selection falls back to the older live registrant.
33031    b.kill();
33032    assert_eq!(
33033      registry.current().map(|(id, _)| id),
33034      Some(a_id),
33035      "dead newest must be skipped"
33036    );
33037    // The sweep is permanent: resurrecting the flag must not bring the
33038    // evicted registration back.
33039    b.live.store(true, Ordering::SeqCst);
33040    assert_eq!(
33041      registry.current().map(|(id, _)| id),
33042      Some(a_id),
33043      "swept entries stay gone"
33044    );
33045
33046    // Explicit unregister (the binding's env-cleanup path) empties the
33047    // registry; empty and dead-only both read as NO live driver.
33048    registry.unregister(a_id);
33049    assert!(registry.current().is_none());
33050    assert!(
33051      !registry.has_live_driver(),
33052      "an emptied registry must not report a live driver"
33053    );
33054  }
33055
33056  #[test]
33057  fn timer_driver_registry_contains_callback_panics_and_falls_back() {
33058    let registry = TimerDriverRegistry::default();
33059    let fallback = ManualStubDriver::new();
33060    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33061    let panicking = Arc::new(PanickingTimerDriver {
33062      swept: AtomicBool::new(false),
33063    });
33064    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
33065
33066    assert_eq!(
33067      registry.current().map(|(id, _)| id),
33068      Some(fallback_id),
33069      "a panicking liveness callback must be treated as a dead driver"
33070    );
33071    assert!(
33072      panicking.swept.load(Ordering::SeqCst),
33073      "a panicking liveness callback must still run its contained sweep hook"
33074    );
33075    assert!(
33076      registry.has_live_driver(),
33077      "the healthy fallback must remain selectable"
33078    );
33079  }
33080
33081  #[test]
33082  fn timer_driver_registry_callbacks_and_drops_run_outside_registry_lock() {
33083    use std::{sync::mpsc, time::Duration};
33084
33085    let registry = Arc::new(TimerDriverRegistry::default());
33086    let fallback = ManualStubDriver::new();
33087    let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33088    let reentrant = Arc::new(ReentrantLivenessDriver {
33089      registry: Arc::downgrade(&registry),
33090      registration: Mutex::new(None),
33091    });
33092    let reentrant_id = registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
33093    *reentrant.registration.lock().unwrap() = Some(reentrant_id);
33094
33095    let (selection_tx, selection_rx) = mpsc::sync_channel(1);
33096    let selection_registry = Arc::clone(&registry);
33097    let selection_thread = std::thread::spawn(move || {
33098      selection_tx
33099        .send(selection_registry.current().map(|(id, _)| id))
33100        .unwrap();
33101    });
33102    assert_eq!(
33103      selection_rx
33104        .recv_timeout(Duration::from_secs(10))
33105        .expect("is_live re-entry deadlocked"),
33106      Some(fallback_id)
33107    );
33108    selection_thread.join().unwrap();
33109
33110    let drop_driver: Arc<dyn TimerDriver> = Arc::new(ReentrantDropDriver {
33111      registry: Arc::downgrade(&registry),
33112      fallback: fallback_id,
33113    });
33114    let drop_id = registry.register(Arc::clone(&drop_driver));
33115    drop(drop_driver);
33116
33117    let (drop_tx, drop_rx) = mpsc::sync_channel(1);
33118    let drop_registry = Arc::clone(&registry);
33119    let drop_thread = std::thread::spawn(move || {
33120      drop_registry.unregister(drop_id);
33121      drop_tx.send(()).unwrap();
33122    });
33123    drop_rx
33124      .recv_timeout(Duration::from_secs(10))
33125      .expect("driver destructor re-entry deadlocked");
33126    drop_thread.join().unwrap();
33127    assert!(
33128      registry.current().is_none(),
33129      "the destructor must be able to unregister the fallback"
33130    );
33131  }
33132
33133  #[test]
33134  fn timer_driver_registry_large_reentrant_sweep_preserves_keyed_order() {
33135    const DRIVER_PAIRS: usize = 2_048;
33136
33137    let registry = Arc::new(TimerDriverRegistry::default());
33138    let mut dead = Vec::with_capacity(DRIVER_PAIRS + 1);
33139    let mut live = Vec::with_capacity(DRIVER_PAIRS);
33140    let mut live_ids = Vec::with_capacity(DRIVER_PAIRS);
33141    for _ in 0..DRIVER_PAIRS {
33142      let driver = Arc::new(ReentrantSweepTimerDriver {
33143        registry: Arc::downgrade(&registry),
33144        registration: Mutex::new(None),
33145        sweeps: AtomicUsize::new(0),
33146        removed_before_sweep: AtomicBool::new(false),
33147      });
33148      let id = registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33149      *driver.registration.lock().unwrap() = Some(id);
33150      dead.push(driver);
33151
33152      let driver = ManualStubDriver::new();
33153      live_ids.push(registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>));
33154      live.push(driver);
33155    }
33156    let dead_tail = Arc::new(ReentrantSweepTimerDriver {
33157      registry: Arc::downgrade(&registry),
33158      registration: Mutex::new(None),
33159      sweeps: AtomicUsize::new(0),
33160      removed_before_sweep: AtomicBool::new(false),
33161    });
33162    let dead_tail_id = registry.register(Arc::clone(&dead_tail) as Arc<dyn TimerDriver>);
33163    *dead_tail.registration.lock().unwrap() = Some(dead_tail_id);
33164    dead.push(dead_tail);
33165
33166    let selection = registry.live();
33167    assert_eq!(
33168      selection
33169        .as_slice()
33170        .iter()
33171        .map(|(id, _)| *id)
33172        .collect::<Vec<_>>(),
33173      live_ids,
33174      "sweeping every interleaved dead host must retain live registration order"
33175    );
33176    drop(selection);
33177    assert!(
33178      dead.iter().all(|driver| {
33179        driver.sweeps.load(Ordering::SeqCst) == 1
33180          && driver.removed_before_sweep.load(Ordering::SeqCst)
33181          && driver.registration.lock().unwrap().is_none()
33182      }),
33183      "every dead host must be unlinked and run exactly one binding-shaped eviction hook"
33184    );
33185
33186    {
33187      let state = registry
33188        .state
33189        .lock()
33190        .unwrap_or_else(std::sync::PoisonError::into_inner);
33191      assert_eq!(state.entries.len(), DRIVER_PAIRS);
33192      assert_eq!(state.oldest, live_ids.first().copied());
33193      assert_eq!(state.newest, live_ids.last().copied());
33194      assert_eq!(
33195        state
33196          .ordered_snapshot()
33197          .iter()
33198          .map(|(id, _)| *id)
33199          .collect::<Vec<_>>(),
33200        live_ids,
33201        "keyed removals must preserve the linked order of every live host"
33202      );
33203    }
33204    let second_selection = registry.live();
33205    assert_eq!(
33206      second_selection
33207        .as_slice()
33208        .iter()
33209        .map(|(id, _)| *id)
33210        .collect::<Vec<_>>(),
33211      live_ids,
33212      "a second selection must retain the same live order"
33213    );
33214    drop(second_selection);
33215    assert!(
33216      dead
33217        .iter()
33218        .all(|driver| driver.sweeps.load(Ordering::SeqCst) == 1),
33219      "permanently swept hosts must not receive repeated eviction callbacks"
33220    );
33221    assert_eq!(
33222      registry.current().map(|(id, _)| id),
33223      live_ids.last().copied(),
33224      "newest-live compatibility selection must survive the mass sweep"
33225    );
33226
33227    drop(live);
33228    drop(dead);
33229  }
33230
33231  #[test]
33232  fn timer_driver_registry_snapshot_contains_each_driver_drop() {
33233    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_SNAPSHOT_DROP_PANIC_CHILD";
33234
33235    if std::env::var_os(CHILD_ENV).is_some() {
33236      let registry = Arc::new(TimerDriverRegistry::default());
33237      let fallback = ManualStubDriver::new();
33238      let fallback_id = registry.register(Arc::clone(&fallback) as Arc<dyn TimerDriver>);
33239      let reentrant = Arc::new(ReentrantMultiUnregisterTimerDriver {
33240        registry: Arc::downgrade(&registry),
33241        registrations: Mutex::new(Vec::new()),
33242      });
33243      registry.register(Arc::clone(&reentrant) as Arc<dyn TimerDriver>);
33244
33245      let first_cancels = Arc::new(AtomicUsize::new(0));
33246      let first_drops = Arc::new(AtomicUsize::new(0));
33247      let first = Arc::new(PanickingCancelDropTimerDriver {
33248        cancels: Arc::clone(&first_cancels),
33249        drops: Arc::clone(&first_drops),
33250      });
33251      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33252      drop(first);
33253
33254      let second_cancels = Arc::new(AtomicUsize::new(0));
33255      let second_drops = Arc::new(AtomicUsize::new(0));
33256      let second = Arc::new(PanickingCancelDropTimerDriver {
33257        cancels: Arc::clone(&second_cancels),
33258        drops: Arc::clone(&second_drops),
33259      });
33260      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33261      drop(second);
33262      *reentrant.registrations.lock().unwrap() = vec![first_id, second_id];
33263
33264      assert_eq!(
33265        registry.current().map(|(id, _)| id),
33266        Some(fallback_id),
33267        "selection must retry after reentrant removals and preserve the healthy fallback"
33268      );
33269      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
33270      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
33271      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33272      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33273      assert_eq!(
33274        registry
33275          .state
33276          .lock()
33277          .unwrap_or_else(std::sync::PoisonError::into_inner)
33278          .entries
33279          .len(),
33280        1
33281      );
33282      return;
33283    }
33284
33285    let output = std::process::Command::new(std::env::current_exe().unwrap())
33286      .arg("--exact")
33287      .arg("async_runtime::tests::timer_driver_registry_snapshot_contains_each_driver_drop")
33288      .arg("--nocapture")
33289      .env(CHILD_ENV, "1")
33290      .output()
33291      .expect("the hostile timer-driver snapshot subprocess test must start");
33292    assert!(
33293      output.status.success(),
33294      "reentrant liveness removal must not combine timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
33295      output.status.code(),
33296      String::from_utf8_lossy(&output.stdout),
33297      String::from_utf8_lossy(&output.stderr)
33298    );
33299  }
33300
33301  #[test]
33302  fn timer_driver_registry_live_result_contains_each_driver_drop() {
33303    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_LIVE_DROP_PANIC_CHILD";
33304
33305    if std::env::var_os(CHILD_ENV).is_some() {
33306      let registry = TimerDriverRegistry::default();
33307
33308      let first_cancels = Arc::new(AtomicUsize::new(0));
33309      let first_drops = Arc::new(AtomicUsize::new(0));
33310      let first = Arc::new(PanickingCancelDropTimerDriver {
33311        cancels: Arc::clone(&first_cancels),
33312        drops: Arc::clone(&first_drops),
33313      });
33314      let first_id = registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33315      drop(first);
33316
33317      let second_cancels = Arc::new(AtomicUsize::new(0));
33318      let second_drops = Arc::new(AtomicUsize::new(0));
33319      let second = Arc::new(PanickingCancelDropTimerDriver {
33320        cancels: Arc::clone(&second_cancels),
33321        drops: Arc::clone(&second_drops),
33322      });
33323      let second_id = registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33324      drop(second);
33325
33326      let live = registry.live();
33327      assert_eq!(live.len(), 2);
33328      registry.unregister(first_id);
33329      registry.unregister(second_id);
33330      drop(live);
33331
33332      assert_eq!(first_cancels.load(Ordering::SeqCst), 0);
33333      assert_eq!(second_cancels.load(Ordering::SeqCst), 0);
33334      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33335      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33336      assert!(
33337        registry
33338          .state
33339          .lock()
33340          .unwrap_or_else(std::sync::PoisonError::into_inner)
33341          .entries
33342          .is_empty()
33343      );
33344      return;
33345    }
33346
33347    let output = std::process::Command::new(std::env::current_exe().unwrap())
33348      .arg("--exact")
33349      .arg("async_runtime::tests::timer_driver_registry_live_result_contains_each_driver_drop")
33350      .arg("--nocapture")
33351      .env(CHILD_ENV, "1")
33352      .output()
33353      .expect("the hostile live timer-driver snapshot subprocess test must start");
33354    assert!(
33355      output.status.success(),
33356      "a returned live snapshot must not combine final timer-driver destructor panics; status={:?}\nstdout={}\nstderr={}",
33357      output.status.code(),
33358      String::from_utf8_lossy(&output.stdout),
33359      String::from_utf8_lossy(&output.stderr)
33360    );
33361  }
33362
33363  #[test]
33364  fn timer_driver_registry_drop_contains_each_driver_destructor() {
33365    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_TIMER_DRIVER_REGISTRY_DROP_PANIC_CHILD";
33366
33367    if std::env::var_os(CHILD_ENV).is_some() {
33368      let first_drops = Arc::new(AtomicUsize::new(0));
33369      let second_drops = Arc::new(AtomicUsize::new(0));
33370      let registry = TimerDriverRegistry::default();
33371      let first = Arc::new(PanickingCancelDropTimerDriver {
33372        cancels: Arc::new(AtomicUsize::new(0)),
33373        drops: Arc::clone(&first_drops),
33374      });
33375      let second = Arc::new(PanickingCancelDropTimerDriver {
33376        cancels: Arc::new(AtomicUsize::new(0)),
33377        drops: Arc::clone(&second_drops),
33378      });
33379      registry.register(Arc::clone(&first) as Arc<dyn TimerDriver>);
33380      registry.register(Arc::clone(&second) as Arc<dyn TimerDriver>);
33381      drop(first);
33382      drop(second);
33383
33384      drop(registry);
33385
33386      assert_eq!(first_drops.load(Ordering::SeqCst), 1);
33387      assert_eq!(second_drops.load(Ordering::SeqCst), 1);
33388      return;
33389    }
33390
33391    let output = std::process::Command::new(std::env::current_exe().unwrap())
33392      .arg("--exact")
33393      .arg("async_runtime::tests::timer_driver_registry_drop_contains_each_driver_destructor")
33394      .arg("--nocapture")
33395      .env(CHILD_ENV, "1")
33396      .output()
33397      .expect("the hostile timer-driver registry drop subprocess test must start");
33398    assert!(
33399      output.status.success(),
33400      "registry drop must contain every driver destructor panic; status={:?}\nstdout={}\nstderr={}",
33401      output.status.code(),
33402      String::from_utf8_lossy(&output.stdout),
33403      String::from_utf8_lossy(&output.stderr)
33404    );
33405  }
33406
33407  #[test]
33408  fn host_sleep_reconciles_large_driver_sets_without_duplicate_arms() {
33409    const DRIVERS: usize = 512;
33410
33411    let metrics = Arc::new(RuntimeMetrics::default());
33412    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33413    let backend =
33414      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33415    let registry = Arc::new(TimerDriverRegistry::default());
33416    let drivers = (0..DRIVERS)
33417      .map(|_| {
33418        let driver = ManualStubDriver::new();
33419        registry.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33420        driver
33421      })
33422      .collect::<Vec<_>>();
33423    let mut sleep = make_sleep(
33424      &backend,
33425      &registry,
33426      Instant::now() + Duration::from_secs(60),
33427    );
33428    let mut cx = Context::from_waker(Waker::noop());
33429
33430    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33431    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33432    let timer_id = drivers[0].registers.lock().unwrap()[0].0;
33433    match &sleep.inner {
33434      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS),
33435      #[cfg(not(target_family = "wasm"))]
33436      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
33437    }
33438    for driver in &drivers {
33439      assert_eq!(
33440        driver.registers.lock().unwrap().len(),
33441        2,
33442        "a re-poll must refresh each live driver exactly once"
33443      );
33444    }
33445
33446    for driver in drivers.iter().step_by(2) {
33447      driver.kill();
33448    }
33449    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33450    match &sleep.inner {
33451      SleepInner::Host(host) => assert_eq!(host.armed.len(), DRIVERS / 2),
33452      #[cfg(not(target_family = "wasm"))]
33453      SleepInner::Heap(_) => panic!("CurrentThread sleep must use host timers"),
33454    }
33455    for (index, driver) in drivers.iter().enumerate() {
33456      let registers = driver.registers.lock().unwrap().len();
33457      let cancels = driver.cancels.lock().unwrap().clone();
33458      if index % 2 == 0 {
33459        assert_eq!(registers, 2);
33460        assert_eq!(cancels, vec![timer_id]);
33461      } else {
33462        assert_eq!(registers, 3);
33463        assert!(cancels.is_empty());
33464      }
33465    }
33466
33467    drop(sleep);
33468    for driver in drivers {
33469      assert_eq!(
33470        driver.cancels.lock().unwrap().as_slice(),
33471        &[timer_id],
33472        "every physical arm must be cancelled exactly once"
33473      );
33474    }
33475    assert_eq!(registry.pending_len(), 0);
33476    assert!(!executor.host_timers.has_pending());
33477  }
33478
33479  #[test]
33480  fn host_sleep_register_unwind_preserves_every_possible_arm_for_cancellation() {
33481    let metrics = Arc::new(RuntimeMetrics::default());
33482    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33483    let backend =
33484      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33485    let healthy = ManualStubDriver::new();
33486    let registry = registry_with(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
33487    let deadline = Instant::now() + Duration::from_secs(60);
33488    let mut sleep = make_sleep(&backend, &registry, deadline);
33489    let mut cx = Context::from_waker(Waker::noop());
33490
33491    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33492    let timer_id = healthy.registers.lock().unwrap()[0].0;
33493
33494    let panicking = PanickingRegisterTimerDriver::new();
33495    registry.register(Arc::clone(&panicking) as Arc<dyn TimerDriver>);
33496    let result = catch_unwind(AssertUnwindSafe(|| {
33497      let _ = Pin::new(&mut sleep).poll(&mut cx);
33498    }));
33499    assert!(
33500      result.is_err(),
33501      "the hostile register panic must retain its normal propagation"
33502    );
33503    assert_eq!(
33504      healthy.registers.lock().unwrap().len(),
33505      2,
33506      "the previously armed driver must have completed its refresh before the later panic"
33507    );
33508    assert_eq!(panicking.registers.load(Ordering::SeqCst), 1);
33509    assert!(
33510      panicking.pending.lock().unwrap().contains_key(&timer_id),
33511      "the hostile driver must model an arm committed before it panics"
33512    );
33513
33514    drop(sleep);
33515    assert_eq!(
33516      healthy.cancels.lock().unwrap().as_slice(),
33517      &[timer_id],
33518      "unwind must preserve cancellation ownership for the previously armed driver"
33519    );
33520    assert_eq!(
33521      panicking.cancels.lock().unwrap().as_slice(),
33522      &[timer_id],
33523      "unwind must preserve cancellation ownership for the driver that armed and then panicked"
33524    );
33525    assert!(panicking.pending.lock().unwrap().is_empty());
33526    assert_eq!(registry.pending_len(), 0);
33527    assert!(!executor.host_timers.has_pending());
33528  }
33529
33530  #[test]
33531  fn host_sleep_register_unwind_contains_later_unregistered_driver_drop() {
33532    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_SNAPSHOT_DROP_PANIC_CHILD";
33533
33534    if std::env::var_os(CHILD_ENV).is_some() {
33535      let metrics = Arc::new(RuntimeMetrics::default());
33536      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33537      let backend =
33538        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33539      let registry = Arc::new(TimerDriverRegistry::default());
33540      let earlier = Arc::new(UnregisterLaterThenPanicTimerDriver {
33541        registry: Arc::downgrade(&registry),
33542        later: Mutex::new(None),
33543        registers: AtomicUsize::new(0),
33544      });
33545      registry.register(Arc::clone(&earlier) as Arc<dyn TimerDriver>);
33546
33547      let later_cancels = Arc::new(AtomicUsize::new(0));
33548      let later_drops = Arc::new(AtomicUsize::new(0));
33549      let later = Arc::new(PanickingCancelDropTimerDriver {
33550        cancels: Arc::clone(&later_cancels),
33551        drops: Arc::clone(&later_drops),
33552      });
33553      let later_id = registry.register(Arc::clone(&later) as Arc<dyn TimerDriver>);
33554      *earlier.later.lock().unwrap() = Some(later_id);
33555      drop(later);
33556
33557      let mut sleep = make_sleep(
33558        &backend,
33559        &registry,
33560        Instant::now() + Duration::from_secs(60),
33561      );
33562      let mut cx = Context::from_waker(Waker::noop());
33563      let result = catch_unwind(AssertUnwindSafe(|| {
33564        let _ = Pin::new(&mut sleep).poll(&mut cx);
33565      }));
33566
33567      assert!(
33568        result.is_err(),
33569        "the earlier driver's register panic must still propagate"
33570      );
33571      assert_eq!(earlier.registers.load(Ordering::SeqCst), 1);
33572      assert_eq!(
33573        later_drops.load(Ordering::SeqCst),
33574        0,
33575        "the complete live snapshot must enter Sleep cancellation ownership before callbacks"
33576      );
33577
33578      drop(sleep);
33579      assert_eq!(
33580        later_cancels.load(Ordering::SeqCst),
33581        1,
33582        "the later driver must remain owned for cancellation despite its registry removal"
33583      );
33584      assert_eq!(
33585        later_drops.load(Ordering::SeqCst),
33586        1,
33587        "the later driver's final destructor must run once behind containment"
33588      );
33589      assert_eq!(registry.pending_len(), 0);
33590      assert!(!executor.host_timers.has_pending());
33591      return;
33592    }
33593
33594    let output = std::process::Command::new(std::env::current_exe().unwrap())
33595      .arg("--exact")
33596      .arg(
33597        "async_runtime::tests::host_sleep_register_unwind_contains_later_unregistered_driver_drop",
33598      )
33599      .arg("--nocapture")
33600      .env(CHILD_ENV, "1")
33601      .output()
33602      .expect("the hostile host timer snapshot subprocess test must start");
33603    assert!(
33604      output.status.success(),
33605      "an earlier register panic must not combine with a later unregistered driver's destructor panic; status={:?}\nstdout={}\nstderr={}",
33606      output.status.code(),
33607      String::from_utf8_lossy(&output.stdout),
33608      String::from_utf8_lossy(&output.stderr)
33609    );
33610  }
33611
33612  #[test]
33613  fn host_sleep_cancel_contains_each_callback_and_driver_drop_independently() {
33614    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_CANCEL_DROP_PANIC_CHILD";
33615
33616    if std::env::var_os(CHILD_ENV).is_some() {
33617      let metrics = Arc::new(RuntimeMetrics::default());
33618      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33619      let backend =
33620        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33621      let cancels = Arc::new(AtomicUsize::new(0));
33622      let drops = Arc::new(AtomicUsize::new(0));
33623      let hostile = Arc::new(PanickingCancelDropTimerDriver {
33624        cancels: Arc::clone(&cancels),
33625        drops: Arc::clone(&drops),
33626      });
33627      let healthy = ManualStubDriver::new();
33628      let registry = Arc::new(TimerDriverRegistry::default());
33629      let hostile_id = registry.register(Arc::clone(&hostile) as Arc<dyn TimerDriver>);
33630      registry.register(Arc::clone(&healthy) as Arc<dyn TimerDriver>);
33631      let mut sleep = make_sleep(
33632        &backend,
33633        &registry,
33634        Instant::now() + Duration::from_secs(60),
33635      );
33636      let mut cx = Context::from_waker(Waker::noop());
33637
33638      assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33639      let timer_id = healthy.registers.lock().unwrap()[0].0;
33640
33641      // Leave the Sleep's arm as the hostile driver's final Arc, so cleanup must
33642      // independently contain both its cancel panic and its destructor panic.
33643      registry.unregister(hostile_id);
33644      drop(hostile);
33645      drop(sleep);
33646      assert_eq!(cancels.load(Ordering::SeqCst), 1);
33647      assert_eq!(drops.load(Ordering::SeqCst), 1);
33648      assert_eq!(
33649        healthy.cancels.lock().unwrap().as_slice(),
33650        &[timer_id],
33651        "one hostile cancellation must not skip later armed drivers"
33652      );
33653      assert_eq!(registry.pending_len(), 0);
33654      assert!(!executor.host_timers.has_pending());
33655      return;
33656    }
33657
33658    let output = std::process::Command::new(std::env::current_exe().unwrap())
33659      .arg("--exact")
33660      .arg(
33661        "async_runtime::tests::host_sleep_cancel_contains_each_callback_and_driver_drop_independently",
33662      )
33663      .arg("--nocapture")
33664      .env(CHILD_ENV, "1")
33665      .output()
33666      .expect("the hostile timer-driver cancellation subprocess test must start");
33667    assert!(
33668      output.status.success(),
33669      "host timer cancellation and driver destruction must not double-panic or skip later arms; status={:?}\nstdout={}\nstderr={}",
33670      output.status.code(),
33671      String::from_utf8_lossy(&output.stdout),
33672      String::from_utf8_lossy(&output.stderr)
33673    );
33674  }
33675
33676  #[test]
33677  fn host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic() {
33678    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_WAKER_CLONE_DROP_PANIC_CHILD";
33679
33680    if std::env::var_os(CHILD_ENV).is_some() {
33681      let metrics = Arc::new(RuntimeMetrics::default());
33682      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33683      let backend =
33684        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33685      let driver = ManualStubDriver::new();
33686      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33687      let state = Arc::new(TestHostileRawWakerState {
33688        clones: AtomicUsize::new(0),
33689        drop_panics: AtomicUsize::new(0),
33690        panic_on_clone: Some(2),
33691      });
33692
33693      let result = catch_unwind(AssertUnwindSafe(|| {
33694        let mut sleep = make_sleep(
33695          &backend,
33696          &registry,
33697          Instant::now() + Duration::from_secs(60),
33698        );
33699        let waker = test_hostile_waker(Arc::clone(&state));
33700        let mut cx = Context::from_waker(&waker);
33701        let _ = Pin::new(&mut sleep).poll(&mut cx);
33702      }));
33703      assert!(
33704        result.is_err(),
33705        "the original RawWaker clone panic must still propagate"
33706      );
33707      assert_eq!(state.clones.load(Ordering::SeqCst), 2);
33708      assert_eq!(
33709        state.drop_panics.load(Ordering::SeqCst),
33710        1,
33711        "the first clone must be destroyed once behind a contained boundary"
33712      );
33713      assert!(driver.registers.lock().unwrap().is_empty());
33714      assert_eq!(registry.pending_len(), 0);
33715      assert!(!executor.host_timers.has_pending());
33716      return;
33717    }
33718
33719    let output = std::process::Command::new(std::env::current_exe().unwrap())
33720      .arg("--exact")
33721      .arg("async_runtime::tests::host_sleep_first_poll_clone_and_drop_panics_do_not_double_panic")
33722      .arg("--nocapture")
33723      .env(CHILD_ENV, "1")
33724      .output()
33725      .expect("the hostile RawWaker subprocess test must start");
33726    assert!(
33727      output.status.success(),
33728      "host timer first-poll cleanup must not abort after clone and destructor panics; status={:?}\nstdout={}\nstderr={}",
33729      output.status.code(),
33730      String::from_utf8_lossy(&output.stdout),
33731      String::from_utf8_lossy(&output.stderr)
33732    );
33733  }
33734
33735  #[test]
33736  fn host_sleep_register_unwind_contains_unretained_waker_drop() {
33737    const CHILD_ENV: &str = "NAPI_RUNTIME_TEST_HOST_TIMER_REGISTER_WAKER_DROP_PANIC_CHILD";
33738
33739    if std::env::var_os(CHILD_ENV).is_some() {
33740      let metrics = Arc::new(RuntimeMetrics::default());
33741      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33742      let backend =
33743        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33744      let driver = Arc::new(PanicBeforeRetainingTimerDriver {
33745        registers: AtomicUsize::new(0),
33746      });
33747      let registry = registry_with(Arc::clone(&driver) as Arc<dyn TimerDriver>);
33748      let state = Arc::new(TestHostileRawWakerState {
33749        clones: AtomicUsize::new(0),
33750        drop_panics: AtomicUsize::new(0),
33751        panic_on_clone: None,
33752      });
33753      let mut sleep = make_sleep(
33754        &backend,
33755        &registry,
33756        Instant::now() + Duration::from_secs(60),
33757      );
33758      let waker = test_hostile_waker(Arc::clone(&state));
33759      let mut cx = Context::from_waker(&waker);
33760
33761      let result = catch_unwind(AssertUnwindSafe(|| {
33762        let _ = Pin::new(&mut sleep).poll(&mut cx);
33763      }));
33764      assert!(
33765        result.is_err(),
33766        "the timer-driver register panic must still propagate"
33767      );
33768      assert_eq!(driver.registers.load(Ordering::SeqCst), 1);
33769      assert_eq!(state.clones.load(Ordering::SeqCst), 3);
33770      assert_eq!(
33771        state.drop_panics.load(Ordering::SeqCst),
33772        1,
33773        "the unretained register argument must be destroyed once behind the proxy boundary"
33774      );
33775      assert_eq!(registry.pending_len(), 1);
33776      assert!(executor.host_timers.has_pending());
33777
33778      drop(sleep);
33779      assert_eq!(
33780        state.drop_panics.load(Ordering::SeqCst),
33781        3,
33782        "repoll and registry wakers must retain their existing contained cleanup"
33783      );
33784      assert_eq!(registry.pending_len(), 0);
33785      assert!(!executor.host_timers.has_pending());
33786      return;
33787    }
33788
33789    let output = std::process::Command::new(std::env::current_exe().unwrap())
33790      .arg("--exact")
33791      .arg("async_runtime::tests::host_sleep_register_unwind_contains_unretained_waker_drop")
33792      .arg("--nocapture")
33793      .env(CHILD_ENV, "1")
33794      .output()
33795      .expect("the hostile register-waker subprocess test must start");
33796    assert!(
33797      output.status.success(),
33798      "timer-driver register unwind must not double-panic through its unretained waker; status={:?}\nstdout={}\nstderr={}",
33799      output.status.code(),
33800      String::from_utf8_lossy(&output.stdout),
33801      String::from_utf8_lossy(&output.stderr)
33802    );
33803  }
33804
33805  #[test]
33806  fn host_sleep_rearms_on_next_live_driver_after_eviction() {
33807    // A sleep armed on a driver whose host dies must keep every remaining live
33808    // registrant armed with its remaining time preserved
33809    // (absolute deadline), keep the executor's wake-token accounting at
33810    // exactly one entry throughout, and cancel on the driver it is CURRENTLY
33811    // armed on when dropped.
33812    let metrics = Arc::new(RuntimeMetrics::default());
33813    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33814    let backend =
33815      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33816
33817    let a = ManualStubDriver::new();
33818    let b = ManualStubDriver::new();
33819    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
33820    let deadline = Instant::now() + Duration::from_secs(60);
33821
33822    let (timer_id, b_id) = {
33823      let mut sleep = std::pin::pin!(make_sleep(&backend, &registry, deadline));
33824      let mut cx = Context::from_waker(Waker::noop());
33825
33826      // First poll arms on A (the only registrant) and takes the wake-token.
33827      assert!(sleep.as_mut().poll(&mut cx).is_pending());
33828      let (timer_id, armed_deadline) = a.registers.lock().unwrap()[0];
33829      assert_eq!(armed_deadline, deadline);
33830      assert!(
33831        executor.host_timers.has_pending(),
33832        "first poll must arm the wake-token"
33833      );
33834
33835      // A's host dies; a newer live registrant appears (the binding shape:
33836      // eviction wakes every pending sleep, which then re-polls -- here the
33837      // re-poll is driven manually).
33838      a.kill();
33839      let b_id = registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
33840      assert!(sleep.as_mut().poll(&mut cx).is_pending());
33841      assert_eq!(
33842        b.registers.lock().unwrap().as_slice(),
33843        &[(timer_id, deadline)],
33844        "the re-poll must re-arm the SAME timer id at the SAME absolute deadline on the live driver"
33845      );
33846      assert_eq!(
33847        a.cancels.lock().unwrap().as_slice(),
33848        &[timer_id],
33849        "the dead driver gets a best-effort cancel for the old arm"
33850      );
33851      assert_eq!(
33852        registry.current().map(|(id, _)| id),
33853        Some(b_id),
33854        "the dead driver was evicted"
33855      );
33856      assert!(
33857        executor.host_timers.has_pending(),
33858        "the wake-token survives the re-arm (still exactly this sleep's entry)"
33859      );
33860      (timer_id, b_id)
33861    };
33862
33863    // Drop (the pin! scope ended): cancel goes to the CURRENT driver, and the
33864    // wake-token retires.
33865    let _ = b_id;
33866    assert_eq!(b.cancels.lock().unwrap().as_slice(), &[timer_id]);
33867    assert!(
33868      !executor.host_timers.has_pending(),
33869      "drop must retire the wake-token"
33870    );
33871  }
33872
33873  #[test]
33874  fn host_sleep_races_live_timer_drivers_and_cancels_the_starved_arm() {
33875    let metrics = Arc::new(RuntimeMetrics::default());
33876    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33877    let backend =
33878      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33879    let responsive = Arc::new(StubHostTimerDriver::new());
33880    let starved = ManualStubDriver::new();
33881    let registry = Arc::new(TimerDriverRegistry::default());
33882    registry.register(Arc::clone(&responsive) as Arc<dyn TimerDriver>);
33883    // Newest-live-only selection would arm exclusively on this host and never
33884    // receive a wake. The responsive older host must be raced in parallel.
33885    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
33886
33887    let wake_count = Arc::new(CountingWake(AtomicUsize::new(0)));
33888    let waker = Waker::from(Arc::clone(&wake_count));
33889    let mut cx = Context::from_waker(&waker);
33890    // This deadline's pre-fire budget covers only `make_sleep` plus the very
33891    // next poll (a past-due Sleep completes inline and would bypass the
33892    // racing-arm machinery under test); 500ms dwarfs those two statements on
33893    // a starved runner without dragging the fire wait out.
33894    let deadline = Instant::now() + Duration::from_millis(500);
33895    let mut sleep = make_sleep(&backend, &registry, deadline);
33896    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
33897
33898    let responsive_timer = *responsive
33899      .timers
33900      .lock()
33901      .unwrap()
33902      .keys()
33903      .next()
33904      .expect("responsive timer must be armed");
33905    assert_eq!(
33906      starved.registers.lock().unwrap().as_slice(),
33907      &[(responsive_timer, deadline)],
33908      "every live host must receive the same timer identity and absolute deadline"
33909    );
33910
33911    // Event-based fire wait (this was a fixed 140ms sleep, which required
33912    // the responsive stub's freshly spawned fire thread to get CPU inside
33913    // ~80ms on a starved CI host). The wait is structurally sound: the
33914    // starved ManualStubDriver has NO fire path (it only records), so the
33915    // one-and-only wake can come from the responsive host -- the bound is a
33916    // pure hang detector.
33917    let fire_bound = Instant::now() + Duration::from_secs(30);
33918    while wake_count.0.load(Ordering::SeqCst) == 0 {
33919      assert!(
33920        Instant::now() < fire_bound,
33921        "the responsive host must wake a sleep whose newest host is live but starved"
33922      );
33923      std::thread::sleep(Duration::from_millis(10));
33924    }
33925    // The stub fires at (or after) the shared absolute deadline, so the wall
33926    // clock is past `deadline` here and the poll completes.
33927    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
33928    assert_eq!(
33929      starved.cancels.lock().unwrap().as_slice(),
33930      &[responsive_timer],
33931      "completion through the winning host must cancel the starved losing arm"
33932    );
33933  }
33934
33935  #[test]
33936  fn host_sleep_repolls_all_pending_timers_onto_a_late_registered_driver() {
33937    use std::sync::mpsc;
33938
33939    struct ReentrantTimerRegistrationWake {
33940      registry: Weak<TimerDriverRegistry>,
33941      wakes: AtomicUsize,
33942      reentered: AtomicBool,
33943    }
33944
33945    impl std::task::Wake for ReentrantTimerRegistrationWake {
33946      fn wake(self: Arc<Self>) {
33947        self.wakes.fetch_add(1, Ordering::SeqCst);
33948        if let Some(registry) = self.registry.upgrade() {
33949          let _ = registry.has_live_driver();
33950          self.reentered.store(true, Ordering::SeqCst);
33951        }
33952      }
33953    }
33954
33955    let metrics = Arc::new(RuntimeMetrics::default());
33956    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
33957    let backend =
33958      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
33959    let registry = Arc::new(TimerDriverRegistry::default());
33960    let starved = ManualStubDriver::new();
33961    registry.register(Arc::clone(&starved) as Arc<dyn TimerDriver>);
33962
33963    // The whole pre-fire phase (four polls plus a registration-thread round
33964    // trip) must finish while the wall clock is still BEFORE this shared
33965    // deadline: a past-due `Sleep` completes inline at poll
33966    // (`Instant::now() >= deadline`) and bypasses the repoll machinery under
33967    // test. 2s dwarfs the phase's worst observed CI overhead (tens of ms on
33968    // a starved GitHub macOS runner) without dragging the fire wait out.
33969    let deadline = Instant::now() + Duration::from_secs(2);
33970    let mut first = make_sleep(&backend, &registry, deadline);
33971    let mut second = make_sleep(&backend, &registry, deadline);
33972    let first_wake = Arc::new(ReentrantTimerRegistrationWake {
33973      registry: Arc::downgrade(&registry),
33974      wakes: AtomicUsize::new(0),
33975      reentered: AtomicBool::new(false),
33976    });
33977    let second_wake = Arc::new(ReentrantTimerRegistrationWake {
33978      registry: Arc::downgrade(&registry),
33979      wakes: AtomicUsize::new(0),
33980      reentered: AtomicBool::new(false),
33981    });
33982    let first_waker = Waker::from(Arc::clone(&first_wake));
33983    let second_waker = Waker::from(Arc::clone(&second_wake));
33984    let mut first_cx = Context::from_waker(&first_waker);
33985    let mut second_cx = Context::from_waker(&second_waker);
33986
33987    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
33988    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
33989    let initial = starved.registers.lock().unwrap().clone();
33990    let first_id = initial[0].0;
33991    let second_id = initial[1].0;
33992    assert_ne!(first_id, second_id);
33993    assert_eq!(
33994      registry.pending_len(),
33995      2,
33996      "one stable repoll entry is retained per sleep"
33997    );
33998
33999    let responsive = Arc::new(StubHostTimerDriver::new());
34000    let (registered_tx, registered_rx) = mpsc::sync_channel(1);
34001    let registration_registry = Arc::clone(&registry);
34002    let registration_driver = Arc::clone(&responsive);
34003    let registration = std::thread::spawn(move || {
34004      let id = registration_registry.register(registration_driver as Arc<dyn TimerDriver>);
34005      registered_tx.send(id).unwrap();
34006    });
34007    // Hang detector only (the regression it pins is a PERMANENT deadlock:
34008    // `register` re-entering the repoll callback), so the bound is generous
34009    // rather than a latency assertion a starved runner could trip.
34010    registered_rx
34011      .recv_timeout(Duration::from_secs(10))
34012      .expect("late driver registration or repoll callback re-entry deadlocked");
34013    registration.join().unwrap();
34014
34015    assert_eq!(first_wake.wakes.load(Ordering::SeqCst), 1);
34016    assert_eq!(second_wake.wakes.load(Ordering::SeqCst), 1);
34017    assert!(first_wake.reentered.load(Ordering::SeqCst));
34018    assert!(second_wake.reentered.load(Ordering::SeqCst));
34019    assert!(
34020      responsive.timers.lock().unwrap().is_empty(),
34021      "registration requests bounded re-polls instead of invoking driver callbacks inline"
34022    );
34023
34024    // Both sleeps must still be PENDING when they re-arm on the late driver;
34025    // see the deadline-margin note above. Fail with a diagnosis instead of a
34026    // misleading Pending-assert failure if a pathological stall consumed the
34027    // whole margin anyway.
34028    assert!(
34029      Instant::now() < deadline,
34030      "test overhead consumed the entire pre-fire deadline margin before the repoll phase; \
34031       this runner is starved beyond the margin this test budgets for"
34032    );
34033    assert!(Pin::new(&mut first).poll(&mut first_cx).is_pending());
34034    assert!(Pin::new(&mut second).poll(&mut second_cx).is_pending());
34035    let responsive_ids = responsive
34036      .timers
34037      .lock()
34038      .unwrap()
34039      .keys()
34040      .copied()
34041      .collect::<FxHashSet<_>>();
34042    assert_eq!(
34043      responsive_ids,
34044      FxHashSet::from_iter([first_id, second_id]),
34045      "every pending sleep must re-arm its existing identity on the new driver"
34046    );
34047    assert_eq!(
34048      starved
34049        .registers
34050        .lock()
34051        .unwrap()
34052        .iter()
34053        .filter(|(id, _)| *id == first_id)
34054        .count(),
34055      2,
34056      "the old host receives only the normal waker refresh on the one re-poll"
34057    );
34058    assert_eq!(
34059      starved
34060        .registers
34061        .lock()
34062        .unwrap()
34063        .iter()
34064        .filter(|(id, _)| *id == second_id)
34065        .count(),
34066      2,
34067      "the old host receives only the normal waker refresh on the one re-poll"
34068    );
34069    assert_eq!(
34070      registry.pending_len(),
34071      2,
34072      "late registration must not duplicate pending state"
34073    );
34074
34075    // Event-based fire wait instead of a fixed sleep: the starved host has
34076    // NO fire path by construction (ManualStubDriver only records), so the
34077    // second wake per sleep can only come from the late responsive host --
34078    // "while the original host remains starved" is structural, not a
34079    // wall-clock race. The bound is purely a hang detector, generous enough
34080    // for a starved runner to schedule the stub's two fire threads.
34081    let fire_bound = Instant::now() + Duration::from_secs(30);
34082    while first_wake.wakes.load(Ordering::SeqCst) < 2
34083      || second_wake.wakes.load(Ordering::SeqCst) < 2
34084    {
34085      assert!(
34086        Instant::now() < fire_bound,
34087        "the late responsive host must fire both timers while the original host remains starved"
34088      );
34089      std::thread::sleep(Duration::from_millis(10));
34090    }
34091    assert!(Pin::new(&mut first).poll(&mut first_cx).is_ready());
34092    assert!(Pin::new(&mut second).poll(&mut second_cx).is_ready());
34093
34094    let mut cancelled = starved.cancels.lock().unwrap().clone();
34095    cancelled.sort_unstable();
34096    let mut expected = vec![first_id, second_id];
34097    expected.sort_unstable();
34098    assert_eq!(
34099      cancelled, expected,
34100      "completion must cancel both losing starved-host arms"
34101    );
34102    assert_eq!(
34103      registry.pending_len(),
34104      0,
34105      "completion retires every repoll entry"
34106    );
34107    assert!(!executor.host_timers.has_pending());
34108  }
34109
34110  #[test]
34111  fn host_sleep_panics_loud_when_every_driver_dies_mid_flight() {
34112    // With NO live registrant left mid-flight there is nothing to re-arm on:
34113    // the poll must fail LOUD with the typed diagnostic, never park a wake-less
34114    // sleep silently.
34115    let metrics = Arc::new(RuntimeMetrics::default());
34116    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34117    let backend =
34118      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34119
34120    let a = ManualStubDriver::new();
34121    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
34122    let mut sleep = std::pin::pin!(make_sleep(
34123      &backend,
34124      &registry,
34125      Instant::now() + Duration::from_secs(60)
34126    ));
34127    let mut cx = Context::from_waker(Waker::noop());
34128    assert!(sleep.as_mut().poll(&mut cx).is_pending());
34129
34130    a.kill();
34131    let payload = catch_unwind(AssertUnwindSafe(|| {
34132      let _ = sleep.as_mut().poll(&mut cx);
34133    }))
34134    .expect_err("a sleep with every driver dead must panic, not pend silently");
34135    let message = payload
34136      .downcast_ref::<String>()
34137      .cloned()
34138      .or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
34139      .expect("panic payload must be a message");
34140    assert!(
34141      message.contains("lost every live timer driver mid-sleep"),
34142      "the diagnostic must name the mid-flight driver loss, got: {message}"
34143    );
34144  }
34145
34146  #[test]
34147  fn registry_sweep_wakes_sleeps_pending_on_the_swept_driver() {
34148    // Sweep-eviction invariant: the registry's selection sweep can be the
34149    // FIRST layer to notice a dead driver, before the owner's env-cleanup hook
34150    // or any call failure. A retain-only sweep would strand every sleep whose
34151    // armed waker lives in the swept driver's pending map. The sweep must call
34152    // `on_swept` outside the registry lock so the driver wakes those sleeps
34153    // into re-selection.
34154    let metrics = Arc::new(RuntimeMetrics::default());
34155    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34156    let backend =
34157      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34158
34159    let a = ManualStubDriver::new();
34160    let b = ManualStubDriver::new();
34161    let registry = registry_with(Arc::clone(&a) as Arc<dyn TimerDriver>);
34162    let mut sleep = std::pin::pin!(make_sleep(
34163      &backend,
34164      &registry,
34165      Instant::now() + Duration::from_secs(60)
34166    ));
34167
34168    // Arm on A with an observable waker.
34169    let flag = Arc::new(WakeFlag::default());
34170    let waker = Waker::from(Arc::clone(&flag));
34171    let mut cx = Context::from_waker(&waker);
34172    assert!(sleep.as_mut().poll(&mut cx).is_pending());
34173    assert!(!flag.0.load(Ordering::SeqCst));
34174
34175    // A's host dies; a DIFFERENT selection (any registry touch -- here a
34176    // liveness query, in production another sleep's poll) sweeps it.
34177    a.kill();
34178    registry.register(Arc::clone(&b) as Arc<dyn TimerDriver>);
34179    let _ = registry.has_live_driver();
34180
34181    assert!(
34182      a.swept.load(Ordering::SeqCst),
34183      "the sweep must notify the swept driver"
34184    );
34185    assert!(
34186      flag.0.load(Ordering::SeqCst),
34187      "a sleep armed on the swept driver must be woken into re-selection, not stranded"
34188    );
34189
34190    // The woken sleep's re-poll lands on the live registrant.
34191    assert!(sleep.as_mut().poll(&mut cx).is_pending());
34192    assert_eq!(
34193      b.registers.lock().unwrap().len(),
34194      1,
34195      "the re-poll must re-arm on the live driver"
34196    );
34197  }
34198
34199  #[test]
34200  fn current_thread_sleep_fires_through_stub_host_driver() {
34201    // CT delegation: a registered host driver's fire must complete a sleep
34202    // awaited through the CT `block_on` drive loop.
34203    use std::sync::mpsc;
34204
34205    let (done_tx, done_rx) = mpsc::channel();
34206    let runner = std::thread::spawn(move || {
34207      let metrics = Arc::new(RuntimeMetrics::default());
34208      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34209      let backend =
34210        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34211      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34212
34213      let started = Instant::now();
34214      let mut future = std::pin::pin!(async {
34215        make_sleep(
34216          &backend,
34217          &registry_with(driver),
34218          Instant::now() + Duration::from_millis(60),
34219        )
34220        .await;
34221      });
34222      executor.block_on(future.as_mut());
34223      assert!(started.elapsed() >= Duration::from_millis(60));
34224      assert!(
34225        !executor.host_timers.has_pending(),
34226        "a completed sleep must retire its pending-registry entry"
34227      );
34228      let _ = done_tx.send(());
34229    });
34230
34231    match done_rx.recv_timeout(Duration::from_secs(10)) {
34232      Ok(()) => runner.join().unwrap(),
34233      Err(error) => panic!("a CT sleep through the host driver never fired ({error})"),
34234    }
34235  }
34236
34237  #[test]
34238  fn current_thread_timer_shutdown_retires_pending_repoll_state() {
34239    let metrics = Arc::new(RuntimeMetrics::default());
34240    let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, false, None));
34241    let backend =
34242      RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34243    let drivers = Arc::new(TimerDriverRegistry::default());
34244    let driver = ManualStubDriver::new();
34245    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
34246    let mut sleep = make_sleep(&backend, &drivers, Instant::now() + Duration::from_secs(60));
34247    let mut cx = Context::from_waker(Waker::noop());
34248
34249    assert!(Pin::new(&mut sleep).poll(&mut cx).is_pending());
34250    let timer_id = driver.registers.lock().unwrap()[0].0;
34251    assert_eq!(drivers.pending_len(), 1);
34252
34253    executor.host_timers.shutdown();
34254    assert_eq!(
34255      drivers.pending_len(),
34256      0,
34257      "shutdown must retire the registration-repoll index before the Sleep is dropped"
34258    );
34259    assert!(Pin::new(&mut sleep).poll(&mut cx).is_ready());
34260    assert_eq!(driver.cancels.lock().unwrap().as_slice(), &[timer_id]);
34261  }
34262
34263  #[test]
34264  fn current_thread_shutdown_drain_fires_active_host_timer_block_on() {
34265    use std::sync::mpsc;
34266
34267    let controller = Arc::new(current_thread_controller("host-timer-shutdown"));
34268    let backend = controller.backend();
34269    let drivers = Arc::new(TimerDriverRegistry::default());
34270    let driver = ManualStubDriver::new();
34271    drivers.register(Arc::clone(&driver) as Arc<dyn TimerDriver>);
34272    let sleep = make_sleep(
34273      &backend,
34274      &drivers,
34275      Instant::now() + Duration::from_secs(120),
34276    );
34277    drop(backend);
34278
34279    let (polled_tx, polled_rx) = mpsc::channel();
34280    let block_controller = Arc::clone(&controller);
34281    let blocker = std::thread::spawn(move || {
34282      let mut announced = false;
34283      let mut sleep = std::pin::pin!(sleep);
34284      let mut future = std::pin::pin!(futures::future::poll_fn(move |cx| {
34285        let poll = sleep.as_mut().poll(cx);
34286        if !announced {
34287          announced = true;
34288          polled_tx.send(()).unwrap();
34289        }
34290        poll
34291      }));
34292      block_controller.block_on(future.as_mut());
34293    });
34294    polled_rx.recv_timeout(Duration::from_secs(2)).unwrap();
34295
34296    let (shutdown_tx, shutdown_rx) = mpsc::channel();
34297    let shutdown_controller = Arc::clone(&controller);
34298    let shutdown = std::thread::spawn(move || {
34299      shutdown_tx.send(shutdown_controller.shutdown()).unwrap();
34300    });
34301    shutdown_rx
34302      .recv_timeout(Duration::from_secs(2))
34303      .expect("shutdown must drain-fire host timers and wake CurrentThread block_on")
34304      .unwrap();
34305    shutdown.join().unwrap();
34306    blocker.join().unwrap();
34307    assert_eq!(driver.cancels.lock().unwrap().len(), 1);
34308  }
34309
34310  #[test]
34311  fn current_thread_threadless_park_with_pending_host_timer_is_still_certain_deadlock() {
34312    // Threadless pending-timer invariant: on a
34313    // THREADLESS build a pending host timer is NOT a wake-token source -- the
34314    // host event loop's `setTimeout` relay can only run on the very thread
34315    // that is about to park, so it can never fire while `block_on` holds it.
34316    // The certain check must panic with the typed diagnostic anyway (the
34317    // timer-backed kind, so the message names the shape), never fall through
34318    // to the park -- on the real wasip1 target that park is an untyped
34319    // "condvar wait not supported" abort. A native helper thread can fire the
34320    // timer in this test harness, but that concurrency cannot exist on the
34321    // threadless target and must not rescue the park.
34322    use std::sync::mpsc;
34323
34324    let (done_tx, done_rx) = mpsc::channel();
34325    let runner = std::thread::spawn(move || {
34326      let metrics = Arc::new(RuntimeMetrics::default());
34327      // Native stand-in for the threadless wasm build; the stub driver would
34328      // play the host loop from a helper thread, but the panic must land at
34329      // the first park decision, long before its 60ms fire.
34330      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
34331      let backend =
34332        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34333      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34334
34335      let payload = catch_unwind(AssertUnwindSafe(|| {
34336        let mut future = std::pin::pin!(async {
34337          make_sleep(
34338            &backend,
34339            &registry_with(driver),
34340            Instant::now() + Duration::from_millis(60),
34341          )
34342          .await;
34343        });
34344        executor.block_on(future.as_mut());
34345      }))
34346      .expect_err("a threadless park must self-detect even while a host timer is pending");
34347      let diagnostic = payload
34348        .downcast_ref::<BlockOnDeadlock>()
34349        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
34350      assert_eq!(
34351        diagnostic.kind,
34352        BlockOnDeadlockKind::CurrentThreadCertainTimerBacked
34353      );
34354      assert_eq!(
34355        diagnostic.park_deadline, None,
34356        "the certain case carries no deadline"
34357      );
34358      let message = diagnostic.to_string();
34359      assert!(
34360        message.contains("host timer") && message.contains("block_on") && message.contains("JS"),
34361        "the diagnostic must name the pending host timer and the block_on-awaiting-JS hazard, got: {message}"
34362      );
34363      let _ = done_tx.send(());
34364    });
34365
34366    match done_rx.recv_timeout(Duration::from_secs(10)) {
34367      Ok(()) => runner.join().unwrap(),
34368      Err(error) => panic!(
34369        "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})"
34370      ),
34371    }
34372  }
34373
34374  #[test]
34375  fn current_thread_threadless_certain_check_ignores_unpolled_sleep() {
34376    // Unpolled-sleep invariant: the registry entry only exists once the host
34377    // timer is ARMED (first poll runs `driver.register`), never at `Sleep`
34378    // creation. A created-but-never-polled Sleep has no host callback behind
34379    // it, and on a parked single thread nothing can ever poll it into
34380    // arming -- it is provably NOT a future wake source. Counting it would
34381    // mislabel the certain diagnostic as timer-backed. (The armed twin above
34382    // pins the polled side: a POLLED pending timer still certain-panics, just
34383    // with the timer-backed kind.)
34384    use std::sync::mpsc;
34385
34386    let (done_tx, done_rx) = mpsc::channel();
34387    let runner = std::thread::spawn(move || {
34388      let metrics = Arc::new(RuntimeMetrics::default());
34389      let executor = Arc::new(CurrentThreadExecutor::with_detection(metrics, true, None));
34390      let backend =
34391        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34392      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34393
34394      // Created and held across the park decision, but NEVER polled: no host
34395      // timer armed, no wake source.
34396      let _unpolled = make_sleep(
34397        &backend,
34398        &registry_with(driver),
34399        Instant::now() + Duration::from_secs(60),
34400      );
34401
34402      let payload = catch_unwind(AssertUnwindSafe(|| {
34403        let mut future = std::pin::pin!(NeverReady);
34404        executor.block_on(future.as_mut());
34405      }))
34406      .expect_err("a threadless park holding only an unpolled Sleep must still self-detect");
34407      let diagnostic = payload
34408        .downcast_ref::<BlockOnDeadlock>()
34409        .expect("the panic payload must be the typed BlockOnDeadlock diagnostic");
34410      assert_eq!(diagnostic.kind, BlockOnDeadlockKind::CurrentThreadCertain);
34411      let _ = done_tx.send(());
34412    });
34413
34414    match done_rx.recv_timeout(Duration::from_secs(10)) {
34415      Ok(()) => runner.join().unwrap(),
34416      Err(error) => panic!(
34417        "an unpolled Sleep suppressed the threadless certain-deadlock diagnostic -- block_on parked forever instead of panicking (the silent-hang class) ({error})"
34418      ),
34419    }
34420  }
34421
34422  #[cfg(not(target_family = "wasm"))]
34423  #[test]
34424  fn current_thread_park_deadline_with_live_host_timer_does_not_panic() {
34425    // CurrentThread timer/deadline invariant: deadline armed (30ms) shorter than a pending
34426    // host timer (200ms). Waiting out a LIVE host timer with zero progress is
34427    // legitimate (the wake is scheduled on the host loop); the deadline
34428    // verdict must not fire. Once the timer fires, the sleep completes.
34429    use std::sync::mpsc;
34430
34431    let (done_tx, done_rx) = mpsc::channel();
34432    let runner = std::thread::spawn(move || {
34433      let metrics = Arc::new(RuntimeMetrics::default());
34434      // Deadline/grace ratio: the armed park deadline doubles as the
34435      // `has_live_wait` GRACE -- once the host timer is past due by more
34436      // than one deadline window without firing, the veto stops holding BY
34437      // DESIGN (that is the frozen-JS-loop signature). With a 30ms window
34438      // the stub host's freshly spawned fire thread had to get CPU within
34439      // ~230ms of test start or the verdict fired -- the starved-CI failure.
34440      // A 2s window keeps the invariant identical (a LIVE host-timer wait
34441      // with zero progress must not be declared dead) while tolerating
34442      // multi-second scheduling stalls of the fire thread: the veto holds
34443      // until deadline+2s, and the expiry checks land on 2s boundaries, so
34444      // the fire has >4s before a verdict could stick.
34445      let executor = Arc::new(CurrentThreadExecutor::with_detection(
34446        metrics,
34447        false,
34448        Some(Duration::from_secs(2)),
34449      ));
34450      let backend =
34451        RuntimeBackend::from_executor(RuntimeExecutor::CurrentThread(Arc::clone(&executor)));
34452      let driver: Arc<dyn TimerDriver> = Arc::new(StubHostTimerDriver::new());
34453
34454      let started = Instant::now();
34455      let mut future = std::pin::pin!(async {
34456        make_sleep(
34457          &backend,
34458          &registry_with(driver),
34459          Instant::now() + Duration::from_millis(200),
34460        )
34461        .await;
34462      });
34463      executor.block_on(future.as_mut());
34464      assert!(started.elapsed() >= Duration::from_millis(200));
34465      let _ = done_tx.send(());
34466    });
34467
34468    match done_rx.recv_timeout(Duration::from_secs(30)) {
34469      Ok(()) => runner.join().unwrap(),
34470      Err(error) => {
34471        panic!("a CT park waiting out a live host timer fired the armed deadline or hung ({error})")
34472      }
34473    }
34474  }
34475
34476  #[test]
34477  fn host_timer_unrepresentable_grace_remains_live() {
34478    let registry = HostTimerRegistry::default();
34479    let fired = Arc::new(AtomicBool::new(false));
34480    let drivers = Arc::new(TimerDriverRegistry::default());
34481    let repoll = Arc::new(HostTimerRepoll::default());
34482    assert!(
34483      Instant::now().checked_add(Duration::MAX).is_none(),
34484      "Duration::MAX must exercise the unrepresentable-grace path"
34485    );
34486    assert!(
34487      registry.register(
34488        1,
34489        Instant::now(),
34490        futures::task::noop_waker(),
34491        &fired,
34492        &drivers,
34493        &repoll,
34494      ),
34495      "the open registry must accept the timer"
34496    );
34497
34498    assert!(
34499      registry.has_live_wait(Duration::MAX),
34500      "an unrepresentable grace deadline is effectively unbounded"
34501    );
34502    registry.remove(1);
34503  }
34504
34505  #[test]
34506  fn block_on_deadlock_panic_payload_surfaces_through_join_error() {
34507    // A deadline firing inside a spawned task's poll unwinds into the spawn
34508    // wrapper's catch_unwind and becomes a JoinError; the typed diagnostic's
34509    // message must survive that trip (JoinError::from_panic downcasts it), or
34510    // the build error would read "async runtime task panicked" with no clue.
34511    let diagnostic = BlockOnDeadlock::multi_thread_cooperative(Duration::from_millis(75));
34512    let expected = diagnostic.to_string();
34513    let payload: Box<dyn Any + Send> = Box::new(diagnostic);
34514    assert_eq!(JoinError::from_panic(&*payload).to_string(), expected);
34515  }
34516}